diff options
Diffstat (limited to 'src/java')
207 files changed, 12133 insertions, 4566 deletions
diff --git a/src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory b/src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory index 76d6ebfbc..cabf917eb 100644 --- a/src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory +++ b/src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory @@ -1,3 +1,4 @@ org.apache.fop.render.afp.extensions.AFPExtensionHandlerFactory +org.apache.fop.render.pdf.extensions.PDFExtensionHandlerFactory org.apache.fop.render.ps.extensions.PSExtensionHandlerFactory org.apache.fop.fo.extensions.xmp.XMPContentHandlerFactory diff --git a/src/java/org/apache/fop/Version.java b/src/java/org/apache/fop/Version.java index 86861a60b..a8903c74e 100644 --- a/src/java/org/apache/fop/Version.java +++ b/src/java/org/apache/fop/Version.java @@ -40,8 +40,9 @@ public final class Version { } if (version == null) { //Fallback if FOP is used in a development environment - String headURL - = "$HeadURL$"; + // CSOFF: LineLength + String headURL = "$HeadURL$"; + // CSON: LineLength version = headURL; final String pathPrefix = "/xmlgraphics/fop/"; int pos = version.indexOf(pathPrefix); diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index 68064343e..8b9e079ab 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -408,7 +408,7 @@ public class FOUserAgent { try { // Have to do this so we can resolve data URIs StreamSource src = new StreamSource(resourceResolver.getResource(uri)); - src.setSystemId(uri); + src.setSystemId(new File(uri).toURI().toURL().toExternalForm()); return src; } catch (URISyntaxException use) { return null; diff --git a/src/java/org/apache/fop/apps/FopConfParser.java b/src/java/org/apache/fop/apps/FopConfParser.java index b4918ef30..b0fa40f45 100644 --- a/src/java/org/apache/fop/apps/FopConfParser.java +++ b/src/java/org/apache/fop/apps/FopConfParser.java @@ -70,16 +70,7 @@ public class FopConfParser { */ public FopConfParser(InputStream fopConfStream, EnvironmentProfile enviro) throws SAXException, IOException { - DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder(); - Configuration cfg; - try { - cfg = cfgBuilder.build(fopConfStream); - } catch (ConfigurationException e) { - throw new FOPException(e); - } - // The default base URI is taken from the directory in which the fopConf resides - fopFactoryBuilder = new FopFactoryBuilder(enviro).setConfiguration(cfg); - configure(enviro.getDefaultBaseURI(), enviro.getResourceResolver(), cfg); + this(fopConfStream, enviro.getDefaultBaseURI(), enviro); } /** @@ -94,7 +85,8 @@ public class FopConfParser { */ public FopConfParser(InputStream fopConfStream, URI defaultBaseURI, ResourceResolver resourceResolver) throws SAXException, IOException { - this(fopConfStream, EnvironmentalProfileFactory.createDefault(defaultBaseURI, resourceResolver)); + this(fopConfStream, defaultBaseURI, + EnvironmentalProfileFactory.createDefault(defaultBaseURI, resourceResolver)); } /** @@ -123,6 +115,20 @@ public class FopConfParser { } /** + * Constructor that takes the FOP conf and a default base URI and uses the default URI resolver. + * + * @param fopConfFile the FOP conf file + * @param defaultBaseURI the default base URI + * @throws SAXException if a SAX error was thrown parsing the FOP conf + * @throws IOException if an I/O error is thrown while parsing the FOP conf + */ + public FopConfParser(File fopConfFile, URI defaultBaseURI) throws SAXException, IOException { + this(new FileInputStream(fopConfFile), fopConfFile.toURI(), + EnvironmentalProfileFactory.createDefault(defaultBaseURI, + ResourceResolverFactory.createDefaultResourceResolver())); + } + + /** * Constructor that parses the FOP conf and uses the URI resolver given. * * @param fopConfFile the FOP conf file @@ -132,11 +138,24 @@ public class FopConfParser { */ public FopConfParser(File fopConfFile, ResourceResolver resourceResolver) throws SAXException, IOException { - this(new FileInputStream(fopConfFile), - fopConfFile.getAbsoluteFile().getParentFile().toURI(), resourceResolver); + this(new FileInputStream(fopConfFile), fopConfFile.toURI(), resourceResolver); + } + + private FopConfParser(InputStream fopConfStream, URI baseURI, EnvironmentProfile enviro) + throws SAXException, IOException { + DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder(); + Configuration cfg; + try { + cfg = cfgBuilder.build(fopConfStream); + } catch (ConfigurationException e) { + throw new FOPException(e); + } + // The default base URI is taken from the directory in which the fopConf resides + fopFactoryBuilder = new FopFactoryBuilder(enviro).setConfiguration(cfg); + configure(baseURI, enviro.getResourceResolver(), cfg); } - private void configure(final URI defaultBaseURI, final ResourceResolver resourceResolver, + private void configure(final URI baseURI, final ResourceResolver resourceResolver, Configuration cfg) throws FOPException { if (log.isDebugEnabled()) { log.debug("Initializing FopFactory Configuration"); @@ -174,7 +193,7 @@ public class FopConfParser { if (cfg.getChild("base", false) != null) { try { URI confUri = InternalResourceResolver.getBaseURI(cfg.getChild("base").getValue(null)); - fopFactoryBuilder.setBaseURI(defaultBaseURI.resolve(confUri)); + fopFactoryBuilder.setBaseURI(baseURI.resolve(confUri)); } catch (URISyntaxException use) { LogUtil.handleException(log, use, strict); } @@ -242,8 +261,8 @@ public class FopConfParser { } // configure font manager - new FontManagerConfigurator(cfg, fopFactoryBuilder.getBaseURI(), resourceResolver).configure( - fopFactoryBuilder.getFontManager(), strict); + new FontManagerConfigurator(cfg, baseURI, fopFactoryBuilder.getBaseURI(), resourceResolver) + .configure(fopFactoryBuilder.getFontManager(), strict); // configure image loader framework configureImageLoading(cfg.getChild("image-loading", false), strict); diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index c79f975c1..640f04335 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -62,6 +62,7 @@ import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Trait.Background; import org.apache.fop.area.Trait.InternalLink; import org.apache.fop.area.inline.AbstractTextArea; +import org.apache.fop.area.inline.Container; import org.apache.fop.area.inline.ForeignObject; import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.InlineArea; @@ -195,6 +196,7 @@ public class AreaTreeParser { makers.put("space", new SpaceMaker()); makers.put("leader", new LeaderMaker()); makers.put("viewport", new InlineViewportMaker()); + makers.put("container", new ContainerMaker()); makers.put("image", new ImageMaker()); makers.put("foreignObject", new ForeignObjectMaker()); makers.put("bookmarkTree", new BookmarkTreeMaker()); @@ -863,6 +865,21 @@ public class AreaTreeParser { } } + private class ContainerMaker extends AbstractMaker { + + public void startElement(Attributes attributes) { + Container container = new Container(); + transferForeignObjects(attributes, container); + InlineViewport parent = (InlineViewport) areaStack.peek(); + parent.setContent(container); + areaStack.push(container); + } + + public void endElement() { + assertObjectOfClass(areaStack.pop(), Container.class); + } + } + private class InlineViewportMaker extends AbstractMaker { public void startElement(Attributes attributes) { @@ -1040,7 +1057,7 @@ public class AreaTreeParser { } private static final Object[] SUBSET_COMMON = new Object[] { - Trait.PROD_ID}; + Trait.PROD_ID, Trait.LAYER}; private static final Object[] SUBSET_LINK = new Object[] { Trait.INTERNAL_LINK, Trait.EXTERNAL_LINK}; private static final Object[] SUBSET_COLOR = new Object[] { diff --git a/src/java/org/apache/fop/area/Footnote.java b/src/java/org/apache/fop/area/Footnote.java index bc9f27b24..28f3ee523 100644 --- a/src/java/org/apache/fop/area/Footnote.java +++ b/src/java/org/apache/fop/area/Footnote.java @@ -83,7 +83,7 @@ public class Footnote extends BlockParent { @Override public void addBlock(Block child) { addChildArea(child); - this.setBPD(this.getBPD() + child.getBPD()); + setBPD(getBPD() + child.getAllocBPD()); } } diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java index cd0d4becf..eac9d440d 100644 --- a/src/java/org/apache/fop/area/Trait.java +++ b/src/java/org/apache/fop/area/Trait.java @@ -169,9 +169,11 @@ public final class Trait implements Serializable { /** shift direction trait */ public static final Integer SHIFT_DIRECTION = 42; + /** For optional content groups. */ + public static final Integer LAYER = 43; /** Maximum value used by trait keys */ - public static final int MAX_TRAIT_KEY = 42; + public static final int MAX_TRAIT_KEY = 43; private static final TraitInfo[] TRAIT_INFO = new TraitInfo[MAX_TRAIT_KEY + 1]; @@ -243,6 +245,7 @@ public final class Trait implements Serializable { new TraitInfo("block-progression-direction", Direction.class)); put(SHIFT_DIRECTION, new TraitInfo("shift-direction", Direction.class)); + put(LAYER, new TraitInfo("layer", String.class)); } diff --git a/src/java/org/apache/fop/area/inline/Container.java b/src/java/org/apache/fop/area/inline/Container.java index bc2acaa28..3d0060007 100644 --- a/src/java/org/apache/fop/area/inline/Container.java +++ b/src/java/org/apache/fop/area/inline/Container.java @@ -51,13 +51,12 @@ public class Container extends Area { public Container() { } - /** - * Add the block to this area. - * - * @param block the block area to add - */ - public void addBlock(Block block) { - blocks.add(block); + @Override + public void addChildArea(Area child) { + if (!(child instanceof Block)) { + throw new IllegalArgumentException("Container only accepts block areas"); + } + blocks.add((Block) child); } /** @@ -65,7 +64,7 @@ public class Container extends Area { * * @return the list of block areas */ - public List getBlocks() { + public List<Block> getBlocks() { return blocks; } diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java index 080fe7930..ad8019a7d 100644 --- a/src/java/org/apache/fop/cli/CommandLineOptions.java +++ b/src/java/org/apache/fop/cli/CommandLineOptions.java @@ -58,7 +58,6 @@ import org.apache.fop.render.pdf.PDFEncryptionOption; import org.apache.fop.render.print.PagesMode; import org.apache.fop.render.print.PrintRenderer; import org.apache.fop.render.xml.XMLRenderer; -import org.apache.fop.util.CommandLineLogger; /** * Options parses the commandline arguments @@ -145,16 +144,6 @@ public class CommandLineOptions { * Construct a command line option object. */ public CommandLineOptions() { - LogFactory logFactory = LogFactory.getFactory(); - - // Enable the simple command line logging when no other logger is - // defined. - if (System.getProperty("org.apache.commons.logging.Log") == null) { - logFactory.setAttribute("org.apache.commons.logging.Log", - CommandLineLogger.class.getName()); - setLogLevel("info"); - } - log = LogFactory.getLog("FOP"); } @@ -184,9 +173,6 @@ public class CommandLineOptions { //Factory config is set up, now we can create the user agent foUserAgent = factory.newFOUserAgent(); foUserAgent.getRendererOptions().putAll(renderingOptions); - if (targetResolution != 0) { - foUserAgent.setTargetResolution(targetResolution); - } addXSLTParameter("fop-output-format", getOutputFormat()); addXSLTParameter("fop-version", Version.getVersion()); foUserAgent.setConserveMemoryPolicy(conserveMemoryPolicy); @@ -292,7 +278,7 @@ public class CommandLineOptions { } else if (args[i].equals("-s")) { suppressLowLevelAreas = Boolean.TRUE; } else if (args[i].equals("-d")) { - setLogOption("debug", "debug"); + // nop. Left there for backwards compatibility } else if (args[i].equals("-r")) { strictValidation = false; } else if (args[i].equals("-conserve")) { @@ -304,7 +290,7 @@ public class CommandLineOptions { } else if (args[i].equals("-dpi")) { i = i + parseResolution(args, i); } else if (args[i].equals("-q") || args[i].equals("--quiet")) { - setLogOption("quiet", "error"); + // nop. Left there for backwards compatibility } else if (args[i].equals("-fo")) { i = i + parseFOInputOption(args, i); } else if (args[i].equals("-xsl")) { @@ -904,27 +890,6 @@ public class CommandLineOptions { } } - private void setLogOption(String option, String level) { - if (log instanceof CommandLineLogger - || System.getProperty("org.apache.commons.logging.Log") == null) { - setLogLevel(level); - } else if (log != null) { - log.warn("The option " + option + " can only be used"); - log.warn("with FOP's command line logger,"); - log.warn("which is the default on the command line."); - log.warn("Configure other loggers using Java system properties."); - } - } - - private void setLogLevel(String level) { - // Set the level for future loggers. - LogFactory.getFactory().setAttribute("level", level); - if (log instanceof CommandLineLogger) { - // Set the level for the logger created already. - ((CommandLineLogger) log).setLogLevel(level); - } - } - private void setInputFormat(int format) throws FOPException { if (inputmode == NOT_SET || inputmode == format) { inputmode = format; @@ -1050,7 +1015,8 @@ public class CommandLineOptions { fopFactoryBuilder.setComplexScriptFeatures(useComplexScriptFeatures); } else { try { - fopFactoryBuilder = new FopConfParser(userConfigFile).getFopFactoryBuilder(); + FopConfParser fopConfParser = new FopConfParser(userConfigFile, baseURI); + fopFactoryBuilder = fopConfParser.getFopFactoryBuilder(); } catch (SAXException e) { throw new FOPException(e); } @@ -1208,9 +1174,7 @@ public class CommandLineOptions { + "[-awt|-pdf|-mif|-rtf|-tiff|-png|-pcl|-ps|-txt|-at [mime]|-print] <outfile>\n" + " [OPTIONS] \n" + " -version print FOP version and exit\n" - + " -d debug mode \n" + " -x dump configuration settings \n" - + " -q quiet mode \n" + " -c cfg.xml use additional configuration file cfg.xml\n" + " -l lang the language to use for user information \n" + " -nocs disable complex script features\n" diff --git a/src/java/org/apache/fop/complexscripts/bidi/BidiClass.java b/src/java/org/apache/fop/complexscripts/bidi/BidiClass.java index eed7b983b..4267e275a 100644 --- a/src/java/org/apache/fop/complexscripts/bidi/BidiClass.java +++ b/src/java/org/apache/fop/complexscripts/bidi/BidiClass.java @@ -21,7 +21,6 @@ package org.apache.fop.complexscripts.bidi; import java.util.Arrays; -// CSOFF: WhitespaceAfterCheck // CSOFF: LineLengthCheck /* diff --git a/src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java b/src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java index 8b78615d4..f1a4d2a69 100644 --- a/src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java +++ b/src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java @@ -32,11 +32,7 @@ import org.apache.fop.area.LineArea; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.fo.pagination.PageSequence; -// CSOFF: EmptyForIteratorPadCheck -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: SimplifyBooleanReturnCheck /** * <p>A utility class for performing bidirectional resolution processing.</p> @@ -140,7 +136,7 @@ public final class BidiResolver { runsNew.addAll(ir.split()); } } - if (! runsNew.equals(runs)) { + if (!runsNew.equals(runs)) { runs = runsNew; } return runs; @@ -180,7 +176,7 @@ public final class BidiResolver { i = e - 1; } } - if (! runsNew.equals(runs)) { + if (!runsNew.equals(runs)) { runs = runsNew; } return runs; @@ -232,7 +228,7 @@ public final class BidiResolver { Vector rv = new Vector(); for (Iterator it = ranges.iterator(); it.hasNext(); ) { DelimitedTextRange r = (DelimitedTextRange) it.next(); - if (! r.isEmpty()) { + if (!r.isEmpty()) { rv.add(r); } } diff --git a/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java b/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java index 2f69eb73b..67bb0aae1 100644 --- a/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java +++ b/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java @@ -34,10 +34,7 @@ import org.apache.fop.traits.WritingModeTraits; import org.apache.fop.traits.WritingModeTraitsGetter; import org.apache.fop.util.CharUtilities; -// CSOFF: EmptyForIteratorPadCheck -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck /** * The <code>DelimitedTextRange</code> class implements the "delimited text range" as described @@ -151,7 +148,7 @@ public class DelimitedTextRange { TextInterval ti = (TextInterval) it.next(); intervalsNew.addAll(assignLevels(ti, levels)); } - if (! intervalsNew.equals(intervals)) { + if (!intervalsNew.equals(intervals)) { intervals = intervalsNew; } } diff --git a/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java b/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java index c51d95c6e..f8f67f953 100644 --- a/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java +++ b/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java @@ -35,11 +35,6 @@ import org.apache.fop.area.inline.UnresolvedPageNumber; import org.apache.fop.area.inline.WordArea; import org.apache.fop.util.CharUtilities; -// CSOFF: EmptyForIteratorPadCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: SimplifyBooleanReturnCheck - /** * The <code>InlineRun</code> class is a utility class, the instances of which are used * to capture a sequence of reordering levels associated with an inline area. @@ -179,7 +174,7 @@ public class InlineRun { if (inline instanceof WordArea) { WordArea w = (WordArea) inline; // if not already reversed, then reverse now - if (! w.isReversed()) { + if (!w.isReversed()) { if ((reversals & 1) != 0) { w.reverse(mirror); } else if (mirror && maybeNeedsMirroring()) { @@ -209,10 +204,8 @@ public class InlineRun { } return true; } - } else if ((ir.levels == null) && (levels == null)) { - return true; } else { - return false; + return (ir.levels == null) && (levels == null); } } else { return false; diff --git a/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java b/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java index d904aedbb..e1a42a473 100644 --- a/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java +++ b/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java @@ -28,7 +28,6 @@ import org.apache.fop.fo.flow.Character; import org.apache.fop.fo.flow.Leader; // CSOFF: LineLengthCheck -// CSOFF: SimplifyBooleanReturnCheck /** * <p>The <code>TextInterval</code> class is a utility class, the instances of which are used @@ -102,10 +101,8 @@ class TextInterval { return false; } else if (ti.getStart() != start) { return false; - } else if (ti.getEnd() != end) { - return false; } else { - return true; + return ti.getEnd() == end; } } else { return false; diff --git a/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java b/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java index 60ad7ba5b..c511dbd70 100644 --- a/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java +++ b/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java @@ -35,10 +35,7 @@ import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.UnresolvedPageNumber; -// CSOFF: EmptyForIteratorPadCheck // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: SimplifyBooleanReturnCheck /** * <p>The <code>UnflattenProcessor</code> class is used to reconstruct (by unflattening) a line @@ -86,10 +83,8 @@ class UnflattenProcessor { private boolean shouldFinishTextContainer(TextArea tc, InlineArea ia) { if ((tcOrig != null) && (tc != tcOrig)) { return true; - } else if ((iaLevelLast != -1) && (ia.getBidiLevel() != iaLevelLast)) { - return true; } else { - return false; + return (iaLevelLast != -1) && (ia.getBidiLevel() != iaLevelLast); } } private void finishTextContainer() { @@ -98,7 +93,7 @@ class UnflattenProcessor { private void finishTextContainer(TextArea tc, InlineArea ia) { if (tcNew != null) { updateIPD(tcNew); - if (! icNew.empty()) { + if (!icNew.empty()) { icNew.peek().addChildArea(tcNew); } else { ilNew.add(tcNew); @@ -113,12 +108,12 @@ class UnflattenProcessor { } private boolean shouldFinishInlineContainer(List<InlineParent> ich, TextArea tc, InlineArea ia) { if ((ich == null) || ich.isEmpty()) { - return ! icOrig.empty(); + return !icOrig.empty(); } else { - if (! icOrig.empty()) { + if (!icOrig.empty()) { InlineParent ic = ich.get(0); InlineParent ic0 = icOrig.peek(); - return (ic != ic0) && ! isInlineParentOf(ic, ic0); + return (ic != ic0) && !isInlineParentOf(ic, ic0); } else { return false; } @@ -128,14 +123,14 @@ class UnflattenProcessor { finishInlineContainer(null, null, null); } private void finishInlineContainer(List<InlineParent> ich, TextArea tc, InlineArea ia) { - if ((ich != null) && ! ich.isEmpty()) { // finish non-matching inner inline container(s) + if ((ich != null) && !ich.isEmpty()) { // finish non-matching inner inline container(s) for (Iterator<InlineParent> it = ich.iterator(); it.hasNext(); ) { InlineParent ic = it.next(); InlineParent ic0 = icOrig.empty() ? null : icOrig.peek(); if (ic0 == null) { assert icNew.empty(); } else if (ic != ic0) { - assert ! icNew.empty(); + assert !icNew.empty(); InlineParent icO0 = icOrig.pop(); InlineParent icN0 = icNew.pop(); assert icO0 != null; @@ -145,7 +140,7 @@ class UnflattenProcessor { } else { icNew.peek().addChildArea(icN0); } - if (! icOrig.empty() && (icOrig.peek() == ic)) { + if (!icOrig.empty() && (icOrig.peek() == ic)) { break; } } else { @@ -153,7 +148,7 @@ class UnflattenProcessor { } } } else { // finish all inline containers - while (! icNew.empty()) { + while (!icNew.empty()) { InlineParent icO0 = icOrig.pop(); InlineParent icN0 = icNew.pop(); assert icO0 != null; @@ -176,8 +171,8 @@ class UnflattenProcessor { finishInlineContainer(); } private void update(List<InlineParent> ich, TextArea tc, InlineArea ia) { - if (! alreadyUnflattened(ia)) { - if ((ich != null) && ! ich.isEmpty()) { + if (!alreadyUnflattened(ia)) { + if ((ich != null) && !ich.isEmpty()) { pushInlineContainers(ich); } if (tc != null) { @@ -345,7 +340,7 @@ class UnflattenProcessor { Area a = ia.getParentArea(); while (a != null) { if (a instanceof InlineArea) { - if ((a instanceof InlineParent) && ! (a instanceof TextArea)) { + if ((a instanceof InlineParent) && !(a instanceof TextArea)) { ich.add((InlineParent) a); } a = ((InlineArea) a) .getParentArea(); diff --git a/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java b/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java index b1234d323..694cc9245 100644 --- a/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java +++ b/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java @@ -25,12 +25,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.traits.Direction; import org.apache.fop.util.CharUtilities; -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: EmptyForIteratorPadCheck -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: ParameterNumberCheck /** * <p>The <code>UnicodeBidiAlgorithm</code> class implements functionality prescribed by @@ -125,47 +120,39 @@ public final class UnicodeBidiAlgorithm implements BidiConstants { case RLE: // start right-to-left embedding case LRO: // start left-to-right override case RLO: // start right-to-left override - { - int en; /* new embedding level */ - if ((bc == RLE) || (bc == RLO)) { - en = ((ec & ~OVERRIDE) + 1) | 1; - } else { - en = ((ec & ~OVERRIDE) + 2) & ~1; - } - if (en < (MAX_LEVELS + 1)) { - es [ ei++ ] = ec; - if ((bc == LRO) || (bc == RLO)) { - ec = en | OVERRIDE; - } else { - ec = en & ~OVERRIDE; - } + int en; /* new embedding level */ + if ((bc == RLE) || (bc == RLO)) { + en = ((ec & ~OVERRIDE) + 1) | 1; + } else { + en = ((ec & ~OVERRIDE) + 2) & ~1; + } + if (en < (MAX_LEVELS + 1)) { + es [ ei++ ] = ec; + if ((bc == LRO) || (bc == RLO)) { + ec = en | OVERRIDE; } else { - // max levels exceeded, so don't change level or override + ec = en & ~OVERRIDE; } - el = ec; - break; + } else { + // max levels exceeded, so don't change level or override } + el = ec; + break; case PDF: // pop directional formatting - { - el = ec; - if (ei > 0) { - ec = es [ --ei ]; - } else { - // ignore isolated PDF - } - break; + el = ec; + if (ei > 0) { + ec = es [ --ei ]; + } else { + // ignore isolated PDF } + break; case B: // paragraph separator - { - el = ec = defaultLevel; - ei = 0; - break; - } + el = ec = defaultLevel; + ei = 0; + break; default: - { - el = ec; - break; - } + el = ec; + break; } switch (bc) { case BN: @@ -608,7 +595,7 @@ public final class UnicodeBidiAlgorithm implements BidiConstants { private static boolean isRetainedFormatting(int[] ca, int s, int e) { for (int i = s; i < e; i++) { - if (! isRetainedFormatting(ca[i])) { + if (!isRetainedFormatting(ca[i])) { return false; } } @@ -680,7 +667,7 @@ public final class UnicodeBidiAlgorithm implements BidiConstants { } else { chOut = chIn; } - if (! triggered && triggersBidi(chOut)) { + if (!triggered && triggersBidi(chOut)) { triggered = true; } if ((chOut & 0xFF0000) == 0) { diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java index 62926dc5e..301ab846e 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java @@ -23,7 +23,6 @@ import java.util.Iterator; import java.util.List; // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck /** * <p>Base class implementation of glyph class table.</p> @@ -100,7 +99,7 @@ public final class GlyphClassTable extends GlyphMappingTable implements GlyphCla } else { for (Iterator it = entries.iterator(); it.hasNext();) { Object o = it.next(); - if (! (o instanceof Integer)) { + if (!(o instanceof Integer)) { return false; } } @@ -114,7 +113,7 @@ public final class GlyphClassTable extends GlyphMappingTable implements GlyphCla } else { for (Iterator it = entries.iterator(); it.hasNext();) { Object o = it.next(); - if (! (o instanceof MappingRange)) { + if (!(o instanceof MappingRange)) { return false; } } @@ -128,7 +127,7 @@ public final class GlyphClassTable extends GlyphMappingTable implements GlyphCla } else { for (Iterator it = entries.iterator(); it.hasNext();) { Object o = it.next(); - if (! (o instanceof GlyphCoverageTable)) { + if (!(o instanceof GlyphCoverageTable)) { return false; } } @@ -197,7 +196,7 @@ public final class GlyphClassTable extends GlyphMappingTable implements GlyphCla if (it.hasNext()) { Object o = it.next(); if (o instanceof Integer) { - firstGlyph = ((Integer) o) . intValue(); + firstGlyph = ((Integer) o) .intValue(); } else { throw new AdvancedTypographicTableFormatException("illegal entry, first entry must be Integer denoting first glyph value, but is: " + o); } @@ -210,7 +209,7 @@ public final class GlyphClassTable extends GlyphMappingTable implements GlyphCla while (it.hasNext()) { Object o = it.next(); if (o instanceof Integer) { - int gc = ((Integer) o) . intValue(); + int gc = ((Integer) o) .intValue(); gca [ i++ ] = gc; if (gc > gcMax) { gcMax = gc; diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java index 7e2d0e818..699672439 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java @@ -27,8 +27,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; // CSOFF: LineLengthCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: NoWhitespaceAfterCheck /** * <p>.Base class implementation of glyph coverage table.</p> @@ -103,7 +101,7 @@ public final class GlyphCoverageTable extends GlyphMappingTable implements Glyph } else { for (Iterator it = entries.iterator(); it.hasNext();) { Object o = it.next(); - if (! (o instanceof Integer)) { + if (!(o instanceof Integer)) { return false; } } @@ -117,7 +115,7 @@ public final class GlyphCoverageTable extends GlyphMappingTable implements Glyph } else { for (Iterator it = entries.iterator(); it.hasNext();) { Object o = it.next(); - if (! (o instanceof MappingRange)) { + if (!(o instanceof MappingRange)) { return false; } } @@ -183,7 +181,7 @@ public final class GlyphCoverageTable extends GlyphMappingTable implements Glyph for (Iterator it = entries.iterator(); it.hasNext();) { Object o = it.next(); if (o instanceof Integer) { - int gid = ((Integer) o) . intValue(); + int gid = ((Integer) o) .intValue(); if ((gid >= 0) && (gid < 65536)) { if (gid > gidMax) { map [ i++ ] = gidMax = gid; diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java index eaa16146a..3ef9a0152 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java @@ -20,7 +20,6 @@ package org.apache.fop.complexscripts.fonts; // CSOFF: LineLengthCheck -// CSOFF: InnerAssignmentCheck /** * <p>The <code>GlyphDefinitionSubtable</code> implements an abstract base of a glyph definition subtable, diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java index 0e98e4588..a5942536c 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java @@ -29,7 +29,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.complexscripts.scripts.ScriptProcessor; import org.apache.fop.complexscripts.util.GlyphSequence; -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck /** diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java index eef49c399..410f5e544 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java @@ -23,8 +23,6 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck /** @@ -162,7 +160,7 @@ public class GlyphMappingTable { int mi; if ((i = Arrays.binarySearch(sa, gid)) >= 0) { mi = getMappedIndex(gid, sa [ i ], ma [ i ]); // matches start of (some) range - } else if ((i = - (i + 1)) == 0) { + } else if ((i = -(i + 1)) == 0) { mi = -1; // precedes first range } else if (gid > ea [ --i ]) { mi = -1; // follows preceding (or last) range diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java index 6600a8256..08c533860 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java @@ -23,7 +23,6 @@ import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; // CSOFF: LineLengthCheck -// CSOFF: ParameterNumberCheck /** * <p>The <code>GlyphPositioningState</code> implements an state object used during glyph positioning diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java index d269a342a..a94fac20f 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java @@ -23,8 +23,6 @@ import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: ParameterNumberCheck /** * <p>The <code>GlyphPositioningSubtable</code> implements an abstract base of a glyph subtable, @@ -90,8 +88,8 @@ public abstract class GlyphPositioningSubtable extends GlyphSubtable implements boolean appliedOneShot = false; while (ps.hasNext()) { boolean applied = false; - if (! appliedOneShot && ps.maybeApplicable()) { - for (int i = 0, n = sta.length; ! applied && (i < n); i++) { + if (!appliedOneShot && ps.maybeApplicable()) { + for (int i = 0, n = sta.length; !applied && (i < n); i++) { if (sequenceIndex < 0) { applied = ps.apply(sta [ i ]); } else if (ps.getPosition() == (sequenceStart + sequenceIndex)) { @@ -102,7 +100,7 @@ public abstract class GlyphPositioningSubtable extends GlyphSubtable implements } } } - if (! applied || ! ps.didConsume()) { + if (!applied || !ps.didConsume()) { ps.applyDefault(); } ps.next(); diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java index b22766013..ecc933ae2 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java @@ -33,9 +33,6 @@ import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.GlyphTester; // CSOFF: LineLengthCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: ParameterNumberCheck /** * <p>The <code>GlyphPositioningTable</code> class is a glyph table that implements @@ -368,7 +365,7 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof Value[])) { + if (((o = entries.get(0)) == null) || !(o instanceof Value[])) { throw new AdvancedTypographicTableFormatException("illegal entries, single entry must be a Value[], but is: " + ((o != null) ? o.getClass() : null)); } else { Value[] va = (Value[]) o; @@ -412,7 +409,7 @@ public class GlyphPositioningTable extends GlyphTable { int offsetLast = counts[0] + counts[1]; // skip any ignored glyphs prior to first non-ignored glyph for ( ; offset < offsetLast; ++offset) { - if (! ps.isIgnoredGlyph(offset)) { + if (!ps.isIgnoredGlyph(offset)) { break; } else { ps.consume(1); @@ -429,7 +426,7 @@ public class GlyphPositioningTable extends GlyphTable { } // skip any ignored glyphs prior to second non-ignored glyph for ( ; offset < offsetLast; ++offset) { - if (! ps.isIgnoredGlyph(offset)) { + if (!ps.isIgnoredGlyph(offset)) { break; } else { ps.consume(1); @@ -513,7 +510,7 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof PairValues[][])) { + if (((o = entries.get(0)) == null) || !(o instanceof PairValues[][])) { throw new AdvancedTypographicTableFormatException("illegal entries, first (and only) entry must be a PairValues[][], but is: " + ((o != null) ? o.getClass() : null)); } else { pvm = (PairValues[][]) o; @@ -569,27 +566,27 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { cdt1 = (GlyphClassTable) o; } - if (((o = entries.get(1)) == null) || ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(1)) == null) || !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { cdt2 = (GlyphClassTable) o; } - if (((o = entries.get(2)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(2)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { nc1 = ((Integer)(o)).intValue(); } - if (((o = entries.get(3)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(3)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { nc2 = ((Integer)(o)).intValue(); } - if (((o = entries.get(4)) == null) || ! (o instanceof PairValues[][])) { + if (((o = entries.get(4)) == null) || !(o instanceof PairValues[][])) { throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be a PairValues[][], but is: " + ((o != null) ? o.getClass() : null)); } else { pvm = (PairValues[][]) o; @@ -633,7 +630,7 @@ public class GlyphPositioningTable extends GlyphTable { int enw = ps.getWidth(gi2); if ((exa != null) && (ena != null)) { Value v = ena.getAlignmentAdjustment(exa); - v.adjust(- enw, 0, 0, 0); + v.adjust(-enw, 0, 0, 0); if (ps.adjust(v)) { ps.setAdjusted(true); } @@ -704,10 +701,10 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof Anchor[])) { + if (((o = entries.get(0)) == null) || !(o instanceof Anchor[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first (and only) entry must be a Anchor[], but is: " + ((o != null) ? o.getClass() : null)); - } else if ((((Anchor[]) o) . length % 2) != 0) { - throw new AdvancedTypographicTableFormatException("illegal entries, Anchor[] array must have an even number of entries, but has: " + ((Anchor[]) o) . length); + } else if ((((Anchor[]) o) .length % 2) != 0) { + throw new AdvancedTypographicTableFormatException("illegal entries, Anchor[] array must have an even number of entries, but has: " + ((Anchor[]) o) .length); } else { aa = (Anchor[]) o; } @@ -736,7 +733,7 @@ public class GlyphPositioningTable extends GlyphTable { MarkAnchor ma = getMarkAnchor(ciMark, giMark); if (ma != null) { for (int i = 0, n = ps.getPosition(); i < n; i++) { - int gi = ps.getGlyph(- (i + 1)); + int gi = ps.getGlyph(-(i + 1)); if (ps.isMark(gi)) { continue; } else { @@ -746,7 +743,7 @@ public class GlyphPositioningTable extends GlyphTable { // start experimental fix for END OF AYAH in Lateef/Scheherazade int[] aa = ps.getAdjustment(); if (aa[2] == 0) { - v.adjust(0, 0, - ps.getWidth(giMark), 0); + v.adjust(0, 0, -ps.getWidth(giMark), 0); } // end experimental fix for END OF AYAH in Lateef/Scheherazade if (ps.adjust(v)) { @@ -835,22 +832,22 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 4 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphCoverageTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null)); } else { bct = (GlyphCoverageTable) o; } - if (((o = entries.get(1)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { nmc = ((Integer)(o)).intValue(); } - if (((o = entries.get(2)) == null) || ! (o instanceof MarkAnchor[])) { + if (((o = entries.get(2)) == null) || !(o instanceof MarkAnchor[])) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null)); } else { maa = (MarkAnchor[]) o; } - if (((o = entries.get(3)) == null) || ! (o instanceof Anchor[][])) { + if (((o = entries.get(3)) == null) || !(o instanceof Anchor[][])) { throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a Anchor[][], but is: " + ((o != null) ? o.getClass() : null)); } else { bam = (Anchor[][]) o; @@ -881,7 +878,7 @@ public class GlyphPositioningTable extends GlyphTable { int mxc = getMaxComponentCount(); if (ma != null) { for (int i = 0, n = ps.getPosition(); i < n; i++) { - int gi = ps.getGlyph(- (i + 1)); + int gi = ps.getGlyph(-(i + 1)); if (ps.isMark(gi)) { continue; } else { @@ -989,27 +986,27 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphCoverageTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null)); } else { lct = (GlyphCoverageTable) o; } - if (((o = entries.get(1)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { nmc = ((Integer)(o)).intValue(); } - if (((o = entries.get(2)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(2)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { mxc = ((Integer)(o)).intValue(); } - if (((o = entries.get(3)) == null) || ! (o instanceof MarkAnchor[])) { + if (((o = entries.get(3)) == null) || !(o instanceof MarkAnchor[])) { throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null)); } else { maa = (MarkAnchor[]) o; } - if (((o = entries.get(4)) == null) || ! (o instanceof Anchor[][][])) { + if (((o = entries.get(4)) == null) || !(o instanceof Anchor[][][])) { throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be a Anchor[][][], but is: " + ((o != null) ? o.getClass() : null)); } else { lam = (Anchor[][][]) o; @@ -1125,22 +1122,22 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 4 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphCoverageTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null)); } else { mct2 = (GlyphCoverageTable) o; } - if (((o = entries.get(1)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { nmc = ((Integer)(o)).intValue(); } - if (((o = entries.get(2)) == null) || ! (o instanceof MarkAnchor[])) { + if (((o = entries.get(2)) == null) || !(o instanceof MarkAnchor[])) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null)); } else { maa = (MarkAnchor[]) o; } - if (((o = entries.get(3)) == null) || ! (o instanceof Anchor[][])) { + if (((o = entries.get(3)) == null) || !(o instanceof Anchor[][])) { throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a Anchor[][], but is: " + ((o != null) ? o.getClass() : null)); } else { mam = (Anchor[][]) o; @@ -1274,7 +1271,7 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1367,17 +1364,17 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 3 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { cdt = (GlyphClassTable) o; } - if (((o = entries.get(1)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { ngc = ((Integer)(o)).intValue(); } - if (((o = entries.get(2)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(2)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1467,7 +1464,7 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1585,7 +1582,7 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1661,27 +1658,27 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { icdt = (GlyphClassTable) o; } - if (((o = entries.get(1)) != null) && ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(1)) != null) && !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an GlyphClassTable, but is: " + o.getClass()); } else { bcdt = (GlyphClassTable) o; } - if (((o = entries.get(2)) != null) && ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(2)) != null) && !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an GlyphClassTable, but is: " + o.getClass()); } else { lcdt = (GlyphClassTable) o; } - if (((o = entries.get(3)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(3)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { ngc = ((Integer)(o)).intValue(); } - if (((o = entries.get(4)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(4)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1752,7 +1749,7 @@ public class GlyphPositioningTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -2005,7 +2002,7 @@ public class GlyphPositioningTable extends GlyphTable { boolean first = true; sb.append("{ "); if (xPlacement != 0) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2013,7 +2010,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("xPlacement = " + xPlacement); } if (yPlacement != 0) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2021,7 +2018,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("yPlacement = " + yPlacement); } if (xAdvance != 0) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2029,7 +2026,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("xAdvance = " + xAdvance); } if (yAdvance != 0) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2037,7 +2034,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("yAdvance = " + yAdvance); } if (xPlaDevice != null) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2045,7 +2042,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("xPlaDevice = " + xPlaDevice); } if (yPlaDevice != null) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2053,7 +2050,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("xPlaDevice = " + yPlaDevice); } if (xAdvDevice != null) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2061,7 +2058,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("xAdvDevice = " + xAdvDevice); } if (yAdvDevice != null) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2118,7 +2115,7 @@ public class GlyphPositioningTable extends GlyphTable { boolean first = true; sb.append("{ "); if (glyph != 0) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2126,7 +2123,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("glyph = " + glyph); } if (value1 != null) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; @@ -2134,7 +2131,7 @@ public class GlyphPositioningTable extends GlyphTable { sb.append("value1 = " + value1); } if (value2 != null) { - if (! first) { + if (!first) { sb.append(", "); } else { first = false; diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java index 330a27593..4f6e4181c 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java @@ -29,7 +29,6 @@ import org.apache.fop.complexscripts.util.GlyphTester; import org.apache.fop.complexscripts.util.ScriptContextTester; // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck /** * <p>The <code>GlyphProcessingState</code> implements a common, base state object used during glyph substitution @@ -459,7 +458,7 @@ public class GlyphProcessingState { int start = index + offset; if (start < 0) { throw new IndexOutOfBoundsException("will attempt index at " + start); - } else if (! reverseOrder && ((start + count) > indexLast)) { + } else if (!reverseOrder && ((start + count) > indexLast)) { throw new IndexOutOfBoundsException("will attempt index at " + (start + count)); } else if (reverseOrder && ((start + 1) < count)) { throw new IndexOutOfBoundsException("will attempt index at " + (start - count)); @@ -469,7 +468,7 @@ public class GlyphProcessingState { } else if (glyphs.length != count) { throw new IllegalArgumentException("glyphs array is non-null, but its length (" + glyphs.length + "), is not equal to count (" + count + ")"); } - if (! reverseOrder) { + if (!reverseOrder) { return getGlyphsForward(start, count, ignoreTester, glyphs, counts); } else { return getGlyphsReverse(start, count, ignoreTester, glyphs, counts); @@ -484,7 +483,7 @@ public class GlyphProcessingState { if (gi == 65535) { ignored++; } else { - if ((ignoreTester == null) || ! ignoreTester.test(gi, getLookupFlags())) { + if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) { glyphs [ counted++ ] = gi; } else { ignored++; @@ -506,7 +505,7 @@ public class GlyphProcessingState { if (gi == 65535) { ignored++; } else { - if ((ignoreTester == null) || ! ignoreTester.test(gi, getLookupFlags())) { + if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) { glyphs [ counted++ ] = gi; } else { ignored++; @@ -629,7 +628,7 @@ public class GlyphProcessingState { int start = index + offset; if ((start < 0) || (start > indexLast)) { return new int[] { 0, 0 }; - } else if (! reverseOrder) { + } else if (!reverseOrder) { return getGlyphsAvailableForward(start, ignoreTester); } else { return getGlyphsAvailableReverse(start, ignoreTester); @@ -731,7 +730,7 @@ public class GlyphProcessingState { int start = index + offset; if (start < 0) { throw new IndexOutOfBoundsException("will attempt index at " + start); - } else if (! reverseOrder && ((start + count) > indexLast)) { + } else if (!reverseOrder && ((start + count) > indexLast)) { throw new IndexOutOfBoundsException("will attempt index at " + (start + count)); } else if (reverseOrder && ((start + 1) < count)) { throw new IndexOutOfBoundsException("will attempt index at " + (start - count)); @@ -741,7 +740,7 @@ public class GlyphProcessingState { } else if (associations.length != count) { throw new IllegalArgumentException("associations array is non-null, but its length (" + associations.length + "), is not equal to count (" + count + ")"); } - if (! reverseOrder) { + if (!reverseOrder) { return getAssociationsForward(start, count, ignoreTester, associations, counts); } else { return getAssociationsReverse(start, count, ignoreTester, associations, counts); @@ -757,7 +756,7 @@ public class GlyphProcessingState { if (gi == 65535) { ignored++; } else { - if ((ignoreTester == null) || ! ignoreTester.test(gi, getLookupFlags())) { + if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) { if (k < count) { associations [ k++ ] = getAssociation(i - index); counted++; @@ -785,7 +784,7 @@ public class GlyphProcessingState { if (gi == 65535) { ignored++; } else { - if ((ignoreTester == null) || ! ignoreTester.test(gi, getLookupFlags())) { + if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) { if (k < count) { associations [ k++ ] = getAssociation(i - index); counted++; @@ -1169,7 +1168,7 @@ public class GlyphProcessingState { for (int i = 0, n = ngt; i < n; i++) { GlyphTester gt = gta [ i ]; if (gt != null) { - if (! gt.test(gi, flags)) { + if (!gt.test(gi, flags)) { return false; } } diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java index 0b599d088..108f26c82 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java @@ -27,7 +27,6 @@ import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck /** * <p>The <code>GlyphSubstitutionState</code> implements an state object used during glyph substitution @@ -130,7 +129,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState { * @param predication a predication value to add to association A if predications enabled */ public void putGlyph(int glyph, GlyphSequence.CharAssociation a, Object predication) { - if (! ogb.hasRemaining()) { + if (!ogb.hasRemaining()) { ogb = growBuffer(ogb); } ogb.put(glyph); diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java index ebcf35b8d..f4e6fb924 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java @@ -23,7 +23,6 @@ import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck /** * <p>The <code>GlyphSubstitutionSubtable</code> implements an abstract base of a glyph substitution subtable, @@ -90,8 +89,8 @@ public abstract class GlyphSubstitutionSubtable extends GlyphSubtable implements boolean appliedOneShot = false; while (ss.hasNext()) { boolean applied = false; - if (! appliedOneShot && ss.maybeApplicable()) { - for (int i = 0, n = sta.length; ! applied && (i < n); i++) { + if (!appliedOneShot && ss.maybeApplicable()) { + for (int i = 0, n = sta.length; !applied && (i < n); i++) { if (sequenceIndex < 0) { applied = ss.apply(sta [ i ]); } else if (ss.getPosition() == (sequenceStart + sequenceIndex)) { @@ -102,7 +101,7 @@ public abstract class GlyphSubstitutionSubtable extends GlyphSubtable implements } } } - if (! applied || ! ss.didConsume()) { + if (!applied || !ss.didConsume()) { ss.applyDefault(); } ss.next(); diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java index 18aba212c..da708bf45 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java @@ -31,9 +31,7 @@ import org.apache.fop.complexscripts.scripts.ScriptProcessor; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.GlyphTester; -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck /** * <p>The <code>GlyphSubstitutionTable</code> class is a glyph table that implements @@ -306,7 +304,7 @@ public class GlyphSubstitutionTable extends GlyphTable { Object o = entries.get(0); int delta = 0; if (o instanceof Integer) { - delta = ((Integer) o) . intValue(); + delta = ((Integer) o) .intValue(); } else { throw new AdvancedTypographicTableFormatException("illegal entries entry, must be Integer, but is: " + o); } @@ -440,7 +438,7 @@ public class GlyphSubstitutionTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof int[][])) { + if (((o = entries.get(0)) == null) || !(o instanceof int[][])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an int[][], but is: " + ((o != null) ? o.getClass() : null)); } else { gsa = (int[][]) o; @@ -801,7 +799,7 @@ public class GlyphSubstitutionTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -894,17 +892,17 @@ public class GlyphSubstitutionTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 3 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { cdt = (GlyphClassTable) o; } - if (((o = entries.get(1)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(1)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { ngc = ((Integer)(o)).intValue(); } - if (((o = entries.get(2)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(2)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -994,7 +992,7 @@ public class GlyphSubstitutionTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1113,7 +1111,7 @@ public class GlyphSubstitutionTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1189,27 +1187,27 @@ public class GlyphSubstitutionTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { icdt = (GlyphClassTable) o; } - if (((o = entries.get(1)) != null) && ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(1)) != null) && !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an GlyphClassTable, but is: " + o.getClass()); } else { bcdt = (GlyphClassTable) o; } - if (((o = entries.get(2)) != null) && ! (o instanceof GlyphClassTable)) { + if (((o = entries.get(2)) != null) && !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an GlyphClassTable, but is: " + o.getClass()); } else { lcdt = (GlyphClassTable) o; } - if (((o = entries.get(3)) == null) || ! (o instanceof Integer)) { + if (((o = entries.get(3)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { ngc = ((Integer)(o)).intValue(); } - if (((o = entries.get(4)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(4)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; @@ -1280,7 +1278,7 @@ public class GlyphSubstitutionTable extends GlyphTable { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry"); } else { Object o; - if (((o = entries.get(0)) == null) || ! (o instanceof RuleSet[])) { + if (((o = entries.get(0)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java index a3cad4fd7..e5af471af 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java @@ -24,7 +24,6 @@ import java.lang.ref.WeakReference; import java.util.List; import java.util.Map; -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck /** diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java index 33f59194b..d130e654a 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java @@ -37,12 +37,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; -// CSOFF: EmptyForIteratorPadCheck -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: ParameterNumberCheck -// CSOFF: SimplifyBooleanReturnCheck /** * <p>Base class for all advanced typographic glyph tables.</p> @@ -86,7 +81,7 @@ public class GlyphTable { * @param lookups map from lookup specs to lookup tables */ public GlyphTable(GlyphTable gdef, Map/*<LookupSpec,List<String>>*/ lookups) { - if ((gdef != null) && ! (gdef instanceof GlyphDefinitionTable)) { + if ((gdef != null) && !(gdef instanceof GlyphDefinitionTable)) { throw new AdvancedTypographicTableFormatException("bad glyph definition table"); } else if (lookups == null) { throw new AdvancedTypographicTableFormatException("lookups must be non-null map"); @@ -166,7 +161,7 @@ public class GlyphTable { * create resulting cached state. */ protected void freezeSubtables() { - if (! frozen) { + if (!frozen) { for (Iterator it = lookupTables.values().iterator(); it.hasNext(); ) { LookupTable lt = (LookupTable) it.next(); lt.freezeSubtables(lookupTables); @@ -188,18 +183,18 @@ public class GlyphTable { List/*<LookupSpec>*/ matches = new ArrayList/*<LookupSpec>*/(); for (Iterator it = keys.iterator(); it.hasNext();) { LookupSpec ls = (LookupSpec) it.next(); - if (! "*".equals(script)) { - if (! ls.getScript().equals(script)) { + if (!"*".equals(script)) { + if (!ls.getScript().equals(script)) { continue; } } - if (! "*".equals(language)) { - if (! ls.getLanguage().equals(language)) { + if (!"*".equals(language)) { + if (!ls.getLanguage().equals(language)) { continue; } } - if (! "*".equals(feature)) { - if (! ls.getFeature().equals(feature)) { + if (!"*".equals(feature)) { + if (!ls.getFeature().equals(feature)) { continue; } } @@ -359,17 +354,17 @@ public class GlyphTable { * @param permitWildcard if true the permit wildcard script, language, or feature */ LookupSpec(String script, String language, String feature, boolean permitEmpty, boolean permitWildcard) { - if ((script == null) || (! permitEmpty && (script.length() == 0))) { + if ((script == null) || (!permitEmpty && (script.length() == 0))) { throw new AdvancedTypographicTableFormatException("script must be non-empty string"); - } else if ((language == null) || (! permitEmpty && (language.length() == 0))) { + } else if ((language == null) || (!permitEmpty && (language.length() == 0))) { throw new AdvancedTypographicTableFormatException("language must be non-empty string"); - } else if ((feature == null) || (! permitEmpty && (feature.length() == 0))) { + } else if ((feature == null) || (!permitEmpty && (feature.length() == 0))) { throw new AdvancedTypographicTableFormatException("feature must be non-empty string"); - } else if (! permitWildcard && script.equals("*")) { + } else if (!permitWildcard && script.equals("*")) { throw new AdvancedTypographicTableFormatException("script must not be wildcard"); - } else if (! permitWildcard && language.equals("*")) { + } else if (!permitWildcard && language.equals("*")) { throw new AdvancedTypographicTableFormatException("language must not be wildcard"); - } else if (! permitWildcard && feature.equals("*")) { + } else if (!permitWildcard && feature.equals("*")) { throw new AdvancedTypographicTableFormatException("feature must not be wildcard"); } this.script = script.trim(); @@ -405,14 +400,12 @@ public class GlyphTable { public boolean equals(Object o) { if (o instanceof LookupSpec) { LookupSpec l = (LookupSpec) o; - if (! l.script.equals(script)) { + if (!l.script.equals(script)) { return false; - } else if (! l.language.equals(language)) { - return false; - } else if (! l.feature.equals(feature)) { + } else if (!l.language.equals(language)) { return false; } else { - return true; + return l.feature.equals(feature); } } else { return false; @@ -540,7 +533,7 @@ public class GlyphTable { } } // append at end of list - if (! added && (subtable != null)) { + if (!added && (subtable != null)) { subtables.add(subtable); added = true; } @@ -567,7 +560,7 @@ public class GlyphTable { } if (subtables.size() > 0) { GlyphSubtable st = (GlyphSubtable) subtables.get(0); - if (! st.isCompatible(subtable)) { + if (!st.isCompatible(subtable)) { throw new AdvancedTypographicTableFormatException("subtable " + subtable + " is not compatible with subtable " + st); } } @@ -580,7 +573,7 @@ public class GlyphTable { * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables */ public void freezeSubtables(Map/*<String,LookupTable>*/ lookupTables) { - if (! frozen) { + if (!frozen) { GlyphSubtable[] sta = getSubtables(); resolveLookupReferences(sta, lookupTables); this.subtablesArray = sta; @@ -1305,7 +1298,7 @@ public class GlyphTable { Class c = r0.getClass(); for (int i = 1, n = rules.length; i < n; i++) { Rule r = rules[i]; - if ((r != null) && ! c.isInstance(r)) { + if ((r != null) && !c.isInstance(r)) { throw new AdvancedTypographicTableFormatException("rules[" + i + "] is not an instance of " + c.getName()); } } diff --git a/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java index b5457e4e4..de083a995 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java +++ b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java @@ -29,14 +29,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.fonts.truetype.FontFileReader; -import org.apache.fop.fonts.truetype.TTFDirTabEntry; -import org.apache.fop.fonts.truetype.TTFFile; -import org.apache.fop.fonts.truetype.TTFTableName; - -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: SimplifyBooleanReturnCheck +import org.apache.fop.fonts.truetype.OFDirTabEntry; +import org.apache.fop.fonts.truetype.OFTableName; +import org.apache.fop.fonts.truetype.OpenFont; + // CSOFF: LineLengthCheck /** @@ -50,7 +46,7 @@ public final class OTFAdvancedTypographicTableReader { // logging state private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class); // instance state - private TTFFile ttf; // parent font file reader + private OpenFont otf; // parent font file reader private FontFileReader in; // input reader private GlyphDefinitionTable gdef; // glyph definition table private GlyphSubstitutionTable gsub; // glyph substitution table @@ -68,10 +64,10 @@ public final class OTFAdvancedTypographicTableReader { * @param ttf parent font file reader (must be non-null) * @param in font file reader (must be non-null) */ - public OTFAdvancedTypographicTableReader(TTFFile ttf, FontFileReader in) { - assert ttf != null; + public OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in) { + assert otf != null; assert in != null; - this.ttf = ttf; + this.otf = otf; this.in = in; } @@ -127,7 +123,8 @@ public final class OTFAdvancedTypographicTableReader { return gpos; } - private void readLangSysTable(TTFTableName tableTag, long langSysTable, String langSysTag) throws IOException { + private void readLangSysTable(OFTableName tableTag, long langSysTable, String langSysTag) + throws IOException { in.seekSet(langSysTable); if (log.isDebugEnabled()) { log.debug(tableTag + " lang sys table: " + langSysTag); @@ -169,7 +166,7 @@ public final class OTFAdvancedTypographicTableReader { private static String defaultTag = "dflt"; - private void readScriptTable(TTFTableName tableTag, long scriptTable, String scriptTag) throws IOException { + private void readScriptTable(OFTableName tableTag, long scriptTable, String scriptTag) throws IOException { in.seekSet(scriptTable); if (log.isDebugEnabled()) { log.debug(tableTag + " script table: " + scriptTag); @@ -222,7 +219,7 @@ public final class OTFAdvancedTypographicTableReader { seLanguages = null; } - private void readScriptList(TTFTableName tableTag, long scriptList) throws IOException { + private void readScriptList(OFTableName tableTag, long scriptList) throws IOException { in.seekSet(scriptList); // read script record count int ns = in.readTTFUShort(); @@ -251,7 +248,7 @@ public final class OTFAdvancedTypographicTableReader { } } - private void readFeatureTable(TTFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException { + private void readFeatureTable(OFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException { in.seekSet(featureTable); if (log.isDebugEnabled()) { log.debug(tableTag + " feature table: " + featureTag); @@ -279,7 +276,7 @@ public final class OTFAdvancedTypographicTableReader { seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul }); } - private void readFeatureList(TTFTableName tableTag, long featureList) throws IOException { + private void readFeatureList(OFTableName tableTag, long featureList) throws IOException { in.seekSet(featureList); // read feature record count int nf = in.readTTFUShort(); @@ -1736,28 +1733,28 @@ public final class OTFAdvancedTypographicTableReader { // XPlacement int xp; if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) { - xp = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + xp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { xp = 0; } // YPlacement int yp; if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) { - yp = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + yp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { yp = 0; } // XAdvance int xa; if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) { - xa = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + xa = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { xa = 0; } // YAdvance int ya; if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) { - ya = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + ya = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { ya = 0; } @@ -2029,23 +2026,23 @@ public final class OTFAdvancedTypographicTableReader { int af = in.readTTFUShort(); if (af == 1) { // read x coordinate - int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate - int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); a = new GlyphPositioningTable.Anchor(x, y); } else if (af == 2) { // read x coordinate - int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate - int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read anchor point index int ap = in.readTTFUShort(); a = new GlyphPositioningTable.Anchor(x, y, ap); } else if (af == 3) { // read x coordinate - int x = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate - int y = ttf.convertTTFUnit2PDFUnit(in.readTTFShort()); + int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read x device table offset int xdo = in.readTTFUShort(); // read y device table offset @@ -3145,9 +3142,9 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readLookupTable(TTFTableName tableTag, int lookupSequence, long lookupTable) throws IOException { - boolean isGSUB = tableTag.equals(TTFTableName.GSUB); - boolean isGPOS = tableTag.equals(TTFTableName.GPOS); + private void readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable) throws IOException { + boolean isGSUB = tableTag.equals(OFTableName.GSUB); + boolean isGPOS = tableTag.equals(OFTableName.GPOS); in.seekSet(lookupTable); // read lookup type int lt = in.readTTFUShort(); @@ -3198,7 +3195,7 @@ public final class OTFAdvancedTypographicTableReader { } } - private void readLookupList(TTFTableName tableTag, long lookupList) throws IOException { + private void readLookupList(OFTableName tableTag, long lookupList) throws IOException { in.seekSet(lookupList); // read lookup record count int nl = in.readTTFUShort(); @@ -3233,7 +3230,7 @@ public final class OTFAdvancedTypographicTableReader { * @param lookupList offset to lookup list from beginning of font file * @throws IOException In case of a I/O problem */ - private void readCommonLayoutTables(TTFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException { + private void readCommonLayoutTables(OFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException { if (scriptList > 0) { readScriptList(tableTag, scriptList); } @@ -3245,7 +3242,7 @@ public final class OTFAdvancedTypographicTableReader { } } - private void readGDEFClassDefTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // subtable is a bare class definition table @@ -3257,7 +3254,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // read coverage offset @@ -3275,7 +3272,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFLigatureCaretTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFLigatureCaretTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // read coverage offset @@ -3305,7 +3302,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFMarkAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFMarkAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // subtable is a bare class definition table @@ -3317,7 +3314,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFMarkGlyphsTableFormat1(TTFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException { + private void readGDEFMarkGlyphsTableFormat1(OFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException { initATSubState(); in.seekSet(subtableOffset); // skip over format (already known) @@ -3351,7 +3348,7 @@ public final class OTFAdvancedTypographicTableReader { resetATSubState(); } - private void readGDEFMarkGlyphsTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { + private void readGDEFMarkGlyphsTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read mark set subtable format int sf = in.readTTFUShort(); @@ -3367,17 +3364,17 @@ public final class OTFAdvancedTypographicTableReader { * @throws IOException In case of a I/O problem */ private void readGDEF() throws IOException { - TTFTableName tableTag = TTFTableName.GDEF; + OFTableName tableTag = OFTableName.GDEF; // Initialize temporary state initATState(); // Read glyph definition (GDEF) table - TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag); + OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gdef != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { - ttf.seekTab(in, tableTag, 0); + otf.seekTab(in, tableTag, 0); long version = in.readTTFULong(); if (log.isDebugEnabled()) { log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); @@ -3440,17 +3437,17 @@ public final class OTFAdvancedTypographicTableReader { * @throws IOException In case of a I/O problem */ private void readGSUB() throws IOException { - TTFTableName tableTag = TTFTableName.GSUB; + OFTableName tableTag = OFTableName.GSUB; // Initialize temporary state initATState(); // Read glyph substitution (GSUB) table - TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag); + OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gpos != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { - ttf.seekTab(in, tableTag, 0); + otf.seekTab(in, tableTag, 0); int version = in.readTTFLong(); if (log.isDebugEnabled()) { log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); @@ -3477,17 +3474,17 @@ public final class OTFAdvancedTypographicTableReader { * @throws IOException In case of a I/O problem */ private void readGPOS() throws IOException { - TTFTableName tableTag = TTFTableName.GPOS; + OFTableName tableTag = OFTableName.GPOS; // Initialize temporary state initATState(); // Read glyph positioning (GPOS) table - TTFDirTabEntry dirTab = ttf.getDirectoryEntry(tableTag); + OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gpos != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { - ttf.seekTab(in, tableTag, 0); + otf.seekTab(in, tableTag, 0); int version = in.readTTFLong(); if (log.isDebugEnabled()) { log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536)); @@ -3790,7 +3787,7 @@ public final class OTFAdvancedTypographicTableReader { } else { boolean first = true; for (int i = 0; i < ia.length; i++) { - if (! first) { + if (!first) { sb.append(' '); } else { first = false; diff --git a/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java index 1d310b21d..5e68c8763 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java @@ -33,10 +33,6 @@ import org.apache.fop.complexscripts.util.GlyphContextTester; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: SimplifyBooleanReturnCheck // CSOFF: LineLengthCheck /** @@ -159,11 +155,11 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } else { int s = a.getStart(); int e = a.getEnd(); - if (! hasFinalPrecedingContext(ca, nc, s, e)) { + if (!hasFinalPrecedingContext(ca, nc, s, e)) { return false; } else if (forcesFinalThisContext(ca, nc, s, e)) { return true; - } else if (! hasFinalFollowingContext(ca, nc, s, e)) { + } else if (!hasFinalFollowingContext(ca, nc, s, e)) { return false; } else { return true; @@ -180,9 +176,9 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } else { int s = a.getStart(); int e = a.getEnd(); - if (! hasInitialPrecedingContext(ca, nc, s, e)) { + if (!hasInitialPrecedingContext(ca, nc, s, e)) { return false; - } else if (! hasInitialFollowingContext(ca, nc, s, e)) { + } else if (!hasInitialFollowingContext(ca, nc, s, e)) { return false; } else { return true; @@ -211,9 +207,9 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } else { int s = a.getStart(); int e = a.getEnd(); - if (! hasLigaturePrecedingContext(ca, nc, s, e)) { + if (!hasLigaturePrecedingContext(ca, nc, s, e)) { return false; - } else if (! hasLigatureFollowingContext(ca, nc, s, e)) { + } else if (!hasLigatureFollowingContext(ca, nc, s, e)) { return false; } else { return true; @@ -230,11 +226,11 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } else { int s = a.getStart(); int e = a.getEnd(); - if (! hasMedialPrecedingContext(ca, nc, s, e)) { + if (!hasMedialPrecedingContext(ca, nc, s, e)) { return false; - } else if (! hasMedialThisContext(ca, nc, s, e)) { + } else if (!hasMedialThisContext(ca, nc, s, e)) { return false; - } else if (! hasMedialFollowingContext(ca, nc, s, e)) { + } else if (!hasMedialFollowingContext(ca, nc, s, e)) { return false; } else { return true; diff --git a/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java index 0459863ee..e3843f444 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java @@ -24,11 +24,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.complexscripts.util.GlyphSequence; -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: WhitespaceAfter -// CSOFF: InnerAssignmentCheck -// CSOFF: SimplifyBooleanReturnCheck // CSOFF: LineLengthCheck /** @@ -109,7 +104,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } private static boolean containsHalfConsonant(GlyphSequence gs, int k) { - Boolean half = (Boolean) gs.getAssociation(k) . getPredication("half"); + Boolean half = (Boolean) gs.getAssociation(k) .getPredication("half"); return (half != null) ? half.booleanValue() : false; } @@ -134,7 +129,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { // first candidate target is after first non-half consonant for (int i = 0; i < ng; i++) { if ((i != source) && containsConsonant(gs, i)) { - if (! containsHalfConsonant(gs, i)) { + if (!containsHalfConsonant(gs, i)) { c1 = i + 1; break; } @@ -142,7 +137,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark for (int i = (c1 >= 0) ? c1 : 0; i < ng; i++) { - if (containsMatra(gs, i) && ! containsPreBaseMatra(gs, i)) { + if (containsMatra(gs, i) && !containsPreBaseMatra(gs, i)) { c2 = i + 1; } else if (containsOtherMark(gs, i)) { c2 = i; @@ -159,7 +154,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } private static boolean containsReph(GlyphSequence gs, int k) { - Boolean rphf = (Boolean) gs.getAssociation(k) . getPredication("rphf"); + Boolean rphf = (Boolean) gs.getAssociation(k) .getPredication("rphf"); return (rphf != null) ? rphf.booleanValue() : false; } @@ -503,13 +498,13 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } } static boolean isC(int c) { - return isType(c,C_C); + return isType(c, C_C); } static boolean isR(int c) { - return isType(c,C_C) && hasR(c); + return isType(c, C_C) && hasR(c); } static boolean isV(int c) { - return isType(c,C_V); + return isType(c, C_V); } static boolean isN(int c) { return c == 0x093C; @@ -518,10 +513,10 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { return c == 0x094D; } static boolean isM(int c) { - return isType(c,C_M); + return isType(c, C_M); } static boolean isPreM(int c) { - return isType(c,C_M) && hasFlag(c,C_PRE); + return isType(c, C_M) && hasFlag(c, C_PRE); } static boolean isX(int c) { switch (typeOf(c)) { @@ -535,10 +530,10 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } } static boolean hasR(int c) { - return hasFlag(c,C_R); + return hasFlag(c, C_R); } static boolean hasN(int c) { - return hasFlag(c,C_N); + return hasFlag(c, C_N); } } diff --git a/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java index dc97e79a8..e4519623e 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java @@ -24,11 +24,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.complexscripts.util.GlyphSequence; -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: WhitespaceAfter -// CSOFF: InnerAssignmentCheck -// CSOFF: SimplifyBooleanReturnCheck // CSOFF: LineLengthCheck /** @@ -109,7 +104,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } private static boolean containsHalfConsonant(GlyphSequence gs, int k) { - Boolean half = (Boolean) gs.getAssociation(k) . getPredication("half"); + Boolean half = (Boolean) gs.getAssociation(k) .getPredication("half"); return (half != null) ? half.booleanValue() : false; } @@ -134,7 +129,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { // first candidate target is after first non-half consonant for (int i = 0; i < ng; i++) { if ((i != source) && containsConsonant(gs, i)) { - if (! containsHalfConsonant(gs, i)) { + if (!containsHalfConsonant(gs, i)) { c1 = i + 1; break; } @@ -142,7 +137,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark for (int i = (c1 >= 0) ? c1 : 0; i < ng; i++) { - if (containsMatra(gs, i) && ! containsPreBaseMatra(gs, i)) { + if (containsMatra(gs, i) && !containsPreBaseMatra(gs, i)) { c2 = i + 1; } else if (containsOtherMark(gs, i)) { c2 = i; @@ -159,7 +154,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } private static boolean containsReph(GlyphSequence gs, int k) { - Boolean rphf = (Boolean) gs.getAssociation(k) . getPredication("rphf"); + Boolean rphf = (Boolean) gs.getAssociation(k) .getPredication("rphf"); return (rphf != null) ? rphf.booleanValue() : false; } @@ -503,13 +498,13 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } } static boolean isC(int c) { - return isType(c,C_C); + return isType(c, C_C); } static boolean isR(int c) { - return isType(c,C_C) && hasR(c); + return isType(c, C_C) && hasR(c); } static boolean isV(int c) { - return isType(c,C_V); + return isType(c, C_V); } static boolean isN(int c) { return c == 0x0ABC; @@ -518,10 +513,10 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { return c == 0x0ACD; } static boolean isM(int c) { - return isType(c,C_M); + return isType(c, C_M); } static boolean isPreM(int c) { - return isType(c,C_M) && hasFlag(c,C_PRE); + return isType(c, C_M) && hasFlag(c, C_PRE); } static boolean isX(int c) { switch (typeOf(c)) { @@ -535,10 +530,10 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } } static boolean hasR(int c) { - return hasFlag(c,C_R); + return hasFlag(c, C_R); } static boolean hasN(int c) { - return hasFlag(c,C_N); + return hasFlag(c, C_N); } } diff --git a/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java index 262d56864..9c4d49f59 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java @@ -25,11 +25,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; import org.apache.fop.complexscripts.util.GlyphSequence; -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: WhitespaceAfter -// CSOFF: InnerAssignmentCheck -// CSOFF: SimplifyBooleanReturnCheck // CSOFF: LineLengthCheck /** @@ -110,7 +105,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } private static boolean containsHalfConsonant(GlyphSequence gs, int k) { - Boolean half = (Boolean) gs.getAssociation(k) . getPredication("half"); + Boolean half = (Boolean) gs.getAssociation(k) .getPredication("half"); return (half != null) ? half.booleanValue() : false; } @@ -135,7 +130,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { // first candidate target is after first non-half consonant for (int i = 0; i < ng; i++) { if ((i != source) && containsConsonant(gs, i)) { - if (! containsHalfConsonant(gs, i)) { + if (!containsHalfConsonant(gs, i)) { c1 = i + 1; break; } @@ -143,7 +138,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark for (int i = (c1 >= 0) ? c1 : 0; i < ng; i++) { - if (containsMatra(gs, i) && ! containsPreBaseMatra(gs, i)) { + if (containsMatra(gs, i) && !containsPreBaseMatra(gs, i)) { c2 = i + 1; } else if (containsOtherMark(gs, i)) { c2 = i; @@ -160,7 +155,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } private static boolean containsReph(GlyphSequence gs, int k) { - Boolean rphf = (Boolean) gs.getAssociation(k) . getPredication("rphf"); + Boolean rphf = (Boolean) gs.getAssociation(k) .getPredication("rphf"); return (rphf != null) ? rphf.booleanValue() : false; } @@ -504,13 +499,13 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } } static boolean isC(int c) { - return isType(c,C_C); + return isType(c, C_C); } static boolean isR(int c) { - return isType(c,C_C) && hasR(c); + return isType(c, C_C) && hasR(c); } static boolean isV(int c) { - return isType(c,C_V); + return isType(c, C_V); } static boolean isN(int c) { return c == 0x0A3C; @@ -519,10 +514,10 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { return c == 0x0A4D; } static boolean isM(int c) { - return isType(c,C_M); + return isType(c, C_M); } static boolean isPreM(int c) { - return isType(c,C_M) && hasFlag(c,C_PRE); + return isType(c, C_M) && hasFlag(c, C_PRE); } static boolean isX(int c) { switch (typeOf(c)) { @@ -536,10 +531,10 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } } static boolean hasR(int c) { - return hasFlag(c,C_R); + return hasFlag(c, C_R); } static boolean hasN(int c) { - return hasFlag(c,C_N); + return hasFlag(c, C_N); } @Override diff --git a/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java index c16c9fe0c..fcabad396 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java @@ -36,13 +36,6 @@ import org.apache.fop.complexscripts.util.GlyphContextTester; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: SimplifyBooleanReturnCheck -// CSOFF: EmptyForIteratorPadCheck -// CSOFF: WhitespaceAfterCheck -// CSOFF: ParameterNumberCheck // CSOFF: LineLengthCheck /** @@ -222,7 +215,7 @@ public class IndicScriptProcessor extends DefaultScriptProcessor { } private GlyphSequence[] syllabize(GlyphSequence gs, String script, String language) { - return Syllabizer.getSyllabizer(script, language, getSyllabizerClass()) . syllabize(gs); + return Syllabizer.getSyllabizer(script, language, getSyllabizerClass()) .syllabize(gs); } private GlyphSequence unsyllabize(GlyphSequence gs, GlyphSequence[] sa) { @@ -384,12 +377,10 @@ public class IndicScriptProcessor extends DefaultScriptProcessor { public boolean equals(Object o) { if (o instanceof Syllabizer) { Syllabizer s = (Syllabizer) o; - if (! s.script.equals(script)) { - return false; - } else if (! s.language.equals(language)) { + if (!s.script.equals(script)) { return false; } else { - return true; + return s.language.equals(language); } } else { return false; @@ -408,7 +399,7 @@ public class IndicScriptProcessor extends DefaultScriptProcessor { } return d; } - private static Map<String,Syllabizer> syllabizers = new HashMap<String,Syllabizer>(); + private static Map<String, Syllabizer> syllabizers = new HashMap<String, Syllabizer>(); static Syllabizer getSyllabizer(String script, String language, Class<? extends Syllabizer> syllabizerClass) { String sid = makeSyllabizerId(script, language); Syllabizer s = syllabizers.get(sid); diff --git a/src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java index 72d092b12..cfcc4ff59 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java @@ -31,11 +31,7 @@ import org.apache.fop.complexscripts.util.CharScript; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: ParameterNumberCheck -// CSOFF: SimplifyBooleanReturnCheck /** * <p>Abstract script processor base class for which an implementation of the substitution and positioning methods @@ -278,14 +274,12 @@ public abstract class ScriptProcessor { public boolean equals(Object o) { if (o instanceof AssembledLookupsKey) { AssembledLookupsKey k = (AssembledLookupsKey) o; - if (! table.equals(k.table)) { + if (!table.equals(k.table)) { return false; - } else if (! Arrays.equals(features, k.features)) { - return false; - } else if (! lookups.equals(k.lookups)) { + } else if (!Arrays.equals(features, k.features)) { return false; } else { - return true; + return lookups.equals(k.lookups); } } else { return false; diff --git a/src/java/org/apache/fop/complexscripts/util/CharScript.java b/src/java/org/apache/fop/complexscripts/util/CharScript.java index e81313aef..6809be609 100644 --- a/src/java/org/apache/fop/complexscripts/util/CharScript.java +++ b/src/java/org/apache/fop/complexscripts/util/CharScript.java @@ -28,12 +28,6 @@ import java.util.Set; import org.apache.fop.util.CharUtilities; -// CSOFF: AvoidNestedBlocksCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: LineLengthCheck -// CSOFF: SimplifyBooleanReturnCheck -// CSOFF: WhitespaceAfterCheck - /** * <p>Script related utilities.</p> * @@ -41,6 +35,8 @@ import org.apache.fop.util.CharUtilities; */ public final class CharScript { + // CSOFF: LineLength + // // The following script codes are based on ISO 15924. Codes less than 1000 are // official assignments from 15924; those equal to or greater than 1000 are FOP @@ -754,16 +750,14 @@ public final class CharScript { case SCRIPT_UNCODED: break; default: - { - Integer v = (Integer) e.getValue(); - assert v != null; - int c = v.intValue(); - if (c > cMax) { - cMax = c; - sMax = s; - } - break; + Integer v = (Integer) e.getValue(); + assert v != null; + int c = v.intValue(); + if (c > cMax) { + cMax = c; + sMax = s; } + break; } } if (sMax < 0) { @@ -823,7 +817,7 @@ public final class CharScript { * @return a script tag */ public static String scriptTagFromCode(int code) { - Map<Integer,String> m = getScriptTagsMap(); + Map<Integer, String> m = getScriptTagsMap(); if (m != null) { String tag; if ((tag = m.get(Integer.valueOf(code))) != null) { @@ -842,7 +836,7 @@ public final class CharScript { * @return a script code */ public static int scriptCodeFromTag(String tag) { - Map<String,Integer> m = getScriptCodeMap(); + Map<String, Integer> m = getScriptCodeMap(); if (m != null) { Integer c; if ((c = m.get(tag)) != null) { @@ -855,8 +849,8 @@ public final class CharScript { } } - private static Map<Integer,String> scriptTagsMap = null; - private static Map<String,Integer> scriptCodeMap = null; + private static Map<Integer, String> scriptTagsMap = null; + private static Map<String, Integer> scriptCodeMap = null; private static void putScriptTag(Map tm, Map cm, int code, String tag) { assert tag != null; @@ -868,8 +862,8 @@ public final class CharScript { } private static void makeScriptMaps() { - HashMap<Integer,String> tm = new HashMap<Integer,String>(); - HashMap<String,Integer> cm = new HashMap<String,Integer>(); + HashMap<Integer, String> tm = new HashMap<Integer, String>(); + HashMap<String, Integer> cm = new HashMap<String, Integer>(); putScriptTag(tm, cm, SCRIPT_HEBREW, "hebr"); putScriptTag(tm, cm, SCRIPT_MONGOLIAN, "mong"); putScriptTag(tm, cm, SCRIPT_ARABIC, "arab"); @@ -915,14 +909,14 @@ public final class CharScript { scriptCodeMap = cm; } - private static Map<Integer,String> getScriptTagsMap() { + private static Map<Integer, String> getScriptTagsMap() { if (scriptTagsMap == null) { makeScriptMaps(); } return scriptTagsMap; } - private static Map<String,Integer> getScriptCodeMap() { + private static Map<String, Integer> getScriptCodeMap() { if (scriptCodeMap == null) { makeScriptMaps(); } diff --git a/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java b/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java index 696d2c8d3..e59dc9b32 100644 --- a/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java +++ b/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java @@ -26,10 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck -// CSOFF: WhitespaceAfterCheck -// CSOFF: NoWhitespaceAfterCheck /** * <p>A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes, @@ -639,10 +636,10 @@ public class GlyphSequence implements Cloneable { private final int offset; private final int count; private final int[] subIntervals; - private Map<String,Object> predications; + private Map<String, Object> predications; // class state - private static volatile Map<String,PredicationMerger> predicationMergers; + private static volatile Map<String, PredicationMerger> predicationMergers; interface PredicationMerger { Object merge(String key, Object v1, Object v2); @@ -724,7 +721,7 @@ public class GlyphSequence implements Cloneable { public boolean contained(int offset, int count) { int s = offset; int e = offset + count; - if (! isDisjoint()) { + if (!isDisjoint()) { int s0 = getStart(); int e0 = getEnd(); return (s0 >= s) && (e0 <= e); @@ -748,7 +745,7 @@ public class GlyphSequence implements Cloneable { */ public void setPredication(String key, Object value) { if (predications == null) { - predications = new HashMap<String,Object>(); + predications = new HashMap<String, Object>(); } if (predications != null) { predications.put(key, value); @@ -775,7 +772,7 @@ public class GlyphSequence implements Cloneable { */ public void mergePredication(String key, Object value) { if (predications == null) { - predications = new HashMap<String,Object>(); + predications = new HashMap<String, Object>(); } if (predications != null) { if (predications.containsKey(key)) { @@ -813,7 +810,7 @@ public class GlyphSequence implements Cloneable { */ public void mergePredications(CharAssociation ca) { if (ca.predications != null) { - for (Map.Entry<String,Object> e : ca.predications.entrySet()) { + for (Map.Entry<String, Object> e : ca.predications.entrySet()) { mergePredication(e.getKey(), e.getValue()); } } @@ -824,7 +821,7 @@ public class GlyphSequence implements Cloneable { try { CharAssociation ca = (CharAssociation) super.clone(); if (predications != null) { - ca.predications = new HashMap<String,Object>(predications); + ca.predications = new HashMap<String, Object>(predications); } return ca; } catch (CloneNotSupportedException e) { @@ -839,7 +836,7 @@ public class GlyphSequence implements Cloneable { */ public static void setPredicationMerger(String key, PredicationMerger pm) { if (predicationMergers == null) { - predicationMergers = new HashMap<String,PredicationMerger>(); + predicationMergers = new HashMap<String, PredicationMerger>(); } if (predicationMergers != null) { predicationMergers.put(key, pm); diff --git a/src/java/org/apache/fop/complexscripts/util/NumberConverter.java b/src/java/org/apache/fop/complexscripts/util/NumberConverter.java index 08b4a4434..bfb8b6571 100644 --- a/src/java/org/apache/fop/complexscripts/util/NumberConverter.java +++ b/src/java/org/apache/fop/complexscripts/util/NumberConverter.java @@ -23,9 +23,6 @@ import java.util.ArrayList; import java.util.List; // CSOFF: LineLengthCheck -// CSOFF: InnerAssignmentCheck -// CSOFF: NoWhitespaceAfterCheck -// CSOFF: AvoidNestedBlocksCheck /** * <p>Implementation of Number to String Conversion algorithm specified by @@ -186,10 +183,10 @@ public class NumberConverter { separators.add(token.toArray(new Integer [ token.size() ])); } } - if (! separators.isEmpty()) { + if (!separators.isEmpty()) { this.prefix = separators.remove(0); } - if (! separators.isEmpty()) { + if (!separators.isEmpty()) { this.suffix = separators.remove(separators.size() - 1); } this.separators = separators.toArray(new Integer [ separators.size() ] []); @@ -260,33 +257,27 @@ public class NumberConverter { int s = token[0].intValue(); switch (s) { case (int) '1': - { - fn = formatNumberAsDecimal(number, (int) '1', 1); - break; - } + fn = formatNumberAsDecimal(number, (int) '1', 1); + break; case (int) 'W': case (int) 'w': - { - fn = formatNumberAsWord(number, (s == (int) 'W') ? Character.UPPERCASE_LETTER : Character.LOWERCASE_LETTER); - break; - } + fn = formatNumberAsWord(number, (s == (int) 'W') ? Character.UPPERCASE_LETTER : Character.LOWERCASE_LETTER); + break; case (int) 'A': // handled as numeric sequence case (int) 'a': // handled as numeric sequence case (int) 'I': // handled as numeric special case (int) 'i': // handled as numeric special default: - { - if (isStartOfDecimalSequence(s)) { - fn = formatNumberAsDecimal(number, s, 1); - } else if (isStartOfAlphabeticSequence(s)) { - fn = formatNumberAsSequence(number, s, getSequenceBase(s), null); - } else if (isStartOfNumericSpecial(s)) { - fn = formatNumberAsSpecial(number, s); - } else { - fn = null; - } - break; + if (isStartOfDecimalSequence(s)) { + fn = formatNumberAsDecimal(number, s, 1); + } else if (isStartOfAlphabeticSequence(s)) { + fn = formatNumberAsSequence(number, s, getSequenceBase(s), null); + } else if (isStartOfNumericSpecial(s)) { + fn = formatNumberAsSpecial(number, s); + } else { + fn = null; } + break; } } else if ((token.length == 2) && (token[0] == (int) 'W') && (token[1] == (int) 'w')) { fn = formatNumberAsWord(number, Character.TITLECASE_LETTER); diff --git a/src/java/org/apache/fop/complexscripts/util/UTF32.java b/src/java/org/apache/fop/complexscripts/util/UTF32.java index 225966f54..b0f92eb88 100644 --- a/src/java/org/apache/fop/complexscripts/util/UTF32.java +++ b/src/java/org/apache/fop/complexscripts/util/UTF32.java @@ -21,7 +21,6 @@ package org.apache.fop.complexscripts.util; import org.apache.fop.util.CharUtilities; -// CSOFF: InnerAssignmentCheck /** * <p>UTF32 related utilities.</p> diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java index 28f02a762..086cbca40 100644 --- a/src/java/org/apache/fop/fo/Constants.java +++ b/src/java/org/apache/fop/fo/Constants.java @@ -816,8 +816,11 @@ public interface Constants { /** Scope for table header */ int PR_X_HEADER_COLUMN = 290; + /** For specifying PDF optional content group (layer) binding. */ + int PR_X_LAYER = 291; + /** Number of property constants defined */ - int PROPERTY_COUNT = 290; + int PROPERTY_COUNT = 291; // compound property constants diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index 36e3f21c4..cc4fe9e2c 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -2721,6 +2721,13 @@ public final class FOPropertyMapping implements Constants { m.addEnum("auto", getEnumProperty(EN_AUTO, "AUTO")); m.setDefault("auto"); addPropertyMaker("z-index", m); + + // fox:layer + m = new StringProperty.Maker(PR_X_LAYER); + m.setInherited(false); + m.setDefault(""); + addPropertyMaker("fox:layer", m); + } private void createShorthandProperties() { diff --git a/src/java/org/apache/fop/fo/FObj.java b/src/java/org/apache/fop/fo/FObj.java index 92dc9fd73..8532b27da 100644 --- a/src/java/org/apache/fop/fo/FObj.java +++ b/src/java/org/apache/fop/fo/FObj.java @@ -74,7 +74,8 @@ public abstract class FObj extends FONode implements Constants { private int bidiLevel = -1; // The value of properties relevant for all fo objects - private String id = null; + private String id; + private String layer; // End of property values /** @@ -148,7 +149,7 @@ public abstract class FObj extends FONode implements Constants { String attributeName = attList.getQName(i); String attributeValue = attList.getValue(i); Property prop = propertyList.getPropertyForAttribute(attList, attributeName, attributeValue); - if (prop.equals(value)) { + if (prop != null && prop.equals(value)) { return attributeName; } } @@ -173,6 +174,7 @@ public abstract class FObj extends FONode implements Constants { */ public void bind(PropertyList pList) throws FOPException { id = pList.get(PR_ID).getString(); + layer = pList.get(PR_X_LAYER).getString(); } /** @@ -583,6 +585,16 @@ public abstract class FObj extends FONode implements Constants { return (id != null && id.length() > 0); } + /** @return the "layer" property. */ + public String getLayer() { + return layer; + } + + /** @return whether this object has an layer set */ + public boolean hasLayer() { + return (layer != null && layer.length() > 0); + } + /** {@inheritDoc} */ public String getNamespaceURI() { return FOElementMapping.URI; @@ -611,7 +623,7 @@ public abstract class FObj extends FONode implements Constants { if (bidiLevel >= 0) { if ((this.bidiLevel < 0) || (bidiLevel < this.bidiLevel)) { this.bidiLevel = bidiLevel; - if (parent != null) { + if ((parent != null) && !isBidiPropagationBoundary()) { FObj foParent = (FObj) parent; int parentBidiLevel = foParent.getBidiLevel(); if ((parentBidiLevel < 0) || (bidiLevel < parentBidiLevel)) { @@ -646,10 +658,25 @@ public abstract class FObj extends FONode implements Constants { return level; } } + if (isBidiInheritanceBoundary()) { + break; + } } return -1; } + protected boolean isBidiBoundary(boolean propagate) { + return false; + } + + private boolean isBidiInheritanceBoundary() { + return isBidiBoundary(false); + } + + private boolean isBidiPropagationBoundary() { + return isBidiBoundary(true); + } + /** * Add a new extension attachment to this FObj. * (see org.apache.fop.fo.FONode for details) diff --git a/src/java/org/apache/fop/fo/expr/FunctionBase.java b/src/java/org/apache/fop/fo/expr/FunctionBase.java index 707424b86..e5e346060 100644 --- a/src/java/org/apache/fop/fo/expr/FunctionBase.java +++ b/src/java/org/apache/fop/fo/expr/FunctionBase.java @@ -36,7 +36,8 @@ public abstract class FunctionBase implements Function { /** {@inheritDoc} */ public Property getOptionalArgDefault(int index, PropertyInfo pi) throws PropertyException { if (index >= getOptionalArgsCount()) { - PropertyException e = new PropertyException(new IndexOutOfBoundsException("illegal optional argument index")); + PropertyException e = new PropertyException( + new IndexOutOfBoundsException("illegal optional argument index")); e.setPropertyInfo(pi); throw e; } else { diff --git a/src/java/org/apache/fop/fo/expr/PropertyParser.java b/src/java/org/apache/fop/fo/expr/PropertyParser.java index cac115d6e..26c8f6c1b 100644 --- a/src/java/org/apache/fop/fo/expr/PropertyParser.java +++ b/src/java/org/apache/fop/fo/expr/PropertyParser.java @@ -385,7 +385,8 @@ public final class PropertyParser extends PropertyTokenizer { } int numArgs = args.size(); if (numArgs < numReq) { - throw new PropertyException("Expected " + numReq + " required arguments, but only " + numArgs + " specified"); + throw new PropertyException("Expected " + numReq + " required arguments, but only " + + numArgs + " specified"); } else { for (int i = 0; i < numOpt; i++) { if (args.size() < (numReq + i + 1)) { diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index 09b47f02a..82db43e59 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -66,6 +66,8 @@ public class ExtensionElementMapping extends ElementMapping { PROPERTY_ATTRIBUTES.add("border-before-end-radius"); PROPERTY_ATTRIBUTES.add("border-after-start-radius"); PROPERTY_ATTRIBUTES.add("border-after-end-radius"); + //Optional content groups (layers) + PROPERTY_ATTRIBUTES.add("layer"); } /** diff --git a/src/java/org/apache/fop/fo/flow/BlockContainer.java b/src/java/org/apache/fop/fo/flow/BlockContainer.java index b8616c1ce..b250a117c 100644 --- a/src/java/org/apache/fop/fo/flow/BlockContainer.java +++ b/src/java/org/apache/fop/fo/flow/BlockContainer.java @@ -98,7 +98,8 @@ public class BlockContainer extends FObj implements BreakPropertySet, WritingMod referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric(); span = pList.get(PR_SPAN).getEnum(); writingModeTraits = new WritingModeTraits( - WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum())); + WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()), + pList.getExplicit(PR_WRITING_MODE) != null); disableColumnBalancing = pList.get(PR_X_DISABLE_COLUMN_BALANCING).getEnum(); } @@ -280,6 +281,14 @@ public class BlockContainer extends FObj implements BreakPropertySet, WritingMod return writingModeTraits.getWritingMode(); } + /** + * Obtain writing mode explicit indicator. + * @return the writing mode explicit indicator + */ + public boolean getExplicitWritingMode() { + return writingModeTraits.getExplicitWritingMode(); + } + /** {@inheritDoc} */ public String getLocalName() { return "block-container"; @@ -292,5 +301,10 @@ public class BlockContainer extends FObj implements BreakPropertySet, WritingMod public int getNameId() { return FO_BLOCK_CONTAINER; } -} + @Override + protected boolean isBidiBoundary(boolean propagate) { + return getExplicitWritingMode(); + } + +} diff --git a/src/java/org/apache/fop/fo/flow/InlineContainer.java b/src/java/org/apache/fop/fo/flow/InlineContainer.java index 748eb593a..5c95fa34e 100644 --- a/src/java/org/apache/fop/fo/flow/InlineContainer.java +++ b/src/java/org/apache/fop/fo/flow/InlineContainer.java @@ -37,49 +37,38 @@ import org.apache.fop.traits.Direction; import org.apache.fop.traits.WritingMode; import org.apache.fop.traits.WritingModeTraits; -/** - * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_inline-container"> - * <code>fo:inline-container</code></a> object. - */ public class InlineContainer extends FObj { - // The value of FO traits (refined properties) that apply to fo:inline-container. - private Length alignmentAdjust; - private int alignmentBaseline; - private Length baselineShift; + private LengthRangeProperty inlineProgressionDimension; private LengthRangeProperty blockProgressionDimension; + private int overflow; private CommonBorderPaddingBackground commonBorderPaddingBackground; private CommonMarginInline commonMarginInline; - private int clip; - private int dominantBaseline; - private LengthRangeProperty inlineProgressionDimension; + private Numeric referenceOrientation; + private int displayAlign; private KeepProperty keepTogether; + private KeepProperty keepWithNext; + private KeepProperty keepWithPrevious; private SpaceProperty lineHeight; - private int overflow; - private Numeric referenceOrientation; + private Length alignmentAdjust; + private int alignmentBaseline; + private Length baselineShift; + private int dominantBaseline; private WritingModeTraits writingModeTraits; - // Unused but valid items, commented out for performance: - // private CommonRelativePosition commonRelativePosition; - // private int displayAlign; - // private Length height; - // private KeepProperty keepWithNext; - // private KeepProperty keepWithPrevious; - // private Length width; - // End of FO trait values /** used for FO validation */ - private boolean blockItemFound = false; + private boolean blockItemFound; /** - * Base constructor + * Creates a new instance. * - * @param parent {@link FONode} that is the parent of this object + * @param parent the parent of this inline-container */ public InlineContainer(FONode parent) { super(parent); } - /** {@inheritDoc} */ + @Override public void bind(PropertyList pList) throws FOPException { super.bind(pList); alignmentAdjust = pList.get(PR_ALIGNMENT_ADJUST).getLength(); @@ -88,27 +77,31 @@ public class InlineContainer extends FObj { blockProgressionDimension = pList.get(PR_BLOCK_PROGRESSION_DIMENSION).getLengthRange(); commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps(); commonMarginInline = pList.getMarginInlineProps(); - clip = pList.get(PR_CLIP).getEnum(); + displayAlign = pList.get(PR_DISPLAY_ALIGN).getEnum(); dominantBaseline = pList.get(PR_DOMINANT_BASELINE).getEnum(); inlineProgressionDimension = pList.get(PR_INLINE_PROGRESSION_DIMENSION).getLengthRange(); keepTogether = pList.get(PR_KEEP_TOGETHER).getKeep(); + keepWithNext = pList.get(PR_KEEP_WITH_NEXT).getKeep(); + keepWithPrevious = pList.get(PR_KEEP_WITH_PREVIOUS).getKeep(); lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); overflow = pList.get(PR_OVERFLOW).getEnum(); referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric(); writingModeTraits = new WritingModeTraits( - WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum())); + WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()), + pList.getExplicit(PR_WRITING_MODE) != null); } /** * {@inheritDoc} * <br>XSL Content Model: marker* (%block;)+ */ + @Override protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { if (FO_URI.equals(nsURI)) { if (localName.equals("marker")) { if (blockItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + nodesOutOfOrderError(loc, "fo:marker", "(%block;)+"); } } else if (!isBlockItem(nsURI, localName)) { invalidChildError(loc, nsURI, localName); @@ -118,142 +111,131 @@ public class InlineContainer extends FObj { } } - /** {@inheritDoc} */ + @Override public void endOfNode() throws FOPException { if (!blockItemFound) { missingChildElementError("marker* (%block;)+"); } } - /** @return the "alignment-adjust" FO trait */ - public Length getAlignmentAdjust() { - return alignmentAdjust; + /** {@inheritDoc} */ + public String getLocalName() { + return "inline-container"; } - /** @return the "alignment-baseline" FO trait */ - public int getAlignmentBaseline() { - return alignmentBaseline; + /** + * {@inheritDoc} + * @return {@link org.apache.fop.fo.Constants#FO_INLINE_CONTAINER} + */ + public int getNameId() { + return FO_INLINE_CONTAINER; } - /** @return the "baseline-shift" FO trait */ - public Length getBaselineShift() { - return baselineShift; + public LengthRangeProperty getInlineProgressionDimension() { + return inlineProgressionDimension; } - /** @return the "block-progression-dimension" FO trait */ public LengthRangeProperty getBlockProgressionDimension() { return blockProgressionDimension; } - /** @return the "clip" FO trait */ - public int getClip() { - return clip; + public int getOverflow() { + return overflow; } - /**@return Returns the {@link CommonBorderPaddingBackground} */ public CommonBorderPaddingBackground getCommonBorderPaddingBackground() { return this.commonBorderPaddingBackground; } - /** @return Returns the {@link CommonMarginInline} */ public CommonMarginInline getCommonMarginInline() { return this.commonMarginInline; } - /** @return the "dominant-baseline" FO trait */ - public int getDominantBaseline() { - return dominantBaseline; + public int getReferenceOrientation() { + return referenceOrientation.getValue(); + } + + public int getDisplayAlign() { + return this.displayAlign; + } + + public KeepProperty getKeepWithPrevious() { + return keepWithPrevious; } - /** @return the "keep-together" FO trait */ public KeepProperty getKeepTogether() { return keepTogether; } - /** @return the "inline-progression-dimension" FO trait */ - public LengthRangeProperty getInlineProgressionDimension() { - return inlineProgressionDimension; + public KeepProperty getKeepWithNext() { + return keepWithNext; } - /** @return the "line-height" FO trait */ public SpaceProperty getLineHeight() { return lineHeight; } - /** @return the "overflow" FO trait */ - public int getOverflow() { - return overflow; + public Length getAlignmentAdjust() { + return alignmentAdjust; } - /** @return the "reference-orientation" FO trait */ - public int getReferenceOrientation() { - return referenceOrientation.getValue(); + public int getAlignmentBaseline() { + return alignmentBaseline; + } + + public Length getBaselineShift() { + return baselineShift; + } + + public int getDominantBaseline() { + return dominantBaseline; + } + + public WritingMode getWritingMode() { + return writingModeTraits.getWritingMode(); } /** - * Obtain inline progression direction. - * @return the inline progression direction + * Obtain writing mode explicit indicator. + * @return the writing mode explicit indicator */ + public boolean getExplicitWritingMode() { + return writingModeTraits.getExplicitWritingMode(); + } + public Direction getInlineProgressionDirection() { return writingModeTraits.getInlineProgressionDirection(); } - /** - * Obtain block progression direction. - * @return the block progression direction - */ public Direction getBlockProgressionDirection() { return writingModeTraits.getBlockProgressionDirection(); } - /** - * Obtain column progression direction. - * @return the column progression direction - */ public Direction getColumnProgressionDirection() { return writingModeTraits.getColumnProgressionDirection(); } - /** - * Obtain row progression direction. - * @return the row progression direction - */ public Direction getRowProgressionDirection() { return writingModeTraits.getRowProgressionDirection(); } - /** - * Obtain (baseline) shift direction. - * @return the (baseline) shift direction - */ public Direction getShiftDirection() { return writingModeTraits.getShiftDirection(); } - /** - * Obtain writing mode. - * @return the writing mode - */ - public WritingMode getWritingMode() { - return writingModeTraits.getWritingMode(); - } - - /** {@inheritDoc} */ - public String getLocalName() { - return "inline-container"; + @Override + public boolean isDelimitedTextRangeBoundary(int boundary) { + return false; } - /** - * {@inheritDoc} - * @return {@link org.apache.fop.fo.Constants#FO_INLINE_CONTAINER} - */ - public int getNameId() { - return FO_INLINE_CONTAINER; + @Override + public boolean generatesReferenceAreas() { + return true; } @Override - public boolean isDelimitedTextRangeBoundary(int boundary) { - return false; + protected boolean isBidiBoundary(boolean propagate) { + return getExplicitWritingMode(); } } diff --git a/src/java/org/apache/fop/fo/flow/table/Table.java b/src/java/org/apache/fop/fo/flow/table/Table.java index 983954e79..e1252e45e 100644 --- a/src/java/org/apache/fop/fo/flow/table/Table.java +++ b/src/java/org/apache/fop/fo/flow/table/Table.java @@ -139,7 +139,8 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break tableOmitFooterAtBreak = pList.get(PR_TABLE_OMIT_FOOTER_AT_BREAK).getEnum(); tableOmitHeaderAtBreak = pList.get(PR_TABLE_OMIT_HEADER_AT_BREAK).getEnum(); writingModeTraits = new WritingModeTraits( - WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum())); + WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()), + pList.getExplicit(PR_WRITING_MODE) != null); //Bind extension properties widowContentLimit = pList.get(PR_X_WIDOW_CONTENT_LIMIT).getLength(); @@ -554,6 +555,11 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break return writingModeTraits.getWritingMode(); } + /** {@inheritDoc} */ + public boolean getExplicitWritingMode() { + return writingModeTraits.getExplicitWritingMode(); + } + /** @return the "fox:widow-content-limit" extension FO trait */ public Length getWidowContentLimit() { return widowContentLimit; @@ -620,4 +626,9 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break return ranges; } + @Override + protected boolean isBidiBoundary(boolean propagate) { + return getExplicitWritingMode(); + } + } diff --git a/src/java/org/apache/fop/fo/pagination/PageSequence.java b/src/java/org/apache/fop/fo/pagination/PageSequence.java index 368b69f90..ba874d31f 100644 --- a/src/java/org/apache/fop/fo/pagination/PageSequence.java +++ b/src/java/org/apache/fop/fo/pagination/PageSequence.java @@ -96,7 +96,8 @@ public class PageSequence extends AbstractPageSequence implements WritingModeTra masterReference = pList.get(PR_MASTER_REFERENCE).getString(); referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric(); writingModeTraits = new WritingModeTraits( - WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum())); + WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()), + pList.getExplicit(PR_WRITING_MODE) != null); if (masterReference == null || masterReference.equals("")) { missingPropertyError("master-reference"); } @@ -403,6 +404,16 @@ public class PageSequence extends AbstractPageSequence implements WritingModeTra } } + /** + * {@inheritDoc} + */ + public boolean getExplicitWritingMode() { + if (writingModeTraits != null) { + return writingModeTraits.getExplicitWritingMode(); + } else { + return false; + } + } @Override protected Stack collectDelimitedTextRanges(Stack ranges, DelimitedTextRange currentRange) { @@ -423,6 +434,11 @@ public class PageSequence extends AbstractPageSequence implements WritingModeTra return ranges; } + @Override + protected boolean isBidiBoundary(boolean propagate) { + return true; + } + /** * Releases a page-sequence's children after the page-sequence has been fully processed. */ diff --git a/src/java/org/apache/fop/fo/properties/GenericShorthandParser.java b/src/java/org/apache/fop/fo/properties/GenericShorthandParser.java index ac3d4d13a..140b96aac 100644 --- a/src/java/org/apache/fop/fo/properties/GenericShorthandParser.java +++ b/src/java/org/apache/fop/fo/properties/GenericShorthandParser.java @@ -83,13 +83,17 @@ public class GenericShorthandParser implements ShorthandParser { PropertyList propertyList) throws PropertyException { Property prop = null; + String vProperty = ""; // Try each of the stored values in turn Iterator iprop = property.getList().iterator(); while (iprop.hasNext() && prop == null) { Property p = (Property)iprop.next(); + if (p.getNCname() != null) { + vProperty += p.getNCname() + " "; + } prop = maker.convertShorthandProperty(propertyList, p, null); - propertyList.validatePropertyValue(p.getNCname(), prop, property); } + propertyList.validatePropertyValue(vProperty.trim(), prop, property); return prop; } diff --git a/src/java/org/apache/fop/fonts/FontLoader.java b/src/java/org/apache/fop/fonts/FontLoader.java index f7ee24cbc..09e38260e 100644 --- a/src/java/org/apache/fop/fonts/FontLoader.java +++ b/src/java/org/apache/fop/fonts/FontLoader.java @@ -26,7 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.apps.io.InternalResourceResolver; -import org.apache.fop.fonts.truetype.TTFFontLoader; +import org.apache.fop.fonts.truetype.OFFontLoader; import org.apache.fop.fonts.type1.Type1FontLoader; /** @@ -105,7 +105,7 @@ public abstract class FontLoader { } loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resourceResolver); } else { - loader = new TTFFontLoader(fontFileURI, subFontName, embedded, embeddingMode, + loader = new OFFontLoader(fontFileURI, subFontName, embedded, embeddingMode, encodingMode, useKerning, useAdvanced, resourceResolver); } return loader.getFont(); diff --git a/src/java/org/apache/fop/fonts/FontManagerConfigurator.java b/src/java/org/apache/fop/fonts/FontManagerConfigurator.java index fc2ce06a6..72c1684b6 100644 --- a/src/java/org/apache/fop/fonts/FontManagerConfigurator.java +++ b/src/java/org/apache/fop/fonts/FontManagerConfigurator.java @@ -48,20 +48,24 @@ public class FontManagerConfigurator { private final Configuration cfg; - private final URI defaultBaseUri; + private final URI baseURI; + + private final URI fallbackURI; private final ResourceResolver resourceResolver; /** * Main constructor * @param cfg the font manager configuration object - * @param defaultBaseUri the default URI base to use for URI resolution + * @param baseURI the URI against which to resolve relative URIs + * @param fallbackURI the URI to use as a fallback if font-base is unspecified * @param resourceResolver the resource resolver */ - public FontManagerConfigurator(Configuration cfg, URI defaultBaseUri, + public FontManagerConfigurator(Configuration cfg, URI baseURI, URI fallbackURI, ResourceResolver resourceResolver) { this.cfg = cfg; - this.defaultBaseUri = defaultBaseUri; + this.baseURI = baseURI; + this.fallbackURI = fallbackURI; this.resourceResolver = resourceResolver; } @@ -77,13 +81,13 @@ public class FontManagerConfigurator { URI fontBase = InternalResourceResolver.getBaseURI(cfg.getChild("font-base") .getValue(null)); fontManager.setResourceResolver(ResourceResolverFactory.createInternalResourceResolver( - defaultBaseUri.resolve(fontBase), resourceResolver)); + baseURI.resolve(fontBase), resourceResolver)); } catch (URISyntaxException use) { LogUtil.handleException(log, use, true); } } else { fontManager.setResourceResolver(ResourceResolverFactory.createInternalResourceResolver( - defaultBaseUri, resourceResolver)); + fallbackURI, resourceResolver)); } // caching (fonts) if (cfg.getChild("use-cache", false) != null) { diff --git a/src/java/org/apache/fop/fonts/FontTriplet.java b/src/java/org/apache/fop/fonts/FontTriplet.java index 2c1d89088..9392cc8b9 100644 --- a/src/java/org/apache/fop/fonts/FontTriplet.java +++ b/src/java/org/apache/fop/fonts/FontTriplet.java @@ -27,7 +27,8 @@ import java.io.Serializable; */ public class FontTriplet implements Comparable<FontTriplet>, Serializable { - public static final FontTriplet DEFAULT_FONT_TRIPLET = new FontTriplet("any", Font.STYLE_NORMAL, Font.WEIGHT_NORMAL); + public static final FontTriplet DEFAULT_FONT_TRIPLET + = new FontTriplet("any", Font.STYLE_NORMAL, Font.WEIGHT_NORMAL); /** serial version UID */ private static final long serialVersionUID = 1168991106658033508L; diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java index 1d1186dcb..cc3d06650 100644 --- a/src/java/org/apache/fop/fonts/MultiByteFont.java +++ b/src/java/org/apache/fop/fonts/MultiByteFont.java @@ -23,6 +23,7 @@ import java.awt.Rectangle; import java.nio.CharBuffer; import java.nio.IntBuffer; import java.util.BitSet; +import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.logging.Log; @@ -71,6 +72,15 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl /** Contains the character bounding boxes for all characters in the font */ protected Rectangle[] boundingBoxes; + private boolean isOTFFile = false; + + // since for most users the most likely glyphs are in the first cmap segments we store their mapping. + private static final int NUM_MOST_LIKELY_GLYPHS = 256; + private int[] mostLikelyGlyphs = new int[NUM_MOST_LIKELY_GLYPHS]; + + //A map to store each used glyph from the CID set against the glyph name. + private LinkedHashMap<Integer, String> usedGlyphNames = new LinkedHashMap<Integer, String>(); + /** * Default constructor */ @@ -115,6 +125,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl return cidType; } + public void setIsOTFFile(boolean isOTFFile) { + this.isOTFFile = isOTFFile; + } + + public boolean isOTFFile() { + return this.isOTFFile; + } + /** * Sets the CIDType. * @param cidType The cidType to set @@ -151,6 +169,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl return this.cidSet; } + public void mapUsedGlyphName(int gid, String value) { + usedGlyphNames.put(gid, value); + } + + public LinkedHashMap<Integer, String> getUsedGlyphNames() { + return usedGlyphNames; + } + /** {@inheritDoc} */ @Override public String getEncodingName() { @@ -187,10 +213,15 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl * @return the glyph index (or 0 if the glyph is not available) */ // [TBD] - needs optimization, i.e., change from linear search to binary search - private int findGlyphIndex(int c) { + public int findGlyphIndex(int c) { int idx = c; int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT; + // for most users the most likely glyphs are in the first cmap segments (meaning the one with + // the lowest unicode start values) + if (idx < NUM_MOST_LIKELY_GLYPHS && mostLikelyGlyphs[idx] != 0) { + return mostLikelyGlyphs[idx]; + } for (int i = 0; (i < cmap.length) && retIdx == 0; i++) { if (cmap[i].getUnicodeStart() <= idx && cmap[i].getUnicodeEnd() >= idx) { @@ -198,6 +229,9 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl retIdx = cmap[i].getGlyphStartIndex() + idx - cmap[i].getUnicodeStart(); + if (idx < NUM_MOST_LIKELY_GLYPHS) { + mostLikelyGlyphs[idx] = retIdx; + } } } return retIdx; @@ -291,22 +325,6 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl return findCharacterFromGlyphIndex(gi, true); } - - /** {@inheritDoc} */ - @Override - public char mapChar(char c) { - notifyMapOperation(); - int glyphIndex = findGlyphIndex(c); - if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) { - warnMissingGlyph(c); - glyphIndex = findGlyphIndex(Typeface.NOT_FOUND); - } - if (isEmbeddable()) { - glyphIndex = cidSet.mapChar(glyphIndex, c); - } - return (char) glyphIndex; - } - protected BitSet getGlyphIndices() { BitSet bitset = new BitSet(); bitset.set(0); @@ -339,6 +357,23 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl /** {@inheritDoc} */ @Override + public char mapChar(char c) { + notifyMapOperation(); + int glyphIndex = findGlyphIndex(c); + if (glyphIndex == SingleByteEncoding.NOT_FOUND_CODE_POINT) { + warnMissingGlyph(c); + if (!isOTFFile) { + glyphIndex = findGlyphIndex(Typeface.NOT_FOUND); + } + } + if (isEmbeddable()) { + glyphIndex = cidSet.mapChar(glyphIndex, c); + } + return (char) glyphIndex; + } + + /** {@inheritDoc} */ + @Override public boolean hasChar(char c) { return (findGlyphIndex(c) != SingleByteEncoding.NOT_FOUND_CODE_POINT); } diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java index d412609d5..2a6b04761 100644 --- a/src/java/org/apache/fop/fonts/SingleByteFont.java +++ b/src/java/org/apache/fop/fonts/SingleByteFont.java @@ -33,7 +33,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.fonts.Glyphs; import org.apache.fop.apps.io.InternalResourceResolver; -import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; +import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; /** * Generic SingleByte font diff --git a/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java b/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java index 39f7e14a4..3da33ec3f 100644 --- a/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java +++ b/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java @@ -32,8 +32,6 @@ import javax.xml.transform.TransformerFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.util.CommandLineLogger; - /** * Abstract base class for the PFM and TTF Reader command-line applications. */ @@ -90,10 +88,6 @@ public abstract class AbstractFontReader { protected static void setLogLevel(String level) { // Set the evel for future loggers. LogFactory.getFactory().setAttribute("level", level); - if (log instanceof CommandLineLogger) { - // Set the level for the logger creates already. - ((CommandLineLogger) log).setLogLevel(level); - } } /** diff --git a/src/java/org/apache/fop/fonts/apps/PFMReader.java b/src/java/org/apache/fop/fonts/apps/PFMReader.java index e5e8ca524..dd7f7343c 100644 --- a/src/java/org/apache/fop/fonts/apps/PFMReader.java +++ b/src/java/org/apache/fop/fonts/apps/PFMReader.java @@ -29,11 +29,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.apache.commons.logging.LogFactory; - import org.apache.fop.Version; import org.apache.fop.fonts.type1.PFMFile; -import org.apache.fop.util.CommandLineLogger; /** * A tool which reads PFM files from Adobe Type 1 fonts and creates @@ -92,14 +89,6 @@ public class PFMReader extends AbstractFontReader { Map options = new java.util.HashMap(); String[] arguments = parseArguments(options, args); - // Enable the simple command line logging when no other logger is - // defined. - LogFactory logFactory = LogFactory.getFactory(); - if (System.getProperty("org.apache.commons.logging.Log") == null) { - logFactory.setAttribute("org.apache.commons.logging.Log", - CommandLineLogger.class.getName()); - } - determineLogLevel(options); PFMReader app = new PFMReader(); diff --git a/src/java/org/apache/fop/fonts/apps/TTFReader.java b/src/java/org/apache/fop/fonts/apps/TTFReader.java index 900417350..f63e2bb07 100644 --- a/src/java/org/apache/fop/fonts/apps/TTFReader.java +++ b/src/java/org/apache/fop/fonts/apps/TTFReader.java @@ -32,16 +32,13 @@ import org.w3c.dom.Element; import org.xml.sax.Attributes; import org.xml.sax.SAXException; -import org.apache.commons.logging.LogFactory; - import org.apache.fop.Version; import org.apache.fop.fonts.CMapSegment; import org.apache.fop.fonts.FontUtil; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; import org.apache.fop.fonts.truetype.TTFFile; -import org.apache.fop.util.CommandLineLogger; -// CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck /** @@ -123,14 +120,6 @@ public class TTFReader extends AbstractFontReader { Map options = new java.util.HashMap(); String[] arguments = parseArguments(options, args); - // Enable the simple command line logging when no other logger is - // defined. - LogFactory logFactory = LogFactory.getFactory(); - if (System.getProperty("org.apache.commons.logging.Log") == null) { - logFactory.setAttribute("org.apache.commons.logging.Log", - CommandLineLogger.class.getName()); - } - determineLogLevel(options); TTFReader app = new TTFReader(); @@ -224,7 +213,8 @@ public class TTFReader extends AbstractFontReader { InputStream stream = new FileInputStream(fileName); try { FontFileReader reader = new FontFileReader(stream); - boolean supported = ttfFile.readFont(reader, fontName); + String header = OFFontLoader.readHeader(reader); + boolean supported = ttfFile.readFont(reader, header, fontName); if (!supported) { return null; } diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java index e115264bb..21ebd4937 100644 --- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java +++ b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java @@ -43,8 +43,8 @@ import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.FontUtil; import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; import org.apache.fop.fonts.truetype.TTFFile; -import org.apache.fop.fonts.truetype.TTFFontLoader; /** * Attempts to determine correct FontInfo @@ -220,7 +220,7 @@ public class FontInfoFinder { log.debug("Loading " + fontName); } try { - TTFFontLoader ttfLoader = new TTFFontLoader(fontURI, fontName, true, + OFFontLoader ttfLoader = new OFFontLoader(fontURI, fontName, true, EmbeddingMode.AUTO, EncodingMode.AUTO, useKerning, useAdvanced, resourceResolver); customFont = ttfLoader.getFont(); diff --git a/src/java/org/apache/fop/fonts/cff/CFFDataReader.java b/src/java/org/apache/fop/fonts/cff/CFFDataReader.java new file mode 100644 index 000000000..66126fb84 --- /dev/null +++ b/src/java/org/apache/fop/fonts/cff/CFFDataReader.java @@ -0,0 +1,927 @@ +/* + * 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.fonts.cff; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.fontbox.cff.CFFDataInput; +import org.apache.fontbox.cff.CFFOperator; + +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OTFFile; + +/** + * A class to read the CFF data from an OTF CFF font file. + */ +public class CFFDataReader { + private CFFDataInput cffData; + + private byte[] header; + private CFFIndexData nameIndex; + private CFFIndexData topDICTIndex; + private CFFIndexData stringIndex; + private CFFIndexData charStringIndex; + private CFFIndexData globalIndexSubr; + private CFFIndexData localIndexSubr; + private CustomEncoding encoding; + private FDSelect fdSelect; + private List<FontDict> fdFonts; + + private static final int DOUBLE_BYTE_OPERATOR = 12; + private static final int NUM_STANDARD_STRINGS = 391; + + /** Commonly used parsed dictionaries */ + private LinkedHashMap<String, DICTEntry> topDict; + + public CFFDataReader() { + + } + + /** + * Constructor for the CFF data reader which accepts the CFF byte data + * as an argument. + * @param cffDataArray A byte array which holds the CFF data + */ + public CFFDataReader(byte[] cffDataArray) throws IOException { + cffData = new CFFDataInput(cffDataArray); + readCFFData(); + } + + /** + * Constructor for the CFF data reader which accepts a FontFileReader object + * which points to the original font file as an argument. + * @param fontFile The font file as represented by a FontFileReader object + */ + public CFFDataReader(FontFileReader fontFile) throws IOException { + cffData = new CFFDataInput(OTFFile.getCFFData(fontFile)); + readCFFData(); + } + + private void readCFFData() throws IOException { + header = readHeader(); + nameIndex = readIndex(); + topDICTIndex = readIndex(); + topDict = parseDictData(topDICTIndex.getData()); + stringIndex = readIndex(); + globalIndexSubr = readIndex(); + charStringIndex = readCharStringIndex(); + encoding = readEncoding(); + fdSelect = readFDSelect(); + localIndexSubr = readLocalIndexSubrs(); + fdFonts = parseCIDData(); + } + + public Map<String, DICTEntry> getPrivateDict(DICTEntry privateEntry) throws IOException { + return parseDictData(getPrivateDictBytes(privateEntry)); + } + + public byte[] getPrivateDictBytes(DICTEntry privateEntry) throws IOException { + int privateLength = privateEntry.getOperands().get(0).intValue(); + int privateOffset = privateEntry.getOperands().get(1).intValue(); + return getCFFOffsetBytes(privateOffset, privateLength); + } + + /** + * Retrieves a number of bytes from the CFF data stream + * @param offset The offset of the bytes to retrieve + * @param length The number of bytes to retrieve + * @return Returns a byte array of requested bytes + * @throws IOException Throws an IO Exception if an error occurs + */ + private byte[] getCFFOffsetBytes(int offset, int length) throws IOException { + cffData.setPosition(offset); + return cffData.readBytes(length); + } + + /** + * Parses the dictionary data and returns a map of objects for each entry + * @param dictData The data for the dictionary data + * @return Returns a map of type DICTEntry identified by the operand name + * @throws IOException Throws an IO Exception if an error occurs + */ + public LinkedHashMap<String, DICTEntry> parseDictData(byte[] dictData) throws IOException { + LinkedHashMap<String, DICTEntry> dictEntries = new LinkedHashMap<String, DICTEntry>(); + List<Number> operands = new ArrayList<Number>(); + List<Integer> operandLengths = new ArrayList<Integer>(); + int lastOperandLength = 0; + for (int i = 0; i < dictData.length; i++) { + int readByte = dictData[i] & 0xFF; + if (readByte < 28) { + int[] operator = new int[(readByte == DOUBLE_BYTE_OPERATOR) ? 2 : 1]; + if (readByte == DOUBLE_BYTE_OPERATOR) { + operator[0] = dictData[i]; + operator[1] = dictData[i + 1]; + i++; + } else { + operator[0] = dictData[i]; + } + String operatorName = ""; + CFFOperator tempOp = null; + if (operator.length > 1) { + tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0], operator[1])); + } else { + tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0])); + } + if (tempOp != null) { + operatorName = tempOp.getName(); + } + DICTEntry newEntry = new DICTEntry(); + newEntry.setOperator(operator); + newEntry.setOperands(new ArrayList<Number>(operands)); + newEntry.setOperatorName(operatorName); + newEntry.setOffset(i - lastOperandLength); + newEntry.setOperandLength(lastOperandLength); + newEntry.setOperandLengths(new ArrayList<Integer>(operandLengths)); + byte[] byteData = new byte[lastOperandLength + operator.length]; + System.arraycopy(dictData, i - operator.length - (lastOperandLength - 1), + byteData, 0, operator.length + lastOperandLength); + newEntry.setByteData(byteData); + dictEntries.put(operatorName, newEntry); + operands.clear(); + operandLengths.clear(); + lastOperandLength = 0; + } else { + if (readByte >= 32 && readByte <= 246) { + operands.add(readByte - 139); + lastOperandLength += 1; + operandLengths.add(1); + } else if (readByte >= 247 && readByte <= 250) { + operands.add((readByte - 247) * 256 + (dictData[i + 1] & 0xFF) + 108); + lastOperandLength += 2; + operandLengths.add(2); + i++; + } else if (readByte >= 251 && readByte <= 254) { + operands.add(-(readByte - 251) * 256 - (dictData[i + 1] & 0xFF) - 108); + lastOperandLength += 2; + operandLengths.add(2); + i++; + } else if (readByte == 28) { + operands.add((dictData[i + 1] & 0xFF) << 8 | (dictData[i + 2] & 0xFF)); + lastOperandLength += 3; + operandLengths.add(3); + i += 2; + } else if (readByte == 29) { + operands.add((dictData[i + 1] & 0xFF) << 24 | (dictData[i + 2] & 0xFF) << 16 + | (dictData[i + 3] & 0xFF) << 8 | (dictData[i + 4] & 0xFF)); + lastOperandLength += 5; + operandLengths.add(5); + i += 4; + } else if (readByte == 30) { + boolean terminatorFound = false; + StringBuilder realNumber = new StringBuilder(); + int byteCount = 1; + do { + byte nibblesByte = dictData[++i]; + byteCount++; + terminatorFound = readNibble(realNumber, (nibblesByte >> 4) & 0x0F); + if (!terminatorFound) { + terminatorFound = readNibble(realNumber, nibblesByte & 0x0F); + } + } while (!terminatorFound); + operands.add(Double.valueOf(realNumber.toString())); + lastOperandLength += byteCount; + operandLengths.add(byteCount); + } + } + } + return dictEntries; + } + + private boolean readNibble(StringBuilder realNumber, int nibble) { + if (nibble <= 0x9) { + realNumber.append(nibble); + } else { + switch (nibble) { + case 0xa: realNumber.append("."); break; + case 0xb: realNumber.append("E"); break; + case 0xc: realNumber.append("E-"); break; + case 0xd: break; + case 0xe: realNumber.append("-"); break; + case 0xf: return true; + default: throw new AssertionError("Unexpected nibble value"); + } + } + return false; + } + + /** + * A class containing data for a dictionary entry + */ + public static class DICTEntry { + private int[] operator; + private List<Number> operands; + private List<Integer> operandLengths; + private String operatorName; + private int offset; + private int operandLength; + private byte[] data = new byte[0]; + + public void setOperator(int[] operator) { + this.operator = operator; + } + + public int[] getOperator() { + return this.operator; + } + + public void setOperands(List<Number> operands) { + this.operands = operands; + } + + public List<Number> getOperands() { + return this.operands; + } + + public void setOperatorName(String operatorName) { + this.operatorName = operatorName; + } + + public String getOperatorName() { + return this.operatorName; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + public int getOffset() { + return this.offset; + } + + public void setOperandLength(int operandLength) { + this.operandLength = operandLength; + } + + public int getOperandLength() { + return this.operandLength; + } + + public void setByteData(byte[] data) { + this.data = data.clone(); + } + + public byte[] getByteData() { + return data.clone(); + } + + public void setOperandLengths(List<Integer> operandLengths) { + this.operandLengths = operandLengths; + } + + public List<Integer> getOperandLengths() { + return operandLengths; + } + } + + private byte[] readHeader() throws IOException { + //Read known header + byte[] fixedHeader = cffData.readBytes(4); + int hdrSize = (fixedHeader[2] & 0xFF); + byte[] extra = cffData.readBytes(hdrSize - 4); + byte[] header = new byte[hdrSize]; + for (int i = 0; i < fixedHeader.length; i++) { + header[i] = fixedHeader[i]; + } + for (int i = 4; i < extra.length; i++) { + header[i] = extra[i - 4]; + } + return header; + } + + /** + * Reads a CFF index object are the specified offset position + * @param offset The position of the index object to read + * @return Returns an object representing the index + * @throws IOException Throws an IO Exception if an error occurs + */ + public CFFIndexData readIndex(int offset) throws IOException { + cffData.setPosition(offset); + return readIndex(); + } + + private CFFIndexData readIndex() throws IOException { + return readIndex(cffData); + } + + /** + * Reads an index from the current position of the CFFDataInput object + * @param input The object holding the CFF byte data + * @return Returns an object representing the index + * @throws IOException Throws an IO Exception if an error occurs + */ + public CFFIndexData readIndex(CFFDataInput input) throws IOException { + CFFIndexData nameIndex = new CFFIndexData(); + if (input != null) { + int origPos = input.getPosition(); + nameIndex.parseIndexHeader(input); + int tableSize = input.getPosition() - origPos; + nameIndex.setByteData(input.getPosition() - tableSize, tableSize); + } + return nameIndex; + } + + /** + * Retrieves the SID for the given GID object + * @param charsetOffset The offset of the charset data + * @param GID The GID for which to retrieve the SID + * @return Returns the SID as an integer + */ + public int getSIDFromGID(int charsetOffset, int gid) throws IOException { + if (gid == 0) { + return 0; + } + cffData.setPosition(charsetOffset); + int charsetFormat = cffData.readCard8(); + switch (charsetFormat) { + case 0: //Adjust for .notdef character + cffData.setPosition(cffData.getPosition() + (--gid * 2)); + return cffData.readSID(); + case 1: return getSIDFromGIDFormat(gid, 1); + case 2: return getSIDFromGIDFormat(gid, 2); + default: return 0; + } + } + + private int getSIDFromGIDFormat(int gid, int format) throws IOException { + int glyphCount = 0; + while (true) { + int oldGlyphCount = glyphCount; + int start = cffData.readSID(); + glyphCount += ((format == 1) ? cffData.readCard8() : cffData.readCard16()) + 1; + if (gid <= glyphCount) { + return start + (gid - oldGlyphCount) - 1; + } + } + } + + public byte[] getHeader() { + return header.clone(); + } + + public CFFIndexData getNameIndex() { + return nameIndex; + } + + public CFFIndexData getTopDictIndex() { + return topDICTIndex; + } + + public LinkedHashMap<String, DICTEntry> getTopDictEntries() { + return topDict; + } + + public CFFIndexData getStringIndex() { + return stringIndex; + } + + public CFFIndexData getGlobalIndexSubr() { + return globalIndexSubr; + } + + public CFFIndexData getLocalIndexSubr() { + return localIndexSubr; + } + + public CFFIndexData getCharStringIndex() { + return charStringIndex; + } + + public CFFDataInput getCFFData() { + return cffData; + } + + public CustomEncoding getEncoding() { + return encoding; + } + + public FDSelect getFDSelect() { + return fdSelect; + } + + public List<FontDict> getFDFonts() { + return fdFonts; + } + + public CFFDataInput getLocalSubrsForGlyph(int glyph) throws IOException { + //Subsets are currently written using a Format0 FDSelect + FDSelect fontDictionary = getFDSelect(); + if (fontDictionary instanceof Format0FDSelect) { + Format0FDSelect fdSelect = (Format0FDSelect)fontDictionary; + int found = fdSelect.getFDIndexes()[glyph]; + FontDict font = getFDFonts().get(found); + byte[] localSubrData = font.getLocalSubrData().getByteData(); + if (localSubrData != null) { + return new CFFDataInput(localSubrData); + } else { + return null; + } + } else if (fontDictionary instanceof Format3FDSelect) { + Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary; + int index = 0; + for (int first : fdSelect.getRanges().keySet()) { + if (first > glyph) { + break; + } + index++; + } + FontDict font = getFDFonts().get(index); + byte[] localSubrsData = font.getLocalSubrData().getByteData(); + if (localSubrsData != null) { + return new CFFDataInput(localSubrsData); + } else { + return null; + } + } + return null; + } + + /** + * Parses the char string index from the CFF byte data + * @param offset The offset to the char string index + * @return Returns the char string index object + * @throws IOException Throws an IO Exception if an error occurs + */ + public CFFIndexData readCharStringIndex() throws IOException { + int offset = topDict.get("CharStrings").getOperands().get(0).intValue(); + cffData.setPosition(offset); + return readIndex(); + } + + private CustomEncoding readEncoding() throws IOException { + CustomEncoding foundEncoding = null; + if (topDict.get("Encoding") != null) { + int offset = topDict.get("Encoding").getOperands().get(0).intValue(); + if (offset != 0 && offset != 1) { + //No need to set the offset as we are reading the data sequentially. + int format = cffData.readCard8(); + int numEntries = cffData.readCard8(); + switch (format) { + case 0: + foundEncoding = readFormat0Encoding(format, numEntries); + break; + case 1: + foundEncoding = readFormat1Encoding(format, numEntries); + break; + default: break; + } + } + } + return foundEncoding; + } + + private Format0Encoding readFormat0Encoding(int format, int numEntries) + throws IOException { + Format0Encoding newEncoding = new Format0Encoding(); + newEncoding.setFormat(format); + newEncoding.setNumEntries(numEntries); + int[] codes = new int[numEntries]; + for (int i = 0; i < numEntries; i++) { + codes[i] = cffData.readCard8(); + } + newEncoding.setCodes(codes); + return newEncoding; + } + + private Format1Encoding readFormat1Encoding(int format, int numEntries) + throws IOException { + Format1Encoding newEncoding = new Format1Encoding(); + newEncoding.setFormat(format); + newEncoding.setNumEntries(numEntries); + LinkedHashMap<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>(); + for (int i = 0; i < numEntries; i++) { + int first = cffData.readCard8(); + int left = cffData.readCard8(); + ranges.put(first, left); + } + newEncoding.setRanges(ranges); + return newEncoding; + } + + private FDSelect readFDSelect() throws IOException { + FDSelect fdSelect = null; + DICTEntry fdSelectEntry = topDict.get("FDSelect"); + if (fdSelectEntry != null) { + int fdOffset = fdSelectEntry.getOperands().get(0).intValue(); + cffData.setPosition(fdOffset); + int format = cffData.readCard8(); + switch (format) { + case 0: + fdSelect = readFormat0FDSelect(); + break; + case 3: + fdSelect = readFormat3FDSelect(); + break; + default: + } + } + return fdSelect; + } + + private Format0FDSelect readFormat0FDSelect() throws IOException { + Format0FDSelect newFDs = new Format0FDSelect(); + newFDs.setFormat(0); + int glyphCount = charStringIndex.getNumObjects(); + int[] fds = new int[glyphCount]; + for (int i = 0; i < glyphCount; i++) { + fds[i] = cffData.readCard8(); + } + newFDs.setFDIndexes(fds); + return newFDs; + } + + private Format3FDSelect readFormat3FDSelect() throws IOException { + Format3FDSelect newFDs = new Format3FDSelect(); + newFDs.setFormat(3); + int rangeCount = cffData.readCard16(); + newFDs.setRangeCount(rangeCount); + LinkedHashMap<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>(); + for (int i = 0; i < rangeCount; i++) { + int first = cffData.readCard16(); + int fd = cffData.readCard8(); + ranges.put(first, fd); + } + newFDs.setRanges(ranges); + newFDs.setSentinelGID(cffData.readCard16()); + return newFDs; + } + + private List<FontDict> parseCIDData() throws IOException { + ArrayList<FontDict> fdFonts = new ArrayList<FontDict>(); + if (topDict.get("ROS") != null) { + DICTEntry fdArray = topDict.get("FDArray"); + if (fdArray != null) { + int fdIndex = fdArray.getOperands().get(0).intValue(); + CFFIndexData fontDicts = readIndex(fdIndex); + for (int i = 0; i < fontDicts.getNumObjects(); i++) { + FontDict newFontDict = new FontDict(); + + byte[] fdData = fontDicts.getValue(i); + LinkedHashMap<String, DICTEntry> fdEntries = parseDictData(fdData); + newFontDict.setByteData(fontDicts.getValuePosition(i), fontDicts.getValueLength(i)); + DICTEntry fontFDEntry = fdEntries.get("FontName"); + newFontDict.setFontName(getString(fontFDEntry.getOperands().get(0).intValue())); + DICTEntry privateFDEntry = fdEntries.get("Private"); + if (privateFDEntry != null) { + newFontDict = setFDData(privateFDEntry, newFontDict); + } + + fdFonts.add(newFontDict); + } + } + } + return fdFonts; + } + + private FontDict setFDData(DICTEntry privateFDEntry, FontDict newFontDict) throws IOException { + int privateFDLength = privateFDEntry.getOperands().get(0).intValue(); + int privateFDOffset = privateFDEntry.getOperands().get(1).intValue(); + cffData.setPosition(privateFDOffset); + byte[] privateDict = cffData.readBytes(privateFDLength); + newFontDict.setPrivateDictData(privateFDOffset, privateFDLength); + LinkedHashMap<String, DICTEntry> privateEntries = parseDictData(privateDict); + DICTEntry subroutines = privateEntries.get("Subrs"); + if (subroutines != null) { + CFFIndexData localSubrs = readIndex(privateFDOffset + + subroutines.getOperands().get(0).intValue()); + newFontDict.setLocalSubrData(localSubrs); + } else { + newFontDict.setLocalSubrData(new CFFIndexData()); + } + return newFontDict; + } + + private String getString(int sid) throws IOException { + return new String(stringIndex.getValue(sid - NUM_STANDARD_STRINGS)); + } + + private CFFIndexData readLocalIndexSubrs() throws IOException { + CFFIndexData localSubrs = null; + DICTEntry privateEntry = topDict.get("Private"); + if (privateEntry != null) { + int length = privateEntry.getOperands().get(0).intValue(); + int offset = privateEntry.getOperands().get(1).intValue(); + cffData.setPosition(offset); + byte[] privateData = cffData.readBytes(length); + LinkedHashMap<String, DICTEntry> privateDict = parseDictData(privateData); + DICTEntry localSubrsEntry = privateDict.get("Subrs"); + if (localSubrsEntry != null) { + int localOffset = offset + localSubrsEntry.getOperands().get(0).intValue(); + cffData.setPosition(localOffset); + localSubrs = readIndex(); + } + } + return localSubrs; + } + + /** + * Parent class which provides the ability to retrieve byte data from + * a sub-table. + */ + public class CFFSubTable { + private DataLocation dataLocation = new DataLocation(); + + public void setByteData(int position, int length) { + dataLocation = new DataLocation(position, length); + } + + public byte[] getByteData() throws IOException { + int oldPos = cffData.getPosition(); + try { + cffData.setPosition(dataLocation.getDataPosition()); + return cffData.readBytes(dataLocation.getDataLength()); + } finally { + cffData.setPosition(oldPos); + } + } + } + + /** + * An object used to hold index data from the CFF data + */ + public class CFFIndexData extends CFFSubTable { + private int numObjects; + private int offSize; + private int[] offsets = new int[0]; + private DataLocation dataLocation = new DataLocation(); + + public void setNumObjects(int numObjects) { + this.numObjects = numObjects; + } + + public int getNumObjects() { + return this.numObjects; + } + + public void setOffSize(int offSize) { + this.offSize = offSize; + } + + public int getOffSize() { + return this.offSize; + } + + public void setOffsets(int[] offsets) { + this.offsets = offsets.clone(); + } + + public int[] getOffsets() { + return offsets.clone(); + } + + public void setData(int position, int length) { + dataLocation = new DataLocation(position, length); + } + + public byte[] getData() throws IOException { + int origPos = cffData.getPosition(); + try { + cffData.setPosition(dataLocation.getDataPosition()); + return cffData.readBytes(dataLocation.getDataLength()); + } finally { + cffData.setPosition(origPos); + } + } + + /** + * Parses index data from an index object found within the CFF byte data + * @param cffData A byte array containing the CFF data + * @throws IOException Throws an IO Exception if an error occurs + */ + public void parseIndexHeader(CFFDataInput cffData) throws IOException { + setNumObjects(cffData.readCard16()); + setOffSize(cffData.readOffSize()); + int[] offsets = new int[getNumObjects() + 1]; + byte[] bytes; + //Fills the offsets array + for (int i = 0; i <= getNumObjects(); i++) { + switch (getOffSize()) { + case 1: + offsets[i] = cffData.readCard8(); + break; + case 2: + offsets[i] = cffData.readCard16(); + break; + case 3: + bytes = cffData.readBytes(3); + offsets[i] = ((bytes[0] & 0xFF) << 16) + ((bytes[1] & 0xFF) << 8) + (bytes[2] & 0xFF); + break; + case 4: + bytes = cffData.readBytes(4); + offsets[i] = ((bytes[0] & 0xFF) << 24) + ((bytes[1] & 0xFF) << 16) + + ((bytes[2] & 0xFF) << 8) + (bytes[3] & 0xFF); + break; + default: continue; + } + } + setOffsets(offsets); + int position = cffData.getPosition(); + int dataSize = offsets[offsets.length - 1] - offsets[0]; + + cffData.setPosition(cffData.getPosition() + dataSize); + setData(position, dataSize); + } + + /** + * Retrieves data from the index data + * @param index The index position of the data to retrieve + * @return Returns the byte data for the given index + * @throws IOException Throws an IO Exception if an error occurs + */ + public byte[] getValue(int index) throws IOException { + int oldPos = cffData.getPosition(); + try { + cffData.setPosition(dataLocation.getDataPosition() + (offsets[index] - 1)); + return cffData.readBytes(offsets[index + 1] - offsets[index]); + } finally { + cffData.setPosition(oldPos); + } + } + + public int getValuePosition(int index) { + return dataLocation.getDataPosition() + (offsets[index] - 1); + } + + public int getValueLength(int index) { + return offsets[index + 1] - offsets[index]; + } + } + + public abstract class CustomEncoding { + private int format; + private int numEntries; + + public void setFormat(int format) { + this.format = format; + } + + public int getFormat() { + return format; + } + + public void setNumEntries(int numEntries) { + this.numEntries = numEntries; + } + + public int getNumEntries() { + return numEntries; + } + } + + public class Format0Encoding extends CustomEncoding { + private int[] codes = new int[0]; + + public void setCodes(int[] codes) { + this.codes = codes.clone(); + } + + public int[] getCodes() { + return codes.clone(); + } + } + + public class Format1Encoding extends CustomEncoding { + private LinkedHashMap<Integer, Integer> ranges; + + public void setRanges(LinkedHashMap<Integer, Integer> ranges) { + this.ranges = ranges; + } + + public LinkedHashMap<Integer, Integer> getRanges() { + return ranges; + } + } + + public class FDSelect { + private int format; + + public void setFormat(int format) { + this.format = format; + } + + public int getFormat() { + return format; + } + } + + public class Format0FDSelect extends FDSelect { + private int[] fds = new int[0]; + + public void setFDIndexes(int[] fds) { + this.fds = fds.clone(); + } + + public int[] getFDIndexes() { + return fds.clone(); + } + } + + public class Format3FDSelect extends FDSelect { + private int rangeCount; + private LinkedHashMap<Integer, Integer> ranges; + private int sentinelGID; + + public void setRangeCount(int rangeCount) { + this.rangeCount = rangeCount; + } + + public int getRangeCount() { + return rangeCount; + } + + public void setRanges(LinkedHashMap<Integer, Integer> ranges) { + this.ranges = ranges; + } + + public LinkedHashMap<Integer, Integer> getRanges() { + return ranges; + } + + public void setSentinelGID(int sentinelGID) { + this.sentinelGID = sentinelGID; + } + + public int getSentinelGID() { + return sentinelGID; + } + } + + public class FontDict extends CFFSubTable { + private String fontName; + private DataLocation dataLocation = new DataLocation(); + private CFFIndexData localSubrData; + + public void setFontName(String groupName) { + this.fontName = groupName; + } + + public String getFontName() { + return fontName; + } + + public void setPrivateDictData(int position, int length) { + dataLocation = new DataLocation(position, length); + } + + public byte[] getPrivateDictData() throws IOException { + int origPos = cffData.getPosition(); + try { + cffData.setPosition(dataLocation.getDataPosition()); + return cffData.readBytes(dataLocation.getDataLength()); + } finally { + cffData.setPosition(origPos); + } + } + + public void setLocalSubrData(CFFIndexData localSubrData) { + this.localSubrData = localSubrData; + } + + public CFFIndexData getLocalSubrData() { + return localSubrData; + } + } + + private static class DataLocation { + private int dataPosition; + private int dataLength; + + public DataLocation() { + dataPosition = 0; + dataLength = 0; + } + + public DataLocation(int position, int length) { + this.dataPosition = position; + this.dataLength = length; + } + + public int getDataPosition() { + return dataPosition; + } + + public int getDataLength() { + return dataLength; + } + } +} diff --git a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java index f26ac2ffd..90abccaf2 100644 --- a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java +++ b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java @@ -31,7 +31,7 @@ import java.util.TreeSet; */ public class GlyfTable { - private final TTFMtxEntry[] mtxTab; + private final OFMtxEntry[] mtxTab; private final long tableOffset; @@ -47,7 +47,7 @@ public class GlyfTable { /** All the glyphs that are composed, but do not appear in the subset. */ private Set<Integer> composedGlyphs = new TreeSet<Integer>(); - GlyfTable(FontFileReader in, TTFMtxEntry[] metrics, TTFDirTabEntry dirTableEntry, + GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry, Map<Integer, Integer> glyphs) throws IOException { mtxTab = metrics; tableOffset = dirTableEntry.getOffset(); diff --git a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java index c273d4471..a9c471d5e 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java +++ b/src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java @@ -26,17 +26,17 @@ import java.io.UnsupportedEncodingException; /** * This class represents an entry to a TrueType font's Dir Tab. */ -public class TTFDirTabEntry { +public class OFDirTabEntry { private byte[] tag = new byte[4]; private int checksum; private long offset; private long length; - public TTFDirTabEntry() { + public OFDirTabEntry() { } - public TTFDirTabEntry(long offset, long length) { + public OFDirTabEntry(long offset, long length) { this.offset = offset; this.length = length; } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java index 98f81ab3e..7168389ff 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java +++ b/src/java/org/apache/fop/fonts/truetype/OFFontLoader.java @@ -38,13 +38,13 @@ import org.apache.fop.fonts.FontType; import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.fonts.NamedCharacter; import org.apache.fop.fonts.SingleByteFont; -import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; +import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; import org.apache.fop.util.HexEncoder; /** * Loads a TrueType font into memory directly from the original font file. */ -public class TTFFontLoader extends FontLoader { +public class OFFontLoader extends FontLoader { private MultiByteFont multiFont; private SingleByteFont singleFont; @@ -57,7 +57,7 @@ public class TTFFontLoader extends FontLoader { * @param fontFileURI the URI representing the font file * @param resourceResolver the resource resolver for font URI resolution */ - public TTFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) { + public OFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) { this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, true, true, resourceResolver); } @@ -73,7 +73,7 @@ public class TTFFontLoader extends FontLoader { * @param useAdvanced true to enable loading advanced info if available, false to disable * @param resolver the FontResolver for font URI resolution */ - public TTFFontLoader(URI fontFileURI, String subFontName, boolean embedded, + public OFFontLoader(URI fontFileURI, String subFontName, boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode, boolean useKerning, boolean useAdvanced, InternalResourceResolver resolver) { super(fontFileURI, embedded, useKerning, useAdvanced, resolver); @@ -102,26 +102,30 @@ public class TTFFontLoader extends FontLoader { private void read(String ttcFontName) throws IOException { InputStream in = resourceResolver.getResource(this.fontFileURI); try { - TTFFile ttf = new TTFFile(useKerning, useAdvanced); FontFileReader reader = new FontFileReader(in); - boolean supported = ttf.readFont(reader, ttcFontName); + String header = readHeader(reader); + boolean isCFF = header.equals("OTTO"); + OpenFont otf = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced); + boolean supported = otf.readFont(reader, header, ttcFontName); if (!supported) { - throw new IOException("TrueType font is not supported: " + fontFileURI); + throw new IOException("The font does not have a Unicode cmap table: " + fontFileURI); } - buildFont(ttf, ttcFontName); + buildFont(otf, ttcFontName); loaded = true; } finally { IOUtils.closeQuietly(in); } } - - private void buildFont(TTFFile ttf, String ttcFontName) { - if (ttf.isCFF()) { - throw new UnsupportedOperationException( - "OpenType fonts with CFF data are not supported, yet"); + public static String readHeader(FontFileReader fontFile) throws IOException { + if (fontFile != null) { + fontFile.seekSet(0); + return fontFile.readTTFString(4); // TTF_FIXED_SIZE (4 bytes) } + return null; + } + private void buildFont(OpenFont otf, String ttcFontName) { boolean isCid = this.embedded; if (this.encodingMode == EncodingMode.SINGLE_BYTE) { isCid = false; @@ -129,6 +133,7 @@ public class TTFFontLoader extends FontLoader { if (isCid) { multiFont = new MultiByteFont(resourceResolver, embeddingMode); + multiFont.setIsOTFFile(otf instanceof OTFFile); returnFont = multiFont; multiFont.setTTCName(ttcFontName); } else { @@ -136,47 +141,51 @@ public class TTFFontLoader extends FontLoader { returnFont = singleFont; } - returnFont.setFontName(ttf.getPostScriptName()); - returnFont.setFullName(ttf.getFullName()); - returnFont.setFamilyNames(ttf.getFamilyNames()); - returnFont.setFontSubFamilyName(ttf.getSubFamilyName()); - returnFont.setCapHeight(ttf.getCapHeight()); - returnFont.setXHeight(ttf.getXHeight()); - returnFont.setAscender(ttf.getLowerCaseAscent()); - returnFont.setDescender(ttf.getLowerCaseDescent()); - returnFont.setFontBBox(ttf.getFontBBox()); - returnFont.setUnderlinePosition(ttf.getUnderlinePosition() - ttf.getUnderlineThickness() / 2); - returnFont.setUnderlineThickness(ttf.getUnderlineThickness()); - returnFont.setStrikeoutPosition(ttf.getStrikeoutPosition() - ttf.getStrikeoutThickness() / 2); - returnFont.setStrikeoutThickness(ttf.getStrikeoutThickness()); - returnFont.setFlags(ttf.getFlags()); - returnFont.setStemV(Integer.parseInt(ttf.getStemV())); //not used for TTF - returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle())); + returnFont.setFontName(otf.getPostScriptName()); + returnFont.setFullName(otf.getFullName()); + returnFont.setFamilyNames(otf.getFamilyNames()); + returnFont.setFontSubFamilyName(otf.getSubFamilyName()); + returnFont.setCapHeight(otf.getCapHeight()); + returnFont.setXHeight(otf.getXHeight()); + returnFont.setAscender(otf.getLowerCaseAscent()); + returnFont.setDescender(otf.getLowerCaseDescent()); + returnFont.setFontBBox(otf.getFontBBox()); + returnFont.setUnderlinePosition(otf.getUnderlinePosition() - otf.getUnderlineThickness() / 2); + returnFont.setUnderlineThickness(otf.getUnderlineThickness()); + returnFont.setStrikeoutPosition(otf.getStrikeoutPosition() - otf.getStrikeoutThickness() / 2); + returnFont.setStrikeoutThickness(otf.getStrikeoutThickness()); + returnFont.setFlags(otf.getFlags()); + returnFont.setStemV(Integer.parseInt(otf.getStemV())); //not used for TTF + returnFont.setItalicAngle(Integer.parseInt(otf.getItalicAngle())); returnFont.setMissingWidth(0); - returnFont.setWeight(ttf.getWeightClass()); + returnFont.setWeight(otf.getWeightClass()); returnFont.setEmbeddingMode(this.embeddingMode); if (isCid) { - multiFont.setCIDType(CIDFontType.CIDTYPE2); - multiFont.setWidthArray(ttf.getWidths()); - multiFont.setBBoxArray(ttf.getBoundingBoxes()); + if (otf instanceof OTFFile) { + multiFont.setCIDType(CIDFontType.CIDTYPE0); + } else { + multiFont.setCIDType(CIDFontType.CIDTYPE2); + } + multiFont.setWidthArray(otf.getWidths()); + multiFont.setBBoxArray(otf.getBoundingBoxes()); } else { singleFont.setFontType(FontType.TRUETYPE); - singleFont.setEncoding(ttf.getCharSetName()); - returnFont.setFirstChar(ttf.getFirstChar()); - returnFont.setLastChar(ttf.getLastChar()); - singleFont.setTrueTypePostScriptVersion(ttf.getPostScriptVersion()); - copyGlyphMetricsSingleByte(ttf); + singleFont.setEncoding(otf.getCharSetName()); + returnFont.setFirstChar(otf.getFirstChar()); + returnFont.setLastChar(otf.getLastChar()); + singleFont.setTrueTypePostScriptVersion(otf.getPostScriptVersion()); + copyGlyphMetricsSingleByte(otf); } - returnFont.setCMap(getCMap(ttf)); + returnFont.setCMap(getCMap(otf)); - if (useKerning) { - copyKerning(ttf, isCid); + if (otf.getKerning() != null && useKerning) { + copyKerning(otf, isCid); } if (useAdvanced) { - copyAdvanced(ttf); + copyAdvanced(otf); } if (this.embedded) { - if (ttf.isEmbeddable()) { + if (otf.isEmbeddable()) { returnFont.setEmbedURI(this.fontFileURI); } else { String msg = "The font " + this.fontFileURI + " is not embeddable due to a" @@ -186,28 +195,29 @@ public class TTFFontLoader extends FontLoader { } } - private CMapSegment[] getCMap(TTFFile ttf) { - CMapSegment[] array = new CMapSegment[ttf.getCMaps().size()]; - return ttf.getCMaps().toArray(array); + private CMapSegment[] getCMap(OpenFont otf) { + CMapSegment[] array = new CMapSegment[otf.getCMaps().size()]; + return otf.getCMaps().toArray(array); } - private void copyGlyphMetricsSingleByte(TTFFile ttf) { - int[] wx = ttf.getWidths(); - Rectangle[] bboxes = ttf.getBoundingBoxes(); + private void copyGlyphMetricsSingleByte(OpenFont otf) { + int[] wx = otf.getWidths(); + Rectangle[] bboxes = otf.getBoundingBoxes(); for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) { - singleFont.setWidth(i, ttf.getCharWidth(i)); - int[] bbox = ttf.getBBox(i); + singleFont.setWidth(i, otf.getCharWidth(i)); + int[] bbox = otf.getBBox(i); singleFont.setBoundingBox(i, new Rectangle(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1])); } - for (CMapSegment segment : ttf.getCMaps()) { + + for (CMapSegment segment : otf.getCMaps()) { if (segment.getUnicodeStart() < 0xFFFE) { for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) { int codePoint = singleFont.getEncoding().mapChar(u); if (codePoint <= 0) { int glyphIndex = segment.getGlyphStartIndex() + u - segment.getUnicodeStart(); - String glyphName = ttf.getGlyphName(glyphIndex); - if (glyphName.length() == 0 && ttf.getPostScriptVersion() != PostScriptVersion.V2) { + String glyphName = otf.getGlyphName(glyphIndex); + if (glyphName.length() == 0 && otf.getPostScriptVersion() != PostScriptVersion.V2) { glyphName = "u" + HexEncoder.encode(u); } if (glyphName.length() > 0) { @@ -224,22 +234,22 @@ public class TTFFontLoader extends FontLoader { /** * Copy kerning information. */ - private void copyKerning(TTFFile ttf, boolean isCid) { + private void copyKerning(OpenFont otf, boolean isCid) { // Get kerning Set<Integer> kerningSet; if (isCid) { - kerningSet = ttf.getKerning().keySet(); + kerningSet = otf.getKerning().keySet(); } else { - kerningSet = ttf.getAnsiKerning().keySet(); + kerningSet = otf.getAnsiKerning().keySet(); } for (Integer kpx1 : kerningSet) { Map<Integer, Integer> h2; if (isCid) { - h2 = ttf.getKerning().get(kpx1); + h2 = otf.getKerning().get(kpx1); } else { - h2 = ttf.getAnsiKerning().get(kpx1); + h2 = otf.getAnsiKerning().get(kpx1); } returnFont.putKerningEntry(kpx1, h2); } @@ -248,12 +258,12 @@ public class TTFFontLoader extends FontLoader { /** * Copy advanced typographic information. */ - private void copyAdvanced(TTFFile ttf) { + private void copyAdvanced(OpenFont otf) { if (returnFont instanceof MultiByteFont) { MultiByteFont mbf = (MultiByteFont) returnFont; - mbf.setGDEF(ttf.getGDEF()); - mbf.setGSUB(ttf.getGSUB()); - mbf.setGPOS(ttf.getGPOS()); + mbf.setGDEF(otf.getGDEF()); + mbf.setGSUB(otf.getGSUB()); + mbf.setGPOS(otf.getGPOS()); } } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java b/src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java index 6884a633d..89af2296f 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java +++ b/src/java/org/apache/fop/fonts/truetype/OFMtxEntry.java @@ -24,7 +24,7 @@ import java.util.List; /** * This class represents a TrueType Mtx Entry. */ -class TTFMtxEntry { +class OFMtxEntry { private int wx; private int lsb; diff --git a/src/java/org/apache/fop/fonts/truetype/TTFTableName.java b/src/java/org/apache/fop/fonts/truetype/OFTableName.java index e5ad63128..f6264129a 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFTableName.java +++ b/src/java/org/apache/fop/fonts/truetype/OFTableName.java @@ -24,98 +24,104 @@ package org.apache.fop.fonts.truetype; * Represents table names as found in a TrueType font's Table Directory. * TrueType fonts may have custom tables so we cannot use an enum. */ -public final class TTFTableName { +public final class OFTableName { /** The first table in a TrueType font file containing metadata about other tables. */ - public static final TTFTableName TABLE_DIRECTORY = new TTFTableName("tableDirectory"); + public static final OFTableName TABLE_DIRECTORY = new OFTableName("tableDirectory"); + + /** Baseline data */ + public static final OFTableName BASE = new OFTableName("BASE"); + + /** CFF data/ */ + public static final OFTableName CFF = new OFTableName("CFF "); /** Embedded bitmap data. */ - public static final TTFTableName EBDT = new TTFTableName("EBDT"); + public static final OFTableName EBDT = new OFTableName("EBDT"); /** Embedded bitmap location data. */ - public static final TTFTableName EBLC = new TTFTableName("EBLC"); + public static final OFTableName EBLC = new OFTableName("EBLC"); /** Embedded bitmap scaling data. */ - public static final TTFTableName EBSC = new TTFTableName("EBSC"); + public static final OFTableName EBSC = new OFTableName("EBSC"); /** A FontForge specific table. */ - public static final TTFTableName FFTM = new TTFTableName("FFTM"); + public static final OFTableName FFTM = new OFTableName("FFTM"); /** Divides glyphs into various classes that make using the GPOS/GSUB tables easier. */ - public static final TTFTableName GDEF = new TTFTableName("GDEF"); + public static final OFTableName GDEF = new OFTableName("GDEF"); /** Provides kerning information, mark-to-base, etc. for opentype fonts. */ - public static final TTFTableName GPOS = new TTFTableName("GPOS"); + public static final OFTableName GPOS = new OFTableName("GPOS"); /** Provides ligature information, swash, etc. for opentype fonts. */ - public static final TTFTableName GSUB = new TTFTableName("GSUB"); + public static final OFTableName GSUB = new OFTableName("GSUB"); /** Linear threshold table. */ - public static final TTFTableName LTSH = new TTFTableName("LTSH"); + public static final OFTableName LTSH = new OFTableName("LTSH"); /** OS/2 and Windows specific metrics. */ - public static final TTFTableName OS2 = new TTFTableName("OS/2"); + public static final OFTableName OS2 = new OFTableName("OS/2"); /** PCL 5 data. */ - public static final TTFTableName PCLT = new TTFTableName("PCLT"); + public static final OFTableName PCLT = new OFTableName("PCLT"); /** Vertical Device Metrics table. */ - public static final TTFTableName VDMX = new TTFTableName("VDMX"); + public static final OFTableName VDMX = new OFTableName("VDMX"); /** Character to glyph mapping. */ - public static final TTFTableName CMAP = new TTFTableName("cmap"); + public static final OFTableName CMAP = new OFTableName("cmap"); /** Control Value Table. */ - public static final TTFTableName CVT = new TTFTableName("cvt "); + public static final OFTableName CVT = new OFTableName("cvt "); /** Font program. */ - public static final TTFTableName FPGM = new TTFTableName("fpgm"); + public static final OFTableName FPGM = new OFTableName("fpgm"); /** Grid-fitting and scan conversion procedure (grayscale). */ - public static final TTFTableName GASP = new TTFTableName("gasp"); + public static final OFTableName GASP = new OFTableName("gasp"); /** Glyph data. */ - public static final TTFTableName GLYF = new TTFTableName("glyf"); + public static final OFTableName GLYF = new OFTableName("glyf"); /** Horizontal device metrics. */ - public static final TTFTableName HDMX = new TTFTableName("hdmx"); + public static final OFTableName HDMX = new OFTableName("hdmx"); /** Font header. */ - public static final TTFTableName HEAD = new TTFTableName("head"); + public static final OFTableName HEAD = new OFTableName("head"); /** Horizontal header. */ - public static final TTFTableName HHEA = new TTFTableName("hhea"); + public static final OFTableName HHEA = new OFTableName("hhea"); /** Horizontal metrics. */ - public static final TTFTableName HMTX = new TTFTableName("hmtx"); + public static final OFTableName HMTX = new OFTableName("hmtx"); /** Kerning. */ - public static final TTFTableName KERN = new TTFTableName("kern"); + public static final OFTableName KERN = new OFTableName("kern"); /** Index to location. */ - public static final TTFTableName LOCA = new TTFTableName("loca"); + public static final OFTableName LOCA = new OFTableName("loca"); /** Maximum profile. */ - public static final TTFTableName MAXP = new TTFTableName("maxp"); + public static final OFTableName MAXP = new OFTableName("maxp"); /** Naming table. */ - public static final TTFTableName NAME = new TTFTableName("name"); + public static final OFTableName NAME = new OFTableName("name"); /** PostScript information. */ - public static final TTFTableName POST = new TTFTableName("post"); + public static final OFTableName POST = new OFTableName("post"); /** CVT Program. */ - public static final TTFTableName PREP = new TTFTableName("prep"); + public static final OFTableName PREP = new OFTableName("prep"); /** Vertical Metrics header. */ - public static final TTFTableName VHEA = new TTFTableName("vhea"); + public static final OFTableName VHEA = new OFTableName("vhea"); /** Vertical Metrics. */ - public static final TTFTableName VMTX = new TTFTableName("vmtx"); + public static final OFTableName VMTX = new OFTableName("vmtx"); private final String name; - private TTFTableName(String name) { + private OFTableName(String name) { this.name = name; } @@ -131,9 +137,9 @@ public final class TTFTableName { * @param tableName table name as in the Table Directory * @return TTFTableName */ - public static TTFTableName getValue(String tableName) { + public static OFTableName getValue(String tableName) { if (tableName != null) { - return new TTFTableName(tableName); + return new OFTableName(tableName); } throw new IllegalArgumentException("A TrueType font table name must not be null"); } @@ -148,10 +154,10 @@ public final class TTFTableName { if (o == this) { return true; } - if (!(o instanceof TTFTableName)) { + if (!(o instanceof OFTableName)) { return false; } - TTFTableName to = (TTFTableName) o; + OFTableName to = (OFTableName) o; return this.name.equals(to.getName()); } diff --git a/src/java/org/apache/fop/fonts/truetype/OTFFile.java b/src/java/org/apache/fop/fonts/truetype/OTFFile.java new file mode 100644 index 000000000..3976b5994 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/OTFFile.java @@ -0,0 +1,109 @@ +/* + * 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.fonts.truetype; + +import java.io.IOException; + +import org.apache.fontbox.cff.CFFDataInput; +import org.apache.fontbox.cff.CFFFont; +import org.apache.fontbox.cff.CFFFont.Mapping; +import org.apache.fontbox.cff.CFFParser; + +public class OTFFile extends OpenFont { + + protected CFFFont fileFont; + + public OTFFile() throws IOException { + checkForFontbox(); + } + + private void checkForFontbox() throws IOException { + try { + Class.forName("org.apache.fontbox.cff.CFFFont"); + } catch (ClassNotFoundException ex) { + throw new IOException("The Fontbox jar was not found in the classpath. This is " + + "required for OTF CFF ssupport."); + } + } + + @Override + protected void updateBBoxAndOffset() throws IOException { + UnicodeMapping[] mappings = unicodeMappings.toArray(new UnicodeMapping[0]); + for (int i = 0; i < mappings.length; i++) { + int glyphIdx = mappings[i].getGlyphIndex(); + Mapping m = fileFont.getGIDMappings().get(glyphIdx); + int[] bbox = fileFont.getBoundingBox(m.getSID()); + String name = fileFont.getNameOfCharFromCode(m.getSID()); + mtxTab[glyphIdx].setBoundingBox(bbox); + mtxTab[glyphIdx].setName(name); + } + } + + @Override + protected void initializeFont(FontFileReader in) throws IOException { + fontFile = in; + fontFile.seekSet(0); + CFFParser parser = new CFFParser(); + fileFont = parser.parse(in.getAllBytes()).get(0); + } + + protected void readName() throws IOException { + Object familyName = fileFont.getProperty("FamilyName"); + if (familyName != null && !familyName.equals("")) { + familyNames.add(familyName.toString()); + fullName = familyName.toString(); + } else { + fullName = fileFont.getName(); + familyNames.add(fullName); + } + } + + /** + * Reads the CFFData from a given font file + * @param fontFile The font file being read + * @return The byte data found in the CFF table + */ + public static byte[] getCFFData(FontFileReader fontFile) throws IOException { + byte[] cff = new byte[0]; + CFFDataInput input = new CFFDataInput(fontFile.getAllBytes()); + input.readBytes(4); //OTTO + short numTables = input.readShort(); + input.readShort(); //searchRange + input.readShort(); //entrySelector + input.readShort(); //rangeShift + + for (int q = 0; q < numTables; q++) { + String tagName = new String(input.readBytes(4)); + readLong(input); //Checksum + long offset = readLong(input); + long length = readLong(input); + if (tagName.equals("CFF ")) { + cff = new byte[(int)length]; + System.arraycopy(fontFile.getAllBytes(), (int)offset, cff, 0, cff.length); + break; + } + } + return cff; + } + + private static long readLong(CFFDataInput input) throws IOException { + return (input.readCard16() << 16) | input.readCard16(); + } +} diff --git a/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java new file mode 100644 index 000000000..4d0cce67a --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java @@ -0,0 +1,1097 @@ +/* + * 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.fonts.truetype; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.fontbox.cff.CFFStandardString; +import org.apache.fontbox.cff.encoding.CFFEncoding; + +import org.apache.fop.fonts.MultiByteFont; +import org.apache.fop.fonts.cff.CFFDataReader; +import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData; +import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry; +import org.apache.fop.fonts.cff.CFFDataReader.FDSelect; +import org.apache.fop.fonts.cff.CFFDataReader.FontDict; +import org.apache.fop.fonts.cff.CFFDataReader.Format0FDSelect; +import org.apache.fop.fonts.cff.CFFDataReader.Format3FDSelect; + +/** + * Reads an OpenType CFF file and generates a subset + * The OpenType specification can be found at the Microsoft + * Typography site: http://www.microsoft.com/typography/otspec/ + */ +public class OTFSubSetFile extends OTFFile { + + private byte[] output; + private int currentPos = 0; + private int realSize = 0; + + /** A map containing each glyph to be included in the subset + * with their existing and new GID's **/ + private LinkedHashMap<Integer, Integer> subsetGlyphs; + + /** A map of the new GID to SID used to construct the charset table **/ + private LinkedHashMap<Integer, Integer> gidToSID; + + private CFFIndexData localIndexSubr; + private CFFIndexData globalIndexSubr; + + /** List of subroutines to write to the local / global indexes in the subset font **/ + private List<byte[]> subsetLocalIndexSubr; + private List<byte[]> subsetGlobalIndexSubr; + + /** For fonts which have an FDSelect or ROS flag in Top Dict, this is used to store the + * local subroutine indexes for each group as opposed to the above subsetLocalIndexSubr */ + private ArrayList<List<byte[]>> fdSubrs; + + /** The subset FD Select table used to store the mappings between glyphs and their + * associated FDFont object which point to a private dict and local subroutines. */ + private LinkedHashMap<Integer, FDIndexReference> subsetFDSelect; + + /** A list of unique subroutines from the global / local subroutine indexes */ + private List<Integer> localUniques; + private List<Integer> globalUniques; + + /** A store of the number of subroutines each global / local subroutine will store **/ + private int subsetLocalSubrCount; + private int subsetGlobalSubrCount; + + /** A list of char string data for each glyph to be stored in the subset font **/ + private List<byte[]> subsetCharStringsIndex; + + /** The embedded name to change in the name table **/ + private String embeddedName; + + /** An array used to hold the string index data for the subset font **/ + private List<byte[]> stringIndexData = new ArrayList<byte[]>(); + + /** The CFF reader object used to read data and offsets from the original font file */ + private CFFDataReader cffReader = null; + + /** The class used to represent this font **/ + private MultiByteFont mbFont; + + /** The number of standard strings in CFF **/ + private static final int NUM_STANDARD_STRINGS = 391; + /** The operator used to identify a local subroutine reference */ + private static final int LOCAL_SUBROUTINE = 10; + /** The operator used to identify a global subroutine reference */ + private static final int GLOBAL_SUBROUTINE = 29; + + public OTFSubSetFile() throws IOException { + super(); + } + + public void readFont(FontFileReader in, String embeddedName, String header, + MultiByteFont mbFont) throws IOException { + this.mbFont = mbFont; + readFont(in, embeddedName, header, mbFont.getUsedGlyphs()); + } + + /** + * Reads and creates a subset of the font. + * + * @param in FontFileReader to read from + * @param name Name to be checked for in the font file + * @param header The header of the font file + * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and + * new index as (Integer) value) + * @throws IOException in case of an I/O problem + */ + void readFont(FontFileReader in, String embeddedName, String header, + Map<Integer, Integer> usedGlyphs) throws IOException { + fontFile = in; + + currentPos = 0; + realSize = 0; + + this.embeddedName = embeddedName; + + //Sort by the new GID and store in a LinkedHashMap + subsetGlyphs = sortByValue(usedGlyphs); + + output = new byte[in.getFileSize()]; + + initializeFont(in); + + cffReader = new CFFDataReader(fontFile); + + //Create the CIDFontType0C data + createCFF(); + } + + private LinkedHashMap<Integer, Integer> sortByValue(Map<Integer, Integer> map) { + List<Entry<Integer, Integer>> list = new ArrayList<Entry<Integer, Integer>>(map.entrySet()); + Collections.sort(list, new Comparator<Entry<Integer, Integer>>() { + public int compare(Entry<Integer, Integer> o1, Entry<Integer, Integer> o2) { + return ((Comparable<Integer>) o1.getValue()).compareTo(o2.getValue()); + } + }); + + LinkedHashMap<Integer, Integer> result = new LinkedHashMap<Integer, Integer>(); + for (Entry<Integer, Integer> entry : list) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } + + private void createCFF() throws IOException { + //Header + writeBytes(cffReader.getHeader()); + + //Name Index + writeIndex(Arrays.asList(embeddedName.getBytes())); + + //Keep offset of the topDICT so it can be updated once all data has been written + int topDictOffset = currentPos; + //Top DICT Index and Data + byte[] topDictIndex = cffReader.getTopDictIndex().getByteData(); + int offSize = topDictIndex[2]; + writeBytes(topDictIndex, 0, 3 + (offSize * 2)); + int topDictDataOffset = currentPos; + writeTopDICT(); + + //Create the char string index data and related local / global subroutines + if (cffReader.getFDSelect() == null) { + createCharStringData(); + } else { + createCharStringDataCID(); + } + + //If it is a CID-Keyed font, store each FD font and add each SID + List<Integer> fontNameSIDs = null; + List<Integer> subsetFDFonts = null; + if (cffReader.getFDSelect() != null) { + subsetFDFonts = getUsedFDFonts(); + fontNameSIDs = storeFDStrings(subsetFDFonts); + } + + //String index + writeStringIndex(); + + //Global subroutine index + writeIndex(subsetGlobalIndexSubr); + + //Encoding + int encodingOffset = currentPos; + writeEncoding(fileFont.getEncoding()); + + //Charset table + int charsetOffset = currentPos; + writeCharsetTable(cffReader.getFDSelect() != null); + + //FDSelect table + int fdSelectOffset = currentPos; + if (cffReader.getFDSelect() != null) { + writeFDSelect(); + } + + //Char Strings Index + int charStringOffset = currentPos; + writeIndex(subsetCharStringsIndex); + + if (cffReader.getFDSelect() == null) { + //Keep offset to modify later with the local subroutine index offset + int privateDictOffset = currentPos; + writePrivateDict(); + + //Local subroutine index + int localIndexOffset = currentPos; + writeIndex(subsetLocalIndexSubr); + + //Update the offsets + updateOffsets(topDictOffset, charsetOffset, charStringOffset, privateDictOffset, + localIndexOffset, encodingOffset); + } else { + List<Integer> privateDictOffsets = writeCIDDictsAndSubrs(subsetFDFonts); + int fdArrayOffset = writeFDArray(subsetFDFonts, privateDictOffsets, fontNameSIDs); + + updateCIDOffsets(topDictDataOffset, fdArrayOffset, fdSelectOffset, charsetOffset, + charStringOffset, encodingOffset); + } + } + + private List<Integer> storeFDStrings(List<Integer> uniqueNewRefs) throws IOException { + ArrayList<Integer> fontNameSIDs = new ArrayList<Integer>(); + List<FontDict> fdFonts = cffReader.getFDFonts(); + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i)); + byte[] fdFontByteData = fdFont.getByteData(); + Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData); + fontNameSIDs.add(stringIndexData.size() + NUM_STANDARD_STRINGS); + stringIndexData.add(cffReader.getStringIndex().getValue(fdFontDict.get("FontName") + .getOperands().get(0).intValue() - NUM_STANDARD_STRINGS)); + } + return fontNameSIDs; + } + + private void writeBytes(byte[] out) { + for (int i = 0; i < out.length; i++) { + output[currentPos++] = out[i]; + realSize++; + } + } + + private void writeBytes(byte[] out, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + output[currentPos++] = out[i]; + realSize++; + } + } + + private void writeEncoding(CFFEncoding encoding) throws IOException { + LinkedHashMap<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + DICTEntry encodingEntry = topDICT.get("Encoding"); + if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0 + && encodingEntry.getOperands().get(0).intValue() != 1) { + writeByte(0); + writeByte(gidToSID.size()); + for (int gid : gidToSID.keySet()) { + int code = encoding.getCode(gidToSID.get(gid)); + writeByte(code); + } + } + } + + private void writeTopDICT() throws IOException { + LinkedHashMap<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + List<String> topDictStringEntries = Arrays.asList("version", "Notice", "Copyright", + "FullName", "FamilyName", "Weight", "PostScript"); + for (Map.Entry<String, DICTEntry> dictEntry : topDICT.entrySet()) { + String dictKey = dictEntry.getKey(); + DICTEntry entry = dictEntry.getValue(); + //If the value is an SID, update the reference but keep the size the same + if (dictKey.equals("ROS")) { + writeROSEntry(entry); + } else if (dictKey.equals("CIDCount")) { + writeCIDCount(entry); + } else if (topDictStringEntries.contains(dictKey)) { + writeTopDictStringEntry(entry); + } else { + writeBytes(entry.getByteData()); + } + } + } + + private void writeROSEntry(DICTEntry dictEntry) throws IOException { + int sidA = dictEntry.getOperands().get(0).intValue(); + if (sidA > 390) { + stringIndexData.add(cffReader.getStringIndex().getValue(sidA - NUM_STANDARD_STRINGS)); + } + int sidAStringIndex = stringIndexData.size() + 390; + int sidB = dictEntry.getOperands().get(1).intValue(); + if (sidB > 390) { + stringIndexData.add("Identity".getBytes()); + } + int sidBStringIndex = stringIndexData.size() + 390; + byte[] cidEntryByteData = dictEntry.getByteData(); + cidEntryByteData = updateOffset(cidEntryByteData, 0, dictEntry.getOperandLengths().get(0), + sidAStringIndex); + cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0), + dictEntry.getOperandLengths().get(1), sidBStringIndex); + cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0) + + dictEntry.getOperandLengths().get(1), dictEntry.getOperandLengths().get(2), 139); + writeBytes(cidEntryByteData); + } + + private void writeCIDCount(DICTEntry dictEntry) throws IOException { + byte[] cidCountByteData = dictEntry.getByteData(); + cidCountByteData = updateOffset(cidCountByteData, 0, dictEntry.getOperandLengths().get(0), + subsetGlyphs.size()); + writeBytes(cidCountByteData); + } + + private void writeTopDictStringEntry(DICTEntry dictEntry) throws IOException { + int sid = dictEntry.getOperands().get(0).intValue(); + if (sid > 391) { + stringIndexData.add(cffReader.getStringIndex().getValue(sid - 391)); + } + + byte[] newDictEntry = createNewRef(stringIndexData.size() + 390, dictEntry.getOperator(), + dictEntry.getOperandLength()); + writeBytes(newDictEntry); + } + + private void writeStringIndex() throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue(); + + gidToSID = new LinkedHashMap<Integer, Integer>(); + + for (int gid : subsetGlyphs.keySet()) { + int sid = cffReader.getSIDFromGID(charsetOffset, gid); + //Check whether the SID falls into the standard string set + if (sid < NUM_STANDARD_STRINGS) { + gidToSID.put(subsetGlyphs.get(gid), sid); + if (mbFont != null) { + mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), + CFFStandardString.getName(sid)); + } + } else { + int index = sid - NUM_STANDARD_STRINGS; + if (index <= cffReader.getStringIndex().getNumObjects()) { + if (mbFont != null) { + mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), + new String(cffReader.getStringIndex().getValue(index))); + } + gidToSID.put(subsetGlyphs.get(gid), stringIndexData.size() + 391); + stringIndexData.add(cffReader.getStringIndex().getValue(index)); + } else { + if (mbFont != null) { + mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), ".notdef"); + } + gidToSID.put(subsetGlyphs.get(gid), index); + } + } + } + //Write the String Index + writeIndex(stringIndexData); + } + + private void createCharStringDataCID() throws IOException { + CFFIndexData charStringsIndex = cffReader.getCharStringIndex(); + + FDSelect fontDictionary = cffReader.getFDSelect(); + if (fontDictionary instanceof Format0FDSelect) { + throw new UnsupportedOperationException("OTF CFF CID Format0 currently not implemented"); + } else if (fontDictionary instanceof Format3FDSelect) { + Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary; + Map<Integer, Integer> subsetGroups = new HashMap<Integer, Integer>(); + + List<Integer> uniqueGroups = new ArrayList<Integer>(); + for (int gid : subsetGlyphs.keySet()) { + Integer[] ranges = fdSelect.getRanges().keySet().toArray(new Integer[0]); + for (int i = 0; i < ranges.length; i++) { + int nextRange = -1; + if (i < ranges.length - 1) { + nextRange = ranges[i + 1]; + } else { + nextRange = fdSelect.getSentinelGID(); + } + if (gid >= ranges[i] && gid < nextRange) { + subsetGroups.put(gid, fdSelect.getRanges().get(ranges[i])); + if (!uniqueGroups.contains(fdSelect.getRanges().get(ranges[i]))) { + uniqueGroups.add(fdSelect.getRanges().get(ranges[i])); + } + } + } + } + + //Prepare resources + globalIndexSubr = cffReader.getGlobalIndexSubr(); + + //Create the new char string index + subsetCharStringsIndex = new ArrayList<byte[]>(); + + globalUniques = new ArrayList<Integer>(); + + subsetFDSelect = new LinkedHashMap<Integer, FDIndexReference>(); + + List<List<Integer>> foundLocalUniques = new ArrayList<List<Integer>>(); + for (int i = 0; i < uniqueGroups.size(); i++) { + foundLocalUniques.add(new ArrayList<Integer>()); + } + for (int gid : subsetGlyphs.keySet()) { + int group = subsetGroups.get(gid); + localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData(); + localUniques = foundLocalUniques.get(uniqueGroups.indexOf(subsetGroups.get(gid))); + + FDIndexReference newFDReference = new FDIndexReference( + uniqueGroups.indexOf(subsetGroups.get(gid)), subsetGroups.get(gid)); + subsetFDSelect.put(subsetGlyphs.get(gid), newFDReference); + byte[] data = charStringsIndex.getValue(gid); + preScanForSubsetIndexSize(data); + } + + //Create the two lists which are to store the local and global subroutines + subsetGlobalIndexSubr = new ArrayList<byte[]>(); + + fdSubrs = new ArrayList<List<byte[]>>(); + subsetGlobalSubrCount = globalUniques.size(); + globalUniques.clear(); + localUniques = null; + + for (int l = 0; l < foundLocalUniques.size(); l++) { + fdSubrs.add(new ArrayList<byte[]>()); + } + List<List<Integer>> foundLocalUniquesB = new ArrayList<List<Integer>>(); + for (int k = 0; k < uniqueGroups.size(); k++) { + foundLocalUniquesB.add(new ArrayList<Integer>()); + } + for (Integer gid : subsetGlyphs.keySet()) { + int group = subsetGroups.get(gid); + localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData(); + localUniques = foundLocalUniquesB.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()); + byte[] data = charStringsIndex.getValue(gid); + subsetLocalIndexSubr = fdSubrs.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()); + subsetLocalSubrCount = foundLocalUniques.get( + subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()).size(); + data = readCharStringData(data, subsetLocalSubrCount); + subsetCharStringsIndex.add(data); + } + } + } + + private void writeFDSelect() { + writeByte(0); //Format + for (Integer gid : subsetFDSelect.keySet()) { + writeByte(subsetFDSelect.get(gid).getNewFDIndex()); + } + } + + private List<Integer> getUsedFDFonts() { + List<Integer> uniqueNewRefs = new ArrayList<Integer>(); + for (int gid : subsetFDSelect.keySet()) { + int fdIndex = subsetFDSelect.get(gid).getOldFDIndex(); + if (!uniqueNewRefs.contains(fdIndex)) { + uniqueNewRefs.add(fdIndex); + } + } + return uniqueNewRefs; + } + + private List<Integer> writeCIDDictsAndSubrs(List<Integer> uniqueNewRefs) + throws IOException { + List<Integer> privateDictOffsets = new ArrayList<Integer>(); + List<FontDict> fdFonts = cffReader.getFDFonts(); + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict curFDFont = fdFonts.get(uniqueNewRefs.get(i)); + HashMap<String, DICTEntry> fdPrivateDict = cffReader.parseDictData( + curFDFont.getPrivateDictData()); + int privateDictOffset = currentPos; + privateDictOffsets.add(privateDictOffset); + byte[] fdPrivateDictByteData = curFDFont.getPrivateDictData(); + if (fdPrivateDict.get("Subrs") != null) { + fdPrivateDictByteData = updateOffset(fdPrivateDictByteData, fdPrivateDict.get("Subrs").getOffset(), + fdPrivateDict.get("Subrs").getOperandLength(), + fdPrivateDictByteData.length); + } + writeBytes(fdPrivateDictByteData); + writeIndex(fdSubrs.get(i)); + } + return privateDictOffsets; + } + + private int writeFDArray(List<Integer> uniqueNewRefs, List<Integer> privateDictOffsets, + List<Integer> fontNameSIDs) + throws IOException { + int offset = currentPos; + List<FontDict> fdFonts = cffReader.getFDFonts(); + + writeCard16(uniqueNewRefs.size()); + writeByte(1); //Offset size + writeByte(1); //First offset + + int count = 1; + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i)); + count += fdFont.getByteData().length; + writeByte(count); + } + + for (int i = 0; i < uniqueNewRefs.size(); i++) { + FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i)); + byte[] fdFontByteData = fdFont.getByteData(); + Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData); + //Update the SID to the FontName + fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("FontName").getOffset() - 1, + fdFontDict.get("FontName").getOperandLengths().get(0), + fontNameSIDs.get(i)); + //Update the Private dict reference + fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("Private").getOffset() + + fdFontDict.get("Private").getOperandLengths().get(0), + fdFontDict.get("Private").getOperandLengths().get(1), + privateDictOffsets.get(i)); + writeBytes(fdFontByteData); + } + return offset; + } + + private class FDIndexReference { + private int newFDIndex; + private int oldFDIndex; + + public FDIndexReference(int newFDIndex, int oldFDIndex) { + this.newFDIndex = newFDIndex; + this.oldFDIndex = oldFDIndex; + } + + public int getNewFDIndex() { + return newFDIndex; + } + + public int getOldFDIndex() { + return oldFDIndex; + } + } + + private void createCharStringData() throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + + CFFIndexData charStringsIndex = cffReader.getCharStringIndex(); + + DICTEntry privateEntry = topDICT.get("Private"); + if (privateEntry != null) { + int privateOffset = privateEntry.getOperands().get(1).intValue(); + Map<String, DICTEntry> privateDICT = cffReader.getPrivateDict(privateEntry); + + if (privateDICT.get("Subrs") != null) { + int localSubrOffset = privateOffset + privateDICT.get("Subrs").getOperands().get(0).intValue(); + localIndexSubr = cffReader.readIndex(localSubrOffset); + } else { + localIndexSubr = cffReader.readIndex(null); + } + } + + globalIndexSubr = cffReader.getGlobalIndexSubr(); + + //Create the two lists which are to store the local and global subroutines + subsetLocalIndexSubr = new ArrayList<byte[]>(); + subsetGlobalIndexSubr = new ArrayList<byte[]>(); + + //Create the new char string index + subsetCharStringsIndex = new ArrayList<byte[]>(); + + localUniques = new ArrayList<Integer>(); + globalUniques = new ArrayList<Integer>(); + + for (int gid : subsetGlyphs.keySet()) { + byte[] data = charStringsIndex.getValue(gid); + preScanForSubsetIndexSize(data); + } + + //Store the size of each subset index and clear the unique arrays + subsetLocalSubrCount = localUniques.size(); + subsetGlobalSubrCount = globalUniques.size(); + localUniques.clear(); + globalUniques.clear(); + + for (int gid : subsetGlyphs.keySet()) { + byte[] data = charStringsIndex.getValue(gid); + //Retrieve modified char string data and fill local / global subroutine arrays + data = readCharStringData(data, subsetLocalSubrCount); + subsetCharStringsIndex.add(data); + } + } + + private void preScanForSubsetIndexSize(byte[] data) throws IOException { + boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0; + boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0; + BytesNumber operand = new BytesNumber(-1, -1); + for (int dataPos = 0; dataPos < data.length; dataPos++) { + int b0 = data[dataPos] & 0xff; + if (b0 == LOCAL_SUBROUTINE && hasLocalSubroutines) { + int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber()); + + if (!localUniques.contains(subrNumber) && subrNumber < localIndexSubr.getNumObjects()) { + localUniques.add(subrNumber); + byte[] subr = localIndexSubr.getValue(subrNumber); + preScanForSubsetIndexSize(subr); + } + operand.clearNumber(); + } else if (b0 == GLOBAL_SUBROUTINE && hasGlobalSubroutines) { + int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber()); + + if (!globalUniques.contains(subrNumber) && subrNumber < globalIndexSubr.getNumObjects()) { + globalUniques.add(subrNumber); + byte[] subr = globalIndexSubr.getValue(subrNumber); + preScanForSubsetIndexSize(subr); + } + operand.clearNumber(); + } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) { + operand.clearNumber(); + if (b0 == 19 || b0 == 20) { + dataPos += 1; + } + } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) { + operand = readNumber(b0, data, dataPos); + dataPos += operand.getNumBytes() - 1; + } + } + } + + private int getSubrNumber(int numSubroutines, int operand) { + int bias = getBias(numSubroutines); + return bias + operand; + } + + private byte[] readCharStringData(byte[] data, int subsetLocalSubrCount) throws IOException { + boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0; + boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0; + BytesNumber operand = new BytesNumber(-1, -1); + for (int dataPos = 0; dataPos < data.length; dataPos++) { + int b0 = data[dataPos] & 0xff; + if (b0 == 10 && hasLocalSubroutines) { + int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber()); + + int newRef = getNewRefForReference(subrNumber, localUniques, localIndexSubr, subsetLocalIndexSubr, + subsetLocalSubrCount); + + if (newRef != -1) { + byte[] newData = constructNewRefData(dataPos, data, operand, subsetLocalSubrCount, + newRef, new int[] {10}); + dataPos -= data.length - newData.length; + data = newData; + } + + operand.clearNumber(); + } else if (b0 == 29 && hasGlobalSubroutines) { + int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber()); + + int newRef = getNewRefForReference(subrNumber, globalUniques, globalIndexSubr, subsetGlobalIndexSubr, + subsetGlobalSubrCount); + + if (newRef != -1) { + byte[] newData = constructNewRefData(dataPos, data, operand, subsetGlobalSubrCount, + newRef, new int[] {29}); + dataPos -= (data.length - newData.length); + data = newData; + } + + operand.clearNumber(); + } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) { + operand.clearNumber(); + if (b0 == 19 || b0 == 20) { + dataPos += 1; + } + } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) { + operand = readNumber(b0, data, dataPos); + dataPos += operand.getNumBytes() - 1; + } + } + + //Return the data with the modified references to our arrays + return data; + } + + private int getNewRefForReference(int subrNumber, List<Integer> uniquesArray, + CFFIndexData indexSubr, List<byte[]> subsetIndexSubr, int subrCount) throws IOException { + int newRef = -1; + if (!uniquesArray.contains(subrNumber)) { + if (subrNumber < indexSubr.getNumObjects()) { + byte[] subr = indexSubr.getValue(subrNumber); + subr = readCharStringData(subr, subrCount); + if (!uniquesArray.contains(subrNumber)) { + uniquesArray.add(subrNumber); + subsetIndexSubr.add(subr); + newRef = subsetIndexSubr.size() - 1; + } else { + newRef = uniquesArray.indexOf(subrNumber); + } + } + } else { + newRef = uniquesArray.indexOf(subrNumber); + } + return newRef; + } + + private int getBias(int subrCount) { + if (subrCount < 1240) { + return 107; + } else if (subrCount < 33900) { + return 1131; + } else { + return 32768; + } + } + + private byte[] constructNewRefData(int curDataPos, byte[] currentData, BytesNumber operand, + int fullSubsetIndexSize, int curSubsetIndexSize, int[] operatorCode) { + //Create the new array with the modified reference + byte[] newData; + int startRef = curDataPos - operand.getNumBytes(); + int length = operand.getNumBytes() + 1; + byte[] preBytes = new byte[startRef]; + System.arraycopy(currentData, 0, preBytes, 0, startRef); + int newBias = getBias(fullSubsetIndexSize); + int newRef = curSubsetIndexSize - newBias; + byte[] newRefBytes = createNewRef(newRef, operatorCode, -1); + newData = concatArray(preBytes, newRefBytes); + byte[] postBytes = new byte[currentData.length - (startRef + length)]; + System.arraycopy(currentData, startRef + length, postBytes, 0, + currentData.length - (startRef + length)); + return concatArray(newData, postBytes); + } + + public static byte[] createNewRef(int newRef, int[] operatorCode, int forceLength) { + byte[] newRefBytes; + int sizeOfOperator = operatorCode.length; + if ((forceLength == -1 && newRef <= 107) || forceLength == 1) { + newRefBytes = new byte[1 + sizeOfOperator]; + //The index values are 0 indexed + newRefBytes[0] = (byte)(newRef + 139); + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[1 + i] = (byte)operatorCode[i]; + } + } else if ((forceLength == -1 && newRef <= 1131) || forceLength == 2) { + newRefBytes = new byte[2 + sizeOfOperator]; + if (newRef <= 363) { + newRefBytes[0] = (byte)247; + } else if (newRef <= 619) { + newRefBytes[0] = (byte)248; + } else if (newRef <= 875) { + newRefBytes[0] = (byte)249; + } else { + newRefBytes[0] = (byte)250; + } + newRefBytes[1] = (byte)(newRef - 108); + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[2 + i] = (byte)operatorCode[i]; + } + } else if ((forceLength == -1 && newRef <= 32767) || forceLength == 3) { + newRefBytes = new byte[3 + sizeOfOperator]; + newRefBytes[0] = 28; + newRefBytes[1] = (byte)(newRef >> 8); + newRefBytes[2] = (byte)newRef; + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[3 + i] = (byte)operatorCode[i]; + } + } else { + newRefBytes = new byte[5 + sizeOfOperator]; + newRefBytes[0] = 29; + newRefBytes[1] = (byte)(newRef >> 24); + newRefBytes[2] = (byte)(newRef >> 16); + newRefBytes[3] = (byte)(newRef >> 8); + newRefBytes[4] = (byte)newRef; + for (int i = 0; i < operatorCode.length; i++) { + newRefBytes[5 + i] = (byte)operatorCode[i]; + } + } + return newRefBytes; + } + + public static byte[] concatArray(byte[] a, byte[] b) { + int aLen = a.length; + int bLen = b.length; + byte[] c = new byte[aLen + bLen]; + System.arraycopy(a, 0, c, 0, aLen); + System.arraycopy(b, 0, c, aLen, bLen); + return c; + } + + private int writeIndex(List<byte[]> dataArray) { + int hdrTotal = 3; + //2 byte number of items + this.writeCard16(dataArray.size()); + //Offset Size: 1 byte = 256, 2 bytes = 65536 etc. + int totLength = 0; + for (int i = 0; i < dataArray.size(); i++) { + totLength += dataArray.get(i).length; + } + int offSize = 1; + if (totLength <= (1 << 8)) { + offSize = 1; + } else if (totLength <= (1 << 16)) { + offSize = 2; + } else if (totLength <= (1 << 24)) { + offSize = 3; + } else { + offSize = 4; + } + this.writeByte(offSize); + //Count the first offset 1 + hdrTotal += offSize; + int total = 0; + for (int i = 0; i < dataArray.size(); i++) { + hdrTotal += offSize; + int length = dataArray.get(i).length; + switch (offSize) { + case 1: + if (i == 0) { + writeByte(1); + } + total += length; + writeByte(total + 1); + break; + case 2: + if (i == 0) { + writeCard16(1); + } + total += length; + writeCard16(total + 1); + break; + case 3: + if (i == 0) { + writeThreeByteNumber(1); + } + total += length; + writeThreeByteNumber(total + 1); + break; + case 4: + if (i == 0) { + writeULong(1); + } + total += length; + writeULong(total + 1); + break; + default: + throw new AssertionError("Offset Size was not an expected value."); + } + } + for (int i = 0; i < dataArray.size(); i++) { + writeBytes(dataArray.get(i)); + } + return hdrTotal + total; + } + + + private BytesNumber readNumber(int b0, byte[] input, int curPos) throws IOException { + if (b0 == 28) { + int b1 = input[curPos + 1] & 0xff; + int b2 = input[curPos + 2] & 0xff; + return new BytesNumber(Integer.valueOf((short) (b1 << 8 | b2)), 3); + } else if (b0 >= 32 && b0 <= 246) { + return new BytesNumber(Integer.valueOf(b0 - 139), 1); + } else if (b0 >= 247 && b0 <= 250) { + int b1 = input[curPos + 1] & 0xff; + return new BytesNumber(Integer.valueOf((b0 - 247) * 256 + b1 + 108), 2); + } else if (b0 >= 251 && b0 <= 254) { + int b1 = input[curPos + 1] & 0xff; + return new BytesNumber(Integer.valueOf(-(b0 - 251) * 256 - b1 - 108), 2); + } else if (b0 == 255) { + int b1 = input[curPos + 1] & 0xff; + int b2 = input[curPos + 2] & 0xff; + return new BytesNumber(Integer.valueOf((short)(b1 << 8 | b2)), 5); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * A class used to store the last number operand and also it's size in bytes + */ + private static final class BytesNumber { + private int number; + private int numBytes; + + public BytesNumber(int number, int numBytes) { + this.number = number; + this.numBytes = numBytes; + } + + public int getNumber() { + return this.number; + } + + public int getNumBytes() { + return this.numBytes; + } + + public void clearNumber() { + this.number = -1; + this.numBytes = -1; + } + } + + private void writeCharsetTable(boolean cidFont) throws IOException { + writeByte(0); + for (int gid : gidToSID.keySet()) { + if (cidFont && gid == 0) { + continue; + } + writeCard16((cidFont) ? gid : gidToSID.get(gid)); + } + } + + private void writePrivateDict() throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + + DICTEntry privateEntry = topDICT.get("Private"); + if (privateEntry != null) { + writeBytes(cffReader.getPrivateDictBytes(privateEntry)); + } + } + + private void updateOffsets(int topDictOffset, int charsetOffset, int charStringOffset, + int privateDictOffset, int localIndexOffset, int encodingOffset) + throws IOException { + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + Map<String, DICTEntry> privateDICT = null; + + DICTEntry privateEntry = topDICT.get("Private"); + if (privateEntry != null) { + privateDICT = cffReader.getPrivateDict(privateEntry); + } + + int dataPos = 3 + (cffReader.getTopDictIndex().getOffSize() + * cffReader.getTopDictIndex().getOffsets().length); + int dataTopDictOffset = topDictOffset + dataPos; + + updateFixedOffsets(topDICT, dataTopDictOffset, charsetOffset, charStringOffset, encodingOffset); + + if (privateDICT != null) { + //Private index offset in the top dict + int oldPrivateOffset = dataTopDictOffset + privateEntry.getOffset(); + output = updateOffset(output, oldPrivateOffset + privateEntry.getOperandLengths().get(0), + privateEntry.getOperandLengths().get(1), privateDictOffset); + + //Update the local subroutine index offset in the private dict + DICTEntry subroutines = privateDICT.get("Subrs"); + int oldLocalSubrOffset = privateDictOffset + subroutines.getOffset(); + //Value needs to be converted to -139 etc. + int encodeValue = 0; + if (subroutines.getOperandLength() == 1) { + encodeValue = 139; + } + output = updateOffset(output, oldLocalSubrOffset, subroutines.getOperandLength(), + (localIndexOffset - privateDictOffset) + encodeValue); + } + } + + private void updateFixedOffsets(Map<String, DICTEntry> topDICT, int dataTopDictOffset, + int charsetOffset, int charStringOffset, int encodingOffset) { + //Charset offset in the top dict + DICTEntry charset = topDICT.get("charset"); + int oldCharsetOffset = dataTopDictOffset + charset.getOffset(); + output = updateOffset(output, oldCharsetOffset, charset.getOperandLength(), charsetOffset); + + //Char string index offset in the private dict + DICTEntry charString = topDICT.get("CharStrings"); + int oldCharStringOffset = dataTopDictOffset + charString.getOffset(); + output = updateOffset(output, oldCharStringOffset, charString.getOperandLength(), charStringOffset); + + DICTEntry encodingEntry = topDICT.get("Encoding"); + if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0 + && encodingEntry.getOperands().get(0).intValue() != 1) { + int oldEncodingOffset = dataTopDictOffset + encodingEntry.getOffset(); + output = updateOffset(output, oldEncodingOffset, encodingEntry.getOperandLength(), encodingOffset); + } + } + + private void updateCIDOffsets(int topDictDataOffset, int fdArrayOffset, int fdSelectOffset, + int charsetOffset, int charStringOffset, int encodingOffset) { + LinkedHashMap<String, DICTEntry> topDict = cffReader.getTopDictEntries(); + + DICTEntry fdArrayEntry = topDict.get("FDArray"); + if (fdArrayEntry != null) { + output = updateOffset(output, topDictDataOffset + fdArrayEntry.getOffset() - 1, + fdArrayEntry.getOperandLength(), fdArrayOffset); + } + + DICTEntry fdSelect = topDict.get("FDSelect"); + if (fdSelect != null) { + output = updateOffset(output, topDictDataOffset + fdSelect.getOffset() - 1, + fdSelect.getOperandLength(), fdSelectOffset); + } + + updateFixedOffsets(topDict, topDictDataOffset, charsetOffset, charStringOffset, encodingOffset); + } + + private byte[] updateOffset(byte[] out, int position, int length, int replacement) { + switch (length) { + case 1: + out[position] = (byte)(replacement & 0xFF); + break; + case 2: + if (replacement <= 363) { + out[position] = (byte)247; + } else if (replacement <= 619) { + out[position] = (byte)248; + } else if (replacement <= 875) { + out[position] = (byte)249; + } else { + out[position] = (byte)250; + } + out[position + 1] = (byte)(replacement - 108); + break; + case 3: + out[position] = (byte)28; + out[position + 1] = (byte)((replacement >> 8) & 0xFF); + out[position + 2] = (byte)(replacement & 0xFF); + break; + case 5: + out[position] = (byte)29; + out[position + 1] = (byte)((replacement >> 24) & 0xFF); + out[position + 2] = (byte)((replacement >> 16) & 0xFF); + out[position + 3] = (byte)((replacement >> 8) & 0xFF); + out[position + 4] = (byte)(replacement & 0xFF); + break; + default: + } + return out; + } + + /** + * Appends a byte to the output array, + * updates currentPost but not realSize + */ + private void writeByte(int b) { + output[currentPos++] = (byte)b; + realSize++; + } + + /** + * Appends a USHORT to the output array, + * updates currentPost but not realSize + */ + private void writeCard16(int s) { + byte b1 = (byte)((s >> 8) & 0xff); + byte b2 = (byte)(s & 0xff); + writeByte(b1); + writeByte(b2); + } + + private void writeThreeByteNumber(int s) { + byte b1 = (byte)((s >> 16) & 0xFF); + byte b2 = (byte)((s >> 8) & 0xFF); + byte b3 = (byte)(s & 0xFF); + output[currentPos++] = b1; + output[currentPos++] = b2; + output[currentPos++] = b3; + realSize += 3; + } + + /** + * Appends a ULONG to the output array, + * at the given position + */ + private void writeULong(int s) { + byte b1 = (byte)((s >> 24) & 0xff); + byte b2 = (byte)((s >> 16) & 0xff); + byte b3 = (byte)((s >> 8) & 0xff); + byte b4 = (byte)(s & 0xff); + output[currentPos++] = b1; + output[currentPos++] = b2; + output[currentPos++] = b3; + output[currentPos++] = b4; + realSize += 4; + } + + /** + * Returns a subset of the fonts (readFont() MUST be called first in order to create the + * subset). + * @return byte array + */ + public byte[] getFontSubset() { + byte[] ret = new byte[realSize]; + System.arraycopy(output, 0, ret, 0, realSize); + return ret; + } +} diff --git a/src/java/org/apache/fop/fonts/truetype/OpenFont.java b/src/java/org/apache/fop/fonts/truetype/OpenFont.java new file mode 100644 index 000000000..3f4765cdc --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/OpenFont.java @@ -0,0 +1,1971 @@ +/* + * 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.fonts.truetype; + +import java.awt.Rectangle; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.fonts.Glyphs; + +import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException; +import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; +import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; +import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable; +import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader; +import org.apache.fop.fonts.CMapSegment; +import org.apache.fop.fonts.FontUtil; +import org.apache.fop.fonts.MultiByteFont; + +public abstract class OpenFont { + + static final byte NTABS = 24; + static final int MAX_CHAR_CODE = 255; + static final int ENC_BUF_SIZE = 1024; + + private static final String[] MAC_GLYPH_ORDERING = { + /* 0x000 */ + ".notdef", ".null", "nonmarkingreturn", "space", + "exclam", "quotedbl", "numbersign", "dollar", + "percent", "ampersand", "quotesingle", "parenleft", + "parenright", "asterisk", "plus", "comma", + /* 0x010 */ + "hyphen", "period", "slash", "zero", + "one", "two", "three", "four", + "five", "six", "seven", "eight", + "nine", "colon", "semicolon", "less", + /* 0x020 */ + "equal", "greater", "question", "at", + "A", "B", "C", "D", + "E", "F", "G", "H", + "I", "J", "K", "L", + /* 0x030 */ + "M", "N", "O", "P", + "Q", "R", "S", "T", + "U", "V", "W", "X", + "Y", "Z", "bracketleft", "backslash", + /* 0x040 */ + "bracketright", "asciicircum", "underscore", "grave", + "a", "b", "c", "d", + "e", "f", "g", "h", + "i", "j", "k", "l", + /* 0x050 */ + "m", "n", "o", "p", + "q", "r", "s", "t", + "u", "v", "w", "x", + "y", "z", "braceleft", "bar", + /* 0x060 */ + "braceright", "asciitilde", "Adieresis", "Aring", + "Ccedilla", "Eacute", "Ntilde", "Odieresis", + "Udieresis", "aacute", "agrave", "acircumflex", + "adieresis", "atilde", "aring", "ccedilla", + /* 0x070 */ + "eacute", "egrave", "ecircumflex", "edieresis", + "iacute", "igrave", "icircumflex", "idieresis", + "ntilde", "oacute", "ograve", "ocircumflex", + "odieresis", "otilde", "uacute", "ugrave", + /* 0x080 */ + "ucircumflex", "udieresis", "dagger", "degree", + "cent", "sterling", "section", "bullet", + "paragraph", "germandbls", "registered", "copyright", + "trademark", "acute", "dieresis", "notequal", + /* 0x090 */ + "AE", "Oslash", "infinity", "plusminus", + "lessequal", "greaterequal", "yen", "mu", + "partialdiff", "summation", "product", "pi", + "integral", "ordfeminine", "ordmasculine", "Omega", + /* 0x0A0 */ + "ae", "oslash", "questiondown", "exclamdown", + "logicalnot", "radical", "florin", "approxequal", + "Delta", "guillemotleft", "guillemotright", "ellipsis", + "nonbreakingspace", "Agrave", "Atilde", "Otilde", + /* 0x0B0 */ + "OE", "oe", "endash", "emdash", + "quotedblleft", "quotedblright", "quoteleft", "quoteright", + "divide", "lozenge", "ydieresis", "Ydieresis", + "fraction", "currency", "guilsinglleft", "guilsinglright", + /* 0x0C0 */ + "fi", "fl", "daggerdbl", "periodcentered", + "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", + "Ecircumflex", "Aacute", "Edieresis", "Egrave", + "Iacute", "Icircumflex", "Idieresis", "Igrave", + /* 0x0D0 */ + "Oacute", "Ocircumflex", "apple", "Ograve", + "Uacute", "Ucircumflex", "Ugrave", "dotlessi", + "circumflex", "tilde", "macron", "breve", + "dotaccent", "ring", "cedilla", "hungarumlaut", + /* 0x0E0 */ + "ogonek", "caron", "Lslash", "lslash", + "Scaron", "scaron", "Zcaron", "zcaron", + "brokenbar", "Eth", "eth", "Yacute", + "yacute", "Thorn", "thorn", "minus", + /* 0x0F0 */ + "multiply", "onesuperior", "twosuperior", "threesuperior", + "onehalf", "onequarter", "threequarters", "franc", + "Gbreve", "gbreve", "Idotaccent", "Scedilla", + "scedilla", "Cacute", "cacute", "Ccaron", + /* 0x100 */ + "ccaron", "dcroat" + }; + + /** The FontFileReader used to read this TrueType font. */ + protected FontFileReader fontFile; + + /** Set to true to get even more debug output than with level DEBUG */ + public static final boolean TRACE_ENABLED = false; + + private static final String ENCODING = "WinAnsiEncoding"; // Default encoding + + private static final short FIRST_CHAR = 0; + + protected boolean useKerning = false; + private boolean isEmbeddable = true; + private boolean hasSerifs = true; + /** + * Table directory + */ + protected Map<OFTableName, OFDirTabEntry> dirTabs; + + private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs + private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding + private List<CMapSegment> cmaps; + protected List<UnicodeMapping> unicodeMappings; + + private int upem; // unitsPerEm from "head" table + private int nhmtx; // Number of horizontal metrics + private PostScriptVersion postScriptVersion; + protected int locaFormat; + /** + * Offset to last loca + */ + protected long lastLoca = 0; + protected int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table) + + /** + * Contains glyph data + */ + protected OFMtxEntry[] mtxTab; // Contains glyph data + + protected String postScriptName = ""; + protected String fullName = ""; + protected String notice = ""; + protected final Set<String> familyNames = new HashSet<String>(); + protected String subFamilyName = ""; + + private long italicAngle = 0; + private long isFixedPitch = 0; + private int fontBBox1 = 0; + private int fontBBox2 = 0; + private int fontBBox3 = 0; + private int fontBBox4 = 0; + private int capHeight = 0; + private int os2CapHeight = 0; + private int underlinePosition; + private int underlineThickness; + private int strikeoutPosition; + private int strikeoutThickness; + private int xHeight = 0; + private int os2xHeight = 0; + //Effective ascender/descender + private int ascender = 0; + private int descender = 0; + //Ascender/descender from hhea table + private int hheaAscender = 0; + private int hheaDescender = 0; + //Ascender/descender from OS/2 table + private int os2Ascender = 0; + private int os2Descender = 0; + private int usWeightClass = 0; + + private short lastChar = 0; + + private int[] ansiWidth; + private Map<Integer, List<Integer>> ansiIndex; + + // internal mapping of glyph indexes to unicode indexes + // used for quick mappings in this class + private final Map<Integer, Integer> glyphToUnicodeMap = new HashMap<Integer, Integer>(); + private final Map<Integer, Integer> unicodeToGlyphMap = new HashMap<Integer, Integer>(); + + private boolean isCFF; + + // advanced typographic table support + protected boolean useAdvanced = false; + protected OTFAdvancedTypographicTableReader advancedTableReader; + + /** + * Version of the PostScript table (<q>post</q>) contained in this font. + */ + public static enum PostScriptVersion { + /** PostScript table version 1.0. */ + V1, + /** PostScript table version 2.0. */ + V2, + /** PostScript table version 3.0. */ + V3, + /** Unknown version of the PostScript table. */ + UNKNOWN; + } + + /** + * logging instance + */ + protected Log log = LogFactory.getLog(TTFFile.class); + + public OpenFont() { + this(true, false); + } + + /** + * Constructor + * @param useKerning true if kerning data should be loaded + * @param useAdvanced true if advanced typographic tables should be loaded + */ + public OpenFont(boolean useKerning, boolean useAdvanced) { + this.useKerning = useKerning; + this.useAdvanced = useAdvanced; + } + + /** + * Key-value helper class. + */ + final class UnicodeMapping implements Comparable { + + private final int unicodeIndex; + private final int glyphIndex; + + UnicodeMapping(int glyphIndex, int unicodeIndex) { + this.unicodeIndex = unicodeIndex; + this.glyphIndex = glyphIndex; + glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex)); + unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex)); + } + + /** + * Returns the glyphIndex. + * @return the glyph index + */ + public int getGlyphIndex() { + return glyphIndex; + } + + /** + * Returns the unicodeIndex. + * @return the Unicode index + */ + public int getUnicodeIndex() { + return unicodeIndex; + } + + + /** {@inheritDoc} */ + public int hashCode() { + int hc = unicodeIndex; + hc = 19 * hc + (hc ^ glyphIndex); + return hc; + } + + /** {@inheritDoc} */ + public boolean equals(Object o) { + if (o instanceof UnicodeMapping) { + UnicodeMapping m = (UnicodeMapping) o; + if (unicodeIndex != m.unicodeIndex) { + return false; + } else { + return (glyphIndex == m.glyphIndex); + } + } else { + return false; + } + } + + /** {@inheritDoc} */ + public int compareTo(Object o) { + if (o instanceof UnicodeMapping) { + UnicodeMapping m = (UnicodeMapping) o; + if (unicodeIndex > m.unicodeIndex) { + return 1; + } else if (unicodeIndex < m.unicodeIndex) { + return -1; + } else { + return 0; + } + } else { + return -1; + } + } + } + + /** + * Obtain directory table entry. + * @param name (tag) of entry + * @return a directory table entry or null if none found + */ + public OFDirTabEntry getDirectoryEntry(OFTableName name) { + return dirTabs.get(name); + } + + /** + * Position inputstream to position indicated + * in the dirtab offset + offset + * @param in font file reader + * @param tableName (tag) of table + * @param offset from start of table + * @return true if seek succeeded + * @throws IOException if I/O exception occurs during seek + */ + public boolean seekTab(FontFileReader in, OFTableName tableName, + long offset) throws IOException { + OFDirTabEntry dt = dirTabs.get(tableName); + if (dt == null) { + log.error("Dirtab " + tableName.getName() + " not found."); + return false; + } else { + in.seekSet(dt.getOffset() + offset); + } + return true; + } + + /** + * Convert from truetype unit to pdf unit based on the + * unitsPerEm field in the "head" table + * @param n truetype unit + * @return pdf unit + */ + public int convertTTFUnit2PDFUnit(int n) { + int ret; + if (n < 0) { + long rest1 = n % upem; + long storrest = 1000 * rest1; + long ledd2 = (storrest != 0 ? rest1 / storrest : 0); + ret = -((-1000 * n) / upem - (int)ledd2); + } else { + ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; + } + + return ret; + } + + /** + * Read the cmap table, + * return false if the table is not present or only unsupported + * tables are present. Currently only unicode cmaps are supported. + * Set the unicodeIndex in the TTFMtxEntries and fills in the + * cmaps vector. + */ + protected boolean readCMAP() throws IOException { + + unicodeMappings = new ArrayList<OpenFont.UnicodeMapping>(); + + seekTab(fontFile, OFTableName.CMAP, 2); + int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables + long cmapUniOffset = 0; + long symbolMapOffset = 0; + + if (log.isDebugEnabled()) { + log.debug(numCMap + " cmap tables"); + } + + //Read offset for all tables. We are only interested in the unicode table + for (int i = 0; i < numCMap; i++) { + int cmapPID = fontFile.readTTFUShort(); + int cmapEID = fontFile.readTTFUShort(); + long cmapOffset = fontFile.readTTFLong(); + + if (log.isDebugEnabled()) { + log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); + } + + if (cmapPID == 3 && cmapEID == 1) { + cmapUniOffset = cmapOffset; + } + if (cmapPID == 3 && cmapEID == 0) { + symbolMapOffset = cmapOffset; + } + } + + if (cmapUniOffset > 0) { + return readUnicodeCmap(cmapUniOffset, 1); + } else if (symbolMapOffset > 0) { + return readUnicodeCmap(symbolMapOffset, 0); + } else { + log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table" + + " not present. Aborting"); + return false; + } + } + + private boolean readUnicodeCmap(long cmapUniOffset, int encodingID) + throws IOException { + //Read CMAP table and correct mtxTab.index + int mtxPtr = 0; + + // Read unicode cmap + seekTab(fontFile, OFTableName.CMAP, cmapUniOffset); + int cmapFormat = fontFile.readTTFUShort(); + /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length + + if (log.isDebugEnabled()) { + log.debug("CMAP format: " + cmapFormat); + } + + if (cmapFormat == 4) { + fontFile.skip(2); // Skip version number + int cmapSegCountX2 = fontFile.readTTFUShort(); + int cmapSearchRange = fontFile.readTTFUShort(); + int cmapEntrySelector = fontFile.readTTFUShort(); + int cmapRangeShift = fontFile.readTTFUShort(); + + if (log.isDebugEnabled()) { + log.debug("segCountX2 : " + cmapSegCountX2); + log.debug("searchRange : " + cmapSearchRange); + log.debug("entrySelector: " + cmapEntrySelector); + log.debug("rangeShift : " + cmapRangeShift); + } + + + int[] cmapEndCounts = new int[cmapSegCountX2 / 2]; + int[] cmapStartCounts = new int[cmapSegCountX2 / 2]; + int[] cmapDeltas = new int[cmapSegCountX2 / 2]; + int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2]; + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapEndCounts[i] = fontFile.readTTFUShort(); + } + + fontFile.skip(2); // Skip reservedPad + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapStartCounts[i] = fontFile.readTTFUShort(); + } + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapDeltas[i] = fontFile.readTTFShort(); + } + + //int startRangeOffset = in.getCurrentPos(); + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapRangeOffsets[i] = fontFile.readTTFUShort(); + } + + int glyphIdArrayOffset = fontFile.getCurrentPos(); + + BitSet eightBitGlyphs = new BitSet(256); + + // Insert the unicode id for the glyphs in mtxTab + // and fill in the cmaps ArrayList + for (int i = 0; i < cmapStartCounts.length; i++) { + + if (log.isTraceEnabled()) { + log.trace(i + ": " + cmapStartCounts[i] + + " - " + cmapEndCounts[i]); + } + if (log.isDebugEnabled()) { + if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) { + log.debug("Font contains glyphs in the Unicode private use area: " + + Integer.toHexString(cmapStartCounts[i]) + " - " + + Integer.toHexString(cmapEndCounts[i])); + } + } + + for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { + + // Update lastChar + if (j < 256 && j > lastChar) { + lastChar = (short)j; + } + + if (j < 256) { + eightBitGlyphs.set(j); + } + + if (mtxPtr < mtxTab.length) { + int glyphIdx; + // the last character 65535 = .notdef + // may have a range offset + if (cmapRangeOffsets[i] != 0 && j != 65535) { + int glyphOffset = glyphIdArrayOffset + + ((cmapRangeOffsets[i] / 2) + + (j - cmapStartCounts[i]) + + (i) + - cmapSegCountX2 / 2) * 2; + fontFile.seekSet(glyphOffset); + glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i]) + & 0xffff; + //mtxTab[glyphIdx].setName(mtxTab[glyphIdx].getName() + " - "+(char)j); + unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + + if (encodingID == 0 && j >= 0xF020 && j <= 0xF0FF) { + //Experimental: Mapping 0xF020-0xF0FF to 0x0020-0x00FF + //Tested with Wingdings and Symbol TTF fonts which map their + //glyphs in the region 0xF020-0xF0FF. + int mapped = j - 0xF000; + if (!eightBitGlyphs.get(mapped)) { + //Only map if Unicode code point hasn't been mapped before + unicodeMappings.add(new UnicodeMapping(glyphIdx, mapped)); + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(mapped)); + } + } + + // Also add winAnsiWidth + List<Integer> v = ansiIndex.get(new Integer(j)); + if (v != null) { + for (Integer aIdx : v) { + ansiWidth[aIdx.intValue()] + = mtxTab[glyphIdx].getWx(); + + if (log.isTraceEnabled()) { + log.trace("Added width " + + mtxTab[glyphIdx].getWx() + + " uni: " + j + + " ansi: " + aIdx.intValue()); + } + } + } + + if (log.isTraceEnabled()) { + log.trace("Idx: " + + glyphIdx + + " Delta: " + cmapDeltas[i] + + " Unicode: " + j + + " name: " + mtxTab[glyphIdx].getName()); + } + } else { + glyphIdx = (j + cmapDeltas[i]) & 0xffff; + + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + log.debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + + unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + log.debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + + // Also add winAnsiWidth + List<Integer> v = ansiIndex.get(new Integer(j)); + if (v != null) { + for (Integer aIdx : v) { + ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); + } + } + + //getLogger().debug("IIdx: " + + // mtxPtr + + // " Delta: " + cmap_deltas[i] + + // " Unicode: " + j + + // " name: " + + // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); + + } + if (glyphIdx < mtxTab.length) { + if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { + mtxPtr++; + } + } + } + } + } + } else { + log.error("Cmap format not supported: " + cmapFormat); + return false; + } + return true; + } + + private boolean isInPrivateUseArea(int start, int end) { + return (isInPrivateUseArea(start) || isInPrivateUseArea(end)); + } + + private boolean isInPrivateUseArea(int unicode) { + return (unicode >= 0xE000 && unicode <= 0xF8FF); + } + + /** + * + * @return mmtx data + */ + public List<OFMtxEntry> getMtx() { + return Collections.unmodifiableList(Arrays.asList(mtxTab)); + } + + /** + * Print first char/last char + */ + private void printMaxMin() { + int min = 255; + int max = 0; + for (int i = 0; i < mtxTab.length; i++) { + if (mtxTab[i].getIndex() < min) { + min = mtxTab[i].getIndex(); + } + if (mtxTab[i].getIndex() > max) { + max = mtxTab[i].getIndex(); + } + } + log.info("Min: " + min); + log.info("Max: " + max); + } + + + /** + * Reads the font using a FontFileReader. + * + * @param in The FontFileReader to use + * @throws IOException In case of an I/O problem + */ + public void readFont(FontFileReader in, String header) throws IOException { + readFont(in, header, (String)null); + } + + /** + * initialize the ansiWidths array (for winAnsiEncoding) + * and fill with the missingwidth + */ + protected void initAnsiWidths() { + ansiWidth = new int[256]; + for (int i = 0; i < 256; i++) { + ansiWidth[i] = mtxTab[0].getWx(); + } + + // Create an index hash to the ansiWidth + // Can't just index the winAnsiEncoding when inserting widths + // same char (eg bullet) is repeated more than one place + ansiIndex = new HashMap<Integer, List<Integer>>(); + for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { + Integer ansi = new Integer(i); + Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]); + + List<Integer> v = ansiIndex.get(uni); + if (v == null) { + v = new ArrayList<Integer>(); + ansiIndex.put(uni, v); + } + v.add(ansi); + } + } + + /** + * Read the font data. + * If the fontfile is a TrueType Collection (.ttc file) + * the name of the font to read data for must be supplied, + * else the name is ignored. + * + * @param in The FontFileReader to use + * @param name The name of the font + * @return boolean Returns true if the font is valid + * @throws IOException In case of an I/O problem + */ + public boolean readFont(FontFileReader in, String header, String name) throws IOException { + initializeFont(in); + /* + * Check if TrueType collection, and that the name + * exists in the collection + */ + if (!checkTTC(header, name)) { + if (name == null) { + throw new IllegalArgumentException( + "For TrueType collection you must specify which font " + + "to select (-ttcname)"); + } else { + throw new IOException( + "Name does not exist in the TrueType collection: " + name); + } + } + + readDirTabs(); + readFontHeader(); + getNumGlyphs(); + if (log.isDebugEnabled()) { + log.debug("Number of glyphs in font: " + numberOfGlyphs); + } + readHorizontalHeader(); + readHorizontalMetrics(); + initAnsiWidths(); + readPostScript(); + readOS2(); + determineAscDesc(); + + readName(); + boolean pcltFound = readPCLT(); + // Read cmap table and fill in ansiwidths + boolean valid = readCMAP(); + if (!valid) { + return false; + } + + // Create cmaps for bfentries + createCMaps(); + updateBBoxAndOffset(); + + if (useKerning) { + readKerning(); + } + handleCharacterSpacing(in); + + guessVerticalMetricsFromGlyphBBox(); + return true; + } + + /** + * Reads a font. + * + * @param in FontFileReader to read from + * @param name Name to be checked for in the font file + * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and + * new index as (Integer) value) + * @throws IOException in case of an I/O problem + */ + public void readFont(FontFileReader in, String header, MultiByteFont mbfont) throws IOException { + readFont(in, header, mbfont.getTTCName()); + } + + protected abstract void updateBBoxAndOffset() throws IOException; + + protected abstract void readName() throws IOException; + + protected abstract void initializeFont(FontFileReader in) throws IOException; + + protected void handleCharacterSpacing(FontFileReader in) throws IOException { + // Read advanced typographic tables. + if (useAdvanced) { + try { + OTFAdvancedTypographicTableReader atr + = new OTFAdvancedTypographicTableReader(this, in); + atr.readAll(); + this.advancedTableReader = atr; + } catch (AdvancedTypographicTableFormatException e) { + log.warn( + "Encountered format constraint violation in advanced (typographic) table (AT) " + + "in font '" + getFullName() + "', ignoring AT data: " + + e.getMessage() + ); + } + } + + } + + protected void createCMaps() { + cmaps = new ArrayList<CMapSegment>(); + int unicodeStart; + int glyphStart; + int unicodeEnd; + + Iterator<UnicodeMapping> e = unicodeMappings.iterator(); + UnicodeMapping um = e.next(); + UnicodeMapping lastMapping = um; + + unicodeStart = um.getUnicodeIndex(); + glyphStart = um.getGlyphIndex(); + + while (e.hasNext()) { + um = e.next(); + if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) + || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { + unicodeEnd = lastMapping.getUnicodeIndex(); + cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); + unicodeStart = um.getUnicodeIndex(); + glyphStart = um.getGlyphIndex(); + } + lastMapping = um; + } + + unicodeEnd = lastMapping.getUnicodeIndex(); + cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); + } + + /** + * Returns the PostScript name of the font. + * @return String The PostScript name + */ + public String getPostScriptName() { + if (postScriptName.length() == 0) { + return FontUtil.stripWhiteSpace(getFullName()); + } else { + return postScriptName; + } + } + + PostScriptVersion getPostScriptVersion() { + return postScriptVersion; + } + + /** + * Returns the font family names of the font. + * @return Set The family names (a Set of Strings) + */ + public Set<String> getFamilyNames() { + return familyNames; + } + + /** + * Returns the font sub family name of the font. + * @return String The sub family name + */ + public String getSubFamilyName() { + return subFamilyName; + } + + /** + * Returns the full name of the font. + * @return String The full name + */ + public String getFullName() { + return fullName; + } + + /** + * Returns the name of the character set used. + * @return String The caracter set + */ + public String getCharSetName() { + return ENCODING; + } + + /** + * Returns the CapHeight attribute of the font. + * @return int The CapHeight + */ + public int getCapHeight() { + return convertTTFUnit2PDFUnit(capHeight); + } + + /** + * Returns the XHeight attribute of the font. + * @return int The XHeight + */ + public int getXHeight() { + return convertTTFUnit2PDFUnit(xHeight); + } + + /** + * Returns the number of bytes necessary to pad the currentPosition so that a table begins + * on a 4-byte boundary. + * @param currentPosition the position to pad. + * @return int the number of bytes to pad. + */ + protected int getPadSize(int currentPosition) { + int padSize = 4 - (currentPosition % 4); + return padSize < 4 ? padSize : 0; + } + + /** + * Returns the Flags attribute of the font. + * @return int The Flags + */ + public int getFlags() { + int flags = 32; // Use Adobe Standard charset + if (italicAngle != 0) { + flags |= 64; + } + if (isFixedPitch != 0) { + flags |= 2; + } + if (hasSerifs) { + flags |= 1; + } + return flags; + } + + /** + * Returns the weight class of this font. Valid values are 100, 200....,800, 900. + * @return the weight class value (or 0 if there was no OS/2 table in the font) + */ + public int getWeightClass() { + return this.usWeightClass; + } + + /** + * Returns the StemV attribute of the font. + * @return String The StemV + */ + public String getStemV() { + return "0"; + } + + /** + * Returns the ItalicAngle attribute of the font. + * @return String The ItalicAngle + */ + public String getItalicAngle() { + String ia = Short.toString((short)(italicAngle / 0x10000)); + + // This is the correct italic angle, however only int italic + // angles are supported at the moment so this is commented out. + /* + * if ((italicAngle % 0x10000) > 0 ) + * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); + */ + return ia; + } + + /** + * @return int[] The font bbox + */ + public int[] getFontBBox() { + final int[] fbb = new int[4]; + fbb[0] = convertTTFUnit2PDFUnit(fontBBox1); + fbb[1] = convertTTFUnit2PDFUnit(fontBBox2); + fbb[2] = convertTTFUnit2PDFUnit(fontBBox3); + fbb[3] = convertTTFUnit2PDFUnit(fontBBox4); + + return fbb; + } + + /** + * Returns the LowerCaseAscent attribute of the font. + * @return int The LowerCaseAscent + */ + public int getLowerCaseAscent() { + return convertTTFUnit2PDFUnit(ascender); + } + + /** + * Returns the LowerCaseDescent attribute of the font. + * @return int The LowerCaseDescent + */ + public int getLowerCaseDescent() { + return convertTTFUnit2PDFUnit(descender); + } + + /** + * Returns the index of the last character, but this is for WinAnsiEncoding + * only, so the last char is < 256. + * @return short Index of the last character (<256) + */ + public short getLastChar() { + return lastChar; + } + + /** + * Returns the index of the first character. + * @return short Index of the first character + */ + public short getFirstChar() { + return FIRST_CHAR; + } + + /** + * Returns an array of character widths. + * @return int[] The character widths + */ + public int[] getWidths() { + int[] wx = new int[mtxTab.length]; + for (int i = 0; i < wx.length; i++) { + wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx()); + } + return wx; + } + + public Rectangle[] getBoundingBoxes() { + Rectangle[] boundingBoxes = new Rectangle[mtxTab.length]; + for (int i = 0; i < boundingBoxes.length; i++) { + int[] boundingBox = mtxTab[i].getBoundingBox(); + boundingBoxes[i] = new Rectangle( + convertTTFUnit2PDFUnit(boundingBox[0]), + convertTTFUnit2PDFUnit(boundingBox[1]), + convertTTFUnit2PDFUnit(boundingBox[2] - boundingBox[0]), + convertTTFUnit2PDFUnit(boundingBox[3] - boundingBox[1])); + } + return boundingBoxes; + } + + /** + * Returns an array (xMin, yMin, xMax, yMax) for a glyph. + * + * @param glyphIndex the index of the glyph + * @return int[] Array defining bounding box. + */ + public int[] getBBox(int glyphIndex) { + int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox(); + int[] bbox = new int[4]; + for (int i = 0; i < 4; i++) { + bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]); + } + return bbox; + } + + /** + * Returns the width of a given character. + * @param idx Index of the character + * @return int Standard width + */ + public int getCharWidth(int idx) { + return convertTTFUnit2PDFUnit(ansiWidth[idx]); + } + + /** + * Returns the kerning table. + * @return Map The kerning table + */ + public Map<Integer, Map<Integer, Integer>> getKerning() { + return kerningTab; + } + + /** + * Returns the ANSI kerning table. + * @return Map The ANSI kerning table + */ + public Map<Integer, Map<Integer, Integer>> getAnsiKerning() { + return ansiKerningTab; + } + + public int getUnderlinePosition() { + return convertTTFUnit2PDFUnit(underlinePosition); + } + + public int getUnderlineThickness() { + return convertTTFUnit2PDFUnit(underlineThickness); + } + + public int getStrikeoutPosition() { + return convertTTFUnit2PDFUnit(strikeoutPosition); + } + + public int getStrikeoutThickness() { + return convertTTFUnit2PDFUnit(strikeoutThickness); + } + + /** + * Indicates if the font may be embedded. + * @return boolean True if it may be embedded + */ + public boolean isEmbeddable() { + return isEmbeddable; + } + + /** + * Indicates whether or not the font is an OpenType + * CFF font (rather than a TrueType font). + * @return true if the font is in OpenType CFF format. + */ + public boolean isCFF() { + return this.isCFF; + } + + /** + * Read Table Directory from the current position in the + * FontFileReader and fill the global HashMap dirTabs + * with the table name (String) as key and a TTFDirTabEntry + * as value. + * @throws IOException in case of an I/O problem + */ + protected void readDirTabs() throws IOException { + int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes) + switch (sfntVersion) { + case 0x10000: + log.debug("sfnt version: OpenType 1.0"); + break; + case 0x4F54544F: //"OTTO" + this.isCFF = true; + log.debug("sfnt version: OpenType with CFF data"); + break; + case 0x74727565: //"true" + log.debug("sfnt version: Apple TrueType"); + break; + case 0x74797031: //"typ1" + log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper"); + break; + default: + log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion)); + break; + } + int ntabs = fontFile.readTTFUShort(); + fontFile.skip(6); // 3xTTF_USHORT_SIZE + + dirTabs = new HashMap<OFTableName, OFDirTabEntry>(); + OFDirTabEntry[] pd = new OFDirTabEntry[ntabs]; + log.debug("Reading " + ntabs + " dir tables"); + + for (int i = 0; i < ntabs; i++) { + pd[i] = new OFDirTabEntry(); + String tableName = pd[i].read(fontFile); + dirTabs.put(OFTableName.getValue(tableName), pd[i]); + } + dirTabs.put(OFTableName.TABLE_DIRECTORY, + new OFDirTabEntry(0L, fontFile.getCurrentPos())); + log.debug("dir tables: " + dirTabs.keySet()); + } + + /** + * Read the "head" table, this reads the bounding box and + * sets the upem (unitsPerEM) variable + * @throws IOException in case of an I/O problem + */ + protected void readFontHeader() throws IOException { + seekTab(fontFile, OFTableName.HEAD, 2 * 4 + 2 * 4); + int flags = fontFile.readTTFUShort(); + if (log.isDebugEnabled()) { + log.debug("flags: " + flags + " - " + Integer.toString(flags, 2)); + } + upem = fontFile.readTTFUShort(); + if (log.isDebugEnabled()) { + log.debug("unit per em: " + upem); + } + + fontFile.skip(16); + + fontBBox1 = fontFile.readTTFShort(); + fontBBox2 = fontFile.readTTFShort(); + fontBBox3 = fontFile.readTTFShort(); + fontBBox4 = fontFile.readTTFShort(); + if (log.isDebugEnabled()) { + log.debug("font bbox: xMin=" + fontBBox1 + + " yMin=" + fontBBox2 + + " xMax=" + fontBBox3 + + " yMax=" + fontBBox4); + } + + fontFile.skip(2 + 2 + 2); + + locaFormat = fontFile.readTTFShort(); + } + + /** + * Read the number of glyphs from the "maxp" table + * @throws IOException in case of an I/O problem + */ + protected void getNumGlyphs() throws IOException { + seekTab(fontFile, OFTableName.MAXP, 4); + numberOfGlyphs = fontFile.readTTFUShort(); + } + + + /** + * Read the "hhea" table to find the ascender and descender and + * size of "hmtx" table, as a fixed size font might have only + * one width. + * @throws IOException in case of an I/O problem + */ + protected void readHorizontalHeader() + throws IOException { + seekTab(fontFile, OFTableName.HHEA, 4); + hheaAscender = fontFile.readTTFShort(); + hheaDescender = fontFile.readTTFShort(); + + fontFile.skip(2 + 2 + 3 * 2 + 8 * 2); + nhmtx = fontFile.readTTFUShort(); + + if (log.isDebugEnabled()) { + log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender)); + log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender)); + log.debug("Number of horizontal metrics: " + nhmtx); + } + } + + /** + * Read "hmtx" table and put the horizontal metrics + * in the mtxTab array. If the number of metrics is less + * than the number of glyphs (eg fixed size fonts), extend + * the mtxTab array and fill in the missing widths + * @throws IOException in case of an I/O problem + */ + protected void readHorizontalMetrics() + throws IOException { + seekTab(fontFile, OFTableName.HMTX, 0); + + int mtxSize = Math.max(numberOfGlyphs, nhmtx); + mtxTab = new OFMtxEntry[mtxSize]; + + if (log.isTraceEnabled()) { + log.trace("*** Widths array: \n"); + } + for (int i = 0; i < mtxSize; i++) { + mtxTab[i] = new OFMtxEntry(); + } + for (int i = 0; i < nhmtx; i++) { + mtxTab[i].setWx(fontFile.readTTFUShort()); + mtxTab[i].setLsb(fontFile.readTTFUShort()); + + if (log.isTraceEnabled()) { + log.trace(" width[" + i + "] = " + + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); + } + } + + if (nhmtx < mtxSize) { + // Fill in the missing widths + int lastWidth = mtxTab[nhmtx - 1].getWx(); + for (int i = nhmtx; i < mtxSize; i++) { + mtxTab[i].setWx(lastWidth); + mtxTab[i].setLsb(fontFile.readTTFUShort()); + } + } + } + + + /** + * Read the "post" table + * containing the PostScript names of the glyphs. + */ + protected void readPostScript() throws IOException { + seekTab(fontFile, OFTableName.POST, 0); + int postFormat = fontFile.readTTFLong(); + italicAngle = fontFile.readTTFULong(); + underlinePosition = fontFile.readTTFShort(); + underlineThickness = fontFile.readTTFShort(); + isFixedPitch = fontFile.readTTFULong(); + + //Skip memory usage values + fontFile.skip(4 * 4); + + log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); + switch (postFormat) { + case 0x00010000: + log.debug("PostScript format 1"); + postScriptVersion = PostScriptVersion.V1; + for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) { + mtxTab[i].setName(MAC_GLYPH_ORDERING[i]); + } + break; + case 0x00020000: + log.debug("PostScript format 2"); + postScriptVersion = PostScriptVersion.V2; + int numGlyphStrings = 0; + + // Read Number of Glyphs + int l = fontFile.readTTFUShort(); + + // Read indexes + for (int i = 0; i < l; i++) { + mtxTab[i].setIndex(fontFile.readTTFUShort()); + + if (mtxTab[i].getIndex() > 257) { + //Index is not in the Macintosh standard set + numGlyphStrings++; + } + + if (log.isTraceEnabled()) { + log.trace("PostScript index: " + mtxTab[i].getIndexAsString()); + } + } + + // firstChar=minIndex; + String[] psGlyphsBuffer = new String[numGlyphStrings]; + if (log.isDebugEnabled()) { + log.debug("Reading " + numGlyphStrings + + " glyphnames, that are not in the standard Macintosh" + + " set. Total number of glyphs=" + l); + } + for (int i = 0; i < psGlyphsBuffer.length; i++) { + psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte()); + } + + //Set glyph names + for (int i = 0; i < l; i++) { + if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) { + mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]); + } else { + if (!mtxTab[i].isIndexReserved()) { + int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length; + + if (log.isTraceEnabled()) { + log.trace(k + " i=" + i + " mtx=" + mtxTab.length + + " ps=" + psGlyphsBuffer.length); + } + + mtxTab[i].setName(psGlyphsBuffer[k]); + } + } + } + + break; + case 0x00030000: + // PostScript format 3 contains no glyph names + log.debug("PostScript format 3"); + postScriptVersion = PostScriptVersion.V3; + break; + default: + log.error("Unknown PostScript format: " + postFormat); + postScriptVersion = PostScriptVersion.UNKNOWN; + } + } + + + /** + * Read the "OS/2" table + */ + protected void readOS2() throws IOException { + // Check if font is embeddable + OFDirTabEntry os2Entry = dirTabs.get(OFTableName.OS2); + if (os2Entry != null) { + seekTab(fontFile, OFTableName.OS2, 0); + int version = fontFile.readTTFUShort(); + if (log.isDebugEnabled()) { + log.debug("OS/2 table: version=" + version + + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength()); + } + fontFile.skip(2); //xAvgCharWidth + this.usWeightClass = fontFile.readTTFUShort(); + + // usWidthClass + fontFile.skip(2); + + int fsType = fontFile.readTTFUShort(); + if (fsType == 2) { + isEmbeddable = false; + } else { + isEmbeddable = true; + } + fontFile.skip(8 * 2); + strikeoutThickness = fontFile.readTTFShort(); + strikeoutPosition = fontFile.readTTFShort(); + fontFile.skip(2); + fontFile.skip(10); //panose array + fontFile.skip(4 * 4); //unicode ranges + fontFile.skip(4); + fontFile.skip(3 * 2); + int v; + os2Ascender = fontFile.readTTFShort(); //sTypoAscender + os2Descender = fontFile.readTTFShort(); //sTypoDescender + if (log.isDebugEnabled()) { + log.debug("sTypoAscender: " + os2Ascender + + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender)); + log.debug("sTypoDescender: " + os2Descender + + " -> internal " + convertTTFUnit2PDFUnit(os2Descender)); + } + v = fontFile.readTTFShort(); //sTypoLineGap + if (log.isDebugEnabled()) { + log.debug("sTypoLineGap: " + v); + } + v = fontFile.readTTFUShort(); //usWinAscent + if (log.isDebugEnabled()) { + log.debug("usWinAscent: " + formatUnitsForDebug(v)); + } + v = fontFile.readTTFUShort(); //usWinDescent + if (log.isDebugEnabled()) { + log.debug("usWinDescent: " + formatUnitsForDebug(v)); + } + + //version 1 OS/2 table might end here + if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) { + fontFile.skip(2 * 4); + this.os2xHeight = fontFile.readTTFShort(); //sxHeight + this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight + if (log.isDebugEnabled()) { + log.debug("sxHeight: " + this.os2xHeight); + log.debug("sCapHeight: " + this.os2CapHeight); + } + } + + } else { + isEmbeddable = true; + } + } + + /** + * Read the "PCLT" table to find xHeight and capHeight. + * @throws IOException In case of a I/O problem + */ + protected boolean readPCLT() throws IOException { + OFDirTabEntry dirTab = dirTabs.get(OFTableName.PCLT); + if (dirTab != null) { + fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2); + xHeight = fontFile.readTTFUShort(); + log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight)); + fontFile.skip(2 * 2); + capHeight = fontFile.readTTFUShort(); + log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight)); + fontFile.skip(2 + 16 + 8 + 6 + 1 + 1); + + int serifStyle = fontFile.readTTFUByte(); + serifStyle = serifStyle >> 6; + serifStyle = serifStyle & 3; + if (serifStyle == 1) { + hasSerifs = false; + } else { + hasSerifs = true; + } + return true; + } else { + return false; + } + } + + /** + * Determines the right source for the ascender and descender values. The problem here is + * that the interpretation of these values is not the same for every font. There doesn't seem + * to be a uniform definition of an ascender and a descender. In some fonts + * the hhea values are defined after the Apple interpretation, but not in every font. The + * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the + * baseline so we need values which add up more or less to the "em box". However, due to + * accent modifiers a character can grow beyond the em box. + */ + protected void determineAscDesc() { + int hheaBoxHeight = hheaAscender - hheaDescender; + int os2BoxHeight = os2Ascender - os2Descender; + if (os2Ascender > 0 && os2BoxHeight <= upem) { + ascender = os2Ascender; + descender = os2Descender; + } else if (hheaAscender > 0 && hheaBoxHeight <= upem) { + ascender = hheaAscender; + descender = hheaDescender; + } else { + if (os2Ascender > 0) { + //Fall back to info from OS/2 if possible + ascender = os2Ascender; + descender = os2Descender; + } else { + ascender = hheaAscender; + descender = hheaDescender; + } + } + + if (log.isDebugEnabled()) { + log.debug("Font box height: " + (ascender - descender)); + if (ascender - descender > upem) { + log.debug("Ascender and descender together are larger than the em box."); + } + } + } + + protected void guessVerticalMetricsFromGlyphBBox() { + // Approximate capHeight from height of "H" + // It's most unlikely that a font misses the PCLT table + // This also assumes that postscriptnames exists ("H") + // Should look it up in the cmap (that wouldn't help + // for charsets without H anyway...) + // Same for xHeight with the letter "x" + int localCapHeight = 0; + int localXHeight = 0; + int localAscender = 0; + int localDescender = 0; + for (int i = 0; i < mtxTab.length; i++) { + if ("H".equals(mtxTab[i].getName())) { + localCapHeight = mtxTab[i].getBoundingBox()[3]; + } else if ("x".equals(mtxTab[i].getName())) { + localXHeight = mtxTab[i].getBoundingBox()[3]; + } else if ("d".equals(mtxTab[i].getName())) { + localAscender = mtxTab[i].getBoundingBox()[3]; + } else if ("p".equals(mtxTab[i].getName())) { + localDescender = mtxTab[i].getBoundingBox()[1]; + } else { + // OpenType Fonts with a version 3.0 "post" table don't have glyph names. + // Use Unicode indices instead. + List unicodeIndex = mtxTab[i].getUnicodeIndex(); + if (unicodeIndex.size() > 0) { + //Only the first index is used + char ch = (char)((Integer)unicodeIndex.get(0)).intValue(); + if (ch == 'H') { + localCapHeight = mtxTab[i].getBoundingBox()[3]; + } else if (ch == 'x') { + localXHeight = mtxTab[i].getBoundingBox()[3]; + } else if (ch == 'd') { + localAscender = mtxTab[i].getBoundingBox()[3]; + } else if (ch == 'p') { + localDescender = mtxTab[i].getBoundingBox()[1]; + } + } + } + } + if (log.isDebugEnabled()) { + log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender)); + log.debug("Descender from glyph 'p': " + formatUnitsForDebug(localDescender)); + } + if (ascender - descender > upem) { + log.debug("Replacing specified ascender/descender with derived values to get values" + + " which fit in the em box."); + ascender = localAscender; + descender = localDescender; + } + + if (log.isDebugEnabled()) { + log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight)); + log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight)); + } + if (capHeight == 0) { + capHeight = localCapHeight; + if (capHeight == 0) { + capHeight = os2CapHeight; + } + if (capHeight == 0) { + log.debug("capHeight value could not be determined." + + " The font may not work as expected."); + } + } + if (xHeight == 0) { + xHeight = localXHeight; + if (xHeight == 0) { + xHeight = os2xHeight; + } + if (xHeight == 0) { + log.debug("xHeight value could not be determined." + + " The font may not work as expected."); + } + } + } + + /** + * Read the kerning table, create a table for both CIDs and + * winAnsiEncoding. + * @throws IOException In case of a I/O problem + */ + protected void readKerning() throws IOException { + // Read kerning + kerningTab = new HashMap<Integer, Map<Integer, Integer>>(); + ansiKerningTab = new HashMap<Integer, Map<Integer, Integer>>(); + OFDirTabEntry dirTab = dirTabs.get(OFTableName.KERN); + if (dirTab != null) { + seekTab(fontFile, OFTableName.KERN, 2); + for (int n = fontFile.readTTFUShort(); n > 0; n--) { + fontFile.skip(2 * 2); + int k = fontFile.readTTFUShort(); + if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { + return; + } + if ((k >> 8) != 0) { + continue; + } + + k = fontFile.readTTFUShort(); + fontFile.skip(3 * 2); + while (k-- > 0) { + int i = fontFile.readTTFUShort(); + int j = fontFile.readTTFUShort(); + int kpx = fontFile.readTTFShort(); + if (kpx != 0) { + // CID kerning table entry, using unicode indexes + final Integer iObj = glyphToUnicode(i); + final Integer u2 = glyphToUnicode(j); + if (iObj == null) { + // happens for many fonts (Ubuntu font set), + // stray entries in the kerning table?? + log.debug("Ignoring kerning pair because no Unicode index was" + + " found for the first glyph " + i); + } else if (u2 == null) { + log.debug("Ignoring kerning pair because Unicode index was" + + " found for the second glyph " + i); + } else { + Map<Integer, Integer> adjTab = kerningTab.get(iObj); + if (adjTab == null) { + adjTab = new HashMap<Integer, Integer>(); + } + adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx))); + kerningTab.put(iObj, adjTab); + } + } + } + } + + // Create winAnsiEncoded kerning table from kerningTab + // (could probably be simplified, for now we remap back to CID indexes and + // then to winAnsi) + for (Integer unicodeKey1 : kerningTab.keySet()) { + Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue()); + Map<Integer, Integer> akpx = new HashMap<Integer, Integer>(); + Map<Integer, Integer> ckpx = kerningTab.get(unicodeKey1); + + for (Integer unicodeKey2 : ckpx.keySet()) { + Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue()); + Integer kern = ckpx.get(unicodeKey2); + + Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + akpx.put(ansiKeys[u], kern); + } + } + } + + if (akpx.size() > 0) { + Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + ansiKerningTab.put(ansiKeys[u], akpx); + } + } + } + } + } + } + + /** + * Streams a font. + * @param ttfOut The interface for streaming TrueType tables. + * @exception IOException file write error + */ + public void stream(TTFOutputStream ttfOut) throws IOException { + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedDirTabs = sortDirTabMap(dirTabs); + byte[] file = fontFile.getAllBytes(); + TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); + TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); + ttfOut.startFontStream(); + for (Map.Entry<OFTableName, OFDirTabEntry> entry : sortedDirTabs) { + int offset = (int) entry.getValue().getOffset(); + int paddedLength = (int) entry.getValue().getLength(); + paddedLength += getPadSize(offset + paddedLength); + if (entry.getKey().equals(OFTableName.GLYF)) { + streamGlyf(glyphOut, file, offset, paddedLength); + } else { + tableOut.streamTable(file, offset, paddedLength); + } + } + ttfOut.endFontStream(); + } + + private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset, + int tableLength) throws IOException { + //Stream all but the last glyph + int glyphStart = 0; + int glyphEnd = 0; + glyphOut.startGlyphStream(); + for (int i = 0; i < mtxTab.length - 1; i++) { + glyphStart = (int) mtxTab[i].getOffset() + tableOffset; + glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset; + glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart); + } + glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd); + glyphOut.endGlyphStream(); + } + + /** + * Returns the order in which the tables in a TrueType font should be written to file. + * @param directoryTabs the map that is to be sorted. + * @return TTFTablesNames[] an array of table names sorted in the order they should appear in + * the TTF file. + */ + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> + sortDirTabMap(Map<OFTableName, OFDirTabEntry> directoryTabs) { + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedSet + = new TreeSet<Map.Entry<OFTableName, OFDirTabEntry>>( + new Comparator<Map.Entry<OFTableName, OFDirTabEntry>>() { + + public int compare(Entry<OFTableName, OFDirTabEntry> o1, + Entry<OFTableName, OFDirTabEntry> o2) { + return (int) (o1.getValue().getOffset() - o2.getValue().getOffset()); + } + }); + sortedSet.addAll(directoryTabs.entrySet()); + return sortedSet; + } + + /** + * Returns this font's character to glyph mapping. + * + * @return the font's cmap + */ + public List<CMapSegment> getCMaps() { + return cmaps; + } + + /** + * Check if this is a TrueType collection and that the given + * name exists in the collection. + * If it does, set offset in fontfile to the beginning of + * the Table Directory for that font. + * @param name The name to check + * @return True if not collection or font name present, false otherwise + * @throws IOException In case of an I/O problem + */ + protected final boolean checkTTC(String tag, String name) throws IOException { + if ("ttcf".equals(tag)) { + // This is a TrueType Collection + fontFile.skip(4); + + // Read directory offsets + int numDirectories = (int)fontFile.readTTFULong(); + // int numDirectories=in.readTTFUShort(); + long[] dirOffsets = new long[numDirectories]; + for (int i = 0; i < numDirectories; i++) { + dirOffsets[i] = fontFile.readTTFULong(); + } + + log.info("This is a TrueType collection file with " + + numDirectories + " fonts"); + log.info("Containing the following fonts: "); + // Read all the directories and name tables to check + // If the font exists - this is a bit ugly, but... + boolean found = false; + + // Iterate through all name tables even if font + // Is found, just to show all the names + long dirTabOffset = 0; + for (int i = 0; (i < numDirectories); i++) { + fontFile.seekSet(dirOffsets[i]); + readDirTabs(); + + readName(); + + if (fullName.equals(name)) { + found = true; + dirTabOffset = dirOffsets[i]; + log.info(fullName + " <-- selected"); + } else { + log.info(fullName); + } + + // Reset names + notice = ""; + fullName = ""; + familyNames.clear(); + postScriptName = ""; + subFamilyName = ""; + } + + fontFile.seekSet(dirTabOffset); + return found; + } else { + fontFile.seekSet(0); + return true; + } + } + + /** + * Return TTC font names + * @param in FontFileReader to read from + * @return True if not collection or font name present, false otherwise + * @throws IOException In case of an I/O problem + */ + public final List<String> getTTCnames(FontFileReader in) throws IOException { + this.fontFile = in; + + List<String> fontNames = new ArrayList<String>(); + String tag = in.readTTFString(4); + + if ("ttcf".equals(tag)) { + // This is a TrueType Collection + in.skip(4); + + // Read directory offsets + int numDirectories = (int)in.readTTFULong(); + long[] dirOffsets = new long[numDirectories]; + for (int i = 0; i < numDirectories; i++) { + dirOffsets[i] = in.readTTFULong(); + } + + log.info("This is a TrueType collection file with " + + numDirectories + " fonts"); + log.info("Containing the following fonts: "); + + for (int i = 0; (i < numDirectories); i++) { + in.seekSet(dirOffsets[i]); + readDirTabs(); + + readName(); + + log.info(fullName); + fontNames.add(fullName); + + // Reset names + notice = ""; + fullName = ""; + familyNames.clear(); + postScriptName = ""; + subFamilyName = ""; + } + + in.seekSet(0); + return fontNames; + } else { + log.error("Not a TTC!"); + return null; + } + } + + /* + * Helper classes, they are not very efficient, but that really + * doesn't matter... + */ + private Integer[] unicodeToWinAnsi(int unicode) { + List<Integer> ret = new ArrayList<Integer>(); + for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { + if (unicode == Glyphs.WINANSI_ENCODING[i]) { + ret.add(new Integer(i)); + } + } + return ret.toArray(new Integer[0]); + } + + /** + * Dumps a few informational values to System.out. + */ + public void printStuff() { + System.out.println("Font name: " + postScriptName); + System.out.println("Full name: " + fullName); + System.out.println("Family name: " + familyNames); + System.out.println("Subfamily name: " + subFamilyName); + System.out.println("Notice: " + notice); + System.out.println("xHeight: " + convertTTFUnit2PDFUnit(xHeight)); + System.out.println("capheight: " + convertTTFUnit2PDFUnit(capHeight)); + + int italic = (int)(italicAngle >> 16); + System.out.println("Italic: " + italic); + System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); + if ((italicAngle % 0x10000) > 0) { + System.out.print("." + + (short)((italicAngle % 0x10000) * 1000) + / 0x10000); + } + System.out.println(); + System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); + System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); + System.out.println("FontBBox: [" + convertTTFUnit2PDFUnit(fontBBox1) + + " " + convertTTFUnit2PDFUnit(fontBBox2) + " " + + convertTTFUnit2PDFUnit(fontBBox3) + " " + + convertTTFUnit2PDFUnit(fontBBox4) + "]"); + } + + private String formatUnitsForDebug(int units) { + return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units"; + } + + /** + * Map a glyph index to the corresponding unicode code point + * + * @param glyphIndex + * @return unicode code point + */ + private Integer glyphToUnicode(int glyphIndex) { + return glyphToUnicodeMap.get(new Integer(glyphIndex)); + } + + /** + * Map a unicode code point to the corresponding glyph index + * + * @param unicodeIndex unicode code point + * @return glyph index + */ + private Integer unicodeToGlyph(int unicodeIndex) throws IOException { + final Integer result + = unicodeToGlyphMap.get(new Integer(unicodeIndex)); + if (result == null) { + throw new IOException( + "Glyph index not found for unicode value " + unicodeIndex); + } + return result; + } + + String getGlyphName(int glyphIndex) { + return mtxTab[glyphIndex].getName(); + } + + /** + * Determine if advanced (typographic) table is present. + * @return true if advanced (typographic) table is present + */ + public boolean hasAdvancedTable() { + if (advancedTableReader != null) { + return advancedTableReader.hasAdvancedTable(); + } else { + return false; + } + } + + /** + * Returns the GDEF table or null if none present. + * @return the GDEF table + */ + public GlyphDefinitionTable getGDEF() { + if (advancedTableReader != null) { + return advancedTableReader.getGDEF(); + } else { + return null; + } + } + + /** + * Returns the GSUB table or null if none present. + * @return the GSUB table + */ + public GlyphSubstitutionTable getGSUB() { + if (advancedTableReader != null) { + return advancedTableReader.getGSUB(); + } else { + return null; + } + } + + /** + * Returns the GPOS table or null if none present. + * @return the GPOS table + */ + public GlyphPositioningTable getGPOS() { + if (advancedTableReader != null) { + return advancedTableReader.getGPOS(); + } else { + return null; + } + } + + /** + * Static main method to get info about a TrueType font. + * @param args The command line arguments + */ + public static void main(String[] args) { + InputStream stream = null; + try { + boolean useKerning = true; + boolean useAdvanced = true; + + stream = new FileInputStream(args[0]); + FontFileReader reader = new FontFileReader(stream); + + String name = null; + if (args.length >= 2) { + name = args[1]; + } + + String header = OFFontLoader.readHeader(reader); + boolean isCFF = header.equals("OTTO"); + OpenFont otfFile = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced); + otfFile.readFont(reader, header, name); + otfFile.printStuff(); + + } catch (IOException ioe) { + System.err.println("Problem reading font: " + ioe.toString()); + ioe.printStackTrace(System.err); + } finally { + IOUtils.closeQuietly(stream); + } + } +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java index 62686bbd4..52df45ffb 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java @@ -19,224 +19,14 @@ package org.apache.fop.fonts.truetype; -import java.awt.Rectangle; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.xmlgraphics.fonts.Glyphs; - -import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException; -import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; -import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; -import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable; -import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader; -import org.apache.fop.fonts.CMapSegment; -import org.apache.fop.fonts.FontUtil; /** * Reads a TrueType file or a TrueType Collection. * The TrueType spec can be found at the Microsoft. * Typography site: http://www.microsoft.com/truetype/ */ -public class TTFFile { - - static final byte NTABS = 24; - static final int MAX_CHAR_CODE = 255; - static final int ENC_BUF_SIZE = 1024; - - private static final String[] MAC_GLYPH_ORDERING = { - /* 0x000 */ - ".notdef", ".null", "nonmarkingreturn", "space", - "exclam", "quotedbl", "numbersign", "dollar", - "percent", "ampersand", "quotesingle", "parenleft", - "parenright", "asterisk", "plus", "comma", - /* 0x010 */ - "hyphen", "period", "slash", "zero", - "one", "two", "three", "four", - "five", "six", "seven", "eight", - "nine", "colon", "semicolon", "less", - /* 0x020 */ - "equal", "greater", "question", "at", - "A", "B", "C", "D", - "E", "F", "G", "H", - "I", "J", "K", "L", - /* 0x030 */ - "M", "N", "O", "P", - "Q", "R", "S", "T", - "U", "V", "W", "X", - "Y", "Z", "bracketleft", "backslash", - /* 0x040 */ - "bracketright", "asciicircum", "underscore", "grave", - "a", "b", "c", "d", - "e", "f", "g", "h", - "i", "j", "k", "l", - /* 0x050 */ - "m", "n", "o", "p", - "q", "r", "s", "t", - "u", "v", "w", "x", - "y", "z", "braceleft", "bar", - /* 0x060 */ - "braceright", "asciitilde", "Adieresis", "Aring", - "Ccedilla", "Eacute", "Ntilde", "Odieresis", - "Udieresis", "aacute", "agrave", "acircumflex", - "adieresis", "atilde", "aring", "ccedilla", - /* 0x070 */ - "eacute", "egrave", "ecircumflex", "edieresis", - "iacute", "igrave", "icircumflex", "idieresis", - "ntilde", "oacute", "ograve", "ocircumflex", - "odieresis", "otilde", "uacute", "ugrave", - /* 0x080 */ - "ucircumflex", "udieresis", "dagger", "degree", - "cent", "sterling", "section", "bullet", - "paragraph", "germandbls", "registered", "copyright", - "trademark", "acute", "dieresis", "notequal", - /* 0x090 */ - "AE", "Oslash", "infinity", "plusminus", - "lessequal", "greaterequal", "yen", "mu", - "partialdiff", "summation", "product", "pi", - "integral", "ordfeminine", "ordmasculine", "Omega", - /* 0x0A0 */ - "ae", "oslash", "questiondown", "exclamdown", - "logicalnot", "radical", "florin", "approxequal", - "Delta", "guillemotleft", "guillemotright", "ellipsis", - "nonbreakingspace", "Agrave", "Atilde", "Otilde", - /* 0x0B0 */ - "OE", "oe", "endash", "emdash", - "quotedblleft", "quotedblright", "quoteleft", "quoteright", - "divide", "lozenge", "ydieresis", "Ydieresis", - "fraction", "currency", "guilsinglleft", "guilsinglright", - /* 0x0C0 */ - "fi", "fl", "daggerdbl", "periodcentered", - "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", - "Ecircumflex", "Aacute", "Edieresis", "Egrave", - "Iacute", "Icircumflex", "Idieresis", "Igrave", - /* 0x0D0 */ - "Oacute", "Ocircumflex", "apple", "Ograve", - "Uacute", "Ucircumflex", "Ugrave", "dotlessi", - "circumflex", "tilde", "macron", "breve", - "dotaccent", "ring", "cedilla", "hungarumlaut", - /* 0x0E0 */ - "ogonek", "caron", "Lslash", "lslash", - "Scaron", "scaron", "Zcaron", "zcaron", - "brokenbar", "Eth", "eth", "Yacute", - "yacute", "Thorn", "thorn", "minus", - /* 0x0F0 */ - "multiply", "onesuperior", "twosuperior", "threesuperior", - "onehalf", "onequarter", "threequarters", "franc", - "Gbreve", "gbreve", "Idotaccent", "Scedilla", - "scedilla", "Cacute", "cacute", "Ccaron", - /* 0x100 */ - "ccaron", "dcroat" - }; - - /** The FontFileReader used to read this TrueType font. */ - protected FontFileReader fontFile; - - /** Set to true to get even more debug output than with level DEBUG */ - public static final boolean TRACE_ENABLED = false; - - private final String encoding = "WinAnsiEncoding"; // Default encoding - - private final short firstChar = 0; - - private boolean useKerning = false; - - private boolean isEmbeddable = true; - private boolean hasSerifs = true; - /** - * Table directory - */ - protected Map<TTFTableName, TTFDirTabEntry> dirTabs; - private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs - private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding - private List<CMapSegment> cmaps; - private Set<UnicodeMapping> unicodeMappings; - - private int upem; // unitsPerEm from "head" table - private int nhmtx; // Number of horizontal metrics - private PostScriptVersion postScriptVersion; - private int locaFormat; - /** - * Offset to last loca - */ - protected long lastLoca = 0; - private int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table) - - /** - * Contains glyph data - */ - protected TTFMtxEntry[] mtxTab; // Contains glyph data - - private String postScriptName = ""; - private String fullName = ""; - private String notice = ""; - private Set<String> familyNames = new HashSet<String>(); - private String subFamilyName = ""; - - private long italicAngle = 0; - private long isFixedPitch = 0; - private int fontBBox1 = 0; - private int fontBBox2 = 0; - private int fontBBox3 = 0; - private int fontBBox4 = 0; - private int capHeight = 0; - private int os2CapHeight = 0; - private int underlinePosition = 0; - private int underlineThickness = 0; - private int strikeoutPosition; - private int strikeoutThickness; - private int xHeight = 0; - private int os2xHeight = 0; - //Effective ascender/descender - private int ascender = 0; - private int descender = 0; - //Ascender/descender from hhea table - private int hheaAscender = 0; - private int hheaDescender = 0; - //Ascender/descender from OS/2 table - private int os2Ascender = 0; - private int os2Descender = 0; - private int usWeightClass = 0; - - private short lastChar = 0; - - private int[] ansiWidth; - private Map<Integer, List<Integer>> ansiIndex; - - // internal mapping of glyph indexes to unicode indexes - // used for quick mappings in this class - private final Map<Integer, Integer> glyphToUnicodeMap = new HashMap<Integer, Integer>(); - private final Map<Integer, Integer> unicodeToGlyphMap = new HashMap<Integer, Integer>(); - - private TTFDirTabEntry currentDirTab; - - private boolean isCFF; - - // advanced typographic table support - private boolean useAdvanced = false; - private OTFAdvancedTypographicTableReader advancedTableReader; - - /** - * logging instance - */ - protected Log log = LogFactory.getLog(TTFFile.class); +public class TTFFile extends OpenFont { public TTFFile() { this(true, false); @@ -248,1214 +38,15 @@ public class TTFFile { * @param useAdvanced true if advanced typographic tables should be loaded */ public TTFFile(boolean useKerning, boolean useAdvanced) { - this.useKerning = useKerning; - this.useAdvanced = useAdvanced; - } - - /** - * Key-value helper class. - */ - final class UnicodeMapping implements Comparable { - - private final int unicodeIndex; - private final int glyphIndex; - - UnicodeMapping(int glyphIndex, int unicodeIndex) { - this.unicodeIndex = unicodeIndex; - this.glyphIndex = glyphIndex; - glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex)); - unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex)); - } - - /** - * Returns the glyphIndex. - * @return the glyph index - */ - public int getGlyphIndex() { - return glyphIndex; - } - - /** - * Returns the unicodeIndex. - * @return the Unicode index - */ - public int getUnicodeIndex() { - return unicodeIndex; - } - - - /** {@inheritDoc} */ - public int hashCode() { - int hc = unicodeIndex; - hc = 19 * hc + (hc ^ glyphIndex); - return hc; - } - - /** {@inheritDoc} */ - public boolean equals(Object o) { - if (o instanceof UnicodeMapping) { - UnicodeMapping m = (UnicodeMapping) o; - if (unicodeIndex != m.unicodeIndex) { - return false; - } else { - return (glyphIndex == m.glyphIndex); - } - } else { - return false; - } - } - - /** {@inheritDoc} */ - public int compareTo(Object o) { - if (o instanceof UnicodeMapping) { - UnicodeMapping m = (UnicodeMapping) o; - if (unicodeIndex > m.unicodeIndex) { - return 1; - } else if (unicodeIndex < m.unicodeIndex) { - return -1; - } else { - return 0; - } - } else { - return -1; - } - } - } - - /** - * Version of the PostScript table (<q>post</q>) contained in this font. - */ - public static enum PostScriptVersion { - /** PostScript table version 1.0. */ - V1, - /** PostScript table version 2.0. */ - V2, - /** PostScript table version 3.0. */ - V3, - /** Unknown version of the PostScript table. */ - UNKNOWN; - } - - /** - * Obtain directory table entry. - * @param name (tag) of entry - * @return a directory table entry or null if none found - */ - public TTFDirTabEntry getDirectoryEntry(TTFTableName name) { - return dirTabs.get(name); - } - - /** - * Position inputstream to position indicated - * in the dirtab offset + offset - * @param in font file reader - * @param tableName (tag) of table - * @param offset from start of table - * @return true if seek succeeded - * @throws IOException if I/O exception occurs during seek - */ - public boolean seekTab(FontFileReader in, TTFTableName tableName, - long offset) throws IOException { - TTFDirTabEntry dt = dirTabs.get(tableName); - if (dt == null) { - log.error("Dirtab " + tableName.getName() + " not found."); - return false; - } else { - in.seekSet(dt.getOffset() + offset); - this.currentDirTab = dt; - } - return true; - } - - /** - * Convert from truetype unit to pdf unit based on the - * unitsPerEm field in the "head" table - * @param n truetype unit - * @return pdf unit - */ - public int convertTTFUnit2PDFUnit(int n) { - int ret; - if (n < 0) { - long rest1 = n % upem; - long storrest = 1000 * rest1; - long ledd2 = (storrest != 0 ? rest1 / storrest : 0); - ret = -((-1000 * n) / upem - (int)ledd2); - } else { - ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; - } - - return ret; - } - - /** - * Read the cmap table, - * return false if the table is not present or only unsupported - * tables are present. Currently only unicode cmaps are supported. - * Set the unicodeIndex in the TTFMtxEntries and fills in the - * cmaps vector. - */ - private boolean readCMAP() throws IOException { - - unicodeMappings = new java.util.TreeSet(); - - seekTab(fontFile, TTFTableName.CMAP, 2); - int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables - long cmapUniOffset = 0; - long symbolMapOffset = 0; - - if (log.isDebugEnabled()) { - log.debug(numCMap + " cmap tables"); - } - - //Read offset for all tables. We are only interested in the unicode table - for (int i = 0; i < numCMap; i++) { - int cmapPID = fontFile.readTTFUShort(); - int cmapEID = fontFile.readTTFUShort(); - long cmapOffset = fontFile.readTTFLong(); - - if (log.isDebugEnabled()) { - log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); - } - - if (cmapPID == 3 && cmapEID == 1) { - cmapUniOffset = cmapOffset; - } - if (cmapPID == 3 && cmapEID == 0) { - symbolMapOffset = cmapOffset; - } - } - - if (cmapUniOffset > 0) { - return readUnicodeCmap(cmapUniOffset, 1); - } else if (symbolMapOffset > 0) { - return readUnicodeCmap(symbolMapOffset, 0); - } else { - log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table" - + " not present. Aborting"); - return false; - } - } - - private boolean readUnicodeCmap( - long cmapUniOffset, int encodingID) - throws IOException { - //Read CMAP table and correct mtxTab.index - int mtxPtr = 0; - - // Read unicode cmap - seekTab(fontFile, TTFTableName.CMAP, cmapUniOffset); - int cmapFormat = fontFile.readTTFUShort(); - /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length - - if (log.isDebugEnabled()) { - log.debug("CMAP format: " + cmapFormat); - } - - if (cmapFormat == 4) { - fontFile.skip(2); // Skip version number - int cmapSegCountX2 = fontFile.readTTFUShort(); - int cmapSearchRange = fontFile.readTTFUShort(); - int cmapEntrySelector = fontFile.readTTFUShort(); - int cmapRangeShift = fontFile.readTTFUShort(); - - if (log.isDebugEnabled()) { - log.debug("segCountX2 : " + cmapSegCountX2); - log.debug("searchRange : " + cmapSearchRange); - log.debug("entrySelector: " + cmapEntrySelector); - log.debug("rangeShift : " + cmapRangeShift); - } - - - int[] cmapEndCounts = new int[cmapSegCountX2 / 2]; - int[] cmapStartCounts = new int[cmapSegCountX2 / 2]; - int[] cmapDeltas = new int[cmapSegCountX2 / 2]; - int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2]; - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapEndCounts[i] = fontFile.readTTFUShort(); - } - - fontFile.skip(2); // Skip reservedPad - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapStartCounts[i] = fontFile.readTTFUShort(); - } - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapDeltas[i] = fontFile.readTTFShort(); - } - - //int startRangeOffset = in.getCurrentPos(); - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapRangeOffsets[i] = fontFile.readTTFUShort(); - } - - int glyphIdArrayOffset = fontFile.getCurrentPos(); - - BitSet eightBitGlyphs = new BitSet(256); - - // Insert the unicode id for the glyphs in mtxTab - // and fill in the cmaps ArrayList - - for (int i = 0; i < cmapStartCounts.length; i++) { - - if (log.isTraceEnabled()) { - log.trace(i + ": " + cmapStartCounts[i] - + " - " + cmapEndCounts[i]); - } - if (log.isDebugEnabled()) { - if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) { - log.debug("Font contains glyphs in the Unicode private use area: " - + Integer.toHexString(cmapStartCounts[i]) + " - " - + Integer.toHexString(cmapEndCounts[i])); - } - } - - for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { - - // Update lastChar - if (j < 256 && j > lastChar) { - lastChar = (short)j; - } - - if (j < 256) { - eightBitGlyphs.set(j); - } - - if (mtxPtr < mtxTab.length) { - int glyphIdx; - // the last character 65535 = .notdef - // may have a range offset - if (cmapRangeOffsets[i] != 0 && j != 65535) { - int glyphOffset = glyphIdArrayOffset - + ((cmapRangeOffsets[i] / 2) - + (j - cmapStartCounts[i]) - + (i) - - cmapSegCountX2 / 2) * 2; - fontFile.seekSet(glyphOffset); - glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i]) - & 0xffff; - - unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - - // Also add winAnsiWidth - List<Integer> v = ansiIndex.get(new Integer(j)); - if (v != null) { - for (Integer aIdx : v) { - ansiWidth[aIdx.intValue()] - = mtxTab[glyphIdx].getWx(); - - if (log.isTraceEnabled()) { - log.trace("Added width " - + mtxTab[glyphIdx].getWx() - + " uni: " + j - + " ansi: " + aIdx.intValue()); - } - } - } - - if (log.isTraceEnabled()) { - log.trace("Idx: " - + glyphIdx - + " Delta: " + cmapDeltas[i] - + " Unicode: " + j - + " name: " + mtxTab[glyphIdx].getName()); - } - } else { - glyphIdx = (j + cmapDeltas[i]) & 0xffff; - - if (glyphIdx < mtxTab.length) { - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - } else { - log.debug("Glyph " + glyphIdx - + " out of range: " - + mtxTab.length); - } - - unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); - if (glyphIdx < mtxTab.length) { - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - } else { - log.debug("Glyph " + glyphIdx - + " out of range: " - + mtxTab.length); - } - - // Also add winAnsiWidth - List<Integer> v = ansiIndex.get(new Integer(j)); - if (v != null) { - for (Integer aIdx : v) { - ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); - } - } - - //getLogger().debug("IIdx: " + - // mtxPtr + - // " Delta: " + cmap_deltas[i] + - // " Unicode: " + j + - // " name: " + - // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); - - } - if (glyphIdx < mtxTab.length) { - if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { - mtxPtr++; - } - } - } - } - } - } else { - log.error("Cmap format not supported: " + cmapFormat); - return false; - } - return true; - } - - private boolean isInPrivateUseArea(int start, int end) { - return (isInPrivateUseArea(start) || isInPrivateUseArea(end)); - } - - private boolean isInPrivateUseArea(int unicode) { - return (unicode >= 0xE000 && unicode <= 0xF8FF); - } - - /** - * Print first char/last char - */ - private void printMaxMin() { - int min = 255; - int max = 0; - for (int i = 0; i < mtxTab.length; i++) { - if (mtxTab[i].getIndex() < min) { - min = mtxTab[i].getIndex(); - } - if (mtxTab[i].getIndex() > max) { - max = mtxTab[i].getIndex(); - } - } - log.info("Min: " + min); - log.info("Max: " + max); - } - - - /** - * Reads the font using a FontFileReader. - * - * @param in The FontFileReader to use - * @throws IOException In case of an I/O problem - */ - public void readFont(FontFileReader in) throws IOException { - readFont(in, (String)null); - } - - /** - * initialize the ansiWidths array (for winAnsiEncoding) - * and fill with the missingwidth - */ - private void initAnsiWidths() { - ansiWidth = new int[256]; - for (int i = 0; i < 256; i++) { - ansiWidth[i] = mtxTab[0].getWx(); - } - - // Create an index hash to the ansiWidth - // Can't just index the winAnsiEncoding when inserting widths - // same char (eg bullet) is repeated more than one place - ansiIndex = new HashMap<Integer, List<Integer>>(); - for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { - Integer ansi = new Integer(i); - Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]); - - List<Integer> v = ansiIndex.get(uni); - if (v == null) { - v = new ArrayList<Integer>(); - ansiIndex.put(uni, v); - } - v.add(ansi); - } - } - - /** - * Read the font data. - * If the fontfile is a TrueType Collection (.ttc file) - * the name of the font to read data for must be supplied, - * else the name is ignored. - * - * @param in The FontFileReader to use - * @param name The name of the font - * @return boolean Returns true if the font is valid - * @throws IOException In case of an I/O problem - */ - public boolean readFont(FontFileReader in, String name) throws IOException { - fontFile = in; - /* - * Check if TrueType collection, and that the name - * exists in the collection - */ - if (!checkTTC(name)) { - if (name == null) { - throw new IllegalArgumentException( - "For TrueType collection you must specify which font " - + "to select (-ttcname)"); - } else { - throw new IOException( - "Name does not exist in the TrueType collection: " + name); - } - } - - readDirTabs(); - readFontHeader(); - getNumGlyphs(); - if (log.isDebugEnabled()) { - log.debug("Number of glyphs in font: " + numberOfGlyphs); - } - readHorizontalHeader(); - readHorizontalMetrics(); - initAnsiWidths(); - readPostScript(); - readOS2(); - determineAscDesc(); - if (!isCFF) { - readIndexToLocation(); - readGlyf(); - } - readName(); - boolean pcltFound = readPCLT(); - // Read cmap table and fill in ansiwidths - boolean valid = readCMAP(); - if (!valid) { - return false; - } - // Create cmaps for bfentries - createCMaps(); - - if (useKerning) { - readKerning(); - } - - // Read advanced typographic tables. - if (useAdvanced) { - try { - OTFAdvancedTypographicTableReader atr - = new OTFAdvancedTypographicTableReader(this, in); - atr.readAll(); - this.advancedTableReader = atr; - } catch (AdvancedTypographicTableFormatException e) { - log.warn( - "Encountered format constraint violation in advanced (typographic) table (AT) " - + "in font '" + getFullName() + "', ignoring AT data: " - + e.getMessage() - ); - } - } - - guessVerticalMetricsFromGlyphBBox(); - return true; - } - - /** - * Reads a font. - * - * @param in FontFileReader to read from - * @param name Name to be checked for in the font file - * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and - * new index as (Integer) value) - * @throws IOException in case of an I/O problem - */ - public void readFont(FontFileReader in, String name, - Map<Integer, Integer> glyphs) throws IOException { - readFont(in, name); - } - - private void createCMaps() { - cmaps = new ArrayList<CMapSegment>(); - int unicodeStart; - int glyphStart; - int unicodeEnd; - - Iterator<UnicodeMapping> e = unicodeMappings.iterator(); - UnicodeMapping um = e.next(); - UnicodeMapping lastMapping = um; - - unicodeStart = um.getUnicodeIndex(); - glyphStart = um.getGlyphIndex(); - - while (e.hasNext()) { - um = e.next(); - if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) - || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { - unicodeEnd = lastMapping.getUnicodeIndex(); - cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); - unicodeStart = um.getUnicodeIndex(); - glyphStart = um.getGlyphIndex(); - } - lastMapping = um; - } - - unicodeEnd = lastMapping.getUnicodeIndex(); - cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); - } - - /** - * Returns the PostScript name of the font. - * @return String The PostScript name - */ - public String getPostScriptName() { - if (postScriptName.length() == 0) { - return FontUtil.stripWhiteSpace(getFullName()); - } else { - return postScriptName; - } - } - - PostScriptVersion getPostScriptVersion() { - return postScriptVersion; - } - - /** - * Returns the font family names of the font. - * @return Set The family names (a Set of Strings) - */ - public Set<String> getFamilyNames() { - return familyNames; - } - - /** - * Returns the font sub family name of the font. - * @return String The sub family name - */ - public String getSubFamilyName() { - return subFamilyName; - } - - /** - * Returns the full name of the font. - * @return String The full name - */ - public String getFullName() { - return fullName; - } - - /** - * Returns the name of the character set used. - * @return String The caracter set - */ - public String getCharSetName() { - return encoding; - } - - /** - * Returns the CapHeight attribute of the font. - * @return int The CapHeight - */ - public int getCapHeight() { - return convertTTFUnit2PDFUnit(capHeight); - } - - /** - * Returns the XHeight attribute of the font. - * @return int The XHeight - */ - public int getXHeight() { - return convertTTFUnit2PDFUnit(xHeight); - } - - /** - * Returns the number of bytes necessary to pad the currentPosition so that a table begins - * on a 4-byte boundary. - * @param currentPosition the position to pad. - * @return int the number of bytes to pad. - */ - protected int getPadSize(int currentPosition) { - int padSize = 4 - (currentPosition % 4); - return padSize < 4 ? padSize : 0; - } - - /** - * Returns the Flags attribute of the font. - * @return int The Flags - */ - public int getFlags() { - int flags = 32; // Use Adobe Standard charset - if (italicAngle != 0) { - flags |= 64; - } - if (isFixedPitch != 0) { - flags |= 2; - } - if (hasSerifs) { - flags |= 1; - } - return flags; - } - - /** - * Returns the weight class of this font. Valid values are 100, 200....,800, 900. - * @return the weight class value (or 0 if there was no OS/2 table in the font) - */ - public int getWeightClass() { - return this.usWeightClass; - } - - /** - * Returns the StemV attribute of the font. - * @return String The StemV - */ - public String getStemV() { - return "0"; - } - - /** - * Returns the ItalicAngle attribute of the font. - * @return String The ItalicAngle - */ - public String getItalicAngle() { - String ia = Short.toString((short)(italicAngle / 0x10000)); - - // This is the correct italic angle, however only int italic - // angles are supported at the moment so this is commented out. - /* - * if ((italicAngle % 0x10000) > 0 ) - * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); - */ - return ia; - } - - /** - * @return int[] The font bbox - */ - public int[] getFontBBox() { - final int[] fbb = new int[4]; - fbb[0] = convertTTFUnit2PDFUnit(fontBBox1); - fbb[1] = convertTTFUnit2PDFUnit(fontBBox2); - fbb[2] = convertTTFUnit2PDFUnit(fontBBox3); - fbb[3] = convertTTFUnit2PDFUnit(fontBBox4); - - return fbb; - } - - /** - * Returns the LowerCaseAscent attribute of the font. - * @return int The LowerCaseAscent - */ - public int getLowerCaseAscent() { - return convertTTFUnit2PDFUnit(ascender); - } - - /** - * Returns the LowerCaseDescent attribute of the font. - * @return int The LowerCaseDescent - */ - public int getLowerCaseDescent() { - return convertTTFUnit2PDFUnit(descender); - } - - /** - * Returns the index of the last character, but this is for WinAnsiEncoding - * only, so the last char is < 256. - * @return short Index of the last character (<256) - */ - public short getLastChar() { - return lastChar; - } - - /** - * Returns the index of the first character. - * @return short Index of the first character - */ - public short getFirstChar() { - return firstChar; - } - - /** - * Returns an array of character widths. - * @return int[] The character widths - */ - public int[] getWidths() { - int[] wx = new int[mtxTab.length]; - for (int i = 0; i < wx.length; i++) { - wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx()); - } - return wx; - } - - public Rectangle[] getBoundingBoxes() { - Rectangle[] boundingBoxes = new Rectangle[mtxTab.length]; - for (int i = 0; i < boundingBoxes.length; i++) { - int[] boundingBox = mtxTab[i].getBoundingBox(); - boundingBoxes[i] = new Rectangle( - convertTTFUnit2PDFUnit(boundingBox[0]), - convertTTFUnit2PDFUnit(boundingBox[1]), - convertTTFUnit2PDFUnit(boundingBox[2] - boundingBox[0]), - convertTTFUnit2PDFUnit(boundingBox[3] - boundingBox[1])); - } - return boundingBoxes; - } - - /** - * Returns an array (xMin, yMin, xMax, yMax) for a glyph. - * - * @param glyphIndex the index of the glyph - * @return int[] Array defining bounding box. - */ - public int[] getBBox(int glyphIndex) { - int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox(); - int[] bbox = new int[4]; - for (int i = 0; i < 4; i++) { - bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]); - } - return bbox; - } - - /** - * Returns the width of a given character. - * @param idx Index of the character - * @return int Standard width - */ - public int getCharWidth(int idx) { - return convertTTFUnit2PDFUnit(ansiWidth[idx]); - } - - /** - * Returns the kerning table. - * @return Map The kerning table - */ - public Map<Integer, Map<Integer, Integer>> getKerning() { - return kerningTab; - } - - /** - * Returns the ANSI kerning table. - * @return Map The ANSI kerning table - */ - public Map<Integer, Map<Integer, Integer>> getAnsiKerning() { - return ansiKerningTab; - } - - public int getUnderlinePosition() { - return convertTTFUnit2PDFUnit(underlinePosition); - } - - public int getUnderlineThickness() { - return convertTTFUnit2PDFUnit(underlineThickness); - } - - public int getStrikeoutPosition() { - return convertTTFUnit2PDFUnit(strikeoutPosition); - } - - public int getStrikeoutThickness() { - return convertTTFUnit2PDFUnit(strikeoutThickness); - } - - /** - * Indicates if the font may be embedded. - * @return boolean True if it may be embedded - */ - public boolean isEmbeddable() { - return isEmbeddable; - } - - /** - * Indicates whether or not the font is an OpenType - * CFF font (rather than a TrueType font). - * @return true if the font is in OpenType CFF format. - */ - public boolean isCFF() { - return this.isCFF; - } - - /** - * Read Table Directory from the current position in the - * FontFileReader and fill the global HashMap dirTabs - * with the table name (String) as key and a TTFDirTabEntry - * as value. - * @throws IOException in case of an I/O problem - */ - protected void readDirTabs() throws IOException { - int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes) - switch (sfntVersion) { - case 0x10000: - log.debug("sfnt version: OpenType 1.0"); - break; - case 0x4F54544F: //"OTTO" - this.isCFF = true; - log.debug("sfnt version: OpenType with CFF data"); - break; - case 0x74727565: //"true" - log.debug("sfnt version: Apple TrueType"); - break; - case 0x74797031: //"typ1" - log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper"); - break; - default: - log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion)); - break; - } - int ntabs = fontFile.readTTFUShort(); - fontFile.skip(6); // 3xTTF_USHORT_SIZE - - dirTabs = new HashMap<TTFTableName, TTFDirTabEntry>(); - TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; - log.debug("Reading " + ntabs + " dir tables"); - - for (int i = 0; i < ntabs; i++) { - pd[i] = new TTFDirTabEntry(); - String tableName = pd[i].read(fontFile); - dirTabs.put(TTFTableName.getValue(tableName), pd[i]); - } - dirTabs.put(TTFTableName.TABLE_DIRECTORY, - new TTFDirTabEntry(0L, fontFile.getCurrentPos())); - log.debug("dir tables: " + dirTabs.keySet()); - } - - /** - * Read the "head" table, this reads the bounding box and - * sets the upem (unitsPerEM) variable - * @throws IOException in case of an I/O problem - */ - protected void readFontHeader() throws IOException { - seekTab(fontFile, TTFTableName.HEAD, 2 * 4 + 2 * 4); - int flags = fontFile.readTTFUShort(); - if (log.isDebugEnabled()) { - log.debug("flags: " + flags + " - " + Integer.toString(flags, 2)); - } - upem = fontFile.readTTFUShort(); - if (log.isDebugEnabled()) { - log.debug("unit per em: " + upem); - } - - fontFile.skip(16); - - fontBBox1 = fontFile.readTTFShort(); - fontBBox2 = fontFile.readTTFShort(); - fontBBox3 = fontFile.readTTFShort(); - fontBBox4 = fontFile.readTTFShort(); - if (log.isDebugEnabled()) { - log.debug("font bbox: xMin=" + fontBBox1 - + " yMin=" + fontBBox2 - + " xMax=" + fontBBox3 - + " yMax=" + fontBBox4); - } - - fontFile.skip(2 + 2 + 2); - - locaFormat = fontFile.readTTFShort(); - } - - /** - * Read the number of glyphs from the "maxp" table - * @throws IOException in case of an I/O problem - */ - protected void getNumGlyphs() throws IOException { - seekTab(fontFile, TTFTableName.MAXP, 4); - numberOfGlyphs = fontFile.readTTFUShort(); - } - - - /** - * Read the "hhea" table to find the ascender and descender and - * size of "hmtx" table, as a fixed size font might have only - * one width. - * @throws IOException in case of an I/O problem - */ - protected void readHorizontalHeader() - throws IOException { - seekTab(fontFile, TTFTableName.HHEA, 4); - hheaAscender = fontFile.readTTFShort(); - hheaDescender = fontFile.readTTFShort(); - - fontFile.skip(2 + 2 + 3 * 2 + 8 * 2); - nhmtx = fontFile.readTTFUShort(); - - if (log.isDebugEnabled()) { - log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender)); - log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender)); - log.debug("Number of horizontal metrics: " + nhmtx); - } - } - - /** - * Read "hmtx" table and put the horizontal metrics - * in the mtxTab array. If the number of metrics is less - * than the number of glyphs (eg fixed size fonts), extend - * the mtxTab array and fill in the missing widths - * @throws IOException in case of an I/O problem - */ - protected void readHorizontalMetrics() - throws IOException { - seekTab(fontFile, TTFTableName.HMTX, 0); - - int mtxSize = Math.max(numberOfGlyphs, nhmtx); - mtxTab = new TTFMtxEntry[mtxSize]; - - if (log.isTraceEnabled()) { - log.trace("*** Widths array: \n"); - } - for (int i = 0; i < mtxSize; i++) { - mtxTab[i] = new TTFMtxEntry(); - } - for (int i = 0; i < nhmtx; i++) { - mtxTab[i].setWx(fontFile.readTTFUShort()); - mtxTab[i].setLsb(fontFile.readTTFUShort()); - - if (log.isTraceEnabled()) { - log.trace(" width[" + i + "] = " - + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); - } - } - - if (nhmtx < mtxSize) { - // Fill in the missing widths - int lastWidth = mtxTab[nhmtx - 1].getWx(); - for (int i = nhmtx; i < mtxSize; i++) { - mtxTab[i].setWx(lastWidth); - mtxTab[i].setLsb(fontFile.readTTFUShort()); - } - } - } - - - /** - * Read the "post" table - * containing the PostScript names of the glyphs. - */ - private void readPostScript() throws IOException { - seekTab(fontFile, TTFTableName.POST, 0); - int postFormat = fontFile.readTTFLong(); - italicAngle = fontFile.readTTFULong(); - underlinePosition = fontFile.readTTFShort(); - underlineThickness = fontFile.readTTFShort(); - isFixedPitch = fontFile.readTTFULong(); - - //Skip memory usage values - fontFile.skip(4 * 4); - - log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); - switch (postFormat) { - case 0x00010000: - log.debug("PostScript format 1"); - postScriptVersion = PostScriptVersion.V1; - for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) { - mtxTab[i].setName(MAC_GLYPH_ORDERING[i]); - } - break; - case 0x00020000: - log.debug("PostScript format 2"); - postScriptVersion = PostScriptVersion.V2; - int numGlyphStrings = 0; - - // Read Number of Glyphs - int l = fontFile.readTTFUShort(); - - // Read indexes - for (int i = 0; i < l; i++) { - mtxTab[i].setIndex(fontFile.readTTFUShort()); - - if (mtxTab[i].getIndex() > 257) { - //Index is not in the Macintosh standard set - numGlyphStrings++; - } - - if (log.isTraceEnabled()) { - log.trace("PostScript index: " + mtxTab[i].getIndexAsString()); - } - } - - // firstChar=minIndex; - String[] psGlyphsBuffer = new String[numGlyphStrings]; - if (log.isDebugEnabled()) { - log.debug("Reading " + numGlyphStrings - + " glyphnames, that are not in the standard Macintosh" - + " set. Total number of glyphs=" + l); - } - for (int i = 0; i < psGlyphsBuffer.length; i++) { - psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte()); - } - - //Set glyph names - for (int i = 0; i < l; i++) { - if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) { - mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]); - } else { - if (!mtxTab[i].isIndexReserved()) { - int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length; - - if (log.isTraceEnabled()) { - log.trace(k + " i=" + i + " mtx=" + mtxTab.length - + " ps=" + psGlyphsBuffer.length); - } - - mtxTab[i].setName(psGlyphsBuffer[k]); - } - } - } - - break; - case 0x00030000: - // PostScript format 3 contains no glyph names - log.debug("PostScript format 3"); - postScriptVersion = PostScriptVersion.V3; - break; - default: - log.error("Unknown PostScript format: " + postFormat); - postScriptVersion = PostScriptVersion.UNKNOWN; - } - } - - - /** - * Read the "OS/2" table - */ - private void readOS2() throws IOException { - // Check if font is embeddable - TTFDirTabEntry os2Entry = dirTabs.get(TTFTableName.OS2); - if (os2Entry != null) { - seekTab(fontFile, TTFTableName.OS2, 0); - int version = fontFile.readTTFUShort(); - if (log.isDebugEnabled()) { - log.debug("OS/2 table: version=" + version - + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength()); - } - fontFile.skip(2); //xAvgCharWidth - this.usWeightClass = fontFile.readTTFUShort(); - - // usWidthClass - fontFile.skip(2); - - int fsType = fontFile.readTTFUShort(); - if (fsType == 2) { - isEmbeddable = false; - } else { - isEmbeddable = true; - } - fontFile.skip(8 * 2); - strikeoutThickness = fontFile.readTTFShort(); - strikeoutPosition = fontFile.readTTFShort(); - fontFile.skip(2); - fontFile.skip(10); //panose array - fontFile.skip(4 * 4); //unicode ranges - fontFile.skip(4); - fontFile.skip(3 * 2); - int v; - os2Ascender = fontFile.readTTFShort(); //sTypoAscender - os2Descender = fontFile.readTTFShort(); //sTypoDescender - if (log.isDebugEnabled()) { - log.debug("sTypoAscender: " + os2Ascender - + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender)); - log.debug("sTypoDescender: " + os2Descender - + " -> internal " + convertTTFUnit2PDFUnit(os2Descender)); - } - v = fontFile.readTTFShort(); //sTypoLineGap - if (log.isDebugEnabled()) { - log.debug("sTypoLineGap: " + v); - } - v = fontFile.readTTFUShort(); //usWinAscent - if (log.isDebugEnabled()) { - log.debug("usWinAscent: " + formatUnitsForDebug(v)); - } - v = fontFile.readTTFUShort(); //usWinDescent - if (log.isDebugEnabled()) { - log.debug("usWinDescent: " + formatUnitsForDebug(v)); - } - - //version 1 OS/2 table might end here - if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) { - fontFile.skip(2 * 4); - this.os2xHeight = fontFile.readTTFShort(); //sxHeight - this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight - if (log.isDebugEnabled()) { - log.debug("sxHeight: " + this.os2xHeight); - log.debug("sCapHeight: " + this.os2CapHeight); - } - } - - } else { - isEmbeddable = true; - } - } - - /** - * Read the "loca" table. - * @throws IOException In case of a I/O problem - */ - protected final void readIndexToLocation() - throws IOException { - if (!seekTab(fontFile, TTFTableName.LOCA, 0)) { - throw new IOException("'loca' table not found, happens when the font file doesn't" - + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)"); - } - for (int i = 0; i < numberOfGlyphs; i++) { - mtxTab[i].setOffset(locaFormat == 1 ? fontFile.readTTFULong() - : (fontFile.readTTFUShort() << 1)); - } - lastLoca = (locaFormat == 1 ? fontFile.readTTFULong() - : (fontFile.readTTFUShort() << 1)); - } - - /** - * Read the "glyf" table to find the bounding boxes. - * @throws IOException In case of a I/O problem - */ - private void readGlyf() throws IOException { - TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.GLYF); - if (dirTab == null) { - throw new IOException("glyf table not found, cannot continue"); - } - for (int i = 0; i < (numberOfGlyphs - 1); i++) { - if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { - fontFile.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); - fontFile.skip(2); - final int[] bbox = { - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort()}; - mtxTab[i].setBoundingBox(bbox); - } else { - mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); - } - } - - - long n = (dirTabs.get(TTFTableName.GLYF)).getOffset(); - for (int i = 0; i < numberOfGlyphs; i++) { - if ((i + 1) >= mtxTab.length - || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { - fontFile.seekSet(n + mtxTab[i].getOffset()); - fontFile.skip(2); - final int[] bbox = { - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort(), - fontFile.readTTFShort()}; - mtxTab[i].setBoundingBox(bbox); - } else { - /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ - final int bbox0 = mtxTab[0].getBoundingBox()[0]; - final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; - mtxTab[i].setBoundingBox(bbox); - /* Original code - mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ - } - if (log.isTraceEnabled()) { - log.trace(mtxTab[i].toString(this)); - } - } + super(useKerning, useAdvanced); } /** * Read the "name" table. * @throws IOException In case of a I/O problem */ - private void readName() throws IOException { - seekTab(fontFile, TTFTableName.NAME, 2); + protected void readName() throws IOException { + seekTab(fontFile, OFTableName.NAME, 2); int i = fontFile.getCurrentPos(); int n = fontFile.readTTFUShort(); int j = fontFile.readTTFUShort() + i - 2; @@ -1521,572 +112,84 @@ public class TTFFile { } /** - * Read the "PCLT" table to find xHeight and capHeight. + * Read the "glyf" table to find the bounding boxes. * @throws IOException In case of a I/O problem */ - private boolean readPCLT() throws IOException { - TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.PCLT); - if (dirTab != null) { - fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2); - xHeight = fontFile.readTTFUShort(); - log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight)); - fontFile.skip(2 * 2); - capHeight = fontFile.readTTFUShort(); - log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight)); - fontFile.skip(2 + 16 + 8 + 6 + 1 + 1); - - int serifStyle = fontFile.readTTFUByte(); - serifStyle = serifStyle >> 6; - serifStyle = serifStyle & 3; - if (serifStyle == 1) { - hasSerifs = false; - } else { - hasSerifs = true; - } - return true; - } else { - return false; - } - } - - /** - * Determines the right source for the ascender and descender values. The problem here is - * that the interpretation of these values is not the same for every font. There doesn't seem - * to be a uniform definition of an ascender and a descender. In some fonts - * the hhea values are defined after the Apple interpretation, but not in every font. The - * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the - * baseline so we need values which add up more or less to the "em box". However, due to - * accent modifiers a character can grow beyond the em box. - */ - private void determineAscDesc() { - int hheaBoxHeight = hheaAscender - hheaDescender; - int os2BoxHeight = os2Ascender - os2Descender; - if (os2Ascender > 0 && os2BoxHeight <= upem) { - ascender = os2Ascender; - descender = os2Descender; - } else if (hheaAscender > 0 && hheaBoxHeight <= upem) { - ascender = hheaAscender; - descender = hheaDescender; - } else { - if (os2Ascender > 0) { - //Fall back to info from OS/2 if possible - ascender = os2Ascender; - descender = os2Descender; - } else { - ascender = hheaAscender; - descender = hheaDescender; - } - } - - if (log.isDebugEnabled()) { - log.debug("Font box height: " + (ascender - descender)); - if (ascender - descender > upem) { - log.debug("Ascender and descender together are larger than the em box."); - } + private void readGlyf() throws IOException { + OFDirTabEntry dirTab = dirTabs.get(OFTableName.GLYF); + if (dirTab == null) { + throw new IOException("glyf table not found, cannot continue"); } - } - - private void guessVerticalMetricsFromGlyphBBox() { - // Approximate capHeight from height of "H" - // It's most unlikely that a font misses the PCLT table - // This also assumes that postscriptnames exists ("H") - // Should look it up in the cmap (that wouldn't help - // for charsets without H anyway...) - // Same for xHeight with the letter "x" - int localCapHeight = 0; - int localXHeight = 0; - int localAscender = 0; - int localDescender = 0; - for (int i = 0; i < mtxTab.length; i++) { - if ("H".equals(mtxTab[i].getName())) { - localCapHeight = mtxTab[i].getBoundingBox()[3]; - } else if ("x".equals(mtxTab[i].getName())) { - localXHeight = mtxTab[i].getBoundingBox()[3]; - } else if ("d".equals(mtxTab[i].getName())) { - localAscender = mtxTab[i].getBoundingBox()[3]; - } else if ("p".equals(mtxTab[i].getName())) { - localDescender = mtxTab[i].getBoundingBox()[1]; + for (int i = 0; i < (numberOfGlyphs - 1); i++) { + if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { + fontFile.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); + fontFile.skip(2); + final int[] bbox = { + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); } else { - // OpenType Fonts with a version 3.0 "post" table don't have glyph names. - // Use Unicode indices instead. - List unicodeIndex = mtxTab[i].getUnicodeIndex(); - if (unicodeIndex.size() > 0) { - //Only the first index is used - char ch = (char)((Integer)unicodeIndex.get(0)).intValue(); - if (ch == 'H') { - localCapHeight = mtxTab[i].getBoundingBox()[3]; - } else if (ch == 'x') { - localXHeight = mtxTab[i].getBoundingBox()[3]; - } else if (ch == 'd') { - localAscender = mtxTab[i].getBoundingBox()[3]; - } else if (ch == 'p') { - localDescender = mtxTab[i].getBoundingBox()[1]; - } - } - } - } - if (log.isDebugEnabled()) { - log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender)); - log.debug("Descender from glyph 'p': " + formatUnitsForDebug(localDescender)); - } - if (ascender - descender > upem) { - log.debug("Replacing specified ascender/descender with derived values to get values" - + " which fit in the em box."); - ascender = localAscender; - descender = localDescender; - } - - if (log.isDebugEnabled()) { - log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight)); - log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight)); - } - if (capHeight == 0) { - capHeight = localCapHeight; - if (capHeight == 0) { - capHeight = os2CapHeight; - } - if (capHeight == 0) { - log.debug("capHeight value could not be determined." - + " The font may not work as expected."); - } - } - if (xHeight == 0) { - xHeight = localXHeight; - if (xHeight == 0) { - xHeight = os2xHeight; - } - if (xHeight == 0) { - log.debug("xHeight value could not be determined." - + " The font may not work as expected."); - } - } - } - - /** - * Read the kerning table, create a table for both CIDs and - * winAnsiEncoding. - * @throws IOException In case of a I/O problem - */ - private void readKerning() throws IOException { - // Read kerning - kerningTab = new HashMap<Integer, Map<Integer, Integer>>(); - ansiKerningTab = new HashMap<Integer, Map<Integer, Integer>>(); - TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.KERN); - if (dirTab != null) { - seekTab(fontFile, TTFTableName.KERN, 2); - for (int n = fontFile.readTTFUShort(); n > 0; n--) { - fontFile.skip(2 * 2); - int k = fontFile.readTTFUShort(); - if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { - return; - } - if ((k >> 8) != 0) { - continue; - } - - k = fontFile.readTTFUShort(); - fontFile.skip(3 * 2); - while (k-- > 0) { - int i = fontFile.readTTFUShort(); - int j = fontFile.readTTFUShort(); - int kpx = fontFile.readTTFShort(); - if (kpx != 0) { - // CID kerning table entry, using unicode indexes - final Integer iObj = glyphToUnicode(i); - final Integer u2 = glyphToUnicode(j); - if (iObj == null) { - // happens for many fonts (Ubuntu font set), - // stray entries in the kerning table?? - log.debug("Ignoring kerning pair because no Unicode index was" - + " found for the first glyph " + i); - } else if (u2 == null) { - log.debug("Ignoring kerning pair because Unicode index was" - + " found for the second glyph " + i); - } else { - Map<Integer, Integer> adjTab = kerningTab.get(iObj); - if (adjTab == null) { - adjTab = new HashMap<Integer, Integer>(); - } - adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx))); - kerningTab.put(iObj, adjTab); - } - } - } - } - - // Create winAnsiEncoded kerning table from kerningTab - // (could probably be simplified, for now we remap back to CID indexes and - // then to winAnsi) - for (Integer unicodeKey1 : kerningTab.keySet()) { - Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue()); - Map<Integer, Integer> akpx = new HashMap<Integer, Integer>(); - Map<Integer, Integer> ckpx = kerningTab.get(unicodeKey1); - - for (Integer unicodeKey2 : ckpx.keySet()) { - Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue()); - Integer kern = ckpx.get(unicodeKey2); - - Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); - while (uniMap.hasNext()) { - Integer unicodeKey = (Integer)uniMap.next(); - Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); - for (int u = 0; u < ansiKeys.length; u++) { - akpx.put(ansiKeys[u], kern); - } - } - } - - if (akpx.size() > 0) { - Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator(); - while (uniMap.hasNext()) { - Integer unicodeKey = (Integer)uniMap.next(); - Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); - for (int u = 0; u < ansiKeys.length; u++) { - ansiKerningTab.put(ansiKeys[u], akpx); - } - } - } + mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); } } - } - /** - * Streams a font. - * @param ttfOut The interface for streaming TrueType tables. - * @exception IOException file write error - */ - public void stream(TTFOutputStream ttfOut) throws IOException { - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedDirTabs = sortDirTabMap(dirTabs); - byte[] file = fontFile.getAllBytes(); - TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); - TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); - ttfOut.startFontStream(); - for (Map.Entry<TTFTableName, TTFDirTabEntry> entry : sortedDirTabs) { - int offset = (int) entry.getValue().getOffset(); - int paddedLength = (int) entry.getValue().getLength(); - paddedLength += getPadSize(offset + paddedLength); - if (entry.getKey().equals(TTFTableName.GLYF)) { - streamGlyf(glyphOut, file, offset, paddedLength); + long n = (dirTabs.get(OFTableName.GLYF)).getOffset(); + for (int i = 0; i < numberOfGlyphs; i++) { + if ((i + 1) >= mtxTab.length + || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { + fontFile.seekSet(n + mtxTab[i].getOffset()); + fontFile.skip(2); + final int[] bbox = { + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); } else { - tableOut.streamTable(file, offset, paddedLength); - } - } - ttfOut.endFontStream(); - } - - private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset, - int tableLength) throws IOException { - //Stream all but the last glyph - int glyphStart = 0; - int glyphEnd = 0; - glyphOut.startGlyphStream(); - for (int i = 0; i < mtxTab.length - 1; i++) { - glyphStart = (int) mtxTab[i].getOffset() + tableOffset; - glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset; - glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart); - } - glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd); - glyphOut.endGlyphStream(); - } - - /** - * Returns the order in which the tables in a TrueType font should be written to file. - * @param directoryTabs the map that is to be sorted. - * @return TTFTablesNames[] an array of table names sorted in the order they should appear in - * the TTF file. - */ - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> - sortDirTabMap(Map<TTFTableName, TTFDirTabEntry> directoryTabs) { - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedSet - = new TreeSet<Map.Entry<TTFTableName, TTFDirTabEntry>>( - new Comparator<Map.Entry<TTFTableName, TTFDirTabEntry>>() { - - public int compare(Entry<TTFTableName, TTFDirTabEntry> o1, - Entry<TTFTableName, TTFDirTabEntry> o2) { - return (int) (o1.getValue().getOffset() - o2.getValue().getOffset()); - } - }); - sortedSet.addAll(directoryTabs.entrySet()); - return sortedSet; - } - - /** - * Returns this font's character to glyph mapping. - * - * @return the font's cmap - */ - public List<CMapSegment> getCMaps() { - return cmaps; - } - - /** - * Check if this is a TrueType collection and that the given - * name exists in the collection. - * If it does, set offset in fontfile to the beginning of - * the Table Directory for that font. - * @param name The name to check - * @return True if not collection or font name present, false otherwise - * @throws IOException In case of an I/O problem - */ - protected final boolean checkTTC(String name) throws IOException { - String tag = fontFile.readTTFString(4); - - if ("ttcf".equals(tag)) { - // This is a TrueType Collection - fontFile.skip(4); - - // Read directory offsets - int numDirectories = (int)fontFile.readTTFULong(); - // int numDirectories=in.readTTFUShort(); - long[] dirOffsets = new long[numDirectories]; - for (int i = 0; i < numDirectories; i++) { - dirOffsets[i] = fontFile.readTTFULong(); - } - - log.info("This is a TrueType collection file with " - + numDirectories + " fonts"); - log.info("Containing the following fonts: "); - // Read all the directories and name tables to check - // If the font exists - this is a bit ugly, but... - boolean found = false; - - // Iterate through all name tables even if font - // Is found, just to show all the names - long dirTabOffset = 0; - for (int i = 0; (i < numDirectories); i++) { - fontFile.seekSet(dirOffsets[i]); - readDirTabs(); - - readName(); - - if (fullName.equals(name)) { - found = true; - dirTabOffset = dirOffsets[i]; - log.info(fullName + " <-- selected"); - } else { - log.info(fullName); - } - - // Reset names - notice = ""; - fullName = ""; - familyNames.clear(); - postScriptName = ""; - subFamilyName = ""; - } - - fontFile.seekSet(dirTabOffset); - return found; - } else { - fontFile.seekSet(0); - return true; - } - } - - /** - * Return TTC font names - * @param in FontFileReader to read from - * @return True if not collection or font name present, false otherwise - * @throws IOException In case of an I/O problem - */ - public final List<String> getTTCnames(FontFileReader in) throws IOException { - this.fontFile = in; - - List<String> fontNames = new ArrayList<String>(); - String tag = in.readTTFString(4); - - if ("ttcf".equals(tag)) { - // This is a TrueType Collection - in.skip(4); - - // Read directory offsets - int numDirectories = (int)in.readTTFULong(); - long[] dirOffsets = new long[numDirectories]; - for (int i = 0; i < numDirectories; i++) { - dirOffsets[i] = in.readTTFULong(); - } - - log.info("This is a TrueType collection file with " - + numDirectories + " fonts"); - log.info("Containing the following fonts: "); - - for (int i = 0; (i < numDirectories); i++) { - in.seekSet(dirOffsets[i]); - readDirTabs(); - - readName(); - - log.info(fullName); - fontNames.add(fullName); - - // Reset names - notice = ""; - fullName = ""; - familyNames.clear(); - postScriptName = ""; - subFamilyName = ""; + /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ + final int bbox0 = mtxTab[0].getBoundingBox()[0]; + final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; + mtxTab[i].setBoundingBox(bbox); + /* Original code + mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ } - - in.seekSet(0); - return fontNames; - } else { - log.error("Not a TTC!"); - return null; - } - } - - /* - * Helper classes, they are not very efficient, but that really - * doesn't matter... - */ - private Integer[] unicodeToWinAnsi(int unicode) { - List<Integer> ret = new ArrayList<Integer>(); - for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { - if (unicode == Glyphs.WINANSI_ENCODING[i]) { - ret.add(new Integer(i)); + if (log.isTraceEnabled()) { + log.trace(mtxTab[i].toString(this)); } } - return ret.toArray(new Integer[0]); } - /** - * Dumps a few informational values to System.out. - */ - public void printStuff() { - System.out.println("Font name: " + postScriptName); - System.out.println("Full name: " + fullName); - System.out.println("Family name: " + familyNames); - System.out.println("Subfamily name: " + subFamilyName); - System.out.println("Notice: " + notice); - System.out.println("xHeight: " + convertTTFUnit2PDFUnit(xHeight)); - System.out.println("capheight: " + convertTTFUnit2PDFUnit(capHeight)); - - int italic = (int)(italicAngle >> 16); - System.out.println("Italic: " + italic); - System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); - if ((italicAngle % 0x10000) > 0) { - System.out.print("." - + (short)((italicAngle % 0x10000) * 1000) - / 0x10000); - } - System.out.println(); - System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); - System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); - System.out.println("FontBBox: [" + convertTTFUnit2PDFUnit(fontBBox1) - + " " + convertTTFUnit2PDFUnit(fontBBox2) + " " - + convertTTFUnit2PDFUnit(fontBBox3) + " " - + convertTTFUnit2PDFUnit(fontBBox4) + "]"); - } - - private String formatUnitsForDebug(int units) { - return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units"; - } - - /** - * Map a glyph index to the corresponding unicode code point - * - * @param glyphIndex - * @return unicode code point - */ - private Integer glyphToUnicode(int glyphIndex) { - return glyphToUnicodeMap.get(new Integer(glyphIndex)); + @Override + protected void updateBBoxAndOffset() throws IOException { + readIndexToLocation(); + readGlyf(); } /** - * Map a unicode code point to the corresponding glyph index - * - * @param unicodeIndex unicode code point - * @return glyph index - */ - private Integer unicodeToGlyph(int unicodeIndex) throws IOException { - final Integer result - = unicodeToGlyphMap.get(new Integer(unicodeIndex)); - if (result == null) { - throw new IOException( - "Glyph index not found for unicode value " + unicodeIndex); - } - return result; - } - - String getGlyphName(int glyphIndex) { - return mtxTab[glyphIndex].getName(); - } - - /** - * Determine if advanced (typographic) table is present. - * @return true if advanced (typographic) table is present - */ - public boolean hasAdvancedTable() { - if (advancedTableReader != null) { - return advancedTableReader.hasAdvancedTable(); - } else { - return false; - } - } - - /** - * Returns the GDEF table or null if none present. - * @return the GDEF table - */ - public GlyphDefinitionTable getGDEF() { - if (advancedTableReader != null) { - return advancedTableReader.getGDEF(); - } else { - return null; - } - } - - /** - * Returns the GSUB table or null if none present. - * @return the GSUB table + * Read the "loca" table. + * @throws IOException In case of a I/O problem */ - public GlyphSubstitutionTable getGSUB() { - if (advancedTableReader != null) { - return advancedTableReader.getGSUB(); - } else { - return null; + protected final void readIndexToLocation() + throws IOException { + if (!seekTab(fontFile, OFTableName.LOCA, 0)) { + throw new IOException("'loca' table not found, happens when the font file doesn't" + + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)"); } - } - - /** - * Returns the GPOS table or null if none present. - * @return the GPOS table - */ - public GlyphPositioningTable getGPOS() { - if (advancedTableReader != null) { - return advancedTableReader.getGPOS(); - } else { - return null; + for (int i = 0; i < numberOfGlyphs; i++) { + mtxTab[i].setOffset(locaFormat == 1 ? fontFile.readTTFULong() + : (fontFile.readTTFUShort() << 1)); } + lastLoca = (locaFormat == 1 ? fontFile.readTTFULong() + : (fontFile.readTTFUShort() << 1)); } - /** - * Static main method to get info about a TrueType font. - * @param args The command line arguments - */ - public static void main(String[] args) { - InputStream stream = null; - try { - boolean useKerning = true; - boolean useAdvanced = true; - TTFFile ttfFile = new TTFFile(useKerning, useAdvanced); - - stream = new FileInputStream(args[0]); - FontFileReader reader = new FontFileReader(stream); - - String name = null; - if (args.length >= 2) { - name = args[1]; - } - - ttfFile.readFont(reader, name); - ttfFile.printStuff(); - - } catch (IOException ioe) { - System.err.println("Problem reading font: " + ioe.toString()); - ioe.printStackTrace(System.err); - } finally { - IOUtils.closeQuietly(stream); - } + @Override + protected void initializeFont(FontFileReader in) throws IOException { + fontFile = in; } } diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java index c9a0e6c8e..ff46af1c7 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java @@ -43,7 +43,7 @@ public class TTFSubSetFile extends TTFFile { * Offsets in name table to be filled out by table. * The offsets are to the checkSum field */ - private Map<TTFTableName, Integer> offsets = new HashMap<TTFTableName, Integer>(); + private Map<OFTableName, Integer> offsets = new HashMap<OFTableName, Integer>(); private int checkSumAdjustmentOffset = 0; private int locaOffset = 0; @@ -67,8 +67,8 @@ public class TTFSubSetFile extends TTFFile { } /** The dir tab entries in the new subset font. */ - private Map<TTFTableName, TTFDirTabEntry> newDirTabs - = new HashMap<TTFTableName, TTFDirTabEntry>(); + private Map<OFTableName, OFDirTabEntry> newDirTabs + = new HashMap<OFTableName, OFDirTabEntry>(); private int determineTableCount() { int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp @@ -117,29 +117,29 @@ public class TTFSubSetFile extends TTFFile { writeUShort((numTables * 16) - searchRange); realSize += 2; // Create space for the table entries (these must be in ASCII alphabetical order[A-Z] then[a-z]) - writeTableName(TTFTableName.OS2); + writeTableName(OFTableName.OS2); if (hasCvt()) { - writeTableName(TTFTableName.CVT); + writeTableName(OFTableName.CVT); } if (hasFpgm()) { - writeTableName(TTFTableName.FPGM); + writeTableName(OFTableName.FPGM); } - writeTableName(TTFTableName.GLYF); - writeTableName(TTFTableName.HEAD); - writeTableName(TTFTableName.HHEA); - writeTableName(TTFTableName.HMTX); - writeTableName(TTFTableName.LOCA); - writeTableName(TTFTableName.MAXP); - writeTableName(TTFTableName.NAME); - writeTableName(TTFTableName.POST); + writeTableName(OFTableName.GLYF); + writeTableName(OFTableName.HEAD); + writeTableName(OFTableName.HHEA); + writeTableName(OFTableName.HMTX); + writeTableName(OFTableName.LOCA); + writeTableName(OFTableName.MAXP); + writeTableName(OFTableName.NAME); + writeTableName(OFTableName.POST); if (hasPrep()) { - writeTableName(TTFTableName.PREP); + writeTableName(OFTableName.PREP); } - newDirTabs.put(TTFTableName.TABLE_DIRECTORY, new TTFDirTabEntry(0, currentPos)); + newDirTabs.put(OFTableName.TABLE_DIRECTORY, new OFDirTabEntry(0, currentPos)); } - private void writeTableName(TTFTableName tableName) { + private void writeTableName(OFTableName tableName) { writeString(tableName.getName()); offsets.put(tableName, currentPos); currentPos += 12; @@ -148,15 +148,15 @@ public class TTFSubSetFile extends TTFFile { private boolean hasCvt() { - return dirTabs.containsKey(TTFTableName.CVT); + return dirTabs.containsKey(OFTableName.CVT); } private boolean hasFpgm() { - return dirTabs.containsKey(TTFTableName.FPGM); + return dirTabs.containsKey(OFTableName.FPGM); } private boolean hasPrep() { - return dirTabs.containsKey(TTFTableName.PREP); + return dirTabs.containsKey(OFTableName.PREP); } /** @@ -165,15 +165,15 @@ public class TTFSubSetFile extends TTFFile { private void createLoca(int size) throws IOException { pad4(); locaOffset = currentPos; - int dirTableOffset = offsets.get(TTFTableName.LOCA); + int dirTableOffset = offsets.get(OFTableName.LOCA); writeULong(dirTableOffset + 4, currentPos); writeULong(dirTableOffset + 8, size * 4 + 4); currentPos += size * 4 + 4; realSize += size * 4 + 4; } - private boolean copyTable(FontFileReader in, TTFTableName tableName) throws IOException { - TTFDirTabEntry entry = dirTabs.get(tableName); + private boolean copyTable(FontFileReader in, OFTableName tableName) throws IOException { + OFDirTabEntry entry = dirTabs.get(tableName); if (entry != null) { pad4(); seekTab(in, tableName, 0); @@ -193,28 +193,28 @@ public class TTFSubSetFile extends TTFFile { * Copy the cvt table as is from original font to subset font */ private boolean createCvt(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.CVT); + return copyTable(in, OFTableName.CVT); } /** * Copy the fpgm table as is from original font to subset font */ private boolean createFpgm(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.FPGM); + return copyTable(in, OFTableName.FPGM); } /** * Copy the name table as is from the original. */ private boolean createName(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.NAME); + return copyTable(in, OFTableName.NAME); } /** * Copy the OS/2 table as is from the original. */ private boolean createOS2(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.OS2); + return copyTable(in, OFTableName.OS2); } /** @@ -222,8 +222,8 @@ public class TTFSubSetFile extends TTFFile { * and set num glyphs to size */ private void createMaxp(FontFileReader in, int size) throws IOException { - TTFTableName maxp = TTFTableName.MAXP; - TTFDirTabEntry entry = dirTabs.get(maxp); + OFTableName maxp = OFTableName.MAXP; + OFDirTabEntry entry = dirTabs.get(maxp); if (entry != null) { pad4(); seekTab(in, maxp, 0); @@ -240,8 +240,8 @@ public class TTFSubSetFile extends TTFFile { } private void createPost(FontFileReader in) throws IOException { - TTFTableName post = TTFTableName.POST; - TTFDirTabEntry entry = dirTabs.get(post); + OFTableName post = OFTableName.POST; + OFDirTabEntry entry = dirTabs.get(post); if (entry != null) { pad4(); seekTab(in, post, 0); @@ -266,7 +266,7 @@ public class TTFSubSetFile extends TTFFile { * Copy the prep table as is from original font to subset font */ private boolean createPrep(FontFileReader in) throws IOException { - return copyTable(in, TTFTableName.PREP); + return copyTable(in, OFTableName.PREP); } @@ -275,15 +275,15 @@ public class TTFSubSetFile extends TTFFile { * and fill in size of hmtx table */ private void createHhea(FontFileReader in, int size) throws IOException { - TTFDirTabEntry entry = dirTabs.get(TTFTableName.HHEA); + OFDirTabEntry entry = dirTabs.get(OFTableName.HHEA); if (entry != null) { pad4(); - seekTab(in, TTFTableName.HHEA, 0); + seekTab(in, OFTableName.HHEA, 0); System.arraycopy(in.getBytes((int) entry.getOffset(), (int) entry.getLength()), 0, output, currentPos, (int) entry.getLength()); writeUShort((int) entry.getLength() + currentPos - 2, size); - updateCheckSum(currentPos, (int) entry.getLength(), TTFTableName.HHEA); + updateCheckSum(currentPos, (int) entry.getLength(), OFTableName.HHEA); currentPos += (int) entry.getLength(); realSize += (int) entry.getLength(); } else { @@ -299,8 +299,8 @@ public class TTFSubSetFile extends TTFFile { * in checkSumAdjustmentOffset */ private void createHead(FontFileReader in) throws IOException { - TTFTableName head = TTFTableName.HEAD; - TTFDirTabEntry entry = dirTabs.get(head); + OFTableName head = OFTableName.HEAD; + OFDirTabEntry entry = dirTabs.get(head); if (entry != null) { pad4(); seekTab(in, head, 0); @@ -329,8 +329,8 @@ public class TTFSubSetFile extends TTFFile { */ private void createGlyf(FontFileReader in, Map<Integer, Integer> glyphs) throws IOException { - TTFTableName glyf = TTFTableName.GLYF; - TTFDirTabEntry entry = dirTabs.get(glyf); + OFTableName glyf = OFTableName.GLYF; + OFDirTabEntry entry = dirTabs.get(glyf); int size = 0; int startPos = 0; int endOffset = 0; // Store this as the last loca @@ -393,10 +393,10 @@ public class TTFSubSetFile extends TTFFile { writeULong(locaOffset + glyphs.size() * 4, endOffset); int locaSize = glyphs.size() * 4 + 4; int checksum = getCheckSum(output, locaOffset, locaSize); - writeULong(offsets.get(TTFTableName.LOCA), checksum); + writeULong(offsets.get(OFTableName.LOCA), checksum); int padSize = (locaOffset + locaSize) % 4; - newDirTabs.put(TTFTableName.LOCA, - new TTFDirTabEntry(locaOffset, locaSize + padSize)); + newDirTabs.put(OFTableName.LOCA, + new OFDirTabEntry(locaOffset, locaSize + padSize)); } else { throw new IOException("Can't find glyf table"); } @@ -420,8 +420,8 @@ public class TTFSubSetFile extends TTFFile { */ private void createHmtx(FontFileReader in, Map<Integer, Integer> glyphs) throws IOException { - TTFTableName hmtx = TTFTableName.HMTX; - TTFDirTabEntry entry = dirTabs.get(hmtx); + OFTableName hmtx = OFTableName.HMTX; + OFDirTabEntry entry = dirTabs.get(hmtx); int longHorMetricSize = glyphs.size() * 2; int leftSideBearingSize = glyphs.size() * 2; @@ -457,11 +457,11 @@ public class TTFSubSetFile extends TTFFile { * new index as (Integer) value) * @throws IOException in case of an I/O problem */ - public void readFont(FontFileReader in, String name, + public void readFont(FontFileReader in, String name, String header, Map<Integer, Integer> glyphs) throws IOException { fontFile = in; //Check if TrueType collection, and that the name exists in the collection - if (!checkTTC(name)) { + if (!checkTTC(header, name)) { throw new IOException("Failed to read font"); } @@ -533,7 +533,7 @@ public class TTFSubSetFile extends TTFFile { glyphOffsets[i + 1] - glyphOffsets[i]); } // Stream the last glyph - TTFDirTabEntry glyf = newDirTabs.get(TTFTableName.GLYF); + OFDirTabEntry glyf = newDirTabs.get(OFTableName.GLYF); long lastGlyphLength = glyf.getLength() - (glyphOffsets[glyphOffsets.length - 1] - glyf.getOffset()); glyphOut.streamGlyph(output, glyphOffsets[glyphOffsets.length - 1], @@ -543,14 +543,14 @@ public class TTFSubSetFile extends TTFFile { @Override public void stream(TTFOutputStream ttfOut) throws IOException { - SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedDirTabs + SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedDirTabs = sortDirTabMap(newDirTabs); TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); ttfOut.startFontStream(); - for (Map.Entry<TTFTableName, TTFDirTabEntry> entry : sortedDirTabs) { - if (entry.getKey().equals(TTFTableName.GLYF)) { + for (Map.Entry<OFTableName, OFDirTabEntry> entry : sortedDirTabs) { + if (entry.getKey().equals(OFTableName.GLYF)) { handleGlyphSubset(glyphOut); } else { tableOut.streamTable(output, (int) entry.getValue().getOffset(), @@ -562,7 +562,7 @@ public class TTFSubSetFile extends TTFFile { private void scanGlyphs(FontFileReader in, Map<Integer, Integer> subsetGlyphs) throws IOException { - TTFDirTabEntry glyfTableInfo = dirTabs.get(TTFTableName.GLYF); + OFDirTabEntry glyfTableInfo = dirTabs.get(OFTableName.GLYF); if (glyfTableInfo == null) { throw new IOException("Glyf table could not be found"); } @@ -663,11 +663,11 @@ public class TTFSubSetFile extends TTFFile { } - private void updateCheckSum(int tableStart, int tableSize, TTFTableName tableName) { + private void updateCheckSum(int tableStart, int tableSize, OFTableName tableName) { int checksum = getCheckSum(output, tableStart, tableSize); int offset = offsets.get(tableName); int padSize = getPadSize(tableStart + tableSize); - newDirTabs.put(tableName, new TTFDirTabEntry(tableStart, tableSize + padSize)); + newDirTabs.put(tableName, new OFDirTabEntry(tableStart, tableSize + padSize)); writeULong(offset, checksum); writeULong(offset + 4, tableStart); writeULong(offset + 8, tableSize); diff --git a/src/java/org/apache/fop/fonts/type1/AFMFile.java b/src/java/org/apache/fop/fonts/type1/AFMFile.java index f6bc3b163..2aa718ea0 100644 --- a/src/java/org/apache/fop/fonts/type1/AFMFile.java +++ b/src/java/org/apache/fop/fonts/type1/AFMFile.java @@ -319,7 +319,7 @@ public class AFMFile { */ public void addCharMetrics(AFMCharMetrics metrics) { String name = metrics.getCharName(); - if (metrics.getUnicodeSequence() == null) { + if (metrics.getUnicodeSequence() == null && name.equals(".notdef")) { //Ignore as no Unicode assignment is possible return; } diff --git a/src/java/org/apache/fop/fonts/type1/CharMetricsHandler.java b/src/java/org/apache/fop/fonts/type1/CharMetricsHandler.java index 8ef172875..79753f3f8 100644 --- a/src/java/org/apache/fop/fonts/type1/CharMetricsHandler.java +++ b/src/java/org/apache/fop/fonts/type1/CharMetricsHandler.java @@ -28,6 +28,8 @@ import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.fonts.Glyphs; + import org.apache.fop.fonts.NamedCharacter; import org.apache.fop.fonts.type1.AFMParser.ValueHandler; @@ -102,9 +104,10 @@ abstract class CharMetricsHandler { AFMCharMetrics chm = defaultHandler.parse(line, stack, afmFileName); NamedCharacter namedChar = chm.getCharacter(); if (namedChar != null) { - int codePoint = AdobeStandardEncoding.getAdobeCodePoint(namedChar.getName()); - if (chm.getCharCode() != codePoint) { - LOG.info(afmFileName + ": named character '" + namedChar.getName() + "'" + String charName = namedChar.getName(); + int codePoint = AdobeStandardEncoding.getAdobeCodePoint(charName); + if (chm.getCharCode() != codePoint && !Glyphs.NOTDEF.equals(charName)) { + LOG.info(afmFileName + ": named character '" + charName + "'" + " has an incorrect code point: " + chm.getCharCode() + ". Changed to " + codePoint); chm.setCharCode(codePoint); diff --git a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java index 7922ff6fd..260ef209f 100644 --- a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java +++ b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java @@ -173,7 +173,21 @@ public class Type1FontLoader extends FontLoader { addUnencodedBasedOnAFM(afm); } } else { - if (pfm.getCharSet() >= 0 && pfm.getCharSet() <= 2) { + if (pfm.getCharSet() == 2 && !pfm.getCharSetName().equals("Symbol")) { + int[] table = new int[256]; + String[] charNameMap = new String[256]; + int j = 0; + for (int i = pfm.getFirstChar(); i < pfm.getLastChar(); i++) { + if (j < table.length) { + table[j] = i; + table[j + 1] = i; + j += 2; + } + charNameMap[i] = String.format("x%03o", i); + } + CodePointMapping mapping = new CodePointMapping("custom", table, charNameMap); + singleFont.setEncoding(mapping); + } else if (pfm.getCharSet() >= 0 && pfm.getCharSet() <= 2) { singleFont.setEncoding(pfm.getCharSetName() + "Encoding"); } else { log.warn("The PFM reports an unsupported encoding (" @@ -400,10 +414,7 @@ public class Type1FontLoader extends FontLoader { List<AFMCharMetrics> chars = afm.getCharMetrics(); for (AFMCharMetrics charMetrics : chars) { if (charMetrics.getCharCode() >= 0) { - String u = charMetrics.getUnicodeSequence(); - if (u != null && u.length() == 1) { - mappingCount++; - } + mappingCount++; } } // ...and now build the table. @@ -416,6 +427,10 @@ public class Type1FontLoader extends FontLoader { String unicodes = charMetrics.getUnicodeSequence(); if (unicodes == null) { log.info("No Unicode mapping for glyph: " + charMetrics); + table[idx] = charMetrics.getCharCode(); + idx++; + table[idx] = charMetrics.getCharCode(); + idx++; } else if (unicodes.length() == 1) { table[idx] = charMetrics.getCharCode(); idx++; diff --git a/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java index 5d7cc0b64..635e267c1 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java @@ -65,9 +65,6 @@ public abstract class AbstractBaseLayoutManager public AbstractBaseLayoutManager(FObj fo) { this.fobj = fo; setGeneratesReferenceArea(fo.generatesReferenceAreas()); - if (getGeneratesReferenceArea()) { - setGeneratesBlockArea(true); - } } // --------- Property Resolution related functions --------- // diff --git a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java index 0285a41e7..2dd18e4ee 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java @@ -342,6 +342,34 @@ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager im && isFinished()); } + public boolean hasLineAreaDescendant() { + if (childLMs == null || childLMs.isEmpty()) { + return false; + } else { + for (LayoutManager childLM : childLMs) { + if (childLM.hasLineAreaDescendant()) { + return true; + } + } + } + return false; + } + + public int getBaselineOffset() { + if (childLMs != null) { + for (LayoutManager childLM : childLMs) { + if (childLM.hasLineAreaDescendant()) { + return childLM.getBaselineOffset(); + } + } + } + throw newNoLineAreaDescendantException(); + } + + protected IllegalStateException newNoLineAreaDescendantException() { + return new IllegalStateException("getBaselineOffset called on an object that has no line-area descendant"); + } + /** * Transfers foreign attributes from the formatting object to the area. * @param targetArea the area to set the attributes on diff --git a/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java index 0ed6cb69b..d731ab62a 100644 --- a/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java +++ b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java @@ -33,11 +33,11 @@ public final class AreaAdditionUtil { /** * Creates the child areas for the given layout manager. - * @param bslm the BlockStackingLayoutManager instance for which "addAreas" is performed. + * @param parentLM the parent layout manager * @param parentIter the position iterator * @param layoutContext the layout context */ - public static void addAreas(BlockStackingLayoutManager bslm, + public static void addAreas(AbstractLayoutManager parentLM, PositionIterator parentIter, LayoutContext layoutContext) { LayoutManager childLM; LayoutContext lc = LayoutContext.offspringOf(layoutContext); @@ -46,8 +46,8 @@ public final class AreaAdditionUtil { Position firstPos = null; Position lastPos = null; - if (bslm != null) { - bslm.addId(); + if (parentLM != null) { + parentLM.addId(); } // "unwrap" the NonLeafPositions stored in parentIter @@ -86,11 +86,11 @@ public final class AreaAdditionUtil { //doesn't give us that info. } - if (bslm != null) { - bslm.registerMarkers( + if (parentLM != null) { + parentLM.registerMarkers( true, - bslm.isFirst(firstPos), - bslm.isLast(lastPos)); + parentLM.isFirst(firstPos), + parentLM.isLast(lastPos)); } PositionIterator childPosIter = new PositionIterator(positionList.listIterator()); @@ -113,11 +113,11 @@ public final class AreaAdditionUtil { childLM.addAreas(childPosIter, lc); } - if (bslm != null) { - bslm.registerMarkers( + if (parentLM != null) { + parentLM.registerMarkers( false, - bslm.isFirst(firstPos), - bslm.isLast(lastPos)); + parentLM.isFirst(firstPos), + parentLM.isLast(lastPos)); } diff --git a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index a23cd28f1..3da6974a6 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -37,6 +37,7 @@ import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.flow.BlockContainer; import org.apache.fop.fo.properties.CommonAbsolutePosition; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; @@ -44,8 +45,8 @@ import org.apache.fop.traits.SpaceVal; /** * LayoutManager for a block-container FO. */ -public class BlockContainerLayoutManager extends BlockStackingLayoutManager implements - ConditionalElementListener, BreakOpportunity { +public class BlockContainerLayoutManager extends SpacedBorderedPaddedBlockLayoutManager + implements BreakOpportunity { /** * logging instance @@ -79,13 +80,6 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager impl private MinOptMax foBlockSpaceBefore; private MinOptMax foBlockSpaceAfter; - private boolean discardBorderBefore; - private boolean discardBorderAfter; - private boolean discardPaddingBefore; - private boolean discardPaddingAfter; - private MinOptMax effSpaceBefore; - private MinOptMax effSpaceAfter; - private int horizontalOverflow; private double contentRectOffsetX = 0; private double contentRectOffsetY = 0; @@ -96,6 +90,7 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager impl */ public BlockContainerLayoutManager(BlockContainer node) { super(node); + setGeneratesBlockArea(true); } /** {@inheritDoc} */ @@ -128,6 +123,11 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager impl .spaceAfter.getSpace().getOptimum(this).getLength().getValue(this); } + @Override + protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() { + return getBlockContainerFO().getCommonBorderPaddingBackground(); + } + private void resetSpaces() { this.discardBorderBefore = false; this.discardBorderAfter = false; @@ -867,6 +867,7 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager impl transferForeignAttributes(viewportBlockArea); TraitSetter.setProducerID(viewportBlockArea, getBlockContainerFO().getId()); + TraitSetter.setLayer(viewportBlockArea, getBlockContainerFO().getLayer()); TraitSetter.addBorders(viewportBlockArea, getBlockContainerFO().getCommonBorderPaddingBackground(), discardBorderBefore, discardBorderAfter, false, false, this); @@ -994,51 +995,6 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager impl } /** {@inheritDoc} */ - public void notifySpace(RelSide side, MinOptMax effectiveLength) { - if (RelSide.BEFORE == side) { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceBefore + "-> " + effectiveLength); - } - this.effSpaceBefore = effectiveLength; - } else { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceAfter + "-> " + effectiveLength); - } - this.effSpaceAfter = effectiveLength; - } - } - - /** {@inheritDoc} */ - public void notifyBorder(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardBorderBefore = true; - } else { - this.discardBorderAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Border " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ - public void notifyPadding(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardPaddingBefore = true; - } else { - this.discardPaddingAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Padding " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ public boolean handleOverflow(int milliPoints) { if (milliPoints > this.horizontalOverflow) { this.horizontalOverflow = milliPoints; diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java index 0fb738aea..d3bdc7b85 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java @@ -32,6 +32,7 @@ import org.apache.fop.area.Block; import org.apache.fop.area.LineArea; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.FONode; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; @@ -44,8 +45,8 @@ import org.apache.fop.traits.SpaceVal; /** * LayoutManager for a block FO. */ -public class BlockLayoutManager extends BlockStackingLayoutManager implements ConditionalElementListener, - BreakOpportunity { +public class BlockLayoutManager extends SpacedBorderedPaddedBlockLayoutManager + implements BreakOpportunity { /** logging instance */ private static Log log = LogFactory.getLog(BlockLayoutManager.class); @@ -60,13 +61,6 @@ public class BlockLayoutManager extends BlockStackingLayoutManager implements Co private int follow = 2000; //private int middleShift = 0; - private boolean discardBorderBefore; - private boolean discardBorderAfter; - private boolean discardPaddingBefore; - private boolean discardPaddingAfter; - private MinOptMax effSpaceBefore; - private MinOptMax effSpaceAfter; - /** * Creates a new BlockLayoutManager. * @param inBlock the block FO object to create the layout manager for. @@ -100,6 +94,11 @@ public class BlockLayoutManager extends BlockStackingLayoutManager implements Co .getOptimum(this).getLength().getValue(this); } + @Override + protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() { + return getBlockFO().getCommonBorderPaddingBackground(); + } + /** {@inheritDoc} */ @Override public List getNextKnuthElements(LayoutContext context, int alignment) { @@ -360,7 +359,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager implements Co curBlockArea.setIPD(super.getContentAreaIPD()); - curBlockArea.setBidiLevel(getBlockFO().getBidiLevel()); + curBlockArea.setBidiLevel(getBlockFO().getBidiLevelRecursive()); TraitSetter.addBreaks(curBlockArea, getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter()); @@ -381,6 +380,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager implements Co getBlockFO().getCommonBorderPaddingBackground(), startIndent, endIndent, this); + TraitSetter.setLayer(curBlockArea, getBlockFO().getLayer()); curBlockArea.setLocale(getBlockFO().getCommonHyphenation().getLocale()); curBlockArea.setLocation(FONode.getLocatorString(getBlockFO().getLocator())); @@ -457,51 +457,6 @@ public class BlockLayoutManager extends BlockStackingLayoutManager implements Co } /** {@inheritDoc} */ - public void notifySpace(RelSide side, MinOptMax effectiveLength) { - if (RelSide.BEFORE == side) { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceBefore + "-> " + effectiveLength); - } - this.effSpaceBefore = effectiveLength; - } else { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceAfter + "-> " + effectiveLength); - } - this.effSpaceAfter = effectiveLength; - } - } - - /** {@inheritDoc} */ - public void notifyBorder(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardBorderBefore = true; - } else { - this.discardBorderAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Border " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ - public void notifyPadding(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardPaddingBefore = true; - } else { - this.discardPaddingAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Padding " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ @Override public boolean isRestartable() { return true; diff --git a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java index d11f062cb..250e07727 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java @@ -36,6 +36,7 @@ import org.apache.fop.fo.properties.BreakPropertySet; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.SpaceProperty; +import org.apache.fop.layoutmgr.inline.InlineContainerLayoutManager; import org.apache.fop.layoutmgr.inline.InlineLayoutManager; import org.apache.fop.traits.MinOptMax; import org.apache.fop.util.ListUtil; @@ -1246,6 +1247,8 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager public boolean handleOverflow(int milliPoints) { if (getParent() instanceof BlockStackingLayoutManager) { return ((BlockStackingLayoutManager) getParent()).handleOverflow(milliPoints); + } else if (getParent() instanceof InlineContainerLayoutManager) { + return ((InlineContainerLayoutManager) getParent()).handleOverflow(milliPoints); } return false; } diff --git a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java index 7cbe33f26..aae5a6b30 100644 --- a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java @@ -242,8 +242,14 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan pv.setPage(pageArea); RegionViewport rv = new RegionViewport(referenceRect); - rv.setIPD(referenceRect.width); - rv.setBPD(referenceRect.height); + + if (pageSeq.getReferenceOrientation() % 180 == 0) { + rv.setIPD(referenceRect.width); + rv.setBPD(referenceRect.height); + } else { + rv.setIPD(referenceRect.height); + rv.setBPD(referenceRect.width); + } rv.setClip(true); BodyRegion body = new BodyRegion(Constants.FO_REGION_BODY, diff --git a/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java b/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java index 0260046e1..4c7afa8d6 100644 --- a/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java @@ -58,6 +58,7 @@ public class FlowLayoutManager extends BlockStackingLayoutManager */ public FlowLayoutManager(PageSequenceLayoutManager pslm, Flow node) { super(node); + setGeneratesBlockArea(true); setParent(pslm); } diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManager.java b/src/java/org/apache/fop/layoutmgr/LayoutManager.java index 985131bf1..8d1e4001c 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManager.java @@ -178,6 +178,25 @@ public interface LayoutManager extends PercentBaseContext { List getChangedKnuthElements(List oldList, int alignment); /** + * Whether the FO handled by this layout manager has a descendant (including itself) + * that will generate a line-area. + * + * @return {@code true} if a descendant line-area will be generated, {@code false} otherwise + */ + boolean hasLineAreaDescendant(); + + /** + * Returns the position of the dominant-baseline of this FO's first descendant + * line-area. <p>The behavior of this method is undefined if this FO has no descendant + * line-area, and an exception may be thrown. See {@link #hasLineAreaDescendant()}</p> + * + * @return this FO's space-before plus the distance from the before-edge of its + * allocation-rectangle to the dominant-baseline of the first line-area descendant + * @see #hasLineAreaDescendant() + */ + int getBaselineOffset(); + + /** * Returns the IPD of the content area * @return the IPD of the content area */ diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java index 292251a84..0e333d219 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java @@ -73,7 +73,7 @@ import org.apache.fop.layoutmgr.inline.CharacterLayoutManager; import org.apache.fop.layoutmgr.inline.ContentLayoutManager; import org.apache.fop.layoutmgr.inline.ExternalGraphicLayoutManager; import org.apache.fop.layoutmgr.inline.FootnoteLayoutManager; -import org.apache.fop.layoutmgr.inline.ICLayoutManager; +import org.apache.fop.layoutmgr.inline.InlineContainerLayoutManager; import org.apache.fop.layoutmgr.inline.InlineLayoutManager; import org.apache.fop.layoutmgr.inline.InstreamForeignObjectLM; import org.apache.fop.layoutmgr.inline.LeaderLayoutManager; @@ -257,9 +257,9 @@ public class LayoutManagerMapping implements LayoutManagerMaker { /** a layout manager maker */ public static class InlineLayoutManagerMaker extends Maker { /** {@inheritDoc} */ - public void make(FONode node, List lms) { - lms.add(new InlineLayoutManager((InlineLevel) node)); - } + public void make(FONode node, List lms) { + lms.add(new InlineLayoutManager((InlineLevel) node)); + } } /** a layout manager maker */ @@ -274,9 +274,7 @@ public class LayoutManagerMapping implements LayoutManagerMaker { public static class InlineContainerLayoutManagerMaker extends Maker { /** {@inheritDoc} */ public void make(FONode node, List lms) { - ArrayList childList = new ArrayList(); - super.make(node, childList); - lms.add(new ICLayoutManager((InlineContainer) node, childList)); + lms.add(new InlineContainerLayoutManager((InlineContainer) node)); } } diff --git a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java index 59145dd72..a5084bac5 100644 --- a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java +++ b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java @@ -140,7 +140,10 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { */ protected class KnuthPageNode extends KnuthNode { - /** Additional length due to footnotes. */ + /** Additional length due to already inserted footnotes. */ + public int insertedFootnotes; + + /** Total length of the footnotes. */ public int totalFootnotes; /** Index of the last inserted footnote. */ @@ -152,7 +155,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { public KnuthPageNode(int position, int line, int fitness, int totalWidth, int totalStretch, int totalShrink, - int totalFootnotes, int footnoteListIndex, int footnoteElementIndex, + int insertedFootnotes, int totalFootnotes, + int footnoteListIndex, int footnoteElementIndex, double adjustRatio, int availableShrink, int availableStretch, int difference, double totalDemerits, KnuthNode previous) { super(position, line, fitness, @@ -160,6 +164,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { adjustRatio, availableShrink, availableStretch, difference, totalDemerits, previous); this.totalFootnotes = totalFootnotes; + this.insertedFootnotes = insertedFootnotes; this.footnoteListIndex = footnoteListIndex; this.footnoteElementIndex = footnoteElementIndex; } @@ -172,7 +177,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { */ protected class BestPageRecords extends BestRecords { - private int[] bestFootnotesLength = new int[4]; + private int[] bestInsertedFootnotesLength = new int[4]; + private int[] bestTotalFootnotesLength = new int[4]; private int[] bestFootnoteListIndex = new int[4]; private int[] bestFootnoteElementIndex = new int[4]; @@ -182,13 +188,18 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { super.addRecord(demerits, node, adjust, availableShrink, availableStretch, difference, fitness); - bestFootnotesLength[fitness] = insertedFootnotesLength; + bestInsertedFootnotesLength[fitness] = insertedFootnotesLength; + bestTotalFootnotesLength[fitness] = totalFootnotesLength; bestFootnoteListIndex[fitness] = footnoteListIndex; bestFootnoteElementIndex[fitness] = footnoteElementIndex; } - public int getFootnotesLength(int fitness) { - return bestFootnotesLength[fitness]; + public int getInsertedFootnotesLength(int fitness) { + return bestInsertedFootnotesLength[fitness]; + } + + public int getTotalFootnotesLength(int fitness) { + return bestTotalFootnotesLength[fitness]; } public int getFootnoteListIndex(int fitness) { @@ -287,7 +298,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { int difference, double totalDemerits, KnuthNode previous) { return new KnuthPageNode(position, line, fitness, totalWidth, totalStretch, totalShrink, - insertedFootnotesLength, footnoteListIndex, footnoteElementIndex, + insertedFootnotesLength, totalFootnotesLength, + footnoteListIndex, footnoteElementIndex, adjustRatio, availableShrink, availableStretch, difference, totalDemerits, previous); } @@ -298,7 +310,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { int totalWidth, int totalStretch, int totalShrink) { return new KnuthPageNode(position, line, fitness, totalWidth, totalStretch, totalShrink, - ((BestPageRecords) best).getFootnotesLength(fitness), + ((BestPageRecords) best).getInsertedFootnotesLength(fitness), + ((BestPageRecords) best).getTotalFootnotesLength(fitness), ((BestPageRecords) best).getFootnoteListIndex(fitness), ((BestPageRecords) best).getFootnoteElementIndex(fitness), best.getAdjust(fitness), best.getAvailableShrink(fitness), @@ -405,6 +418,12 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { resetFootnotes(((KnuthBlockBox) resetElement).getElementLists()); } } + assert restartingNode instanceof KnuthPageNode; + KnuthPageNode restartingPageNode = (KnuthPageNode) restartingNode; + footnoteElementIndex = restartingPageNode.footnoteElementIndex; + footnoteListIndex = restartingPageNode.footnoteListIndex; + totalFootnotesLength = restartingPageNode.totalFootnotes; + insertedFootnotesLength = restartingPageNode.insertedFootnotes; } return returnValue; } @@ -413,13 +432,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { for (int i = 0; i < elementLists.size(); i++) { ListUtil.removeLast(footnotesList); ListUtil.removeLast(lengthList); - - // update totalFootnotesLength - if (!lengthList.isEmpty()) { - totalFootnotesLength = ListUtil.getLast(lengthList); - } else { - totalFootnotesLength = 0; - } } // update footnotesPending; if (footnotesList.size() == 0) { @@ -502,7 +514,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } if (footnotesPending) { // compute the total length of the footnotes not yet inserted - int allFootnotes = totalFootnotesLength - pageNode.totalFootnotes; + int allFootnotes = totalFootnotesLength - pageNode.insertedFootnotes; if (allFootnotes > 0) { // this page contains some footnote citations // add the footnote separator width @@ -511,7 +523,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // there is enough space to insert all footnotes: // add the whole allFootnotes length actualWidth += allFootnotes; - insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; + insertedFootnotesLength = pageNode.insertedFootnotes + allFootnotes; footnoteListIndex = footnotesList.size() - 1; footnoteElementIndex = getFootnoteList(footnoteListIndex).size() - 1; @@ -528,7 +540,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // this is the first feasible break; in this case it is allowed // to break and defer, if necessary, old and new footnotes actualWidth += footnoteSplit; - insertedFootnotesLength = pageNode.totalFootnotes + footnoteSplit; + insertedFootnotesLength = pageNode.insertedFootnotes + footnoteSplit; // footnoteListIndex has been set in getFootnoteSplit() // footnoteElementIndex has been set in getFootnoteSplit() } else { @@ -538,7 +550,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // that cannot be broken: // add the whole allFootnotes length, so this breakpoint will be discarded actualWidth += allFootnotes; - insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; + insertedFootnotesLength = pageNode.insertedFootnotes + allFootnotes; footnoteListIndex = footnotesList.size() - 1; footnoteElementIndex = getFootnoteList(footnoteListIndex).size() - 1; @@ -569,7 +581,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { private boolean canDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) { return (noBreakBetween(node.position, contentElementIndex) && deferredFootnotes(node.footnoteListIndex, - node.footnoteElementIndex, node.totalFootnotes)); + node.footnoteElementIndex, node.insertedFootnotes)); } /** @@ -649,7 +661,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { boolean canDeferOldFootnotes) { return getFootnoteSplit(activeNode.footnoteListIndex, activeNode.footnoteElementIndex, - activeNode.totalFootnotes, + activeNode.insertedFootnotes, availableLength, canDeferOldFootnotes); } @@ -714,10 +726,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { int prevIndex = -1; int index = -1; - while (!(somethingAdded && splitLength > availableLength)) { - if (!somethingAdded) { - somethingAdded = true; - } else { + while (splitLength <= availableLength) { + if (somethingAdded) { prevSplitLength = splitLength; prevIndex = index; } @@ -733,6 +743,10 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // element is a box splitLength += element.getWidth(); boxPreceding = true; + if (splitLength > prevSplitLength) { + // and it is non-empty + somethingAdded = true; + } } else if (element.isGlue()) { // element is a glue if (boxPreceding) { @@ -749,6 +763,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { index = noteListIterator.previousIndex(); break; } + boxPreceding = false; } } } @@ -758,7 +773,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // page here // if prevSplitLength is > 0 we can insert some footnote content in this page // and insert the remaining in the following one - //TODO: check this conditional, as the first one is always false...? if (!somethingAdded) { // there was not enough space to add a piece of the first new footnote // this is not a good break @@ -781,7 +795,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { if (difference > 0) { int maxAdjustment = totalStretch - activeNode.totalStretch; // add the footnote separator stretch if some footnote content will be added - if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) { + if (((KnuthPageNode) activeNode).insertedFootnotes < totalFootnotesLength) { maxAdjustment += footnoteSeparatorLength.getStretch(); } if (maxAdjustment > 0) { @@ -792,7 +806,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } else if (difference < 0) { int maxAdjustment = totalShrink - activeNode.totalShrink; // add the footnote separator shrink if some footnote content will be added - if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) { + if (((KnuthPageNode) activeNode).insertedFootnotes < totalFootnotesLength) { maxAdjustment += footnoteSeparatorLength.getShrink(); } if (maxAdjustment > 0) { @@ -866,7 +880,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { for (KnuthPageNode node = (KnuthPageNode) getNode(i); node != null; node = (KnuthPageNode) node.next) { - if (node.totalFootnotes < totalFootnotesLength) { + if (node.insertedFootnotes < totalFootnotesLength) { // layout remaining footnote bodies createFootnotePages(node); } @@ -876,7 +890,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { private void createFootnotePages(KnuthPageNode lastNode) { - insertedFootnotesLength = lastNode.totalFootnotes; + insertedFootnotesLength = lastNode.insertedFootnotes; footnoteListIndex = lastNode.footnoteListIndex; footnoteElementIndex = lastNode.footnoteElementIndex; int availableBPD = getLineWidth(lastNode.line); @@ -902,7 +916,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // cannot add any content: create a new node and start again KnuthPageNode node = (KnuthPageNode) createNode(lastNode.position, prevNode.line + 1, 1, - insertedFootnotesLength - prevNode.totalFootnotes, + insertedFootnotesLength - prevNode.insertedFootnotes, 0, 0, 0, 0, 0, 0, 0, prevNode); @@ -916,7 +930,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // create the last node KnuthPageNode node = (KnuthPageNode) createNode(lastNode.position, prevNode.line + 1, 1, - totalFootnotesLength - prevNode.totalFootnotes, 0, 0, + totalFootnotesLength - prevNode.insertedFootnotes, 0, 0, 0, 0, 0, 0, 0, prevNode); addNode(node.line, node); diff --git a/src/java/org/apache/fop/layoutmgr/SpacedBorderedPaddedBlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/SpacedBorderedPaddedBlockLayoutManager.java new file mode 100644 index 000000000..2ac41e96a --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/SpacedBorderedPaddedBlockLayoutManager.java @@ -0,0 +1,112 @@ +/* + * 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.layoutmgr; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fo.FObj; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.traits.MinOptMax; + +/** + * A block-stacking layout manager for an FO that supports spaces, border and padding. + */ +public abstract class SpacedBorderedPaddedBlockLayoutManager extends BlockStackingLayoutManager + implements ConditionalElementListener { + + private static final Log LOG = LogFactory.getLog(BlockLayoutManager.class); + + protected MinOptMax effSpaceBefore; + + protected MinOptMax effSpaceAfter; + + protected boolean discardBorderBefore; + protected boolean discardBorderAfter; + protected boolean discardPaddingBefore; + protected boolean discardPaddingAfter; + + public SpacedBorderedPaddedBlockLayoutManager(FObj node) { + super(node); + } + + public void notifySpace(RelSide side, MinOptMax effectiveLength) { + if (RelSide.BEFORE == side) { + if (LOG.isDebugEnabled()) { + LOG.debug(this + ": Space " + side + ", " + + this.effSpaceBefore + "-> " + effectiveLength); + } + this.effSpaceBefore = effectiveLength; + } else { + if (LOG.isDebugEnabled()) { + LOG.debug(this + ": Space " + side + ", " + + this.effSpaceAfter + "-> " + effectiveLength); + } + this.effSpaceAfter = effectiveLength; + } + } + + public void notifyBorder(RelSide side, MinOptMax effectiveLength) { + if (effectiveLength == null) { + if (RelSide.BEFORE == side) { + this.discardBorderBefore = true; + } else { + this.discardBorderAfter = true; + } + } + if (LOG.isDebugEnabled()) { + LOG.debug(this + ": Border " + side + " -> " + effectiveLength); + } + } + + public void notifyPadding(RelSide side, MinOptMax effectiveLength) { + if (effectiveLength == null) { + if (RelSide.BEFORE == side) { + this.discardPaddingBefore = true; + } else { + this.discardPaddingAfter = true; + } + } + if (LOG.isDebugEnabled()) { + LOG.debug(this + ": Padding " + side + " -> " + effectiveLength); + } + } + + @Override + public int getBaselineOffset() { + int baselineOffset = super.getBaselineOffset(); + if (effSpaceBefore != null) { + baselineOffset += effSpaceBefore.getOpt(); + } + if (!discardBorderBefore) { + baselineOffset += getCommonBorderPaddingBackground().getBorderBeforeWidth(false); + } + if (!discardPaddingBefore) { + baselineOffset += getCommonBorderPaddingBackground().getPaddingBefore(false, this); + } + return baselineOffset; + } + + /** + * Returns the {@link CommonBorderPaddingBackground} instance from the FO handled by this layout manager. + */ + protected abstract CommonBorderPaddingBackground getCommonBorderPaddingBackground(); + +} diff --git a/src/java/org/apache/fop/layoutmgr/TraitSetter.java b/src/java/org/apache/fop/layoutmgr/TraitSetter.java index 739d535ca..af40f0681 100644 --- a/src/java/org/apache/fop/layoutmgr/TraitSetter.java +++ b/src/java/org/apache/fop/layoutmgr/TraitSetter.java @@ -617,4 +617,15 @@ public final class TraitSetter { area.addTrait(Trait.PROD_ID, id); } } + + /** + * Sets the optional content group layer as a trait on the area. + * @param area the area to set the traits on + * @param layer the layer ID to set + */ + public static void setLayer(Area area, String layer) { + if (layer != null && layer.length() > 0) { + area.addTrait(Trait.LAYER, layer); + } + } } diff --git a/src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java b/src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java index c1992965c..192956abc 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java +++ b/src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java @@ -295,7 +295,7 @@ public class AlignmentContext implements Constants { * Return the dominant baseline identifier. * @return the dominant baseline identifier */ - private int getDominantBaselineIdentifier() { + public int getDominantBaselineIdentifier() { return actualBaselineTable.getDominantBaselineIdentifier(); } diff --git a/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java index b3c768987..c067b040f 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java @@ -332,6 +332,15 @@ public class ContentLayoutManager extends AbstractBaseLayoutManager return parentLM.getPSLM(); } + + public boolean hasLineAreaDescendant() { + return true; + } + + public int getBaselineOffset() { + return childLM.getBaselineOffset(); + } + // --------- Property Resolution related functions --------- // /** diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/InlineContainerLayoutManager.java new file mode 100644 index 000000000..54237a914 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/inline/InlineContainerLayoutManager.java @@ -0,0 +1,326 @@ +/* + * 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.layoutmgr.inline; + +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.apache.fop.area.Area; +import org.apache.fop.area.Trait; +import org.apache.fop.area.inline.Container; +import org.apache.fop.area.inline.InlineViewport; +import org.apache.fop.datatypes.Length; +import org.apache.fop.datatypes.LengthBase; +import org.apache.fop.datatypes.SimplePercentBaseContext; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.flow.InlineContainer; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.LengthRangeProperty; +import org.apache.fop.fo.properties.Property; +import org.apache.fop.layoutmgr.AbstractLayoutManager; +import org.apache.fop.layoutmgr.AreaAdditionUtil; +import org.apache.fop.layoutmgr.BlockLevelEventProducer; +import org.apache.fop.layoutmgr.ElementListUtils; +import org.apache.fop.layoutmgr.InlineKnuthSequence; +import org.apache.fop.layoutmgr.KnuthPossPosIter; +import org.apache.fop.layoutmgr.KnuthSequence; +import org.apache.fop.layoutmgr.LayoutContext; +import org.apache.fop.layoutmgr.LayoutManager; +import org.apache.fop.layoutmgr.ListElement; +import org.apache.fop.layoutmgr.NonLeafPosition; +import org.apache.fop.layoutmgr.Position; +import org.apache.fop.layoutmgr.PositionIterator; +import org.apache.fop.layoutmgr.SpaceResolver; +import org.apache.fop.layoutmgr.TraitSetter; + +/** + * This creates a single inline container area after + * laying out the child block areas. All footnotes, floats + * and id areas are maintained for later retrieval. + */ +public class InlineContainerLayoutManager extends AbstractLayoutManager implements InlineLevelLayoutManager { + + private CommonBorderPaddingBackground borderProps; + private int contentAreaIPD; + private int contentAreaBPD; + + private List<ListElement> childElements; + private int ipdOverflow; + private AlignmentContext alignmentContext; + private InlineViewport currentViewport; + private Container referenceArea; + + public InlineContainerLayoutManager(InlineContainer node) { + super(node); + setGeneratesReferenceArea(true); + } + + @Override + public void initialize() { + InlineContainer node = (InlineContainer) fobj; + borderProps = node.getCommonBorderPaddingBackground(); + } + + private InlineContainer getInlineContainer() { + assert fobj instanceof InlineContainer; + return (InlineContainer) fobj; + } + + @Override + public List<KnuthSequence> getNextKnuthElements(LayoutContext context, int alignment) { + determineIPD(context); + childElements = getChildKnuthElements(context, alignment); + determineBPD(); + alignmentContext = makeAlignmentContext(context); + Position position = new Position(this, 0); + KnuthSequence knuthSequence = new InlineKnuthSequence(); + knuthSequence.add(new KnuthInlineBox(contentAreaIPD, alignmentContext, position, false)); + List<KnuthSequence> knuthElements = new ArrayList<KnuthSequence>(1); + knuthElements.add(knuthSequence); + setFinished(true); + return knuthElements; + } + + private void determineIPD(LayoutContext layoutContext) { + LengthRangeProperty ipd = getInlineContainer().getInlineProgressionDimension(); + Property optimum = ipd.getOptimum(this); + if (optimum.isAuto()) { + contentAreaIPD = layoutContext.getRefIPD(); + InlineLevelEventProducer eventProducer = InlineLevelEventProducer.Provider.get( + fobj.getUserAgent().getEventBroadcaster()); + eventProducer.inlineContainerAutoIPDNotSupported(this, contentAreaIPD / 1000f); + } else { + contentAreaIPD = optimum.getLength().getValue(this); + } + } + + private List<ListElement> getChildKnuthElements(LayoutContext layoutContext, int alignment) { + List<ListElement> allChildElements = new LinkedList<ListElement>(); + LayoutManager childLM; + while ((childLM = getChildLM()) != null) { + LayoutContext childLC = LayoutContext.offspringOf(layoutContext); + childLC.setRefIPD(contentAreaIPD); + @SuppressWarnings("unchecked") + List<ListElement> childElements = childLM.getNextKnuthElements(childLC, alignment); + allChildElements.addAll(childElements); + } + handleIPDOverflow(); + wrapPositions(allChildElements); + SpaceResolver.resolveElementList(allChildElements); + SpaceResolver.performConditionalsNotification(allChildElements, 0, allChildElements.size() - 1, -1); + return allChildElements; + } + + private void determineBPD() { + LengthRangeProperty bpd = getInlineContainer().getBlockProgressionDimension(); + Property optimum = bpd.getOptimum(this); + int actualBPD = ElementListUtils.calcContentLength(childElements); + if (optimum.isAuto()) { + contentAreaBPD = actualBPD; + } else { + double bpdValue = optimum.getLength().getNumericValue(this); + if (bpdValue < 0) { + contentAreaBPD = actualBPD; + } else { + contentAreaBPD = (int) Math.round(bpdValue); + if (contentAreaBPD < actualBPD) { + BlockLevelEventProducer eventProducer = getBlockLevelEventProducer(); + eventProducer.viewportBPDOverflow(this, fobj.getName(), + actualBPD - contentAreaBPD, needClip(), canRecoverFromOverflow(), + fobj.getLocator()); + } + } + } + } + + protected AlignmentContext makeAlignmentContext(LayoutContext context) { + InlineContainer ic = (InlineContainer) fobj; + AlignmentContext ac = new AlignmentContext(contentAreaBPD, + ic.getAlignmentAdjust(), ic.getAlignmentBaseline(), + ic.getBaselineShift(), ic.getDominantBaseline(), + context.getAlignmentContext()); + int baselineOffset = getAlignmentPoint(ac.getDominantBaselineIdentifier()); + ac.resizeLine(contentAreaBPD, baselineOffset); + return ac; + } + + private void handleIPDOverflow() { + if (ipdOverflow > 0) { + BlockLevelEventProducer eventProducer = getBlockLevelEventProducer(); + eventProducer.viewportIPDOverflow(this, fobj.getName(), + ipdOverflow, needClip(), canRecoverFromOverflow(), + fobj.getLocator()); + } + } + + private void wrapPositions(List<ListElement> elements) { + for (ListElement element : elements) { + Position position = new NonLeafPosition(this, element.getPosition()); + notifyPos(position); + element.setPosition(position); + } + } + + private BlockLevelEventProducer getBlockLevelEventProducer() { + return BlockLevelEventProducer.Provider.get(fobj.getUserAgent().getEventBroadcaster()); + } + + private boolean canRecoverFromOverflow() { + return getInlineContainer().getOverflow() != EN_ERROR_IF_OVERFLOW; + } + + private int getAlignmentPoint(int dominantBaseline) { + Length alignmentAdjust = getInlineContainer().getAlignmentAdjust(); + int baseline = alignmentAdjust.getEnum(); + if (baseline == Constants.EN_AUTO) { + return getInlineContainerBaselineOffset(getInlineContainer().getAlignmentBaseline()); + } else if (baseline == Constants.EN_BASELINE) { + return getInlineContainerBaselineOffset(dominantBaseline); + } else if (baseline != 0) { + return getInlineContainerBaselineOffset(baseline); + } else { + int baselineOffset = getInlineContainerBaselineOffset(dominantBaseline); + int lineHeight = getInlineContainer().getLineHeight().getOptimum(this).getLength().getValue(this); + int adjust = alignmentAdjust.getValue( + new SimplePercentBaseContext(null, LengthBase.ALIGNMENT_ADJUST, lineHeight)); + return baselineOffset + adjust; + } + } + + private int getInlineContainerBaselineOffset(int property) { + switch (property) { + case Constants.EN_BEFORE_EDGE: + case Constants.EN_TEXT_BEFORE_EDGE: + return 0; + case Constants.EN_AFTER_EDGE: + case Constants.EN_TEXT_AFTER_EDGE: + return contentAreaBPD; + case Constants.EN_MIDDLE: + case Constants.EN_CENTRAL: + case Constants.EN_MATHEMATICAL: + return contentAreaBPD / 2; + case Constants.EN_IDEOGRAPHIC: + return contentAreaBPD * 7 / 10; + case Constants.EN_ALPHABETIC: + return contentAreaBPD * 6 / 10; + case Constants.EN_HANGING: + return contentAreaBPD * 2 / 10; + case Constants.EN_AUTO: + case Constants.EN_BASELINE: + return hasLineAreaDescendant() ? getBaselineOffset() : contentAreaBPD; + default: + throw new AssertionError("Unknown baseline value: " + property); + } + } + + @Override + public void addAreas(PositionIterator posIter, LayoutContext context) { + Position inlineContainerPosition = null; + while (posIter.hasNext()) { + /* + * Should iterate only once, but hasNext must be called twice for its + * side-effects to apply and the iterator to switch to the next LM. + */ + assert inlineContainerPosition == null; + inlineContainerPosition = posIter.next(); + assert inlineContainerPosition.getLM() == this; + } + assert inlineContainerPosition != null; + KnuthPossPosIter childPosIter = new KnuthPossPosIter(childElements); + AreaAdditionUtil.addAreas(this, childPosIter, context); + } + + @Override + public Area getParentArea(Area childArea) { + if (referenceArea == null) { + referenceArea = new Container(); + referenceArea.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); + TraitSetter.setProducerID(referenceArea, fobj.getId()); + referenceArea.setIPD(contentAreaIPD); + currentViewport = new InlineViewport(referenceArea); + currentViewport.addTrait(Trait.IS_VIEWPORT_AREA, Boolean.TRUE); + TraitSetter.setProducerID(currentViewport, fobj.getId()); + currentViewport.setBlockProgressionOffset(alignmentContext.getOffset()); + currentViewport.setIPD(getContentAreaIPD()); + currentViewport.setBPD(getContentAreaBPD()); + TraitSetter.addBackground(currentViewport, borderProps, this); + currentViewport.setClip(needClip()); + currentViewport.setContentPosition( + new Rectangle2D.Float(0, 0, getContentAreaIPD(), getContentAreaBPD())); + getParent().addChildArea(currentViewport); + } + return referenceArea; + } + + @Override + public int getContentAreaIPD() { + return contentAreaIPD; + } + + @Override + public int getContentAreaBPD() { + return contentAreaBPD; + } + + @Override + public void addChildArea(Area childArea) { + referenceArea.addChildArea(childArea); + } + + private boolean needClip() { + int overflow = getInlineContainer().getOverflow(); + return (overflow == EN_HIDDEN || overflow == EN_ERROR_IF_OVERFLOW); + } + + public boolean handleOverflow(int milliPoints) { + ipdOverflow = Math.max(ipdOverflow, milliPoints); + return true; + } + + public List addALetterSpaceTo(List oldList) { + return oldList; + } + + public List addALetterSpaceTo(List oldList, int depth) { + return oldList; + } + + public String getWordChars(Position pos) { + return ""; + } + + public void hyphenate(Position pos, HyphContext hyphContext) { + } + + public boolean applyChanges(List oldList) { + return false; + } + + public boolean applyChanges(List oldList, int depth) { + return false; + } + + public List getChangedKnuthElements(List oldList, int alignment, int depth) { + return oldList; + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java index dd80db1d1..61dcf45c9 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java @@ -213,6 +213,7 @@ public class InlineLayoutManager extends InlineStackingLayoutManager { } if (fobj instanceof Inline || fobj instanceof BasicLink) { TraitSetter.setProducerID(area, fobj.getId()); + TraitSetter.setLayer(area, fobj.getLayer()); } return area; } diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java b/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java index 15284ae0a..332e14935 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java +++ b/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java @@ -67,4 +67,13 @@ public interface InlineLevelEventProducer extends EventProducer { */ void lineOverflows(Object source, String elementName, int line, int overflowLength, Locator loc); + /** + * Auto IPD on inline-container is not supported. + * + * @param source the event source + * @param fallback the value in points that will be used as a fallback + * @event.severity WARN + */ + void inlineContainerAutoIPDNotSupported(Object source, float fallback); + } diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.xml b/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.xml index 66d352eb7..8d699f6bc 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.xml +++ b/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.xml @@ -20,4 +20,5 @@ <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message> <message key="leaderWithoutContent">fo:leader is set to "use-content" but has no content.{{locator}}</message> <message key="lineOverflows">The contents of {elementName} line {line} exceed the available area in the inline-progression direction by {overflowLength,choice,50000#{overflowLength} millipoints|50000<more than 50 points}.{{locator}}</message> + <message key="inlineContainerAutoIPDNotSupported">A value of "auto" for the inline-progression-dimension property on fo:inline-container is not supported. Falling back to {fallback}pt.{{locator}}</message> </catalogue> diff --git a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java index b3987a075..25d8c0872 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java @@ -160,6 +160,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager private final int follow; private AlignmentContext alignmentContext; + private int baselineOffset = -1; + private List<KnuthSequence> knuthParagraphs; private LineLayoutPossibilities lineLayouts; @@ -556,7 +558,6 @@ public class LineLayoutManager extends InlineStackingLayoutManager private int constantLineHeight = 12000; - /** * Create a new Line Layout Manager. * This is used by the block layout manager to create @@ -939,7 +940,11 @@ public class LineLayoutManager extends InlineStackingLayoutManager while (listIter.hasNext()) { ListElement tempElement; tempElement = (ListElement) listIter.next(); - if (tempElement.getLayoutManager() != this) { + LayoutManager lm = tempElement.getLayoutManager(); + if (baselineOffset < 0 && lm != null && lm.hasLineAreaDescendant()) { + baselineOffset = lm.getBaselineOffset(); + } + if (lm != this) { tempElement.setPosition(notifyPos(new NonLeafPosition(this, tempElement.getPosition()))); } @@ -987,6 +992,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager } startIndex = endIndex + 1; LineBreakPosition lbp = (LineBreakPosition) llPoss.getChosenPosition(i); + if (baselineOffset < 0) { + baselineOffset = lbp.spaceBefore + lbp.baseline; + } returnList.add(new KnuthBlockBox( lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter, footnoteList, lbp, false)); @@ -1424,6 +1432,16 @@ public class LineLayoutManager extends InlineStackingLayoutManager } } + @Override + public boolean hasLineAreaDescendant() { + return true; + } + + @Override + public int getBaselineOffset() { + return baselineOffset; + } + /** * Add the areas with the break points. * diff --git a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java index 61d8a891d..062a67b38 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java @@ -28,16 +28,15 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.fo.flow.ListBlock; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; -import org.apache.fop.layoutmgr.BlockStackingLayoutManager; -import org.apache.fop.layoutmgr.ConditionalElementListener; import org.apache.fop.layoutmgr.ElementListUtils; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.LayoutManager; import org.apache.fop.layoutmgr.NonLeafPosition; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.RelSide; +import org.apache.fop.layoutmgr.SpacedBorderedPaddedBlockLayoutManager; import org.apache.fop.layoutmgr.TraitSetter; import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; @@ -47,21 +46,13 @@ import org.apache.fop.traits.SpaceVal; * A list block contains list items which are stacked within * the list block area.. */ -public class ListBlockLayoutManager extends BlockStackingLayoutManager - implements ConditionalElementListener { +public class ListBlockLayoutManager extends SpacedBorderedPaddedBlockLayoutManager { /** logging instance */ private static Log log = LogFactory.getLog(ListBlockLayoutManager.class); private Block curBlockArea; - private boolean discardBorderBefore; - private boolean discardBorderAfter; - private boolean discardPaddingBefore; - private boolean discardPaddingAfter; - private MinOptMax effSpaceBefore; - private MinOptMax effSpaceAfter; - /** * Create a new list block layout manager. * @param node list-block to create the layout manager for @@ -70,6 +61,11 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager super(node); } + @Override + protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() { + return getListBlockFO().getCommonBorderPaddingBackground(); + } + /** * Convenience method. * @return the ListBlock node @@ -242,6 +238,8 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager int contentIPD = referenceIPD - getIPIndents(); curBlockArea.setIPD(contentIPD); + curBlockArea.setBidiLevel(getListBlockFO().getBidiLevel()); + setCurrentArea(curBlockArea); } return curBlockArea; @@ -277,50 +275,5 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager return getListBlockFO().getKeepWithNext(); } - /** {@inheritDoc} */ - public void notifySpace(RelSide side, MinOptMax effectiveLength) { - if (RelSide.BEFORE == side) { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceBefore + "-> " + effectiveLength); - } - this.effSpaceBefore = effectiveLength; - } else { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceAfter + "-> " + effectiveLength); - } - this.effSpaceAfter = effectiveLength; - } - } - - /** {@inheritDoc} */ - public void notifyBorder(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardBorderBefore = true; - } else { - this.discardBorderAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Border " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ - public void notifyPadding(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardPaddingBefore = true; - } else { - this.discardPaddingAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Padding " + side + " -> " + effectiveLength); - } - } - } diff --git a/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java index f017da381..b16c9dfc4 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java @@ -181,6 +181,7 @@ public class ListItemContentLayoutManager extends BlockStackingLayoutManager imp //TODO: Check - itemIPD never set? curBlockArea.setIPD(itemIPD); //curBlockArea.setHeight(); + curBlockArea.setBidiLevel(getPartFO().getBidiLevel()); TraitSetter.setProducerID(curBlockArea, getPartFO().getId()); diff --git a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java index 083e4ee1b..773506632 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java @@ -32,12 +32,11 @@ import org.apache.fop.area.Block; import org.apache.fop.fo.flow.ListItem; import org.apache.fop.fo.flow.ListItemBody; import org.apache.fop.fo.flow.ListItemLabel; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; -import org.apache.fop.layoutmgr.BlockStackingLayoutManager; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.BreakOpportunity; import org.apache.fop.layoutmgr.BreakOpportunityHelper; -import org.apache.fop.layoutmgr.ConditionalElementListener; import org.apache.fop.layoutmgr.ElementListObserver; import org.apache.fop.layoutmgr.ElementListUtils; import org.apache.fop.layoutmgr.FootnoteBodyLayoutManager; @@ -53,10 +52,9 @@ import org.apache.fop.layoutmgr.ListElement; import org.apache.fop.layoutmgr.NonLeafPosition; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.RelSide; import org.apache.fop.layoutmgr.SpaceResolver; +import org.apache.fop.layoutmgr.SpacedBorderedPaddedBlockLayoutManager; import org.apache.fop.layoutmgr.TraitSetter; -import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; import org.apache.fop.util.BreakUtil; @@ -64,8 +62,8 @@ import org.apache.fop.util.BreakUtil; * LayoutManager for a list-item FO. * The list item contains a list item label and a list item body. */ -public class ListItemLayoutManager extends BlockStackingLayoutManager implements ConditionalElementListener, - BreakOpportunity { +public class ListItemLayoutManager extends SpacedBorderedPaddedBlockLayoutManager + implements BreakOpportunity { /** logging instance */ private static Log log = LogFactory.getLog(ListItemLayoutManager.class); @@ -78,13 +76,6 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager implements private List<ListElement> labelList = null; private List<ListElement> bodyList = null; - private boolean discardBorderBefore; - private boolean discardBorderAfter; - private boolean discardPaddingBefore; - private boolean discardPaddingAfter; - private MinOptMax effSpaceBefore; - private MinOptMax effSpaceAfter; - private Keep keepWithNextPendingOnLabel; private Keep keepWithNextPendingOnBody; @@ -145,6 +136,11 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager implements setBody(node.getBody()); } + @Override + protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() { + return getListItemFO().getCommonBorderPaddingBackground(); + } + /** * Convenience method. * @return the ListBlock node @@ -475,6 +471,23 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager implements return returnedList; } + + @Override + public boolean hasLineAreaDescendant() { + return label.hasLineAreaDescendant() || body.hasLineAreaDescendant(); + } + + @Override + public int getBaselineOffset() { + if (label.hasLineAreaDescendant()) { + return label.getBaselineOffset(); + } else if (body.hasLineAreaDescendant()) { + return body.getBaselineOffset(); + } else { + throw newNoLineAreaDescendantException(); + } + } + /** * Add the areas for the break points. * @@ -615,6 +628,8 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager implements int contentIPD = referenceIPD - getIPIndents(); curBlockArea.setIPD(contentIPD); + curBlockArea.setBidiLevel(fo.getBidiLevel()); + setCurrentArea(curBlockArea); } return curBlockArea; @@ -653,51 +668,6 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager implements } /** {@inheritDoc} */ - public void notifySpace(RelSide side, MinOptMax effectiveLength) { - if (RelSide.BEFORE == side) { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceBefore + "-> " + effectiveLength); - } - this.effSpaceBefore = effectiveLength; - } else { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceAfter + "-> " + effectiveLength); - } - this.effSpaceAfter = effectiveLength; - } - } - - /** {@inheritDoc} */ - public void notifyBorder(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardBorderBefore = true; - } else { - this.discardBorderAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Border " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ - public void notifyPadding(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardPaddingBefore = true; - } else { - this.discardPaddingAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Padding " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ @Override public void reset() { super.reset(); diff --git a/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java index 0582a0283..aaa896ce3 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java @@ -184,6 +184,7 @@ public class TableAndCaptionLayoutManager extends BlockStackingLayoutManager { Area parentArea = parentLayoutManager.getParentArea(curBlockArea); int referenceIPD = parentArea.getIPD(); curBlockArea.setIPD(referenceIPD); + curBlockArea.setBidiLevel(getTableAndCaptionFO().getBidiLevel()); // Get reference IPD from parentArea setCurrentArea(curBlockArea); // ??? for generic operations } diff --git a/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java index 66f7ad9f2..8823c0fae 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java @@ -180,6 +180,7 @@ public class TableCaptionLayoutManager extends BlockStackingLayoutManager { Area parentArea = parentLayoutManager.getParentArea(curBlockArea); int referenceIPD = parentArea.getIPD(); curBlockArea.setIPD(referenceIPD); + curBlockArea.setBidiLevel(getTableCaptionFO().getBidiLevel()); // Get reference IPD from parentArea setCurrentArea(curBlockArea); // ??? for generic operations } diff --git a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java index c8f2cea85..b2851c1b0 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java @@ -117,6 +117,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager */ public TableCellLayoutManager(TableCell node, PrimaryGridUnit pgu) { super(node); + setGeneratesBlockArea(true); this.primaryGridUnit = pgu; this.isDescendantOfTableHeader = node.getParent().getParent() instanceof TableHeader || node.getParent() instanceof TableHeader; @@ -441,12 +442,14 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager Block[][] blocks = new Block[getTableCell().getNumberRowsSpanned()][getTableCell() .getNumberColumnsSpanned()]; GridUnit[] gridUnits = (GridUnit[]) primaryGridUnit.getRows().get(startRow); + int level = getTableCell().getBidiLevelRecursive(); for (int x = 0; x < getTableCell().getNumberColumnsSpanned(); x++) { GridUnit gu = gridUnits[x]; BorderInfo border = gu.getBorderBefore(borderBeforeWhich); int borderWidth = border.getRetainedWidth() / 2; if (borderWidth > 0) { - addBorder(blocks, startRow, x, Trait.BORDER_BEFORE, border, firstOnPage); + addBorder(blocks, startRow, x, Trait.BORDER_BEFORE, border, + firstOnPage, level); adjustYOffset(blocks[startRow][x], -borderWidth); adjustBPD(blocks[startRow][x], -borderWidth); } @@ -457,7 +460,8 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager BorderInfo border = gu.getBorderAfter(borderAfterWhich); int borderWidth = border.getRetainedWidth() / 2; if (borderWidth > 0) { - addBorder(blocks, endRow, x, Trait.BORDER_AFTER, border, lastOnPage); + addBorder(blocks, endRow, x, Trait.BORDER_AFTER, border, + lastOnPage, level); adjustBPD(blocks[endRow][x], -borderWidth); } } @@ -466,7 +470,8 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager BorderInfo border = gridUnits[0].getBorderStart(); int borderWidth = border.getRetainedWidth() / 2; if (borderWidth > 0) { - addBorder(blocks, y, 0, Trait.BORDER_START, border, inFirstColumn); + addBorder(blocks, y, 0, Trait.BORDER_START, border, + inFirstColumn, level); adjustXOffset(blocks[y][0], borderWidth); adjustIPD(blocks[y][0], -borderWidth); } @@ -474,7 +479,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager borderWidth = border.getRetainedWidth() / 2; if (borderWidth > 0) { addBorder(blocks, y, gridUnits.length - 1, Trait.BORDER_END, border, - inLastColumn); + inLastColumn, level); adjustIPD(blocks[y][gridUnits.length - 1], -borderWidth); } } @@ -511,10 +516,12 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager if (getTableCell().getDisplayAlign() == EN_CENTER) { Block space = new Block(); space.setBPD((cellBPD - usedBPD) / 2); + space.setBidiLevel(getTableCell().getBidiLevelRecursive()); curBlockArea.addBlock(space); } else if (getTableCell().getDisplayAlign() == EN_AFTER) { Block space = new Block(); space.setBPD(cellBPD - usedBPD); + space.setBidiLevel(getTableCell().getBidiLevelRecursive()); curBlockArea.addBlock(space); } } @@ -590,11 +597,12 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager } private void addBorder(Block[][] blocks, int i, int j, Integer side, BorderInfo border, - boolean outer) { + boolean outer, int level) { if (blocks[i][j] == null) { blocks[i][j] = new Block(); blocks[i][j].addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE); blocks[i][j].setPositioning(Block.ABSOLUTE); + blocks[i][j].setBidiLevel(level); } blocks[i][j].addTrait(side, BorderProps.makeRectangular(border.getStyle(), border.getRetainedWidth(), border.getColor(), @@ -629,6 +637,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager block.setBPD(bpd); block.setXOffset(xoffset + startIndent - paddingStart); block.setYOffset(yoffset + borderBeforeWidth); + block.setBidiLevel(getTableCell().getBidiLevelRecursive()); return block; } @@ -654,6 +663,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager curBlockArea.setXOffset(xoffset + startIndent); curBlockArea.setYOffset(yoffset); curBlockArea.setIPD(cellIPD); + curBlockArea.setBidiLevel(getTableCell().getBidiLevelRecursive()); /*Area parentArea =*/ parentLayoutManager.getParentArea(curBlockArea); // Get reference IPD from parentArea diff --git a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java index 7f1754064..afb6547c0 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -39,12 +39,11 @@ import org.apache.fop.fo.flow.Markers; import org.apache.fop.fo.flow.RetrieveTableMarker; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.layoutmgr.BlockLevelEventProducer; -import org.apache.fop.layoutmgr.BlockStackingLayoutManager; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.BreakOpportunity; -import org.apache.fop.layoutmgr.ConditionalElementListener; import org.apache.fop.layoutmgr.KnuthElement; import org.apache.fop.layoutmgr.KnuthGlue; import org.apache.fop.layoutmgr.LayoutContext; @@ -52,7 +51,7 @@ import org.apache.fop.layoutmgr.LeafPosition; import org.apache.fop.layoutmgr.ListElement; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.layoutmgr.RelSide; +import org.apache.fop.layoutmgr.SpacedBorderedPaddedBlockLayoutManager; import org.apache.fop.layoutmgr.TraitSetter; import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; @@ -66,8 +65,8 @@ import org.apache.fop.util.BreakUtil; * The table then creates areas for the columns, bodies and rows * the render background. */ -public class TableLayoutManager extends BlockStackingLayoutManager - implements ConditionalElementListener, BreakOpportunity { +public class TableLayoutManager extends SpacedBorderedPaddedBlockLayoutManager + implements BreakOpportunity { /** * logging instance @@ -82,13 +81,6 @@ public class TableLayoutManager extends BlockStackingLayoutManager private double tableUnit; private boolean autoLayout = true; - private boolean discardBorderBefore; - private boolean discardBorderAfter; - private boolean discardPaddingBefore; - private boolean discardPaddingAfter; - private MinOptMax effSpaceBefore; - private MinOptMax effSpaceAfter; - private int halfBorderSeparationBPD; private int halfBorderSeparationIPD; @@ -132,6 +124,13 @@ public class TableLayoutManager extends BlockStackingLayoutManager this.columns = new ColumnSetup(node); } + + @Override + protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() { + return getTable().getCommonBorderPaddingBackground(); + } + + /** @return the table FO */ public Table getTable() { return (Table)this.fobj; @@ -522,51 +521,6 @@ public class TableLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public void notifySpace(RelSide side, MinOptMax effectiveLength) { - if (RelSide.BEFORE == side) { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceBefore + "-> " + effectiveLength); - } - this.effSpaceBefore = effectiveLength; - } else { - if (log.isDebugEnabled()) { - log.debug(this + ": Space " + side + ", " - + this.effSpaceAfter + "-> " + effectiveLength); - } - this.effSpaceAfter = effectiveLength; - } - } - - /** {@inheritDoc} */ - public void notifyBorder(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardBorderBefore = true; - } else { - this.discardBorderAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Border " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ - public void notifyPadding(RelSide side, MinOptMax effectiveLength) { - if (effectiveLength == null) { - if (RelSide.BEFORE == side) { - this.discardPaddingBefore = true; - } else { - this.discardPaddingAfter = true; - } - } - if (log.isDebugEnabled()) { - log.debug(this + ": Padding " + side + " -> " + effectiveLength); - } - } - - /** {@inheritDoc} */ public void reset() { super.reset(); curBlockArea = null; diff --git a/src/java/org/apache/fop/pdf/AbstractPDFStream.java b/src/java/org/apache/fop/pdf/AbstractPDFStream.java index 41eed4885..13bd1bda1 100644 --- a/src/java/org/apache/fop/pdf/AbstractPDFStream.java +++ b/src/java/org/apache/fop/pdf/AbstractPDFStream.java @@ -143,7 +143,7 @@ public abstract class AbstractPDFStream extends PDFObject { */ protected int outputStreamData(StreamCache encodedStream, OutputStream out) throws IOException { int length = 0; - byte[] p = encode("stream\n"); + byte[] p = encode("\nstream\n"); out.write(p); length += p.length; @@ -186,7 +186,7 @@ public abstract class AbstractPDFStream extends PDFObject { throws IOException { int bytesWritten = 0; //Stream header - byte[] buf = encode("stream\n"); + byte[] buf = encode("\nstream\n"); out.write(buf); bytesWritten += buf.length; diff --git a/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java b/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java new file mode 100644 index 000000000..53f0b36b4 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java @@ -0,0 +1,74 @@ +/* + * 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.pdf; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * PDFStream for embeddable OpenType CFF fonts. + */ +public class PDFCFFStreamType0C extends AbstractPDFFontStream { + + private byte[] cffData; + private boolean fullEmbed; + + /** + * Main constructor + * @param fullEmbed Determines whether the font is fully embedded + */ + public PDFCFFStreamType0C(boolean fullEmbed) { + super(); + this.fullEmbed = fullEmbed; + } + + protected int getSizeHint() throws IOException { + if (this.cffData != null) { + return cffData.length; + } else { + return 0; //no hint available + } + } + + /** {@inheritDoc} */ + protected void outputRawStreamData(OutputStream out) throws IOException { + out.write(this.cffData); + } + + /** {@inheritDoc} */ + protected void populateStreamDict(Object lengthEntry) { + String type = (fullEmbed) ? "OpenType" : "CIDFontType0C"; + put("Subtype", new PDFName(type)); + super.populateStreamDict(lengthEntry); + } + + /** + * Sets the CFF font data. + * @param data the font payload + * @param size size of the payload + * @throws IOException in case of an I/O problem + */ + public void setData(byte[] data, int size) throws IOException { + this.cffData = new byte[size]; + System.arraycopy(data, 0, this.cffData, 0, size); + } + +} + diff --git a/src/java/org/apache/fop/pdf/PDFDictionary.java b/src/java/org/apache/fop/pdf/PDFDictionary.java index 6bacd31a3..ae0b950fd 100644 --- a/src/java/org/apache/fop/pdf/PDFDictionary.java +++ b/src/java/org/apache/fop/pdf/PDFDictionary.java @@ -131,7 +131,7 @@ public class PDFDictionary extends PDFObject { } else { textBuffer.append('\n'); } - textBuffer.append(">>\n"); + textBuffer.append(">>"); } } diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index ff9f61201..bcd54fcb9 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -155,6 +155,12 @@ public class PDFDocument { private List<PDFLaunch> launches = new ArrayList<PDFLaunch>(); + private List<PDFLayer> layers; + + private List<PDFNavigator> navigators; + + private List<PDFNavigatorAction> navigatorActions; + private PDFFactory factory; private FileIDGenerator fileIDGenerator; @@ -477,6 +483,24 @@ public class PDFDocument { if (obj instanceof PDFGoToRemote) { this.gotoremotes.add((PDFGoToRemote) obj); } + if (obj instanceof PDFLayer) { + if (this.layers == null) { + this.layers = new ArrayList<PDFLayer>(); + } + this.layers.add((PDFLayer) obj); + } + if (obj instanceof PDFNavigator) { + if (this.navigators == null) { + this.navigators = new ArrayList<PDFNavigator>(); + } + this.navigators.add((PDFNavigator) obj); + } + if (obj instanceof PDFNavigatorAction) { + if (this.navigatorActions == null) { + this.navigatorActions = new ArrayList<PDFNavigatorAction>(); + } + this.navigatorActions.add((PDFNavigatorAction) obj); + } } /** @@ -890,6 +914,34 @@ public class PDFDocument { } /** + * + */ + public PDFReference resolveExtensionReference(String id) { + if (layers != null) { + for (PDFLayer layer : layers) { + if (layer.hasId(id)) { + return layer.makeReference(); + } + } + } + if (navigators != null) { + for (PDFNavigator navigator : navigators) { + if (navigator.hasId(id)) { + return navigator.makeReference(); + } + } + } + if (navigatorActions != null) { + for (PDFNavigatorAction action : navigatorActions) { + if (action.hasId(id)) { + return action.makeReference(); + } + } + } + return null; + } + + /** * Writes out the entire document * * @param stream the OutputStream to output the document to @@ -1009,7 +1061,7 @@ public class PDFDocument { streamIndirectObjects(trailerObjects, stream); TrailerDictionary trailerDictionary = createTrailerDictionary(); long startxref = trailerOutputHelper.outputCrossReferenceObject(stream, trailerDictionary); - String trailer = "startxref\n" + startxref + "\n%%EOF\n"; + String trailer = "\nstartxref\n" + startxref + "\n%%EOF\n"; stream.write(encode(trailer)); } diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 1756f1d56..070630274 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -56,6 +56,8 @@ import org.apache.fop.fonts.SingleByteEncoding; import org.apache.fop.fonts.SingleByteFont; import org.apache.fop.fonts.Typeface; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; +import org.apache.fop.fonts.truetype.OTFSubSetFile; import org.apache.fop.fonts.truetype.TTFSubSetFile; import org.apache.fop.fonts.type1.PFBData; import org.apache.fop.fonts.type1.PFBParser; @@ -308,12 +310,7 @@ public class PDFFactory { theFunctionDataStream, theFilter); - PDFFunction oldfunc = getDocument().findFunction(function); - if (oldfunc == null) { - getDocument().registerObject(function); - } else { - function = oldfunc; - } + function = registerFunction(function); return (function); } @@ -350,12 +347,7 @@ public class PDFFactory { PDFFunction function = new PDFFunction(theFunctionType, theDomain, theRange, theCZero, theCOne, theInterpolationExponentN); - PDFFunction oldfunc = getDocument().findFunction(function); - if (oldfunc == null) { - getDocument().registerObject(function); - } else { - function = oldfunc; - } + function = registerFunction(function); return (function); } @@ -405,12 +397,7 @@ public class PDFFactory { theRange, theFunctions, theBounds, theEncode); - PDFFunction oldfunc = getDocument().findFunction(function); - if (oldfunc == null) { - getDocument().registerObject(function); - } else { - function = oldfunc; - } + function = registerFunction(function); return (function); } @@ -432,14 +419,23 @@ public class PDFFactory { theRange, theFunctionDataStream); + function = registerFunction(function); + return (function); + + } + + /** + * Registers a function against the document + * @param function The function to register + */ + public PDFFunction registerFunction(PDFFunction function) { PDFFunction oldfunc = getDocument().findFunction(function); if (oldfunc == null) { getDocument().registerObject(function); } else { function = oldfunc; } - return (function); - + return function; } /* ========================= shadings ================================== */ @@ -479,20 +475,7 @@ public class PDFFactory { theBBox, theAntiAlias, theDomain, theMatrix, theFunction); - PDFShading oldshad = getDocument().findShading(shading); - if (oldshad == null) { - getDocument().registerObject(shading); - } else { - shading = oldshad; - } - - // add this shading to resources - if (res != null) { - res.getPDFResources().addShading(shading); - } else { - getDocument().getResources().addShading(shading); - } - + shading = registerShading(res, shading); return (shading); } @@ -532,18 +515,7 @@ public class PDFFactory { theDomain, theFunction, theExtend); - PDFShading oldshad = getDocument().findShading(shading); - if (oldshad == null) { - getDocument().registerObject(shading); - } else { - shading = oldshad; - } - - if (res != null) { - res.getPDFResources().addShading(shading); - } else { - getDocument().getResources().addShading(shading); - } + shading = registerShading(res, shading); return (shading); } @@ -589,18 +561,7 @@ public class PDFFactory { theBitsPerFlag, theDecode, theFunction); - PDFShading oldshad = getDocument().findShading(shading); - if (oldshad == null) { - getDocument().registerObject(shading); - } else { - shading = oldshad; - } - - if (res != null) { - res.getPDFResources().addShading(shading); - } else { - getDocument().getResources().addShading(shading); - } + shading = registerShading(res, shading); return (shading); } @@ -643,6 +604,17 @@ public class PDFFactory { theBitsPerComponent, theDecode, theVerticesPerRow, theFunction); + shading = registerShading(res, shading); + + return (shading); + } + + /** + * Registers a shading object against the document + * @param res The PDF resource context + * @param shading The shading object to be registered + */ + public PDFShading registerShading(PDFResourceContext res, PDFShading shading) { PDFShading oldshad = getDocument().findShading(shading); if (oldshad == null) { getDocument().registerObject(shading); @@ -650,13 +622,13 @@ public class PDFFactory { shading = oldshad; } + // add this shading to resources if (res != null) { res.getPDFResources().addShading(shading); } else { getDocument().getResources().addShading(shading); } - - return (shading); + return shading; } /* ========================= patterns ================================== */ @@ -705,6 +677,22 @@ public class PDFFactory { return (pattern); } + public PDFPattern registerPattern(PDFResourceContext res, PDFPattern pattern) { + PDFPattern oldpatt = getDocument().findPattern(pattern); + if (oldpatt == null) { + getDocument().registerObject(pattern); + } else { + pattern = oldpatt; + } + + if (res != null) { + res.getPDFResources().addPattern(pattern); + } else { + getDocument().getResources().addPattern(pattern); + } + return pattern; + } + /** * Make a smooth shading pattern * @@ -1387,15 +1375,15 @@ public class PDFFactory { int firstChar = singleByteFont.getFirstChar(); int lastChar = singleByteFont.getLastChar(); nonBase14.setWidthMetrics(firstChar, - lastChar, - new PDFArray(null, metrics.getWidths())); + lastChar, + new PDFArray(null, metrics.getWidths())); //Handle encoding SingleByteEncoding mapping = singleByteFont.getEncoding(); if (singleByteFont.isSymbolicFont()) { //no encoding, use the font's encoding if (forceToUnicode) { - generateToUnicodeCmap(nonBase14, mapping); + generateToUnicodeCmap(nonBase14, mapping); } } else if (PDFEncoding.isPredefinedEncoding(mapping.getName())) { font.setEncoding(mapping.getName()); @@ -1403,7 +1391,7 @@ public class PDFFactory { //believed. } else { Object pdfEncoding = createPDFEncoding(mapping, - singleByteFont.getFontName()); + singleByteFont.getFontName()); if (pdfEncoding instanceof PDFEncoding) { font.setEncoding((PDFEncoding)pdfEncoding); } else { @@ -1518,7 +1506,8 @@ public class PDFFactory { // Check if the font is embeddable if (desc.isEmbeddable()) { - AbstractPDFStream stream = makeFontFile(desc); + AbstractPDFStream stream = makeFontFile(desc, fontPrefix); + if (stream != null) { descriptor.setFontFile(desc.getFontType(), stream); getDocument().registerObject(stream); @@ -1564,7 +1553,7 @@ public class PDFFactory { * @param desc FontDescriptor of the font. * @return PDFStream The embedded font file */ - public AbstractPDFStream makeFontFile(FontDescriptor desc) { + public AbstractPDFStream makeFontFile(FontDescriptor desc, String fontPrefix) { if (desc.getFontType() == FontType.OTHER) { throw new IllegalArgumentException("Trying to embed unsupported font type: " + desc.getFontType()); @@ -1578,20 +1567,24 @@ public class PDFFactory { if (in == null) { return null; } else { - AbstractPDFStream embeddedFont; + AbstractPDFStream embeddedFont = null; if (desc.getFontType() == FontType.TYPE0) { MultiByteFont mbfont = (MultiByteFont) font; FontFileReader reader = new FontFileReader(in); byte[] fontBytes; + String header = OFFontLoader.readHeader(reader); + boolean isCFF = mbfont.isOTFFile(); if (font.getEmbeddingMode() == EmbeddingMode.FULL) { fontBytes = reader.getAllBytes(); + if (isCFF) { + //Ensure version 1.6 for full OTF CFF embedding + document.setPDFVersion(Version.V1_6); + } } else { - TTFSubSetFile ttfFile = new TTFSubSetFile(); - ttfFile.readFont(reader, mbfont.getTTCName(), mbfont.getUsedGlyphs()); - fontBytes = ttfFile.getFontSubset(); + fontBytes = getFontSubsetBytes(reader, mbfont, header, fontPrefix, desc, + isCFF); } - embeddedFont = new PDFTTFStream(fontBytes.length); - ((PDFTTFStream) embeddedFont).setData(fontBytes, fontBytes.length); + embeddedFont = getFontStream(font, fontBytes, isCFF); } else if (desc.getFontType() == FontType.TYPE1) { PFBParser parser = new PFBParser(); PFBData pfb = parser.parsePFB(in); @@ -1621,6 +1614,32 @@ public class PDFFactory { } } + private byte[] getFontSubsetBytes(FontFileReader reader, MultiByteFont mbfont, String header, + String fontPrefix, FontDescriptor desc, boolean isCFF) throws IOException { + if (isCFF) { + OTFSubSetFile otfFile = new OTFSubSetFile(); + otfFile.readFont(reader, fontPrefix + desc.getEmbedFontName(), header, mbfont); + return otfFile.getFontSubset(); + } else { + TTFSubSetFile otfFile = new TTFSubSetFile(); + otfFile.readFont(reader, mbfont.getTTCName(), header, mbfont.getUsedGlyphs()); + return otfFile.getFontSubset(); + } + } + + private AbstractPDFStream getFontStream(CustomFont font, byte[] fontBytes, boolean isCFF) + throws IOException { + AbstractPDFStream embeddedFont; + if (isCFF) { + embeddedFont = new PDFCFFStreamType0C(font.getEmbeddingMode() == EmbeddingMode.FULL); + ((PDFCFFStreamType0C) embeddedFont).setData(fontBytes, fontBytes.length); + } else { + embeddedFont = new PDFTTFStream(fontBytes.length); + ((PDFTTFStream) embeddedFont).setData(fontBytes, fontBytes.length); + } + return embeddedFont; + } + private CustomFont getCustomFont(FontDescriptor desc) { Typeface tempFont; if (desc instanceof LazyFont) { @@ -1788,4 +1807,28 @@ public class PDFFactory { return obj; } + public PDFLayer makeLayer(String id) { + PDFLayer layer = new PDFLayer(id); + getDocument().registerObject(layer); + return layer; + } + + public PDFSetOCGStateAction makeSetOCGStateAction(String id) { + PDFSetOCGStateAction action = new PDFSetOCGStateAction(id); + getDocument().registerObject(action); + return action; + } + + public PDFTransitionAction makeTransitionAction(String id) { + PDFTransitionAction action = new PDFTransitionAction(id); + getDocument().registerObject(action); + return action; + } + + public PDFNavigator makeNavigator(String id) { + PDFNavigator navigator = new PDFNavigator(id); + getDocument().registerObject(navigator); + return navigator; + } + } diff --git a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java index ec4e99101..73dbebc3f 100644 --- a/src/java/org/apache/fop/pdf/PDFFontDescriptor.java +++ b/src/java/org/apache/fop/pdf/PDFFontDescriptor.java @@ -102,6 +102,8 @@ public class PDFFontDescriptor extends PDFDictionary { public void setFontFile(FontType subtype, AbstractPDFStream fontfile) { if (subtype == FontType.TYPE1) { put("FontFile", fontfile); + } else if (fontfile instanceof PDFCFFStreamType0C) { + put("FontFile3", fontfile); } else { put("FontFile2", fontfile); } diff --git a/src/java/org/apache/fop/pdf/PDFFunction.java b/src/java/org/apache/fop/pdf/PDFFunction.java index f424569b9..09cbd9708 100644 --- a/src/java/org/apache/fop/pdf/PDFFunction.java +++ b/src/java/org/apache/fop/pdf/PDFFunction.java @@ -22,6 +22,10 @@ package org.apache.fop.pdf; // Java... import java.util.List; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.FunctionDelegate; +import org.apache.fop.render.shading.FunctionPattern; + /** * class representing a PDF Function. * @@ -33,126 +37,9 @@ import java.util.List; * * All PDF Functions have a FunctionType (0,2,3, or 4), a Domain, and a Range. */ -public class PDFFunction extends PDFObject { - // Guts common to all function types - - /** - * Required: The Type of function (0,2,3,4) default is 0. - */ - protected int functionType = 0; // Default - - /** - * Required: 2 * m Array of Double numbers which are possible inputs to the function - */ - protected List domain = null; - - /** - * Required: 2 * n Array of Double numbers which are possible outputs to the function - */ - protected List range = null; - - /* ********************TYPE 0***************************** */ - // FunctionType 0 specific function guts - - /** - * Required: Array containing the Integer size of the Domain and Range, respectively. - * Note: This is really more like two seperate integers, sizeDomain, and sizeRange, - * but since they're expressed as an array in PDF, my implementation reflects that. - */ - protected List size = null; - - /** - * Required for Type 0: Number of Bits used to represent each sample value. - * Limited to 1,2,4,8,12,16,24, or 32 - */ - protected int bitsPerSample = 1; - - /** - * Optional for Type 0: order of interpolation between samples. - * Limited to linear (1) or cubic (3). Default is 1 - */ - protected int order = 1; - - /** - * Optional for Type 0: A 2 * m array of Doubles which provides a - * linear mapping of input values to the domain. - * - * Required for Type 3: A 2 * k array of Doubles that, taken - * in pairs, map each subset of the domain defined by Domain - * and the Bounds array to the domain of the corresponding function. - * Should be two values per function, usually (0,1), - * as in [0 1 0 1] for 2 functions. - */ - protected List encode = null; - - /** - * Optional for Type 0: A 2 * n array of Doubles which provides - * a linear mapping of sample values to the range. Defaults to Range. - */ - protected List decode = null; - - /** - * Optional For Type 0: A stream of sample values - */ - - /** - * Required For Type 4: Postscript Calculator function - * composed of arithmetic, boolean, and stack operators + boolean constants - */ - protected StringBuffer functionDataStream = null; - - /** - * Required (possibly) For Type 0: A vector of Strings for the - * various filters to be used to decode the stream. - * These are how the string is compressed. Flate, LZW, etc. - */ - protected List filter = null; - /* *************************TYPE 2************************** */ - - /** - * Required For Type 2: An Array of n Doubles defining - * the function result when x=0. Default is [0]. - */ - protected List cZero = null; - - /** - * Required For Type 2: An Array of n Doubles defining - * the function result when x=1. Default is [1]. - */ - protected List cOne = null; +public class PDFFunction extends PDFObject implements Function { - /** - * Required for Type 2: The interpolation exponent. - * Each value x will return n results. - * Must be greater than 0. - */ - protected double interpolationExponentN = 1; - - /* *************************TYPE 3************************** */ - - /** - * Required for Type 3: An vector of PDFFunctions which - * form an array of k single input functions making up - * the stitching function. - */ - protected List functions = null; - - /** - * Optional for Type 3: An array of (k-1) Doubles that, - * in combination with Domain, define the intervals to which - * each function from the Functions array apply. Bounds - * elements must be in order of increasing magnitude, - * and each value must be within the value of Domain. - * k is the number of functions. - * If you pass null, it will output (1/k) in an array of k-1 elements. - * This makes each function responsible for an equal amount of the stitching function. - * It makes the gradient even. - */ - protected List bounds = null; - // See encode above, as it's also part of Type 3 Functions. - - /* *************************TYPE 4************************** */ - // See 'data' above. + private FunctionDelegate delegate; /** * create an complete Function object of Type 0, A Sampled function. @@ -211,26 +98,13 @@ public class PDFFunction extends PDFObject { * @param theFunctionType This is the type of function (0,2,3, or 4). * It should be 0 as this is the constructor for sampled functions. */ - public PDFFunction(int theFunctionType, List theDomain, - List theRange, List theSize, int theBitsPerSample, - int theOrder, List theEncode, List theDecode, - StringBuffer theFunctionDataStream, List theFilter) { - super(); - - this.functionType = 0; // dang well better be 0; - this.size = theSize; - this.bitsPerSample = theBitsPerSample; - this.order = theOrder; // int - this.encode = theEncode; // vector of int - this.decode = theDecode; // vector of int - this.functionDataStream = theFunctionDataStream; - this.filter = theFilter; // vector of Strings - - // the domain and range are actually two dimensional arrays. - // so if there's not an even number of items, bad stuff - // happens. - this.domain = theDomain; - this.range = theRange; + public PDFFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theSize, int theBitsPerSample, + int theOrder, List<Double> theEncode, List<Double> theDecode, + StringBuffer theFunctionDataStream, List<String> theFilter) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theSize, theBitsPerSample, theOrder, theEncode, theDecode, + theFunctionDataStream, theFilter); } /** @@ -260,20 +134,11 @@ public class PDFFunction extends PDFObject { * PDF Spec page 268 * @param theFunctionType The type of the function, which should be 2. */ - public PDFFunction(int theFunctionType, List theDomain, - List theRange, List theCZero, List theCOne, + public PDFFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, double theInterpolationExponentN) { - super(); - - this.functionType = 2; // dang well better be 2; - - this.cZero = theCZero; - this.cOne = theCOne; - this.interpolationExponentN = theInterpolationExponentN; - - - this.domain = theDomain; - this.range = theRange; + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theCZero, theCOne, theInterpolationExponentN); } @@ -312,18 +177,11 @@ public class PDFFunction extends PDFObject { * @param theFunctionType This is the function type. It should be 3, * for a stitching function. */ - public PDFFunction(int theFunctionType, List theDomain, - List theRange, List theFunctions, - List theBounds, List theEncode) { - super(); - - this.functionType = 3; // dang well better be 3; - - this.functions = theFunctions; - this.bounds = theBounds; - this.encode = theEncode; - this.domain = theDomain; - this.range = theRange; + public PDFFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theFunctions, theBounds, theEncode); } @@ -349,20 +207,12 @@ public class PDFFunction extends PDFObject { * @param theFunctionType The type of function which should be 4, as this is * a Postscript calculator function */ - public PDFFunction(int theFunctionType, List theDomain, - List theRange, StringBuffer theFunctionDataStream) { - super(); - - this.functionType = 4; // dang well better be 4; - this.functionDataStream = theFunctionDataStream; - - this.domain = theDomain; - - this.range = theRange; - + public PDFFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, StringBuffer theFunctionDataStream) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theFunctionDataStream); } - /** * represent as PDF. Whatever the FunctionType is, the correct * representation spits out. The sets of required and optional @@ -375,319 +225,13 @@ public class PDFFunction extends PDFObject { * @return the PDF string. */ public byte[] toPDF() { - int vectorSize = 0; - int numberOfFunctions = 0; - int tempInt = 0; - StringBuffer p = new StringBuffer(256); - p.append("<< \n/FunctionType " + this.functionType + " \n"); - - // FunctionType 0 - if (this.functionType == 0) { - if (this.domain != null) { - // DOMAIN - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - // SIZE - if (this.size != null) { - p.append("/Size [ "); - vectorSize = this.size.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.size.get(tempInt)) - + " "); - } - p.append("] \n"); - } - // ENCODE - if (this.encode != null) { - p.append("/Encode [ "); - vectorSize = this.encode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.encode.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Encode [ "); - vectorSize = this.functions.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append("0 1 "); - } - p.append("] \n"); - - } - - // BITSPERSAMPLE - p.append("/BitsPerSample " + this.bitsPerSample); - - // ORDER (optional) - if (this.order == 1 || this.order == 3) { - p.append(" \n/Order " + this.order + " \n"); - } - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // DECODE - if (this.decode != null) { - p.append("/Decode [ "); - vectorSize = this.decode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.decode.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // LENGTH - if (this.functionDataStream != null) { - p.append("/Length " + (this.functionDataStream.length() + 1) - + " \n"); - } - - // FILTER? - if (this.filter != null) { // if there's a filter - vectorSize = this.filter.size(); - p.append("/Filter "); - if (vectorSize == 1) { - p.append("/" + ((String)this.filter.get(0)) - + " \n"); - } else { - p.append("[ "); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append("/" + ((String)this.filter.get(0)) - + " "); - } - p.append("] \n"); - } - } - p.append(">>"); - - // stream representing the function - if (this.functionDataStream != null) { - p.append("\nstream\n" + this.functionDataStream - + "\nendstream"); - } - - // end of if FunctionType 0 - - } else if (this.functionType == 2) { - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // FunctionType, C0, C1, N are required in PDF - - // C0 - if (this.cZero != null) { - p.append("/C0 [ "); - vectorSize = this.cZero.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.cZero.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - // C1 - if (this.cOne != null) { - p.append("/C1 [ "); - vectorSize = this.cOne.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.cOne.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - // N: The interpolation Exponent - p.append("/N " - + PDFNumber.doubleOut(new Double(this.interpolationExponentN)) - + " \n"); - - p.append(">>"); - - } else if (this.functionType - == 3) { // fix this up when my eyes uncross - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // FUNCTIONS - if (this.functions != null) { - p.append("/Functions [ "); - numberOfFunctions = this.functions.size(); - for (tempInt = 0; tempInt < numberOfFunctions; tempInt++) { - p.append(((PDFFunction)this.functions.get(tempInt)).referencePDF() - + " "); - - } - p.append("] \n"); - } - - - // ENCODE - if (this.encode != null) { - p.append("/Encode [ "); - vectorSize = this.encode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.encode.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Encode [ "); - vectorSize = this.functions.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append("0 1 "); - } - p.append("] \n"); - - } - - - // BOUNDS, required, but can be empty - p.append("/Bounds [ "); - if (this.bounds != null) { - - vectorSize = this.bounds.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.bounds.get(tempInt)) - + " "); - } - - } else { - if (this.functions != null) { - // if there are n functions, - // there must be n-1 bounds. - // so let each function handle an equal portion - // of the whole. e.g. if there are 4, then [ 0.25 0.25 0.25 ] - - String functionsFraction = PDFNumber.doubleOut(new Double(1.0 - / ((double)numberOfFunctions))); - - for (tempInt = 0; tempInt + 1 < numberOfFunctions; - tempInt++) { - - p.append(functionsFraction + " "); - } - functionsFraction = null; // clean reference. - - } - - } - p.append("]\n>>"); - } else if (this.functionType - == 4) { // fix this up when my eyes uncross - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // LENGTH - if (this.functionDataStream != null) { - p.append("/Length " + (this.functionDataStream.length() + 1) - + " \n"); - } - - p.append(">>"); - - // stream representing the function - if (this.functionDataStream != null) { - p.append("\nstream\n{ " + this.functionDataStream - + " }\nendstream"); - } - - - } + return toByteString(); + } - return encode(p.toString()); + public byte[] toByteString() { + FunctionPattern pattern = new FunctionPattern(this); + return encode(pattern.toWriteableString()); } /** {@inheritDoc} */ @@ -702,96 +246,155 @@ public class PDFFunction extends PDFObject { return false; } PDFFunction func = (PDFFunction)obj; - if (functionType != func.functionType) { + if (delegate.getFunctionType() != func.getFunctionType()) { return false; } - if (bitsPerSample != func.bitsPerSample) { + if (delegate.getBitsPerSample() != func.getBitsPerSample()) { return false; } - if (order != func.order) { + if (delegate.getOrder() != func.getOrder()) { return false; } - if (interpolationExponentN != func.interpolationExponentN) { + if (delegate.getInterpolationExponentN() != func.getInterpolationExponentN()) { return false; } - if (domain != null) { - if (!domain.equals(func.domain)) { + if (delegate.getDomain() != null) { + if (!delegate.getDomain().equals(func.getDomain())) { return false; } - } else if (func.domain != null) { + } else if (func.getDomain() != null) { return false; } - if (range != null) { - if (!range.equals(func.range)) { + if (delegate.getRange() != null) { + if (!delegate.getRange().equals(func.getRange())) { return false; } - } else if (func.range != null) { + } else if (func.getRange() != null) { return false; } - if (size != null) { - if (!size.equals(func.size)) { + if (delegate.getSize() != null) { + if (!delegate.getSize().equals(func.getSize())) { return false; } - } else if (func.size != null) { + } else if (func.getSize() != null) { return false; } - if (encode != null) { - if (!encode.equals(func.encode)) { + if (delegate.getEncode() != null) { + if (!delegate.getEncode().equals(func.getEncode())) { return false; } - } else if (func.encode != null) { + } else if (func.getEncode() != null) { return false; } - if (decode != null) { - if (!decode.equals(func.decode)) { + if (delegate.getDecode() != null) { + if (!delegate.getDecode().equals(func.getDecode())) { return false; } - } else if (func.decode != null) { + } else if (func.getDecode() != null) { return false; } - if (functionDataStream != null) { - if (!functionDataStream.equals(func.functionDataStream)) { + if (delegate.getDataStream() != null) { + if (!delegate.getDataStream().equals(func.getDataStream())) { return false; } - } else if (func.functionDataStream != null) { + } else if (func.getDataStream() != null) { return false; } - if (filter != null) { - if (!filter.equals(func.filter)) { + if (delegate.getFilter() != null) { + if (!delegate.getFilter().equals(func.getFilter())) { return false; } - } else if (func.filter != null) { + } else if (func.getFilter() != null) { return false; } - if (cZero != null) { - if (!cZero.equals(func.cZero)) { + if (delegate.getCZero() != null) { + if (!delegate.getCZero().equals(func.getCZero())) { return false; } - } else if (func.cZero != null) { + } else if (func.getCZero() != null) { return false; } - if (cOne != null) { - if (!cOne.equals(func.cOne)) { + if (delegate.getCOne() != null) { + if (!delegate.getCOne().equals(func.getCOne())) { return false; } - } else if (func.cOne != null) { + } else if (func.getCOne() != null) { return false; } - if (functions != null) { - if (!functions.equals(func.functions)) { + if (delegate.getFunctions() != null) { + if (!delegate.getFunctions().equals(func.getFunctions())) { return false; } - } else if (func.functions != null) { + } else if (func.getFunctions() != null) { return false; } - if (bounds != null) { - if (!bounds.equals(func.bounds)) { + if (delegate.getBounds() != null) { + if (!delegate.getBounds().equals(func.getBounds())) { return false; } - } else if (func.bounds != null) { + } else if (func.getBounds() != null) { return false; } return true; } + public int getFunctionType() { + return delegate.getFunctionType(); + } + + public List<Double> getBounds() { + return delegate.getBounds(); + } + + public List<Double> getDomain() { + return delegate.getDomain(); + } + + public List<Double> getSize() { + return delegate.getSize(); + } + + public List<String> getFilter() { + return delegate.getFilter(); + } + + public List<Double> getEncode() { + return delegate.getEncode(); + } + + public List<Function> getFunctions() { + return delegate.getFunctions(); + } + + public int getBitsPerSample() { + return delegate.getBitsPerSample(); + } + + public double getInterpolationExponentN() { + return delegate.getInterpolationExponentN(); + } + + public int getOrder() { + return delegate.getOrder(); + } + + public List<Double> getRange() { + return delegate.getRange(); + } + + public List<Double> getDecode() { + return delegate.getDecode(); + } + + public StringBuffer getDataStream() { + return delegate.getDataStream(); + } + + public List<Double> getCZero() { + return delegate.getCZero(); + } + + public List<Double> getCOne() { + return delegate.getCOne(); + } } diff --git a/src/java/org/apache/fop/layoutmgr/inline/ICLayoutManager.java b/src/java/org/apache/fop/pdf/PDFIdentifiedDictionary.java index 7fe90f63c..c2d033aec 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/ICLayoutManager.java +++ b/src/java/org/apache/fop/pdf/PDFIdentifiedDictionary.java @@ -17,38 +17,26 @@ /* $Id$ */ -package org.apache.fop.layoutmgr.inline; +package org.apache.fop.pdf; -// Java -import java.util.List; - -// FOP -import org.apache.fop.area.inline.InlineArea; -import org.apache.fop.fo.flow.InlineContainer; /** - * This creates a single inline container area after - * laying out the child block areas. All footnotes, floats - * and id areas are maintained for later retrieval. + * Identified Dictionary. */ -public class ICLayoutManager extends LeafNodeLayoutManager { - private List childrenLM; - - /** - * Construct inline container layout manager. - * @param node inline container FO node - * @param childLM child layout manager - */ - public ICLayoutManager(InlineContainer node, List childLM) { - super(node); - childrenLM = childLM; +public class PDFIdentifiedDictionary extends PDFDictionary { + + private final String id; + + public PDFIdentifiedDictionary(String id) { + this.id = id; } - /** - * @param index an integer - * @return an inline area or null - */ - public InlineArea get(int index) { - return null; + public String getId() { + return this.id; + } + + public boolean hasId(String id) { + return (this.id != null) && (id != null) && this.id.equals(id); } } + diff --git a/src/java/org/apache/fop/pdf/PDFLayer.java b/src/java/org/apache/fop/pdf/PDFLayer.java new file mode 100644 index 000000000..f8f434e87 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFLayer.java @@ -0,0 +1,86 @@ +/* + * 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.pdf; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Optional Content Group Dictionary, which we will call a 'layer'. + */ +public class PDFLayer extends PDFIdentifiedDictionary { + + public abstract static class Resolver { + private boolean resolved; + private PDFLayer layer; + private Object extension; + public Resolver(PDFLayer layer, Object extension) { + this.layer = layer; + this.extension = extension; + } + public PDFLayer getLayer() { + return layer; + } + public Object getExtension() { + return extension; + } + public void resolve() { + if (!resolved) { + performResolution(); + resolved = true; + } + } + protected void performResolution() { + } + } + + private Resolver resolver; + + public PDFLayer(String id) { + super(id); + put("Type", new PDFName("OCG")); + } + + @Override + public int output(OutputStream stream) throws IOException { + if (resolver != null) { + resolver.resolve(); + } + return super.output(stream); + } + + public void setResolver(Resolver resolver) { + this.resolver = resolver; + } + + public void populate(Object name, Object intent, Object usage) { + if (name != null) { + put("Name", name); + } + if (intent != null) { + put("Intent", intent); + } + if (usage != null) { + put("Usage", usage); + } + } + +} + diff --git a/src/java/org/apache/fop/pdf/PDFNavigator.java b/src/java/org/apache/fop/pdf/PDFNavigator.java new file mode 100644 index 000000000..fdb97469b --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFNavigator.java @@ -0,0 +1,93 @@ +/* + * 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.pdf; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Navigation Node Dictionary, which we call a 'navigator'. + * This class is used to for sub-page navigation. + */ +public class PDFNavigator extends PDFIdentifiedDictionary { + + public abstract static class Resolver { + private boolean resolved; + private PDFNavigator navigator; + private Object extension; + public Resolver(PDFNavigator navigator, Object extension) { + this.navigator = navigator; + this.extension = extension; + } + public PDFNavigator getNavigator() { + return navigator; + } + public Object getExtension() { + return extension; + } + public void resolve() { + if (!resolved) { + performResolution(); + resolved = true; + } + } + protected void performResolution() { + } + } + + private Resolver resolver; + + public PDFNavigator(String id) { + super(id); + put("Type", new PDFName("NavNode")); + } + + @Override + public int output(OutputStream stream) throws IOException { + if (resolver != null) { + resolver.resolve(); + } + return super.output(stream); + } + + public void setResolver(Resolver resolver) { + this.resolver = resolver; + } + + public void populate(Object nextAction, Object nextNode, Object prevAction, Object prevNode, Object duration) { + if (nextAction != null) { + put("NA", nextAction); + } + if (nextNode != null) { + put("Next", nextNode); + } + if (prevAction != null) { + put("PA", prevAction); + } + if (prevNode != null) { + put("Prev", prevNode); + } + if (duration != null) { + put("Dur", duration); + } + } + +} + diff --git a/src/java/org/apache/fop/pdf/PDFNavigatorAction.java b/src/java/org/apache/fop/pdf/PDFNavigatorAction.java new file mode 100644 index 000000000..ba32269b5 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFNavigatorAction.java @@ -0,0 +1,28 @@ +/* + * 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.pdf; + +public abstract class PDFNavigatorAction extends PDFIdentifiedDictionary { + + protected PDFNavigatorAction(String id) { + super(id); + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFNumber.java b/src/java/org/apache/fop/pdf/PDFNumber.java index 1c31f8e9d..754194886 100644 --- a/src/java/org/apache/fop/pdf/PDFNumber.java +++ b/src/java/org/apache/fop/pdf/PDFNumber.java @@ -29,6 +29,14 @@ public class PDFNumber extends PDFObject { private Number number; + public PDFNumber() { + this.number = Integer.valueOf(0); + } + + public PDFNumber(Number number) { + this.number = number; + } + /** * Returns the number. * @return the number diff --git a/src/java/org/apache/fop/pdf/PDFPaintingState.java b/src/java/org/apache/fop/pdf/PDFPaintingState.java index 29d022f61..f6528a30c 100644 --- a/src/java/org/apache/fop/pdf/PDFPaintingState.java +++ b/src/java/org/apache/fop/pdf/PDFPaintingState.java @@ -44,8 +44,6 @@ import org.apache.fop.util.AbstractPaintingState; * previous state then the necessary values can be overridden. * The current transform behaves differently to other values as the * matrix is combined with the current resolved value. - * It is impossible to optimise the result without analysing the all - * the possible combinations after completing. */ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState { @@ -187,6 +185,36 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState return newState; } + public void setLayer(String layer) { + getPDFData().setLayer(layer); + } + + public String getLayer() { + return getPDFData().getLayer(); + } + + public boolean getLayerChanged() { + String layerCurrent = getLayer(); + if (layerCurrent == null) { + return false; + } else if (getStateStack().isEmpty()) { + return true; + } else { + for (int i = getStackLevel(); i > 0; --i) { + String layerPrev = ((PDFData) getStateStack().get(i - 1)).getLayer(); + if (layerPrev == null) { + continue; + } else { + // Both current and prior are set, so, if same, then we know layer + // didn't change (and can stop search), otherwise it did change. + return !layerCurrent.equals(layerPrev); + } + } + // Current layer set, but no prior saved layer set, so must have changed. + return true; + } + } + /** {@inheritDoc} */ @Override protected AbstractData instantiateData() { @@ -209,7 +237,7 @@ public class PDFPaintingState extends org.apache.fop.util.AbstractPaintingState AbstractData data = getData(); AbstractData copy = (AbstractData)data.clone(); data.clearTransform(); - getStateStack().add(copy); + getStateStack().push(copy); } private PDFData getPDFData() { diff --git a/src/java/org/apache/fop/pdf/PDFPattern.java b/src/java/org/apache/fop/pdf/PDFPattern.java index 46a6a7378..df4b0233d 100644 --- a/src/java/org/apache/fop/pdf/PDFPattern.java +++ b/src/java/org/apache/fop/pdf/PDFPattern.java @@ -23,6 +23,9 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; + /** * class representing a PDF Function. * @@ -33,7 +36,7 @@ import java.util.List; * * All PDF Functions have a FunctionType (0,2,3, or 4), a Domain, and a Range. */ -public class PDFPattern extends PDFPathPaint { +public class PDFPattern extends PDFPathPaint implements Pattern { /** * The resources associated with this pattern @@ -146,13 +149,14 @@ public class PDFPattern extends PDFPathPaint { * @param theExtGState optional: the extended graphics state, if used. * @param theMatrix Optional:List of Doubles that specify the matrix. */ - public PDFPattern(int thePatternType, PDFShading theShading, + public PDFPattern(int thePatternType, Shading theShading, List theXUID, StringBuffer theExtGState, List theMatrix) { super(); this.patternType = 2; // thePatternType; - this.shading = theShading; + assert theShading instanceof PDFShading; + this.shading = (PDFShading)theShading; this.xUID = theXUID; // this isn't really implemented, so it should always be null. // I just don't want to have to add a new parameter once it is implemented. @@ -259,7 +263,7 @@ public class PDFPattern extends PDFPathPaint { vectorSize = this.xUID.size(); p.append("/XUID [ "); for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Integer)this.xUID.get(tempInt)) + " "); + p.append((this.xUID.get(tempInt)) + " "); } p.append("] \n"); } @@ -269,13 +273,14 @@ public class PDFPattern extends PDFPathPaint { pdfStream = new PDFStream(); pdfStream.setDocument(getDocumentSafely()); pdfStream.add(this.patternDataStream.toString()); + pdfStream.setObjectNumber(getObjectNumber()); pdfStream.getFilterList().addDefaultFilters( getDocument().getFilterMap(), PDFFilterList.CONTENT_FILTER); + getDocument().applyEncryption(pdfStream); encodedStream = pdfStream.encodeStream(); p.append(pdfStream.getFilterList().buildFilterDictEntries()); - p.append("/Length " + (encodedStream.getSize() + 1) - + " \n"); + p.append("/Length " + encodedStream.getSize() + " \n"); } } else { @@ -289,7 +294,7 @@ public class PDFPattern extends PDFPathPaint { vectorSize = this.xUID.size(); p.append("/XUID [ "); for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Integer)this.xUID.get(tempInt)) + " "); + p.append((this.xUID.get(tempInt)) + " "); } p.append("] \n"); } diff --git a/src/java/org/apache/fop/pdf/PDFResources.java b/src/java/org/apache/fop/pdf/PDFResources.java index cded7c00a..6d09d5738 100644 --- a/src/java/org/apache/fop/pdf/PDFResources.java +++ b/src/java/org/apache/fop/pdf/PDFResources.java @@ -37,8 +37,8 @@ import org.apache.fop.fonts.base14.ZapfDingbats; /** * Class representing a /Resources object. * - * /Resources object contain a list of references to the fonts for the - * document + * /Resources object contain a list of references to the fonts, patterns, + * shadings, etc., for the document. */ public class PDFResources extends PDFDictionary { @@ -73,6 +73,9 @@ public class PDFResources extends PDFDictionary { /** Map of ICC color spaces (key: ICC profile description) */ protected Map<String, PDFICCBasedColorSpace> iccColorSpaces = new LinkedHashMap<String, PDFICCBasedColorSpace>(); + /** Named properties */ + protected Map<String, PDFReference> properties = new LinkedHashMap<String, PDFReference>(); + /** * create a /Resources object. * @@ -191,6 +194,25 @@ public class PDFResources extends PDFDictionary { return cs; } + /** + * Add a named property. + * + * @param name name of property + * @param property reference to property value + */ + public void addProperty(String name, PDFReference property) { + this.properties.put(name, property); + } + + /** + * Get a named property. + * + * @param name name of property + */ + public PDFReference getProperty(String name) { + return this.properties.get(name); + } + @Override public int output(OutputStream stream) throws IOException { populateDictionary(); @@ -253,6 +275,14 @@ public class PDFResources extends PDFDictionary { } put("ColorSpace", dict); } + + if (!properties.isEmpty()) { + PDFDictionary dict = new PDFDictionary(this); + for (String name : properties.keySet()) { + dict.put(name, properties.get(name)); + } + put("Properties", dict); + } } } diff --git a/src/java/org/apache/fop/pdf/PDFSetOCGStateAction.java b/src/java/org/apache/fop/pdf/PDFSetOCGStateAction.java new file mode 100644 index 000000000..a47c5cd59 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFSetOCGStateAction.java @@ -0,0 +1,82 @@ +/* + * 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.pdf; + +import java.io.IOException; +import java.io.OutputStream; + +public class PDFSetOCGStateAction extends PDFNavigatorAction { + + public abstract static class Resolver { + private boolean resolved; + private PDFSetOCGStateAction action; + private Object extension; + public Resolver(PDFSetOCGStateAction action, Object extension) { + this.action = action; + this.extension = extension; + } + public PDFSetOCGStateAction getAction() { + return action; + } + public Object getExtension() { + return extension; + } + public void resolve() { + if (!resolved) { + performResolution(); + resolved = true; + } + } + protected void performResolution() { + } + } + + private Resolver resolver; + + public PDFSetOCGStateAction(String id) { + super(id); + put("Type", new PDFName("Action")); + put("S", new PDFName("SetOCGState")); + } + + @Override + public int output(OutputStream stream) throws IOException { + if (resolver != null) { + resolver.resolve(); + } + return super.output(stream); + } + + public void setResolver(Resolver resolver) { + this.resolver = resolver; + } + + public void populate(Object state, Object preserveRB, Object nextAction) { + if (state != null) { + put("State", state); + } + if (preserveRB != null) { + put("PreserveRB", preserveRB); + } + if (nextAction != null) { + put("Next", nextAction); + } + } +} diff --git a/src/java/org/apache/fop/pdf/PDFShading.java b/src/java/org/apache/fop/pdf/PDFShading.java index 62012b9b2..3f7b2b4b0 100644 --- a/src/java/org/apache/fop/pdf/PDFShading.java +++ b/src/java/org/apache/fop/pdf/PDFShading.java @@ -22,6 +22,10 @@ package org.apache.fop.pdf; // Java... import java.util.List; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.Shading; +import org.apache.fop.render.shading.ShadingPattern; + /** * class representing a PDF Smooth Shading object. * @@ -32,7 +36,7 @@ import java.util.List; * * All PDF Functions have a shadingType (0,2,3, or 4), a Domain, and a Range. */ -public class PDFShading extends PDFObject { +public class PDFShading extends PDFObject implements Shading { // Guts common to all function types /** @@ -205,7 +209,7 @@ public class PDFShading extends PDFObject { public PDFShading(int theShadingType, PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, List theCoords, - List theDomain, PDFFunction theFunction, + List theDomain, Function theFunction, List theExtend) { super(); this.shadingType = theShadingType; // 2 or 3 @@ -216,7 +220,8 @@ public class PDFShading extends PDFObject { this.coords = theCoords; this.domain = theDomain; - this.function = theFunction; + assert theFunction instanceof PDFFunction; + this.function = (PDFFunction)theFunction; this.extend = theExtend; } @@ -335,197 +340,8 @@ public class PDFShading extends PDFObject { * @return the PDF string. */ public String toPDFString() { - int vectorSize; - int tempInt; - StringBuffer p = new StringBuffer(128); - p.append("<<\n/ShadingType " + this.shadingType + " \n"); - if (this.colorSpace != null) { - p.append("/ColorSpace /" - + this.colorSpace.getName() + " \n"); - } - - if (this.background != null) { - p.append("/Background [ "); - vectorSize = this.background.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.background.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - if (this.bBox - != null) { // I've never seen an example, so I guess this is right. - p.append("/BBox [ "); - vectorSize = this.bBox.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.bBox.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - if (this.antiAlias) { - p.append("/AntiAlias " + this.antiAlias + " \n"); - } - - // Here's where we differentiate based on what type it is. - if (this.shadingType == 1) { // function based shading - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - if (this.matrix != null) { - p.append("/Matrix [ "); - vectorSize = this.matrix.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.matrix.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - } else if ((this.shadingType == 2) - || (this.shadingType - == 3)) { // 2 is axial shading (linear gradient) - // 3 is radial shading (circular gradient) - if (this.coords != null) { - p.append("/Coords [ "); - vectorSize = this.coords.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.coords.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - if (this.extend != null) { - p.append("/Extend [ "); - vectorSize = this.extend.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Boolean)this.extend.get(tempInt)) + " "); - } - - p.append("] \n"); - } else { - p.append("/Extend [ true true ] \n"); - } - - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - - - } else if ((this.shadingType == 4) || (this.shadingType == 6) - || (this.shadingType - == 7)) { // 4:Free-form Gouraud-shaded triangle meshes - // 6:coons patch meshes - // 7://tensor product patch meshes (which no one ever uses) - if (this.bitsPerCoordinate > 0) { - p.append("/BitsPerCoordinate " + this.bitsPerCoordinate - + " \n"); - } else { - p.append("/BitsPerCoordinate 1 \n"); - } - - if (this.bitsPerComponent > 0) { - p.append("/BitsPerComponent " + this.bitsPerComponent - + " \n"); - } else { - p.append("/BitsPerComponent 1 \n"); - } - - if (this.bitsPerFlag > 0) { - p.append("/BitsPerFlag " + this.bitsPerFlag + " \n"); - } else { - p.append("/BitsPerFlag 2 \n"); - } - - if (this.decode != null) { - p.append("/Decode [ "); - vectorSize = this.decode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Boolean)this.decode.get(tempInt)) + " "); - } - - p.append("] \n"); - } - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - - } else if (this.shadingType - == 5) { // Lattice Free form gouraud-shaded triangle mesh - - if (this.bitsPerCoordinate > 0) { - p.append("/BitsPerCoordinate " + this.bitsPerCoordinate - + " \n"); - } else { - p.append("/BitsPerCoordinate 1 \n"); - } - - if (this.bitsPerComponent > 0) { - p.append("/BitsPerComponent " + this.bitsPerComponent - + " \n"); - } else { - p.append("/BitsPerComponent 1 \n"); - } - - if (this.decode != null) { - p.append("/Decode [ "); - vectorSize = this.decode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Boolean)this.decode.get(tempInt)) + " "); - } - - p.append("] \n"); - } - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - - if (this.verticesPerRow > 0) { - p.append("/VerticesPerRow " + this.verticesPerRow + " \n"); - } else { - p.append("/VerticesPerRow 2 \n"); - } - - } - - p.append(">>"); - - return (p.toString()); + ShadingPattern pattern = new ShadingPattern(this); + return pattern.toString(colorSpace, shadingType, background, bBox, antiAlias); } /** {@inheritDoc} */ @@ -623,4 +439,173 @@ public class PDFShading extends PDFObject { } return true; } + + /** + * A method to write a type 1 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType1(StringBuffer p) { + if (this.domain != null) { + p.append("/Domain [ "); + for (int domainIndex = 0; domainIndex < domain.size(); domainIndex++) { + p.append(PDFNumber.doubleOut((Double)this.domain.get(domainIndex)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + if (this.matrix != null) { + p.append("/Matrix [ "); + for (int matrixIndex = 0; matrixIndex < matrix.size(); matrixIndex++) { + p.append(PDFNumber.doubleOut((Double)this.matrix.get(matrixIndex)) + + " "); + } + p.append("] \n"); + } + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + return p; + } + + /** + * A method to write a type 2 or 3 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType2or3(StringBuffer p) { + // 3 is radial shading (circular gradient) + if (this.coords != null) { + p.append("/Coords [ "); + for (int coordIndex = 0; coordIndex < coords.size(); coordIndex++) { + p.append(PDFNumber.doubleOut((Double)this.coords.get(coordIndex)) + + " "); + } + p.append("] \n"); + } + + // DOMAIN + if (this.domain != null) { + p.append("/Domain [ "); + for (int domainIndex = 0; domainIndex < domain.size(); domainIndex++) { + p.append(PDFNumber.doubleOut((Double)this.domain.get(domainIndex)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + if (this.extend != null) { + p.append("/Extend [ "); + for (int extendIndex = 0; extendIndex < extend.size(); extendIndex++) { + p.append((this.extend.get(extendIndex)) + " "); + } + + p.append("] \n"); + } else { + p.append("/Extend [ true true ] \n"); + } + + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + + return p; + } + + /** + * A method to write a type 4, 6 or 7 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType4or6or7(StringBuffer p) { + // 6:coons patch meshes + // 7://tensor product patch meshes (which no one ever uses) + if (this.bitsPerCoordinate > 0) { + p.append("/BitsPerCoordinate " + this.bitsPerCoordinate + + " \n"); + } else { + p.append("/BitsPerCoordinate 1 \n"); + } + + if (this.bitsPerComponent > 0) { + p.append("/BitsPerComponent " + this.bitsPerComponent + + " \n"); + } else { + p.append("/BitsPerComponent 1 \n"); + } + + if (this.bitsPerFlag > 0) { + p.append("/BitsPerFlag " + this.bitsPerFlag + " \n"); + } else { + p.append("/BitsPerFlag 2 \n"); + } + + if (this.decode != null) { + p.append("/Decode [ "); + for (int decodeIndex = 0; decodeIndex < decode.size(); decodeIndex++) { + p.append((this.decode.get(decodeIndex)) + " "); + } + + p.append("] \n"); + } + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + + return p; + } + + /** + * A method to write a type 5 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType5(StringBuffer p) { + if (this.bitsPerCoordinate > 0) { + p.append("/BitsPerCoordinate " + this.bitsPerCoordinate + + " \n"); + } else { + p.append("/BitsPerCoordinate 1 \n"); + } + + if (this.bitsPerComponent > 0) { + p.append("/BitsPerComponent " + this.bitsPerComponent + + " \n"); + } else { + p.append("/BitsPerComponent 1 \n"); + } + + if (this.decode != null) { + p.append("/Decode [ "); + for (int decodeIndex = 0; decodeIndex < decode.size(); decodeIndex++) { + p.append((this.decode.get(decodeIndex)) + " "); + } + + p.append("] \n"); + } + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + + if (this.verticesPerRow > 0) { + p.append("/VerticesPerRow " + this.verticesPerRow + " \n"); + } else { + p.append("/VerticesPerRow 2 \n"); + } + + return p; + } } diff --git a/src/java/org/apache/fop/pdf/PDFTransitionAction.java b/src/java/org/apache/fop/pdf/PDFTransitionAction.java new file mode 100644 index 000000000..01f8fcf21 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFTransitionAction.java @@ -0,0 +1,79 @@ +/* + * 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.pdf; + +import java.io.IOException; +import java.io.OutputStream; + +public class PDFTransitionAction extends PDFNavigatorAction { + + public abstract static class Resolver { + private boolean resolved; + private PDFTransitionAction action; + private Object extension; + public Resolver(PDFTransitionAction action, Object extension) { + this.action = action; + this.extension = extension; + } + public PDFTransitionAction getAction() { + return action; + } + public Object getExtension() { + return extension; + } + public void resolve() { + if (!resolved) { + performResolution(); + resolved = true; + } + } + protected void performResolution() { + } + } + + private Resolver resolver; + + public PDFTransitionAction(String id) { + super(id); + put("Type", new PDFName("Action")); + put("S", new PDFName("Trans")); + } + + @Override + public int output(OutputStream stream) throws IOException { + if (resolver != null) { + resolver.resolve(); + } + return super.output(stream); + } + + public void setResolver(Resolver resolver) { + this.resolver = resolver; + } + + public void populate(Object transition, Object nextAction) { + if (transition != null) { + put("Trans", transition); + } + if (nextAction != null) { + put("Next", nextAction); + } + } +} diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index e274e5c4b..9a94e0cc6 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -28,6 +28,7 @@ import java.io.OutputStream; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.Stack; import org.w3c.dom.Document; @@ -112,8 +113,12 @@ public abstract class AbstractRenderer /** the currently active PageViewport */ protected PageViewport currentPageViewport; + /* warned XML handlers */ private Set warnedXMLHandlers; + /* layers stack */ + private Stack<String> layers; + /** {@inheritDoc} */ public abstract void setupFontInfo(FontInfo fontInfo) throws FOPException; @@ -471,6 +476,10 @@ public abstract class AbstractRenderer * @param children The children to render within the block viewport */ protected void renderBlockViewport(BlockViewport bv, List children) { + boolean inNewLayer = false; + if (maybeStartLayer(bv)) { + inNewLayer = true; + } // clip and position viewport if necessary if (bv.getPositioning() == Block.ABSOLUTE) { // save positions @@ -506,6 +515,7 @@ public abstract class AbstractRenderer currentIPPosition = saveIP; currentBPPosition = saveBP + bv.getAllocBPD(); } + maybeEndLayer(bv, inNewLayer); } /** @@ -573,6 +583,10 @@ public abstract class AbstractRenderer protected void renderBlock(Block block) { assert block != null; List children = block.getChildAreas(); + boolean inNewLayer = false; + if (maybeStartLayer(block)) { + inNewLayer = true; + } if (block instanceof BlockViewport) { if (children != null) { renderBlockViewport((BlockViewport) block, children); @@ -607,6 +621,45 @@ public abstract class AbstractRenderer currentBPPosition = saveBP + block.getAllocBPD(); } } + maybeEndLayer(block, inNewLayer); + } + + /** + * Establish new optional content group layer. + * @param layer name of layer + */ + protected abstract void startLayer(String layer); + + /** + * Finish current optional content group layer. + */ + protected abstract void endLayer(); + + protected boolean maybeStartLayer(Area area) { + String layer = (String) area.getTrait(Trait.LAYER); + if (layer != null) { + if (layers == null) { + layers = new Stack<String>(); + } + if (layers.empty() || !layers.peek().equals(layer)) { + layers.push(layer); + startLayer(layer); + return true; + } + } + return false; + } + + protected void maybeEndLayer(Area area, boolean inNewLayer) { + if (inNewLayer) { + assert layers != null; + assert !layers.empty(); + String layer = (String) area.getTrait(Trait.LAYER); + assert layer != null; + assert layers.peek().equals(layer); + endLayer(); + layers.pop(); + } } /** @@ -746,6 +799,10 @@ public abstract class AbstractRenderer * @param ip the inline parent to render */ protected void renderInlineParent(InlineParent ip) { + boolean inNewLayer = false; + if (maybeStartLayer(ip)) { + inNewLayer = true; + } int level = ip.getBidiLevel(); List children = ip.getChildAreas(); renderInlineAreaBackAndBorders(ip); @@ -782,6 +839,7 @@ public abstract class AbstractRenderer } currentIPPosition = saveIP + ip.getAllocIPD(); currentBPPosition = saveBP; + maybeEndLayer(ip, inNewLayer); } /** diff --git a/src/java/org/apache/fop/render/afp/AFPFontConfig.java b/src/java/org/apache/fop/render/afp/AFPFontConfig.java index aef0b666c..149973edd 100644 --- a/src/java/org/apache/fop/render/afp/AFPFontConfig.java +++ b/src/java/org/apache/fop/render/afp/AFPFontConfig.java @@ -316,8 +316,8 @@ public final class AFPFontConfig implements FontConfig { private final String characterset; - private CIDKeyedFontConfig(List<FontTriplet> triplets, String type, String codePage, - String encoding, String characterset, String name, CharacterSetType charsetType, boolean embeddable, String uri) { + private CIDKeyedFontConfig(List<FontTriplet> triplets, String type, String codePage, String encoding, + String characterset, String name, CharacterSetType charsetType, boolean embeddable, String uri) { super(triplets, type, codePage, encoding, name, embeddable, uri); this.characterset = characterset; this.charsetType = charsetType; diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index 12713bb24..aaa702afa 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -164,7 +164,7 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { try { saveGraphicsState(); concatenateTransformationMatrix(transform); diff --git a/src/java/org/apache/fop/render/afp/AFPRendererConfig.java b/src/java/org/apache/fop/render/afp/AFPRendererConfig.java index 90cc6e767..a943f5aad 100644 --- a/src/java/org/apache/fop/render/afp/AFPRendererConfig.java +++ b/src/java/org/apache/fop/render/afp/AFPRendererConfig.java @@ -103,7 +103,8 @@ public final class AFPRendererConfig implements RendererConfig { } } - private final EnumMap<AFPRendererOption, Object> params = new EnumMap<AFPRendererOption, Object>(AFPRendererOption.class); + private final EnumMap<AFPRendererOption, Object> params + = new EnumMap<AFPRendererOption, Object>(AFPRendererOption.class); private final EnumMap<ImagesModeOptions, Object> imageModeParams = new EnumMap<ImagesModeOptions, Object>(ImagesModeOptions.class); diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java index 2dd046fa9..f69fe2091 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java @@ -126,8 +126,8 @@ public abstract class AbstractIFPainter<T extends IFDocumentHandler> implements } /** {@inheritDoc} */ - public void startGroup(AffineTransform[] transforms) throws IFException { - startGroup(combine(transforms)); + public void startGroup(AffineTransform[] transforms, String layer) throws IFException { + startGroup(combine(transforms), layer); } /** diff --git a/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFDocumentHandler.java index fcf8554a6..0c3305797 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFDocumentHandler.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFDocumentHandler.java @@ -54,10 +54,10 @@ public abstract class AbstractXMLWritingIFDocumentHandler extends AbstractIFDocu if (result instanceof SAXResult) { SAXResult saxResult = (SAXResult)result; this.handler = new GenerationHelperContentHandler( - saxResult.getHandler(), getMainNamespace()); + saxResult.getHandler(), getMainNamespace(), getContext()); } else { this.handler = new GenerationHelperContentHandler( - createContentHandler(result), getMainNamespace()); + createContentHandler(result), getMainNamespace(), getContext()); } } diff --git a/src/java/org/apache/fop/render/intermediate/IFContext.java b/src/java/org/apache/fop/render/intermediate/IFContext.java index fda0cff3b..7464e26e0 100644 --- a/src/java/org/apache/fop/render/intermediate/IFContext.java +++ b/src/java/org/apache/fop/render/intermediate/IFContext.java @@ -55,6 +55,8 @@ public class IFContext { private boolean hyphenated; + private int pageIndex = -1; + /** * Main constructor. * @param ua the user agent @@ -216,4 +218,20 @@ public class IFContext { return hyphenated; } + /** + * Record current page index. + * @param pageIndex a zero based page index or -1 (no page) + */ + public void setPageIndex(int pageIndex) { + this.pageIndex = pageIndex; + } + + /** + * Obtain current page index. + * @return a zero based page index or -1 (no page) + */ + public int getPageIndex() { + return this.pageIndex; + } + } diff --git a/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java b/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java index 868615360..c1742be1f 100644 --- a/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java +++ b/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java @@ -89,6 +89,7 @@ public class IFGraphicContext extends GraphicContext { public static class Group { private AffineTransform[] transforms; + private String layer; /** * Construct a Group. @@ -106,6 +107,16 @@ public class IFGraphicContext extends GraphicContext { this(new AffineTransform[] {transform}); } + /** + * Construct a layer Group, i.e., a Group with no transforms + * but with a optional content group layer label. + * @param layer a layer label + */ + public Group(String layer) { + this(); + this.layer = layer; + } + /** Default constructor. */ public Group() { this(EMPTY_TRANSFORM_ARRAY); @@ -116,12 +127,17 @@ public class IFGraphicContext extends GraphicContext { return this.transforms; } + /** @return layer */ + public String getLayer() { + return this.layer; + } + /** * @param painter a painter * @throws IFException in not caught */ public void start(IFPainter painter) throws IFException { - painter.startGroup(transforms); + painter.startGroup(transforms, layer); } /** @@ -136,6 +152,11 @@ public class IFGraphicContext extends GraphicContext { public String toString() { StringBuffer sb = new StringBuffer("group: "); IFUtil.toString(getTransforms(), sb); + if ((layer != null) && (layer.length() > 0)) { + sb.append(" layer("); + sb.append(layer); + sb.append(')'); + } return sb.toString(); } diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 599292287..d43b5cb85 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -113,19 +113,21 @@ public interface IFPainter { /** * Starts a new group of graphical elements. Corresponds to SVG's g element. * @param transforms a series of transformation matrices establishing the new coordinate system + * @param layer an optional layer label (or null if none) * @throws IFException if an error occurs while handling this element */ - void startGroup(AffineTransform[] transforms) throws IFException; + void startGroup(AffineTransform[] transforms, String layer) throws IFException; /** * Starts a new group of graphical elements. Corresponds to SVG's g element. * @param transform the transformation matrix establishing the new coordinate system + * @param layer an optional layer label (or null if none) * @throws IFException if an error occurs while handling this element */ - void startGroup(AffineTransform transform) throws IFException; + void startGroup(AffineTransform transform, String layer) throws IFException; /** - * Ends the current group and restores the previous coordinate system. + * Ends the current group and restores the previous coordinate system (and layer). * @throws IFException if an error occurs while handling this element */ void endGroup() throws IFException; diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index 1af1d4a06..519726291 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -592,7 +592,8 @@ public class IFParser implements IFConstants { String transform = attributes.getValue("transform"); AffineTransform[] transforms = AffineTransformArrayParser.createAffineTransform(transform); - painter.startGroup(transforms); + String layer = attributes.getValue("layer"); + painter.startGroup(transforms, layer); } public void endElement() throws IFException { @@ -800,8 +801,8 @@ public class IFParser implements IFConstants { } painter.drawImage(uri, new Rectangle(x, y, width, height)); } - resetForeignAttributes(); resetStructureTreeElement(); + resetForeignAttributes(); inForeignObject = false; } diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 9e6aae700..e40a8f6b3 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -571,6 +571,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { Dimension dim = new Dimension(viewArea.width, viewArea.height); establishForeignAttributes(page.getForeignAttributes()); + documentHandler.getContext().setPageIndex(page.getPageIndex()); documentHandler.startPage(page.getPageIndex(), page.getPageNumberString(), page.getSimplePageMasterName(), dim); resetForeignAttributes(); @@ -598,6 +599,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { establishForeignAttributes(page.getForeignAttributes()); documentHandler.endPage(); + documentHandler.getContext().setPageIndex(-1); resetForeignAttributes(); } catch (IFException e) { handleIFException(e); @@ -740,6 +742,12 @@ public class IFRenderer extends AbstractPathOrientedRenderer { protected void renderBlockViewport(BlockViewport bv, List children) { //Essentially the same code as in the super class but optimized for the IF + // Handle new layer. + boolean inNewLayer = false; + if (maybeStartLayer(bv)) { + inNewLayer = true; + } + //This is the content-rect Dimension dim = new Dimension(bv.getIPD(), bv.getBPD()); viewportDimensionStack.push(dim); @@ -840,6 +848,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { currentBPPosition += bv.getAllocBPD(); } viewportDimensionStack.pop(); + maybeEndLayer(bv, inNewLayer); } /** {@inheritDoc} */ @@ -847,7 +856,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { StructureTreeElement structElem = (StructureTreeElement) viewport.getTrait(Trait.STRUCTURE_TREE_ELEMENT); establishStructureTreeElement(structElem); - pushdID(viewport); + pushID(viewport); Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD()); viewportDimensionStack.push(dim); super.renderInlineViewport(viewport); @@ -894,9 +903,26 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ + protected void startLayer(String layer) { + if (log.isTraceEnabled()) { + log.trace("startLayer() layer=" + layer); + } + saveGraphicsState(); + pushGroup(new IFGraphicContext.Group(layer)); + } + + /** {@inheritDoc} */ + protected void endLayer() { + if (log.isTraceEnabled()) { + log.trace("endLayer()"); + } + restoreGraphicsState(); + } + + /** {@inheritDoc} */ protected void renderInlineArea(InlineArea inlineArea) { saveInlinePosIfTargetable(inlineArea); - pushdID(inlineArea); + pushID(inlineArea); super.renderInlineArea(inlineArea); popID(inlineArea); } @@ -963,7 +989,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { log.trace("renderBlock() " + block); } saveBlockPosIfTargetable(block); - pushdID(block); + pushID(block); IFContext context = documentHandler.getContext(); Locale oldLocale = context.getLanguage(); context.setLanguage(block.getLocale()); @@ -975,7 +1001,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { popID(block); } - private void pushdID(Area area) { + private void pushID(Area area) { String prodID = (String) area.getTrait(Trait.PROD_ID); if (prodID != null) { ids.push(prodID); diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 62e4cc67d..395e79719 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -303,6 +303,7 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { addAttribute(atts, "width", Integer.toString(size.width)); addAttribute(atts, "height", Integer.toString(size.height)); addForeignAttributes(atts); + getContext().setPageIndex(index); handler.startElement(EL_PAGE, atts); } catch (SAXException e) { throw new IFException("SAX error in startPage()", e); @@ -379,6 +380,7 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { public void endPage() throws IFException { try { handler.endElement(EL_PAGE); + getContext().setPageIndex(-1); } catch (SAXException e) { throw new IFException("SAX error in endPage()", e); } @@ -426,21 +428,24 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { } /** {@inheritDoc} */ - public void startGroup(AffineTransform[] transforms) throws IFException { - startGroup(IFUtil.toString(transforms)); + public void startGroup(AffineTransform[] transforms, String layer) throws IFException { + startGroup(IFUtil.toString(transforms), layer); } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { - startGroup(IFUtil.toString(transform)); + public void startGroup(AffineTransform transform, String layer) throws IFException { + startGroup(IFUtil.toString(transform), layer); } - private void startGroup(String transform) throws IFException { + private void startGroup(String transform, String layer) throws IFException { try { AttributesImpl atts = new AttributesImpl(); if (transform != null && transform.length() > 0) { addAttribute(atts, "transform", transform); } + if (layer != null && layer.length() > 0) { + addAttribute(atts, "layer", layer); + } handler.startElement(EL_GROUP, atts); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java index 07440ff0b..328d1a4f8 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java @@ -143,7 +143,7 @@ public class Java2DPainter extends AbstractIFPainter<Java2DDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { saveGraphicsState(); try { concatenateTransformationMatrix(transform); diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index a98aff353..d343c55d8 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -476,6 +476,14 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem } /** {@inheritDoc} */ + protected void startLayer(String layer) { + } + + /** {@inheritDoc} */ + protected void endLayer() { + } + + /** {@inheritDoc} */ protected List breakOutOfStateStack() { log.debug("Block.FIXED --> break out"); List breakOutList; diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index c51ee834f..f934eed8c 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -126,7 +126,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { saveGraphicsState(); try { concatenateTransformationMatrix(transform); diff --git a/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java b/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java index 58299c528..471efc567 100644 --- a/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java +++ b/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java @@ -23,6 +23,8 @@ import java.awt.color.ICC_Profile; import java.awt.image.DataBufferByte; import java.awt.image.IndexColorModel; import java.awt.image.Raster; +import java.io.IOException; +import java.util.Arrays; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.logging.Log; @@ -239,24 +241,40 @@ public abstract class AbstractImageAdapter implements PDFImage { + " The image may not be handled correctly." + " Base color space: " + icm.getColorSpace() + " Image: " + image.getInfo()); } - indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName())); + ByteArrayOutputStream baout = new ByteArrayOutputStream(); int c = icm.getMapSize(); int hival = c - 1; if (hival > MAX_HIVAL) { throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL); } - indexed.add(Integer.valueOf(hival)); + boolean isDeviceGray = false; int[] palette = new int[c]; icm.getRGBs(palette); - ByteArrayOutputStream baout = new ByteArrayOutputStream(); - for (int i = 0; i < c; i++) { - // TODO Probably doesn't work for non RGB based color spaces - // See log warning above - int entry = palette[i]; - baout.write((entry & 0xFF0000) >> 16); - baout.write((entry & 0xFF00) >> 8); - baout.write(entry & 0xFF); + byte[] reds = new byte[c]; + byte[] greens = new byte[c]; + byte[] blues = new byte[c]; + icm.getReds(reds); + icm.getGreens(greens); + icm.getBlues(blues); + isDeviceGray = Arrays.equals(reds, blues) && Arrays.equals(blues, greens); + if (isDeviceGray) { + indexed.add(new PDFName("DeviceGray")); + try { + baout.write(blues); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName())); + for (int i = 0; i < c; i++) { + int entry = palette[i]; + baout.write((entry & 0xFF0000) >> 16); + baout.write((entry & 0xFF00) >> 8); + baout.write(entry & 0xFF); + } } + indexed.add(hival); + indexed.add(baout.toByteArray()); dict.put("ColorSpace", indexed); diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index dde6b0ef3..ac7b1d905 100644 --- a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -30,6 +30,7 @@ import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFPaintingState; +import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFStream; import org.apache.fop.pdf.PDFText; @@ -55,7 +56,7 @@ public class PDFContentGenerator { private PDFColorHandler colorHandler; /** drawing state */ - protected PDFPaintingState currentState = null; + protected PDFPaintingState currentState; /** Text generation utility holding the current font status */ protected PDFTextUtil textutil; @@ -156,15 +157,23 @@ public class PDFContentGenerator { */ protected void comment(String text) { if (WRITE_COMMENTS) { - currentStream.add("% " + text + "\n"); + getStream().add("% " + text + "\n"); } } /** Save graphics state. */ protected void saveGraphicsState() { endTextObject(); - currentState.save(); - currentStream.add("q\n"); + getState().save(); + getStream().add("q\n"); + } + + /** Save graphics state with optional layer. */ + protected void saveGraphicsState(String layer) { + endTextObject(); + getState().save(); + maybeBeginLayer(layer); + getStream().add("q\n"); } /** @@ -174,9 +183,9 @@ public class PDFContentGenerator { */ protected void saveGraphicsState(String structElemType, int sequenceNum) { endTextObject(); - currentState.save(); + getState().save(); beginMarkedContentSequence(structElemType, sequenceNum); - currentStream.add("q\n"); + getStream().add("q\n"); } /** @@ -208,18 +217,18 @@ public class PDFContentGenerator { if (structElemType != null) { String actualTextProperty = actualText == null ? "" : " /ActualText " + PDFText.escapeText(actualText); - currentStream.add(structElemType + " <</MCID " + String.valueOf(mcid) + getStream().add(structElemType + " <</MCID " + String.valueOf(mcid) + actualTextProperty + ">>\n" + "BDC\n"); } else { - currentStream.add("/Artifact\nBMC\n"); + getStream().add("/Artifact\nBMC\n"); this.inArtifactMode = true; } this.inMarkedContentSequence = true; } void endMarkedContentSequence() { - currentStream.add("EMC\n"); + getStream().add("EMC\n"); this.inMarkedContentSequence = false; this.inArtifactMode = false; } @@ -231,9 +240,10 @@ public class PDFContentGenerator { */ protected void restoreGraphicsState(boolean popState) { endTextObject(); - currentStream.add("Q\n"); + getStream().add("Q\n"); + maybeEndLayer(); if (popState) { - currentState.restore(); + getState().restore(); } } @@ -251,11 +261,42 @@ public class PDFContentGenerator { */ protected void restoreGraphicsStateAccess() { endTextObject(); - currentStream.add("Q\n"); + getStream().add("Q\n"); if (this.inMarkedContentSequence) { endMarkedContentSequence(); } - currentState.restore(); + getState().restore(); + } + + private void maybeBeginLayer(String layer) { + if ((layer != null) && (layer.length() > 0)) { + getState().setLayer(layer); + beginOptionalContent(layer); + } + } + + private void maybeEndLayer() { + if (getState().getLayerChanged()) { + endOptionalContent(); + } + } + + private int ocNameIndex = 0; + + private void beginOptionalContent(String layerId) { + String name; + PDFReference layer = document.resolveExtensionReference(layerId); + if (layer != null) { + name = "oc" + ++ocNameIndex; + document.getResources().addProperty(name, layer); + } else { + name = "unknown"; + } + getStream().add("/OC /" + name + " BDC\n"); + } + + private void endOptionalContent() { + getStream().add("EMC\n"); } /** Indicates the beginning of a text object. */ @@ -310,8 +351,8 @@ public class PDFContentGenerator { public void concatenate(AffineTransform transform) { this.transform = transform; if (!transform.isIdentity()) { - currentState.concatenate(transform); - currentStream.add(CTMHelper.toPDFString(transform, false) + " cm\n"); + getState().concatenate(transform); + getStream().add(CTMHelper.toPDFString(transform, false) + " cm\n"); } } @@ -333,7 +374,7 @@ public class PDFContentGenerator { * @param content the PDF content */ public void add(String content) { - currentStream.add(content); + getStream().add(content); } /** @@ -350,9 +391,9 @@ public class PDFContentGenerator { * @param width line width in points */ public void updateLineWidth(float width) { - if (currentState.setLineWidth(width)) { + if (getState().setLineWidth(width)) { //Only write if value has changed WRT the current line width - currentStream.add(format(width) + " w\n"); + getStream().add(format(width) + " w\n"); } } @@ -362,7 +403,7 @@ public class PDFContentGenerator { */ public void updateCharacterSpacing(float value) { if (getState().setCharacterSpacing(value)) { - currentStream.add(format(value) + " Tc\n"); + getStream().add(format(value) + " Tc\n"); } } @@ -400,7 +441,7 @@ public class PDFContentGenerator { if (pdf != null) { colorHandler.establishColor(pdf, col, fill); } else { - setColor(col, fill, this.currentStream); + setColor(col, fill, getStream()); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index dd9320571..648cdce7a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -50,7 +50,8 @@ import org.apache.fop.render.intermediate.IFDocumentNavigationHandler; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFPainter; import org.apache.fop.render.pdf.PDFRendererConfig.PDFRendererConfigParser; -import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileExtensionAttachment; +import org.apache.fop.render.pdf.extensions.PDFDictionaryAttachment; +import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachment; /** * {@link org.apache.fop.render.intermediate.IFDocumentHandler} implementation that produces PDF. @@ -296,17 +297,21 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } else if (extension instanceof Metadata) { XMPMetadata wrapper = new XMPMetadata(((Metadata) extension)); pdfUtil.renderXMPMetadata(wrapper); - } else if (extension instanceof PDFEmbeddedFileExtensionAttachment) { - PDFEmbeddedFileExtensionAttachment embeddedFile - = (PDFEmbeddedFileExtensionAttachment)extension; + } else if (extension instanceof PDFEmbeddedFileAttachment) { + PDFEmbeddedFileAttachment embeddedFile + = (PDFEmbeddedFileAttachment)extension; try { pdfUtil.addEmbeddedFile(embeddedFile); } catch (IOException ioe) { throw new IFException("Error adding embedded file: " + embeddedFile.getSrc(), ioe); } - } else { + } else if (extension instanceof PDFDictionaryAttachment) { + pdfUtil.renderDictionaryExtension((PDFDictionaryAttachment) extension, currentPage); + } else if (extension != null) { log.debug("Don't know how to handle extension object. Ignoring: " + extension + " (" + extension.getClass().getName() + ")"); + } else { + log.debug("Ignoring null extension object."); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index d9fdd399c..f85328b8b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -143,8 +143,8 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { - generator.saveGraphicsState(); + public void startGroup(AffineTransform transform, String layer) throws IFException { + generator.saveGraphicsState(layer); generator.concatenate(toPoints(transform)); } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index c3b46794d..4352dae6c 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -27,6 +27,7 @@ import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.EnumMap; +import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; @@ -55,16 +56,33 @@ import org.apache.fop.pdf.PDFFileSpec; import org.apache.fop.pdf.PDFICCBasedColorSpace; import org.apache.fop.pdf.PDFICCStream; import org.apache.fop.pdf.PDFInfo; +import org.apache.fop.pdf.PDFLayer; import org.apache.fop.pdf.PDFMetadata; +import org.apache.fop.pdf.PDFName; import org.apache.fop.pdf.PDFNames; +import org.apache.fop.pdf.PDFNavigator; +import org.apache.fop.pdf.PDFNull; +import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFOutputIntent; +import org.apache.fop.pdf.PDFPage; import org.apache.fop.pdf.PDFPageLabels; import org.apache.fop.pdf.PDFReference; +import org.apache.fop.pdf.PDFSetOCGStateAction; import org.apache.fop.pdf.PDFText; +import org.apache.fop.pdf.PDFTransitionAction; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.pdf.Version; import org.apache.fop.pdf.VersionController; -import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileExtensionAttachment; +import org.apache.fop.render.pdf.extensions.PDFActionExtension; +import org.apache.fop.render.pdf.extensions.PDFArrayExtension; +import org.apache.fop.render.pdf.extensions.PDFCollectionEntryExtension; +import org.apache.fop.render.pdf.extensions.PDFDictionaryAttachment; +import org.apache.fop.render.pdf.extensions.PDFDictionaryExtension; +import org.apache.fop.render.pdf.extensions.PDFDictionaryType; +import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachment; +import org.apache.fop.render.pdf.extensions.PDFObjectType; +import org.apache.fop.render.pdf.extensions.PDFPageExtension; +import org.apache.fop.render.pdf.extensions.PDFReferenceExtension; import static org.apache.fop.render.pdf.PDFEncryptionOption.ENCRYPTION_PARAMS; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ACCESSCONTENT; @@ -250,6 +268,274 @@ class PDFRenderingUtil { } } + public void renderDictionaryExtension(PDFDictionaryAttachment attachment, PDFPage currentPage) { + PDFDictionaryExtension extension = attachment.getExtension(); + PDFDictionaryType type = extension.getDictionaryType(); + if (type == PDFDictionaryType.Action) { + addNavigatorAction(extension); + } else if (type == PDFDictionaryType.Layer) { + addLayer(extension); + } else if (type == PDFDictionaryType.Navigator) { + addNavigator(extension); + } else { + renderDictionaryExtension(extension, currentPage); + } + } + + public void addLayer(PDFDictionaryExtension extension) { + assert extension.getDictionaryType() == PDFDictionaryType.Layer; + String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if ((id != null) && (id.length() > 0)) { + PDFLayer layer = pdfDoc.getFactory().makeLayer(id); + layer.setResolver(new PDFLayer.Resolver(layer, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object name = extension.findEntryValue("Name"); + Object intent = extension.findEntryValue("Intent"); + Object usage = makeDictionary(extension.findEntryValue("Usage")); + getLayer().populate(name, intent, usage); + } + }); + } + } + + public void addNavigatorAction(PDFDictionaryExtension extension) { + assert extension.getDictionaryType() == PDFDictionaryType.Action; + String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if ((id != null) && (id.length() > 0)) { + String type = extension.getProperty(PDFActionExtension.PROPERTY_TYPE); + if (type != null) { + if (type.equals("SetOCGState")) { + PDFSetOCGStateAction action = pdfDoc.getFactory().makeSetOCGStateAction(id); + action.setResolver(new PDFSetOCGStateAction.Resolver(action, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object state = makeArray(extension.findEntryValue("State")); + Object preserveRB = extension.findEntryValue("PreserveRB"); + Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next")); + getAction().populate(state, preserveRB, nextAction); + } + }); + } else if (type.equals("Trans")) { + PDFTransitionAction action = pdfDoc.getFactory().makeTransitionAction(id); + action.setResolver(new PDFTransitionAction.Resolver(action, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object transition = makeDictionary(extension.findEntryValue("Trans")); + Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next")); + getAction().populate(transition, nextAction); + } + }); + } else { + throw new UnsupportedOperationException(); + } + } + } + } + + public void addNavigator(PDFDictionaryExtension extension) { + assert extension.getDictionaryType() == PDFDictionaryType.Navigator; + String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if ((id != null) && (id.length() > 0)) { + PDFNavigator navigator = pdfDoc.getFactory().makeNavigator(id); + navigator.setResolver(new PDFNavigator.Resolver(navigator, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object nextAction = makeDictionary(extension.findEntryValue("NA")); + Object next = makeDictionary(extension.findEntryValue("Next")); + Object prevAction = makeDictionary(extension.findEntryValue("PA")); + Object prev = makeDictionary(extension.findEntryValue("Prev")); + Object duration = extension.findEntryValue("Dur"); + getNavigator().populate(nextAction, next, prevAction, prev, duration); + } + }); + } + } + + private Object makeArray(Object value) { + if (value == null) { + return null; + } else if (value instanceof PDFReferenceExtension) { + return resolveReference((PDFReferenceExtension) value); + } else if (value instanceof List<?>) { + return populateArray(new PDFArray(), (List<?>) value); + } else { + throw new IllegalArgumentException(); + } + } + + private Object populateArray(PDFArray array, List<?> entries) { + for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { + PDFObjectType type = entry.getType(); + if (type == PDFObjectType.Array) { + array.add(makeArray(entry.getValue())); + } else if (type == PDFObjectType.Boolean) { + array.add(entry.getValueAsBoolean()); + } else if (type == PDFObjectType.Dictionary) { + array.add(makeDictionary(entry.getValue())); + } else if (type == PDFObjectType.Name) { + array.add(new PDFName(entry.getValueAsString())); + } else if (type == PDFObjectType.Number) { + array.add(new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFObjectType.Reference) { + array.add(resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { + array.add(entry.getValue()); + } + } + return array; + } + + private Object makeDictionary(Object value) { + if (value == null) { + return null; + } else if (value instanceof PDFReferenceExtension) { + return resolveReference((PDFReferenceExtension) value); + } else if (value instanceof List<?>) { + return populateDictionary(new PDFDictionary(), (List<?>) value); + } else { + throw new IllegalArgumentException(); + } + } + + private Object populateDictionary(PDFDictionary dictionary, List<?> entries) { + for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { + PDFObjectType type = entry.getType(); + String key = entry.getKey(); + if (type == PDFObjectType.Array) { + dictionary.put(key, makeArray(entry.getValue())); + } else if (type == PDFObjectType.Boolean) { + dictionary.put(key, entry.getValueAsBoolean()); + } else if (type == PDFObjectType.Dictionary) { + dictionary.put(key, makeDictionary(entry.getValue())); + } else if (type == PDFObjectType.Name) { + dictionary.put(key, new PDFName(entry.getValueAsString())); + } else if (type == PDFObjectType.Number) { + dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFObjectType.Reference) { + dictionary.put(key, resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { + dictionary.put(key, entry.getValue()); + } + } + return dictionary; + } + + private Object makeDictionaryOrArray(Object value) { + if (value == null) { + return null; + } else if (value instanceof PDFReferenceExtension) { + return resolveReference((PDFReferenceExtension) value); + } else if (value instanceof List<?>) { + if (hasKeyedEntry((List<?>) value)) { + return populateDictionary(new PDFDictionary(), (List<?>) value); + } else { + return populateArray(new PDFArray(), (List<?>) value); + } + } else { + throw new IllegalArgumentException(); + } + } + + private boolean hasKeyedEntry(List<?> entries) { + for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { + if (entry.getKey() != null) { + return true; + } + } + return false; + } + + public void renderDictionaryExtension(PDFDictionaryExtension extension, PDFPage currentPage) { + PDFDictionaryType type = extension.getDictionaryType(); + if (type == PDFDictionaryType.Catalog) { + augmentDictionary(pdfDoc.getRoot(), extension); + } else if (type == PDFDictionaryType.Page) { + assert extension instanceof PDFPageExtension; + if (((PDFPageExtension) extension).matchesPageNumber(currentPage.getPageIndex() + 1)) { + augmentDictionary(currentPage, extension); + } + } else { + throw new IllegalStateException(); + } + } + + private PDFDictionary augmentDictionary(PDFDictionary dictionary, PDFDictionaryExtension extension) { + for (PDFCollectionEntryExtension entry : extension.getEntries()) { + if (entry instanceof PDFDictionaryExtension) { + dictionary.put(entry.getKey(), + augmentDictionary(new PDFDictionary(dictionary), (PDFDictionaryExtension) entry)); + } else if (entry instanceof PDFArrayExtension) { + dictionary.put(entry.getKey(), augmentArray(new PDFArray(dictionary), (PDFArrayExtension) entry)); + } else { + augmentDictionary(dictionary, entry); + } + } + return dictionary; + } + + private void augmentDictionary(PDFDictionary dictionary, PDFCollectionEntryExtension entry) { + PDFObjectType type = entry.getType(); + String key = entry.getKey(); + if (type == PDFObjectType.Boolean) { + dictionary.put(key, entry.getValueAsBoolean()); + } else if (type == PDFObjectType.Name) { + dictionary.put(key, new PDFName(entry.getValueAsString())); + } else if (type == PDFObjectType.Number) { + dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFObjectType.Reference) { + assert entry instanceof PDFReferenceExtension; + dictionary.put(key, resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { + dictionary.put(key, entry.getValueAsString()); + } else { + throw new IllegalStateException(); + } + } + + private Object resolveReference(PDFReferenceExtension entry) { + PDFReference reference = (PDFReference) entry.getResolvedReference(); + if (reference == null) { + reference = pdfDoc.resolveExtensionReference(entry.getReferenceId()); + if (reference != null) { + entry.setResolvedReference(reference); + } + return reference; + } + return PDFNull.INSTANCE; + } + + private PDFArray augmentArray(PDFArray array, PDFArrayExtension extension) { + for (PDFCollectionEntryExtension entry : extension.getEntries()) { + if (entry instanceof PDFDictionaryExtension) { + array.add(augmentDictionary(new PDFDictionary(array), (PDFDictionaryExtension) entry)); + } else if (entry instanceof PDFArrayExtension) { + array.add(augmentArray(new PDFArray(array), (PDFArrayExtension) entry)); + } else { + augmentArray(array, entry); + } + } + return array; + } + + private void augmentArray(PDFArray array, PDFCollectionEntryExtension entry) { + PDFObjectType type = entry.getType(); + if (type == PDFObjectType.Boolean) { + array.add(entry.getValueAsBoolean()); + } else if (type == PDFObjectType.Name) { + array.add(new PDFName(entry.getValueAsString())); + } else if (type == PDFObjectType.Number) { + array.add(new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFObjectType.Reference) { + assert entry instanceof PDFReferenceExtension; + array.add(resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { + array.add(entry.getValueAsString()); + } else { + throw new IllegalStateException(); + } + } + public PDFDocument setupPDFDocument(OutputStream out) throws IOException { if (this.pdfDoc != null) { throw new IllegalStateException("PDFDocument already set up"); @@ -315,7 +601,7 @@ class PDFRenderingUtil { * @param embeddedFile the object representing the embedded file to be added * @throws IOException if an I/O error occurs */ - public void addEmbeddedFile(PDFEmbeddedFileExtensionAttachment embeddedFile) + public void addEmbeddedFile(PDFEmbeddedFileAttachment embeddedFile) throws IOException { this.pdfDoc.getProfile().verifyEmbeddedFilesAllowed(); PDFNames names = this.pdfDoc.getRoot().getNames(); diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java index 4d34b8be4..985974b80 100644 --- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java +++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java @@ -358,6 +358,7 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { } public StructureTreeElement startNode(String name, Attributes attributes, StructureTreeElement parent) { + assert parent == null || parent instanceof PDFStructElem; PDFStructElem parentElem = parent == null ? ancestors.getFirst() : (PDFStructElem) parent; PDFStructElem structElem = createStructureElement(name, parentElem, attributes, pdfFactory, eventBroadcaster); diff --git a/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java b/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java index 2b50112c0..b66fe4fad 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java +++ b/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java @@ -19,10 +19,11 @@ package org.apache.fop.render.pdf.extensions; -// FOP import org.apache.fop.fo.FONode; import org.apache.fop.fo.extensions.ExtensionAttachment; +// CSOFF: LineLengthCheck + /** * Base class for the PDF-specific extension elements. */ @@ -67,7 +68,9 @@ public abstract class AbstractPDFExtensionElement extends FONode { * Instantiates extension attachment object. * @return extension attachment */ - protected abstract ExtensionAttachment instantiateExtensionAttachment(); + protected ExtensionAttachment instantiateExtensionAttachment() { + return null; + } } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFActionElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFActionElement.java new file mode 100644 index 000000000..e4a5747e6 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFActionElement.java @@ -0,0 +1,64 @@ +/* + * 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.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:action. + */ +public class PDFActionElement extends PDFDictionaryElement { + + public static final String ATT_TYPE = PDFActionExtension.PROPERTY_TYPE; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFActionElement(FONode parent) { + super(parent, PDFDictionaryType.Action); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + String type = attlist.getValue(ATT_TYPE); + if (type != null) { + getDictionaryExtension().setProperty(PDFActionExtension.PROPERTY_TYPE, type); + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFActionExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFActionExtension.java new file mode 100644 index 000000000..778b8a203 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFActionExtension.java @@ -0,0 +1,32 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFActionExtension extends PDFDictionaryExtension { + + public static final String PROPERTY_TYPE = "type"; + + PDFActionExtension() { + super(PDFDictionaryType.Action); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFArrayElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayElement.java new file mode 100644 index 000000000..1f3ba22b2 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayElement.java @@ -0,0 +1,82 @@ +/* + * 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.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:array. + */ +public class PDFArrayElement extends PDFCollectionEntryElement { + + private PDFArrayExtension extension; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFArrayElement(FONode parent) { + super(parent, PDFObjectType.Array, new PDFArrayExtension()); + } + + public PDFArrayExtension getArrayExtension() { + assert getExtension() instanceof PDFArrayExtension; + return (PDFArrayExtension) getExtension(); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + } + + @Override + protected void addChildNode(FONode child) throws FOPException { + PDFArrayExtension extension = getArrayExtension(); + if (child instanceof PDFCollectionEntryElement) { + PDFCollectionEntryExtension entry = ((PDFCollectionEntryElement) child).getExtension(); + if (entry.getKey() == null) { + extension.addEntry(entry); + } + } + } + + @Override + public void endOfNode() throws FOPException { + super.endOfNode(); + } + + @Override + public String getLocalName() { + return PDFObjectType.Array.elementName(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFArrayExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayExtension.java new file mode 100644 index 000000000..80c6c94e4 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayExtension.java @@ -0,0 +1,84 @@ +/* + * 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.render.pdf.extensions; + +import java.util.List; +import java.util.Map; + +// CSOFF: LineLengthCheck + +public class PDFArrayExtension extends PDFCollectionExtension { + + private static final long serialVersionUID = -1L; + + private Map<String, String> properties; + private List<PDFCollectionEntryExtension> entries; + + PDFArrayExtension() { + super(PDFObjectType.Array); + this.properties = new java.util.HashMap<String, String>(); + this.entries = new java.util.ArrayList<PDFCollectionEntryExtension>(); + } + + @Override + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return getEntries(); + } + + public void setProperty(String name, String value) { + properties.put(name, value); + } + + public String getProperty(String name) { + return properties.get(name); + } + + @Override + public void addEntry(PDFCollectionEntryExtension entry) { + if (entry.getKey() != null) { + throw new IllegalArgumentException(); + } else { + entries.add(entry); + } + } + + public List<PDFCollectionEntryExtension> getEntries() { + return entries; + } + + public PDFCollectionEntryExtension getLastEntry() { + if (entries.size() > 0) { + return entries.get(entries.size() - 1); + } else { + return null; + } + } + + @Override + public String getElementName() { + return PDFObjectType.Array.elementName(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogElement.java new file mode 100644 index 000000000..029357d22 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogElement.java @@ -0,0 +1,45 @@ +/* + * 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.render.pdf.extensions; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:catalog. + */ +public class PDFCatalogElement extends PDFDictionaryElement { + + PDFCatalogElement(FONode parent) { + super(parent, PDFDictionaryType.Catalog); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogExtension.java new file mode 100644 index 000000000..381f6fbe8 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogExtension.java @@ -0,0 +1,29 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFCatalogExtension extends PDFDictionaryExtension { + + PDFCatalogExtension() { + super(PDFDictionaryType.Catalog); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryElement.java new file mode 100644 index 000000000..4185ceef0 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryElement.java @@ -0,0 +1,146 @@ +/* + * 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.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for collection entries: pdf:{array,boolean,dictionary,name,number,reference,string}. The specific type + * of entry is established at construction type. + */ +public class PDFCollectionEntryElement extends AbstractPDFExtensionElement { + + public static final String ATT_KEY = PDFCollectionEntryExtension.PROPERTY_KEY; + + private PDFCollectionEntryExtension extension; + private StringBuffer characters; + + PDFCollectionEntryElement(FONode parent, PDFObjectType type, PDFCollectionEntryExtension extension) { + super(parent); + this.extension = extension; + } + + PDFCollectionEntryElement(FONode parent, PDFObjectType type) { + this(parent, type, createExtension(type)); + } + + private static PDFCollectionEntryExtension createExtension(PDFObjectType type) { + if (type == PDFObjectType.Reference) { + return new PDFReferenceExtension(); + } else { + return new PDFCollectionEntryExtension(type); + } + } + + public PDFCollectionEntryExtension getExtension() { + return extension; + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + if (parent instanceof PDFDictionaryElement) { + String key = attlist.getValue(ATT_KEY); + if (key == null) { + missingPropertyError(ATT_KEY); + } else if (key.length() == 0) { + invalidPropertyValueError(ATT_KEY, key, null); + } else { + extension.setKey(key); + } + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent instanceof PDFDictionaryElement) { + if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName())) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), null); + } + } + } + + @Override + protected void characters(char[] data, int start, int length, PropertyList pList, Locator locator) throws FOPException { + if (capturePCData(extension.getType())) { + if (characters == null) { + characters = new StringBuffer((length < 16) ? 16 : length); + } + characters.append(data, start, length); + } + } + + private boolean capturePCData(PDFObjectType type) { + if (type == PDFObjectType.Array) { + return false; + } else if (type == PDFObjectType.Dictionary) { + return false; + } else { + return (type != PDFObjectType.Reference); + } + } + + @Override + public void endOfNode() throws FOPException { + if (capturePCData(extension.getType())) { + if (extension.getType() == PDFObjectType.Boolean) { + String value = (characters != null) ? characters.toString() : ""; + if (!value.equals("true") && !value.equals("false")) { + invalidPropertyValueError("<value>", value, null); + } + extension.setValue(Boolean.valueOf(value)); + } else if (extension.getType() == PDFObjectType.Name) { + String value = (characters != null) ? characters.toString() : ""; + if (value.length() == 0) { + invalidPropertyValueError("<value>", value, null); + } + extension.setValue(value); + } else if (extension.getType() == PDFObjectType.Number) { + String value = (characters != null) ? characters.toString() : ""; + try { + double d = Double.parseDouble(value); + if (Math.abs(Math.floor(d) - d) < 1E-10) { + extension.setValue(Long.valueOf((long) d)); + } else { + extension.setValue(Double.valueOf(d)); + } + } catch (NumberFormatException e) { + invalidPropertyValueError("<value>", value, null); + } + } else if (extension.getType() == PDFObjectType.String) { + String value = (characters != null) ? characters.toString() : ""; + extension.setValue(value); + } + } + super.endOfNode(); + } + + @Override + public String getLocalName() { + return extension.getType().elementName(); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryExtension.java new file mode 100644 index 000000000..d28f1602f --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryExtension.java @@ -0,0 +1,43 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFCollectionEntryExtension extends PDFObjectExtension { + + public static final String PROPERTY_KEY = "key"; + + /* Non-empty key if used as dictionary entry, otherwise must be null. */ + private String key; + + PDFCollectionEntryExtension(PDFObjectType type) { + super(type); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionExtension.java new file mode 100644 index 000000000..228d69c79 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionExtension.java @@ -0,0 +1,34 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public abstract class PDFCollectionExtension extends PDFCollectionEntryExtension { + + protected PDFCollectionExtension(PDFObjectType type) { + super(type); + } + + public abstract void addEntry(PDFCollectionEntryExtension entry); + + public abstract PDFCollectionEntryExtension getLastEntry(); + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java new file mode 100644 index 000000000..3832619ba --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java @@ -0,0 +1,166 @@ +/* + * 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.render.pdf.extensions; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.fop.render.intermediate.IFContext; +import org.apache.fop.util.GenerationHelperContentHandler; + +// CSOFF: LineLengthCheck + +public class PDFDictionaryAttachment extends PDFExtensionAttachment { + + private static final long serialVersionUID = -5576832955238384505L; + + private PDFDictionaryExtension extension; + + public PDFDictionaryAttachment(PDFDictionaryExtension extension) { + this.extension = extension; + } + + public PDFDictionaryExtension getExtension() { + return extension; + } + + public void toSAX(ContentHandler handler) throws SAXException { + int pageNumber = 0; + if (extension instanceof PDFPageExtension) { + if (handler instanceof GenerationHelperContentHandler) { + Object context = ((GenerationHelperContentHandler) handler).getContentHandlerContext(); + if (context instanceof IFContext) { + int pageIndex = ((IFContext) context).getPageIndex(); + if ((pageIndex >= 0) && ((PDFPageExtension) extension).matchesPageNumber(pageIndex + 1)) { + pageNumber = pageIndex + 1; + } else { + pageNumber = -1; + } + } + } + } + if (pageNumber >= 0) { + toSAX(handler, extension); + } + } + + private void toSAX(ContentHandler handler, PDFDictionaryExtension dictionary) throws SAXException { + AttributesImpl attributes = new AttributesImpl(); + String ln = dictionary.getElementName(); + String qn = PREFIX + ":" + ln; + attributes = extractIFAttributes(attributes, dictionary); + handler.startElement(CATEGORY, ln, qn, attributes); + for (PDFCollectionEntryExtension entry : dictionary.getEntries()) { + toSAX(handler, entry); + } + handler.endElement(CATEGORY, ln, qn); + } + + private void toSAX(ContentHandler handler, PDFArrayExtension array) throws SAXException { + AttributesImpl attributes = new AttributesImpl(); + String ln = array.getElementName(); + String qn = PREFIX + ":" + ln; + attributes = extractIFAttributes(attributes, array); + handler.startElement(CATEGORY, ln, qn, attributes); + for (PDFCollectionEntryExtension entry : array.getEntries()) { + toSAX(handler, entry); + } + handler.endElement(CATEGORY, ln, qn); + } + + private void toSAX(ContentHandler handler, PDFCollectionEntryExtension entry) throws SAXException { + if (entry instanceof PDFDictionaryExtension) { + toSAX(handler, (PDFDictionaryExtension) entry); + } else if (entry instanceof PDFArrayExtension) { + toSAX(handler, (PDFArrayExtension) entry); + } else { + AttributesImpl attributes = new AttributesImpl(); + String ln = entry.getElementName(); + String qn = PREFIX + ":" + ln; + attributes = extractIFAttributes(attributes, entry); + handler.startElement(CATEGORY, ln, qn, attributes); + if (!(entry instanceof PDFReferenceExtension)) { + char[] characters = entry.getValueAsXMLEscapedString().toCharArray(); + if (characters.length > 0) { + handler.characters(characters, 0, characters.length); + } + } + handler.endElement(CATEGORY, ln, qn); + } + } + + private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFDictionaryExtension dictionary) { + PDFDictionaryType type = dictionary.getDictionaryType(); + if (dictionary.usesIDAttribute()) { + String idName = PDFDictionaryElement.ATT_ID; + String id = dictionary.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if (id != null) { + attributes.addAttribute(null, idName, idName, "ID", id); + } + } + if (type == PDFDictionaryType.Action) { + String actionTypeName = PDFActionElement.ATT_TYPE; + String actionType = dictionary.getProperty(PDFActionExtension.PROPERTY_TYPE); + if (actionType != null) { + attributes.addAttribute(null, actionTypeName, actionTypeName, "CDATA", actionType); + } + } else if (type == PDFDictionaryType.Page) { + String pageNumbersName = PDFPageExtension.PROPERTY_PAGE_NUMBERS; + String pageNumbers = dictionary.getProperty(pageNumbersName); + if (pageNumbers != null) { + attributes.addAttribute(null, pageNumbersName, pageNumbersName, "CDATA", pageNumbers); + } + } else if (type == PDFDictionaryType.Dictionary) { + String keyName = PDFCollectionEntryElement.ATT_KEY; + String key = dictionary.getKey(); + if (key != null) { + attributes.addAttribute(null, keyName, keyName, "CDATA", key); + } + } + return attributes; + } + + private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFArrayExtension array) { + String keyName = PDFCollectionEntryExtension.PROPERTY_KEY; + String key = array.getKey(); + if (key != null) { + attributes.addAttribute(null, keyName, keyName, "CDATA", key); + } + return attributes; + } + + private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFCollectionEntryExtension entry) { + String keyName = PDFCollectionEntryElement.ATT_KEY; + String key = entry.getKey(); + if (key != null) { + attributes.addAttribute(null, keyName, keyName, "CDATA", key); + } + if (entry instanceof PDFReferenceExtension) { + String refid = ((PDFReferenceExtension) entry).getReferenceId(); + if (refid != null) { + String refidName = PDFReferenceElement.ATT_REFID; + attributes.addAttribute(null, refidName, refidName, "IDREF", refid); + } + } + return attributes; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java new file mode 100644 index 000000000..9dc127da6 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java @@ -0,0 +1,145 @@ +/* + * 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.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.extensions.ExtensionAttachment; + +// CSOFF: LineLengthCheck + +/** + * Extension element for dictionaries: pdf:{catalog,page,dictionary}. The specific type + * of dictionary is established at construction type. + */ +public class PDFDictionaryElement extends PDFCollectionEntryElement { + + public static final String ATT_ID = PDFDictionaryExtension.PROPERTY_ID; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFDictionaryElement(FONode parent, PDFDictionaryType type) { + super(parent, PDFObjectType.Dictionary, createExtension(type)); + } + + private static PDFDictionaryExtension createExtension(PDFDictionaryType type) { + if (type == PDFDictionaryType.Action) { + return new PDFActionExtension(); + } else if (type == PDFDictionaryType.Catalog) { + return new PDFCatalogExtension(); + } else if (type == PDFDictionaryType.Layer) { + return new PDFLayerExtension(); + } else if (type == PDFDictionaryType.Navigator) { + return new PDFNavigatorExtension(); + } else if (type == PDFDictionaryType.Page) { + return new PDFPageExtension(); + } else { + return new PDFDictionaryExtension(type); + } + } + + public PDFDictionaryExtension getDictionaryExtension() { + assert getExtension() instanceof PDFDictionaryExtension; + return (PDFDictionaryExtension) getExtension(); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + PDFDictionaryExtension extension = getDictionaryExtension(); + if (extension.usesIDAttribute()) { + String id = attlist.getValue(ATT_ID); + if (id != null) { + extension.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); + } + } + if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { + String key = attlist.getValue(ATT_KEY); + if (key == null) { + if (parent instanceof PDFDictionaryElement) { + missingPropertyError(ATT_KEY); + } + } else if (key.length() == 0) { + invalidPropertyValueError(ATT_KEY, key, null); + } else { + extension.setKey(key); + } + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + String localName = getLocalName(); + if (localName.equals("action")) { + // handled in PDFActionElement subclass + } else if (localName.equals("catalog")) { + // handled in PDFCatalogElement subclass + } else if (localName.equals("layer")) { + // handled in PDFLayerElement subclass + } else if (localName.equals("navigator")) { + // handled in PDFNavigattorElement subclass + } else if (localName.equals("page")) { + // handled in PDFPageElement subclass + } else if (localName.equals("dictionary")) { + if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName()) && !PDFObjectType.Array.elementName().equals(parent.getLocalName())) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), null); + } + } else { + throw new IllegalStateException("unknown name: " + localName); + } + } + + @Override + protected void addChildNode(FONode child) throws FOPException { + PDFDictionaryExtension extension = getDictionaryExtension(); + if (child instanceof PDFDictionaryElement) { + PDFDictionaryExtension entry = ((PDFDictionaryElement) child).getDictionaryExtension(); + if (entry.getDictionaryType() == PDFDictionaryType.Dictionary) { + extension.addEntry(entry); + } + } else if (child instanceof PDFCollectionEntryElement) { + PDFCollectionEntryExtension entry = ((PDFCollectionEntryElement) child).getExtension(); + extension.addEntry(entry); + } + } + + @Override + public void endOfNode() throws FOPException { + super.endOfNode(); + } + + @Override + public String getLocalName() { + PDFDictionaryExtension extension = getDictionaryExtension(); + return extension.getDictionaryType().elementName(); + } + + @Override + protected ExtensionAttachment instantiateExtensionAttachment() { + return new PDFDictionaryAttachment(getDictionaryExtension()); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java new file mode 100644 index 000000000..50b6f3a83 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java @@ -0,0 +1,121 @@ +/* + * 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.render.pdf.extensions; + +import java.util.List; +import java.util.Map; + +// CSOFF: LineLengthCheck + +public class PDFDictionaryExtension extends PDFCollectionExtension { + + public static final String PROPERTY_ID = "id"; + public static final String PROPERTY_PAGE_NUMBERS = "page-numbers"; + + private static final long serialVersionUID = -1L; + + private PDFDictionaryType dictionaryType; + private Map<String, String> properties; + private List<PDFCollectionEntryExtension> entries; + + PDFDictionaryExtension() { + this(PDFDictionaryType.Dictionary); + } + + PDFDictionaryExtension(PDFDictionaryType dictionaryType) { + super(PDFObjectType.Dictionary); + this.dictionaryType = dictionaryType; + this.properties = new java.util.HashMap<String, String>(); + this.entries = new java.util.ArrayList<PDFCollectionEntryExtension>(); + } + + @Override + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return getEntries(); + } + + public PDFDictionaryType getDictionaryType() { + return dictionaryType; + } + + public void setProperty(String name, String value) { + properties.put(name, value); + } + + public String getProperty(String name) { + return properties.get(name); + } + + @Override + public void addEntry(PDFCollectionEntryExtension entry) { + if ((entry.getKey() == null) || (entry.getKey().length() == 0)) { + throw new IllegalArgumentException(); + } else { + entries.add(entry); + } + } + + public List<PDFCollectionEntryExtension> getEntries() { + return entries; + } + + public PDFCollectionEntryExtension findEntry(String key) { + for (PDFCollectionEntryExtension entry : entries) { + String entryKey = entry.getKey(); + if ((entryKey != null) && entryKey.equals(key)) { + return entry; + } + } + return null; + } + + public Object findEntryValue(String key) { + for (PDFCollectionEntryExtension entry : entries) { + String entryKey = entry.getKey(); + if ((entryKey != null) && entryKey.equals(key)) { + return entry.getValue(); + } + } + return null; + } + + public PDFCollectionEntryExtension getLastEntry() { + if (entries.size() > 0) { + return entries.get(entries.size() - 1); + } else { + return null; + } + } + + public boolean usesIDAttribute() { + return dictionaryType.usesIDAttribute(); + } + + @Override + public String getElementName() { + return dictionaryType.elementName(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java new file mode 100644 index 000000000..a49a5fc8c --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java @@ -0,0 +1,65 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +/** + * Enumeration type for PDF dictionary extension elements. + */ +public enum PDFDictionaryType { + Action("action", true), // action dictionary element + Catalog("catalog"), // catalog dictionary element + Dictionary("dictionary"), // generic (nested) dictionary element + Layer("layer", true), // optional content group dictionary element + Navigator("navigator", true), // navigation node dictionary element + Page("page"); // page dictionary element + + private String elementName; + private boolean usesIDAttribute; + PDFDictionaryType(String elementName, boolean usesIDAttribute) { + this.elementName = elementName; + this.usesIDAttribute = usesIDAttribute; + } + PDFDictionaryType(String elementName) { + this(elementName, false); + } + public String elementName() { + return elementName; + } + public boolean usesIDAttribute() { + return usesIDAttribute; + } + static PDFDictionaryType valueOfElementName(String elementName) { + for (PDFDictionaryType type : values()) { + if (type.elementName.equals(elementName)) { + return type; + } + } + throw new IllegalArgumentException(); + } + static boolean hasValueOfElementName(String elementName) { + try { + return valueOfElementName(elementName) != null; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java b/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java index c70ed3635..1fba80796 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java @@ -22,6 +22,8 @@ package org.apache.fop.render.pdf.extensions; import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.FONode; +// CSOFF: LineLengthCheck + /** * This class provides the element mapping for the PDF-specific extensions. */ @@ -39,13 +41,97 @@ public class PDFElementMapping extends ElementMapping { protected void initialize() { if (foObjs == null) { foObjs = new java.util.HashMap<String, Maker>(); - foObjs.put(PDFEmbeddedFileElement.ELEMENT, new PDFEmbeddedFileMaker()); + // pdf:action + foObjs.put(PDFDictionaryType.Action.elementName(), new PDFActionElementMaker()); + // pdf:array + foObjs.put(PDFObjectType.Array.elementName(), new PDFArrayElementMaker()); + // pdf:boolean + foObjs.put(PDFObjectType.Boolean.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.Boolean)); + // pdf:catalog + foObjs.put(PDFDictionaryType.Catalog.elementName(), new PDFCatalogElementMaker()); + // pdf:dictionary + foObjs.put(PDFDictionaryType.Dictionary.elementName(), new PDFDictionaryElementMaker()); + // pdf:embedded-file + foObjs.put(PDFEmbeddedFileElement.ELEMENT, new PDFEmbeddedFileElementMaker()); + // pdf:name + foObjs.put(PDFObjectType.Name.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.Name)); + // pdf:number + foObjs.put(PDFObjectType.Number.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.Number)); + // pdf:navigator + foObjs.put(PDFDictionaryType.Navigator.elementName(), new PDFNavigatorElementMaker()); + // pdf:layer + foObjs.put(PDFDictionaryType.Layer.elementName(), new PDFLayerElementMaker()); + // pdf:page + foObjs.put(PDFDictionaryType.Page.elementName(), new PDFPageElementMaker()); + // pdf:reference + foObjs.put(PDFObjectType.Reference.elementName(), new PDFReferenceElementMaker()); + // pdf:string + foObjs.put(PDFObjectType.String.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.String)); + } + } + + static class PDFActionElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFActionElement(parent); + } + } + + static class PDFArrayElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFArrayElement(parent); + } + } + + static class PDFCatalogElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFCatalogElement(parent); + } + } + + static class PDFDictionaryElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFDictionaryElement(parent, PDFDictionaryType.Dictionary); } } - static class PDFEmbeddedFileMaker extends ElementMapping.Maker { + static class PDFEmbeddedFileElementMaker extends ElementMapping.Maker { public FONode make(FONode parent) { return new PDFEmbeddedFileElement(parent); } } + + static class PDFLayerElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFLayerElement(parent); + } + } + + static class PDFNavigatorElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFNavigatorElement(parent); + } + } + + static class PDFPageElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFPageElement(parent); + } + } + + static class PDFCollectionEntryElementMaker extends ElementMapping.Maker { + private PDFObjectType entryType; + PDFCollectionEntryElementMaker(PDFObjectType entryType) { + this.entryType = entryType; + } + public FONode make(FONode parent) { + return new PDFCollectionEntryElement(parent, entryType); + } + } + + static class PDFReferenceElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFReferenceElement(parent); + } + } + } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileAttachment.java index 5f3f16dbb..a62b35a4b 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileAttachment.java @@ -26,7 +26,9 @@ import org.xml.sax.helpers.AttributesImpl; /** * This is the pass-through value object for the PDF extension. */ -public class PDFEmbeddedFileExtensionAttachment extends PDFExtensionAttachment { +public class PDFEmbeddedFileAttachment extends PDFExtensionAttachment { + + private static final long serialVersionUID = -1L; /** element name */ protected static final String ELEMENT = "embedded-file"; @@ -52,7 +54,7 @@ public class PDFEmbeddedFileExtensionAttachment extends PDFExtensionAttachment { /** * No-argument contructor. */ - public PDFEmbeddedFileExtensionAttachment() { + public PDFEmbeddedFileAttachment() { super(); } @@ -62,7 +64,7 @@ public class PDFEmbeddedFileExtensionAttachment extends PDFExtensionAttachment { * @param src the location of the file * @param desc the description of the file */ - public PDFEmbeddedFileExtensionAttachment(String filename, String src, String desc) { + public PDFEmbeddedFileAttachment(String filename, String src, String desc) { super(); this.filename = filename; this.src = src; diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java index 36d7c18d2..3fef5521e 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java @@ -30,7 +30,6 @@ import org.apache.fop.datatypes.URISpecification; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; -import org.apache.fop.fo.extensions.ExtensionAttachment; /** * Extension element for pdf:embedded-file. @@ -44,11 +43,11 @@ public class PDFEmbeddedFileElement extends AbstractPDFExtensionElement { * Main constructor * @param parent parent FO node */ - protected PDFEmbeddedFileElement(FONode parent) { + PDFEmbeddedFileElement(FONode parent) { super(parent); } - /** {@inheritDoc} */ + @Override public void startOfNode() throws FOPException { super.startOfNode(); if (parent.getNameId() != Constants.FO_DECLARATIONS) { @@ -57,12 +56,12 @@ public class PDFEmbeddedFileElement extends AbstractPDFExtensionElement { } } - /** {@inheritDoc} */ + @Override public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { - PDFEmbeddedFileExtensionAttachment embeddedFile - = (PDFEmbeddedFileExtensionAttachment)getExtensionAttachment(); + PDFEmbeddedFileAttachment embeddedFile + = (PDFEmbeddedFileAttachment)getExtensionAttachment(); String desc = attlist.getValue("description"); if (desc != null && desc.length() > 0) { embeddedFile.setDesc(desc); @@ -94,13 +93,8 @@ public class PDFEmbeddedFileElement extends AbstractPDFExtensionElement { embeddedFile.setFilename(filename); } - /** {@inheritDoc} */ + @Override public String getLocalName() { return ELEMENT; } - - /** {@inheritDoc} */ - protected ExtensionAttachment instantiateExtensionAttachment() { - return new PDFEmbeddedFileExtensionAttachment(); - } } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java index a4f5b47b7..9ac858309 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java @@ -23,43 +23,27 @@ import org.apache.xmlgraphics.util.XMLizable; import org.apache.fop.fo.extensions.ExtensionAttachment; -/** - * This is the pass-through value object for the PDF extension. - */ +// CSOFF: LineLengthCheck + public abstract class PDFExtensionAttachment implements ExtensionAttachment, XMLizable { /** The category URI for this extension attachment. */ public static final String CATEGORY = "apache:fop:extensions:pdf"; + /** The prefix to use with qualified names for this extension attachment. */ + public static final String PREFIX = "pdf"; + /** * Default constructor. */ public PDFExtensionAttachment() { - //nop - } - - /** - * @return the category URI - * @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory() - */ - public String getCategory() { - return CATEGORY; } - /** @return type name */ - public String getType() { - String className = getClass().getName(); - return className.substring(className.lastIndexOf('.') + 3); + public String getPrefix() { + return PREFIX; } - /** - * @return a string representation of this object - * @see java.lang.Object#toString() - */ - public String toString() { - return getType(); + public String getCategory() { + return CATEGORY; } - - /** @return element */ - protected abstract String getElement(); } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java index e7e863a23..2fd14058e 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java @@ -19,6 +19,8 @@ package org.apache.fop.render.pdf.extensions; +import java.util.Stack; + import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -30,68 +32,189 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener; +// CSOFF: LineLengthCheck + /** * ContentHandler (parser) for restoring PDF extension objects from XML. */ -public class PDFExtensionHandler extends DefaultHandler - implements ContentHandlerFactory.ObjectSource { +public class PDFExtensionHandler extends DefaultHandler implements ContentHandlerFactory.ObjectSource { /** Logger instance */ protected static final Log log = LogFactory.getLog(PDFExtensionHandler.class); - private Attributes lastAttributes; - private PDFExtensionAttachment returnedObject; private ObjectBuiltListener listener; - /** {@inheritDoc} */ - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - boolean handled = false; + // PDFEmbeddedFileAttachment related state + private Attributes lastAttributes; + + // PDFDictionaryAttachment related + private Stack<PDFCollectionExtension> collections = new Stack<PDFCollectionExtension>(); + private boolean captureContent; + private StringBuffer characters; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (PDFExtensionAttachment.CATEGORY.equals(uri)) { - lastAttributes = new AttributesImpl(attributes); - handled = false; - if (localName.equals(PDFEmbeddedFileExtensionAttachment.ELEMENT)) { - //handled in endElement - handled = true; + if (localName.equals(PDFEmbeddedFileAttachment.ELEMENT)) { + lastAttributes = new AttributesImpl(attributes); + } else if (PDFDictionaryType.Action.elementName().equals(localName)) { + PDFActionExtension action = new PDFActionExtension(); + String id = attributes.getValue(PDFDictionaryElement.ATT_ID); + if (id != null) { + action.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); + } + String type = attributes.getValue(PDFActionElement.ATT_TYPE); + if (type != null) { + action.setProperty(PDFActionExtension.PROPERTY_TYPE, type); + } + collections.push(action); + } else if (PDFObjectType.Array.elementName().equals(localName)) { + PDFArrayExtension array = new PDFArrayExtension(); + String key = attributes.getValue(PDFCollectionEntryElement.ATT_KEY); + if (key != null) { + array.setKey(key); + } + collections.push(array); + } else if (PDFDictionaryType.Catalog.elementName().equals(localName)) { + PDFCatalogExtension catalog = new PDFCatalogExtension(); + collections.push(catalog); + } else if (PDFDictionaryType.Dictionary.elementName().equals(localName)) { + PDFDictionaryExtension dictionary = new PDFDictionaryExtension(); + String key = attributes.getValue(PDFCollectionEntryElement.ATT_KEY); + if (key != null) { + dictionary.setKey(key); + } + collections.push(dictionary); + } else if (PDFDictionaryType.Layer.elementName().equals(localName)) { + PDFLayerExtension layer = new PDFLayerExtension(); + String id = attributes.getValue(PDFDictionaryElement.ATT_ID); + if (id != null) { + layer.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); + } + collections.push(layer); + } else if (PDFDictionaryType.Navigator.elementName().equals(localName)) { + PDFNavigatorExtension navigator = new PDFNavigatorExtension(); + String id = attributes.getValue(PDFDictionaryElement.ATT_ID); + if (id != null) { + navigator.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); + } + collections.push(navigator); + } else if (PDFDictionaryType.Page.elementName().equals(localName)) { + PDFPageExtension page = new PDFPageExtension(); + String pageNumbers = attributes.getValue(PDFPageElement.ATT_PAGE_NUMBERS); + if (pageNumbers != null) { + page.setProperty(PDFPageExtension.PROPERTY_PAGE_NUMBERS, pageNumbers); + } + collections.push(page); + } else if (PDFObjectType.hasValueOfElementName(localName)) { + PDFCollectionEntryExtension entry; + if (PDFObjectType.Reference.elementName().equals(localName)) { + entry = new PDFReferenceExtension(); + } else { + entry = new PDFCollectionEntryExtension(PDFObjectType.valueOfElementName(localName)); + } + String key = attributes.getValue(PDFCollectionEntryElement.ATT_KEY); + if (key != null) { + entry.setKey(key); + } + if (entry instanceof PDFReferenceExtension) { + String refid = attributes.getValue(PDFReferenceElement.ATT_REFID); + if (refid != null) { + ((PDFReferenceExtension) entry).setReferenceId(refid); + } + } + if (!collections.empty()) { + PDFCollectionExtension collection = collections.peek(); + collection.addEntry(entry); + if (!(entry instanceof PDFReferenceExtension)) { + captureContent = true; + } + } + } else { + throw new SAXException("Unhandled element " + localName + " in namespace: " + uri); } + } else { + log.warn("Unhandled element " + localName + " in namespace: " + uri); } - if (!handled) { - if (PDFExtensionAttachment.CATEGORY.equals(uri)) { - throw new SAXException("Unhandled element " + localName - + " in namespace: " + uri); - } else { - log.warn("Unhandled element " + localName - + " in namespace: " + uri); + } + + @Override + public void characters(char[] data, int start, int length) throws SAXException { + if (captureContent) { + if (characters == null) { + characters = new StringBuffer((length < 16) ? 16 : length); } + characters.append(data, start, length); } } - /** {@inheritDoc} */ + @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (PDFExtensionAttachment.CATEGORY.equals(uri)) { - if (PDFEmbeddedFileExtensionAttachment.ELEMENT.equals(localName)) { + if (PDFEmbeddedFileAttachment.ELEMENT.equals(localName)) { String name = lastAttributes.getValue("name"); String src = lastAttributes.getValue("src"); String desc = lastAttributes.getValue("description"); - this.returnedObject = new PDFEmbeddedFileExtensionAttachment(name, src, desc); + this.lastAttributes = null; + this.returnedObject = new PDFEmbeddedFileAttachment(name, src, desc); + } else if (PDFDictionaryType.hasValueOfElementName(localName)) { + if (!collections.empty() && (collections.peek() instanceof PDFDictionaryExtension)) { + PDFDictionaryExtension dictionary = (PDFDictionaryExtension) collections.pop(); + if (!collections.empty()) { + PDFCollectionExtension collectionOuter = collections.peek(); + collectionOuter.addEntry(dictionary); + } else if (dictionary.getDictionaryType() != PDFDictionaryType.Dictionary) { + this.returnedObject = new PDFDictionaryAttachment(dictionary); + } else { + throw new SAXException(new IllegalStateException("generic dictionary not permitted at outer level")); + } + } else { + throw new SAXException(new IllegalStateException("collections stack is empty or not a dictionary")); + } + } else if (PDFObjectType.Array.elementName().equals(localName)) { + if (!collections.empty() && (collections.peek() instanceof PDFArrayExtension)) { + PDFArrayExtension array = (PDFArrayExtension) collections.pop(); + if (!collections.empty()) { + PDFCollectionExtension collectionOuter = collections.peek(); + collectionOuter.addEntry(array); + } else { + throw new SAXException(new IllegalStateException("array not permitted at outer level")); + } + } else { + throw new SAXException(new IllegalStateException("collections stack is empty or not an array")); + } + } else if (PDFObjectType.hasValueOfElementName(localName)) { + if (!collections.empty()) { + PDFCollectionExtension collection = collections.peek(); + PDFCollectionEntryExtension entry = collection.getLastEntry(); + if (entry != null) { + if (characters != null) { + entry.setValue(characters.toString()); + characters = null; + } + } else { + throw new SAXException(new IllegalStateException("no current entry")); + } + } else { + throw new SAXException(new IllegalStateException("entry not permitted at outer level")); + } } } + captureContent = false; } - /** {@inheritDoc} */ + @Override public void endDocument() throws SAXException { if (listener != null) { listener.notifyObjectBuilt(getObject()); } } - /** {@inheritDoc} */ public Object getObject() { return returnedObject; } - /** {@inheritDoc} */ public void setObjectBuiltListener(ObjectBuiltListener listener) { this.listener = listener; } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandlerFactory.java b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandlerFactory.java new file mode 100644 index 000000000..6d83cd58f --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandlerFactory.java @@ -0,0 +1,44 @@ +/* + * 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.render.pdf.extensions; + +import org.xml.sax.ContentHandler; + +import org.apache.fop.util.ContentHandlerFactory; + +// CSOFF: LineLengthCheck + +/** + * Factory for the ContentHandler that handles serialized PDFExtensionAttachment instances. + */ +public class PDFExtensionHandlerFactory implements ContentHandlerFactory { + + private static final String[] NAMESPACES = new String[] {PDFExtensionAttachment.CATEGORY}; + + /** {@inheritDoc} */ + public String[] getSupportedNamespaces() { + return NAMESPACES; + } + + /** {@inheritDoc} */ + public ContentHandler createContentHandler() { + return new PDFExtensionHandler(); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFLayerElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerElement.java new file mode 100644 index 000000000..dab0ecf78 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerElement.java @@ -0,0 +1,45 @@ +/* + * 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.render.pdf.extensions; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:layer. + */ +public class PDFLayerElement extends PDFDictionaryElement { + + PDFLayerElement(FONode parent) { + super(parent, PDFDictionaryType.Layer); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFLayerExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerExtension.java new file mode 100644 index 000000000..d6cc27ab8 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerExtension.java @@ -0,0 +1,29 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFLayerExtension extends PDFDictionaryExtension { + + PDFLayerExtension() { + super(PDFDictionaryType.Layer); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorElement.java new file mode 100644 index 000000000..5c5b779b5 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorElement.java @@ -0,0 +1,45 @@ +/* + * 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.render.pdf.extensions; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:navigator. + */ +public class PDFNavigatorElement extends PDFDictionaryElement { + + PDFNavigatorElement(FONode parent) { + super(parent, PDFDictionaryType.Navigator); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorExtension.java new file mode 100644 index 000000000..ee9dfa7c8 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorExtension.java @@ -0,0 +1,30 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFNavigatorExtension extends PDFDictionaryExtension { + + PDFNavigatorExtension() { + super(PDFDictionaryType.Navigator); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFObjectExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectExtension.java new file mode 100644 index 000000000..5447d87f2 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectExtension.java @@ -0,0 +1,101 @@ +/* + * 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.render.pdf.extensions; + +import org.apache.fop.util.XMLUtil; + +// CSOFF: LineLengthCheck + +public class PDFObjectExtension { + + private PDFObjectType type; + private Object value; + + PDFObjectExtension(PDFObjectType type) { + this.type = type; + } + + public PDFObjectType getType() { + return type; + } + + public void setValue(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + + /** + * Obtain entry value as Boolean. + * @return entry value + */ + public Boolean getValueAsBoolean() { + Object value = getValue(); + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof String) { + return Boolean.valueOf((String)value); + } else { + return false; + } + } + + /** + * Obtain entry value as Number. + * @return entry value + */ + public Number getValueAsNumber() { + Object value = getValue(); + if (value instanceof Number) { + return (Number) value; + } else if (value instanceof String) { + double d = Double.parseDouble((String) value); + if (Math.abs(Math.floor(d) - d) < 1E-10) { + return Long.valueOf((long) d); + } else { + return Double.valueOf(d); + } + } else { + return Integer.valueOf(0); + } + } + + public String getValueAsString() { + Object value = getValue(); + if (value == null) { + return null; + } else if (value instanceof String) { + return (String) value; + } else { + return value.toString(); + } + } + + public String getValueAsXMLEscapedString() { + return XMLUtil.escape(getValueAsString()); + } + + public String getElementName() { + return type.elementName(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFObjectType.java b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectType.java new file mode 100644 index 000000000..c193a3b7b --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectType.java @@ -0,0 +1,60 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +/** + * Enumeration type for leaf PDF object extension types used as singletons, + * dictionary entries, or array entries. + */ +public enum PDFObjectType { + Array("array"), // array valued entry + Boolean("boolean"), // boolean valued entry + Dictionary("dictionary"), // dictionary valued entry + Name("name"), // name valued entry + Number("number"), // number valued entry + Reference("reference"), // indirect object reference entry + String("string"); // string valued entry + + private String elementName; + PDFObjectType(String elementName) { + this.elementName = elementName; + } + public String elementName() { + return elementName; + } + static PDFObjectType valueOfElementName(String elementName) { + for (PDFObjectType type : values()) { + if (type.elementName.equals(elementName)) { + return type; + } + } + throw new IllegalArgumentException(); + } + static boolean hasValueOfElementName(String elementName) { + try { + valueOfElementName(elementName); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFPageElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFPageElement.java new file mode 100644 index 000000000..28a966ead --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFPageElement.java @@ -0,0 +1,64 @@ +/* + * 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.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:page. + */ +public class PDFPageElement extends PDFDictionaryElement { + + public static final String ATT_PAGE_NUMBERS = PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFPageElement(FONode parent) { + super(parent, PDFDictionaryType.Page); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + String pageNumbers = attlist.getValue(ATT_PAGE_NUMBERS); + if (pageNumbers != null) { + getDictionaryExtension().setProperty(PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS, pageNumbers); + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfSPM"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFPageExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFPageExtension.java new file mode 100644 index 000000000..0726a4e55 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFPageExtension.java @@ -0,0 +1,73 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFPageExtension extends PDFDictionaryExtension { + + public static final String PROPERTY_PAGE_NUMBERS = "page-numbers"; + + PDFPageExtension() { + super(PDFDictionaryType.Page); + } + + /** + * Determine if page dictionary and page number matches. + * @param pageNumber page number, where first page number is 1 + * @return true if this dictionary is a page dictionary and specified page number matches specified page-number property + */ + public boolean matchesPageNumber(int pageNumber) { + String pageNumbers = getProperty(PROPERTY_PAGE_NUMBERS); + if ((pageNumbers == null) || (pageNumbers.length() == 0)) { + return false; + } else if (pageNumbers.equals("*")) { + return true; + } else { + for (String interval : pageNumbers.split("\\s*,\\s*")) { + String[] components = interval.split("\\s*-\\s*"); + if (components.length < 1) { + continue; + } else { + try { + int start = Integer.parseInt(components[0]); + int end = 0; + if (components.length > 1) { + if (!components[1].equals("LAST")) { + end = Integer.parseInt(components[1]); + } + } + if ((end == 0) && (pageNumber == start)) { + return true; + } else if ((end > start) && (pageNumber >= start) && (pageNumber < end)) { + return true; + } else { + continue; + } + } catch (NumberFormatException e) { + continue; + } + } + } + } + return false; + } + +} diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionObj.java b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java index c35dcfc73..37aeeb890 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionObj.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java @@ -17,49 +17,42 @@ /* $Id$ */ -package org.apache.fop.fo.extensions; +package org.apache.fop.render.pdf.extensions; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; -import org.apache.fop.fo.FOEventHandler; import org.apache.fop.fo.FONode; -import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; +// CSOFF: LineLengthCheck + /** - * Base class for pdf bookmark extension objects. + * Extension element for pdf:reference. */ -public abstract class ExtensionObj extends FObj { +public class PDFReferenceElement extends PDFCollectionEntryElement { - /** - * Create a new extension object. - * - * @param parent the parent formatting object - */ - public ExtensionObj(FONode parent) { - super(parent); - } + public static final String ATT_REFID = PDFReferenceExtension.PROPERTY_REFID; /** - * {@inheritDoc} + * Main constructor + * @param parent parent FO node */ - public void processNode(String elementName, Locator locator, - Attributes attlist, PropertyList pList) - throws FOPException { + PDFReferenceElement(FONode parent) { + super(parent, PDFObjectType.Reference); } - /** - * Create a default property list for this element. - * @param parent the parent property list - * @param foEventHandler an event handler - * @return property list - * @throws FOPException in case of exception - */ - protected PropertyList createPropertyList(PropertyList parent, - FOEventHandler foEventHandler) throws FOPException { - return null; + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + String refid = attlist.getValue(ATT_REFID); + if (refid == null) { + missingPropertyError(ATT_REFID); + } else if (refid.length() == 0) { + invalidPropertyValueError(ATT_REFID, refid, null); + } else { + ((PDFReferenceExtension) getExtension()).setReferenceId(refid); + } } } - diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceExtension.java new file mode 100644 index 000000000..09621c01c --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceExtension.java @@ -0,0 +1,61 @@ +/* + * 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.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFReferenceExtension extends PDFCollectionEntryExtension { + + public static final String PROPERTY_REFID = "refid"; + + private String refid; + private Object resolvedReference; + + PDFReferenceExtension() { + super(PDFObjectType.Reference); + } + + @Override + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return this; + } + + public String getReferenceId() { + return refid; + } + + public void setReferenceId(String refid) { + this.refid = refid; + } + + public Object getResolvedReference() { + return resolvedReference; + } + + public void setResolvedReference(Object resolvedReference) { + this.resolvedReference = resolvedReference; + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java index e57567b88..06191f84d 100644 --- a/src/java/org/apache/fop/render/ps/PSFontUtils.java +++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java @@ -29,6 +29,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fontbox.cff.CFFStandardString; import org.apache.xmlgraphics.fonts.Glyphs; import org.apache.xmlgraphics.ps.DSCConstants; @@ -50,9 +51,14 @@ import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.fonts.SingleByteEncoding; import org.apache.fop.fonts.SingleByteFont; import org.apache.fop.fonts.Typeface; +import org.apache.fop.fonts.cff.CFFDataReader; +import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry; import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.OFFontLoader; +import org.apache.fop.fonts.truetype.OTFFile; +import org.apache.fop.fonts.truetype.OTFSubSetFile; +import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; import org.apache.fop.fonts.truetype.TTFFile; -import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; import org.apache.fop.fonts.truetype.TTFOutputStream; import org.apache.fop.fonts.truetype.TTFSubSetFile; import org.apache.fop.render.ps.fonts.PSTTFOutputStream; @@ -169,6 +175,15 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { if (!tracker.isResourceSupplied(WINANSI_ENCODING_RESOURCE)) { //Only out Base 14 fonts still use that + for (Typeface tf : fonts.values()) { + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + if (tf instanceof SingleByteFont + && ((SingleByteFont) tf).getEncoding().getName().equals("custom")) { + defineEncoding(gen, ((SingleByteFont) tf).getEncoding()); + } + } + } defineWinAnsiEncoding(gen); } gen.commentln("%FOPBeginFontReencode"); @@ -221,6 +236,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSResource fontRes, PSEventProducer eventProducer) throws IOException { + boolean embeddedFont = false; FontType fontType = tf.getFontType(); PSFontResource fontResource = null; if (!(fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE @@ -232,52 +248,63 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { CustomFont cf = (CustomFont)tf; if (isEmbeddable(cf)) { InputStream in = getInputStreamOnFont(gen, cf); - if (in == null) { + if (in != null) { + if (fontType == FontType.TYPE0) { + if (((MultiByteFont)tf).isOTFFile()) { + checkPostScriptLevel3(gen, eventProducer, "OpenType CFF"); + embedType2CFF(gen, (MultiByteFont) tf, in); + } else { + if (gen.embedIdentityH()) { + checkPostScriptLevel3(gen, eventProducer, "TrueType"); + /* + * First CID-keyed font to be embedded; add + * %%IncludeResource: comment for ProcSet CIDInit. + */ + gen.includeProcsetCIDInitResource(); + } + PSResource cidFontResource; + cidFontResource = embedType2CIDFont(gen, + (MultiByteFont) tf, in); + fontResource = PSFontResource.createFontResource(fontRes, + gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(), + cidFontResource); + } + } + gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes); + if (fontType == FontType.TYPE1) { + embedType1Font(gen, in); + fontResource = PSFontResource.createFontResource(fontRes); + } else if (fontType == FontType.TRUETYPE) { + embedTrueTypeFont(gen, (SingleByteFont) tf, in); + fontResource = PSFontResource.createFontResource(fontRes); + } else { + composeType0Font(gen, (MultiByteFont) tf, in); + } + gen.writeDSCComment(DSCConstants.END_RESOURCE); + gen.getResourceTracker().registerSuppliedResource(fontRes); + embeddedFont = true; + } else { gen.commentln("%WARNING: Could not embed font: " + cf.getEmbedFontName()); log.warn("Font " + cf.getEmbedFontName() + " is marked as supplied in the" + " PostScript file but could not be embedded!"); - gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); - fontResource = PSFontResource.createFontResource(fontRes); - return fontResource; - } - if (fontType == FontType.TYPE0) { - if (gen.embedIdentityH()) { - checkPostScriptLevel3(gen, eventProducer); - /* - * First CID-keyed font to be embedded; add - * %%IncludeResource: comment for ProcSet CIDInit. - */ - gen.includeProcsetCIDInitResource(); - } - PSResource cidFontResource = embedType2CIDFont(gen, - (MultiByteFont) tf, in); - fontResource = PSFontResource.createFontResource(fontRes, - gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(), - cidFontResource); - } - gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes); - if (fontType == FontType.TYPE1) { - embedType1Font(gen, in); - fontResource = PSFontResource.createFontResource(fontRes); - } else if (fontType == FontType.TRUETYPE) { - embedTrueTypeFont(gen, (SingleByteFont) tf, in); - fontResource = PSFontResource.createFontResource(fontRes); - } else { - composeType0Font(gen, (MultiByteFont) tf, in); } - gen.writeDSCComment(DSCConstants.END_RESOURCE); - gen.getResourceTracker().registerSuppliedResource(fontRes); + } + if (!embeddedFont) { + gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); + fontResource = PSFontResource.createFontResource(fontRes); + return fontResource; } return fontResource; } - private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer) { + private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer, + String fontType) { if (gen.getPSLevel() < 3) { if (eventProducer != null) { eventProducer.postscriptLevel3Needed(gen); } else { throw new IllegalStateException("PostScript Level 3 is" - + " required to use TrueType fonts," + + " required to use " + fontType + " fonts," + " configured level is " + gen.getPSLevel()); } @@ -415,6 +442,96 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { gen.writeln("] composefont pop"); } + private static void embedType2CFF(PSGenerator gen, + MultiByteFont font, InputStream fontStream) throws IOException { + FontFileReader reader = new FontFileReader(fontStream); + String header = OFFontLoader.readHeader(reader); + String psName; + CFFDataReader cffReader = new CFFDataReader(reader); + if (cffReader.getFDSelect() != null) { + throw new UnsupportedOperationException("CID-Keyed OTF CFF fonts are not supported" + + " for PostScript output."); + } + + byte[] bytes; + if (font.getEmbeddingMode() == EmbeddingMode.FULL) { + font.setFontName(new String(cffReader.getNameIndex().getValue(0))); + psName = font.getEmbedFontName(); + Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); + int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue(); + for (int gid = 0; gid < cffReader.getCharStringIndex().getNumObjects(); gid++) { + int sid = cffReader.getSIDFromGID(charsetOffset, gid); + + //Check whether the SID falls into the standard string set + if (sid < 391) { + font.mapUsedGlyphName(gid, + CFFStandardString.getName(sid)); + } else { + int index = sid - 391; + if (index < cffReader.getStringIndex().getNumObjects()) { + font.mapUsedGlyphName(gid, + new String(cffReader.getStringIndex().getValue(index))); + } else { + font.mapUsedGlyphName(gid, ".notdef"); + } + } + } + bytes = OTFFile.getCFFData(reader); + } else { + psName = font.getEmbedFontName(); + OTFSubSetFile otfFile = new OTFSubSetFile(); + otfFile.readFont(reader, psName, header, font); + bytes = otfFile.getFontSubset(); + } + + gen.writeln("%!PS-Adobe-3.0 Resource-FontSet"); + gen.writeln("%%DocumentNeedResources:ProcSet(FontSetInit)"); + gen.writeln("%%Title:(FontSet/" + psName + ")"); + gen.writeln("%%Version: 1.000"); + gen.writeln("%%EndComments"); + gen.writeln("%%IncludeResource:ProcSet(FontSetInit)"); + gen.writeln("%%BeginResource: FontSet (" + psName + ")"); + gen.writeln("/FontSetInit /ProcSet findresource begin"); + //Next line + 1 + String fontDeclaration = "/" + psName + " " + bytes.length + " StartData"; + gen.writeln("%%BeginData: " + (fontDeclaration.length() + 1 + bytes.length) + " Binary Bytes"); + gen.writeln(fontDeclaration); + gen.writeByteArr(bytes); + gen.writeln("%%EndData"); + gen.writeln("%%EndResource"); + + gen.writeln("/" + psName + ".0.enc [ "); + int lengthCount = 0; + int charCount = 1; + int encodingCount = 0; + String line = ""; + for (int gid : font.getUsedGlyphNames().keySet()) { + line += "/" + font.getUsedGlyphNames().get(gid) + " "; + lengthCount++; + charCount++; + if (lengthCount == 8) { + gen.writeln(line); + line = ""; + lengthCount = 0; + } + if (charCount > 256) { + encodingCount++; + charCount = 1; + gen.writeln(line); + line = ""; + lengthCount = 0; + gen.writeln("] def"); + gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, + encodingCount - 1, psName, encodingCount - 1, psName)); + gen.writeln("/" + psName + "." + encodingCount + ".enc [ "); + } + } + gen.writeln(line); + gen.writeln("] def"); + gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, encodingCount, + psName, encodingCount, psName)); + } + private static PSResource embedType2CIDFont(PSGenerator gen, MultiByteFont font, InputStream fontStream) throws IOException { assert font.getCIDType() == CIDFontType.CIDTYPE2; @@ -502,17 +619,18 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { } gen.writeln(">] def"); FontFileReader reader = new FontFileReader(fontStream); + String header = OFFontLoader.readHeader(reader); TTFFile ttfFile; if (font.getEmbeddingMode() != EmbeddingMode.FULL) { ttfFile = new TTFSubSetFile(); - ttfFile.readFont(reader, font.getTTCName(), font.getUsedGlyphs()); + //Change the TTFFile to have the abstract method for TTFSubSetFile + ((TTFSubSetFile)ttfFile).readFont(reader, font.getTTCName(), header, font.getUsedGlyphs()); } else { ttfFile = new TTFFile(); ttfFile.readFont(reader, font.getTTCName()); } - createType42DictionaryEntries(gen, font, new CMapSegment[0], ttfFile); gen.writeln("CIDFontName currentdict end /CIDFont defineresource pop"); gen.writeln("end"); @@ -670,7 +788,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { private static PSResource defineDerivedTrueTypeFont(PSGenerator gen, PSEventProducer eventProducer, String baseFontName, String fontName, SingleByteEncoding encoding, CMapSegment[] cmap) throws IOException { - checkPostScriptLevel3(gen, eventProducer); + checkPostScriptLevel3(gen, eventProducer, "TrueType"); PSResource res = new PSResource(PSResource.TYPE_FONT, fontName); gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res); gen.commentln("%XGCDependencies: font " + baseFontName); diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java index bd8e97063..4d215926b 100644 --- a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java @@ -19,26 +19,50 @@ package org.apache.fop.render.ps; +import java.awt.Color; +import java.awt.Dimension; import java.awt.Rectangle; import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.imageio.ImageIO; import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.transcoder.SVGAbstractTranscoder; +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.PNGTranscoder; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; -import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; +import org.apache.xmlgraphics.ps.ImageEncoder; +import org.apache.xmlgraphics.ps.ImageEncodingHelper; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.fop.image.loader.batik.BatikImageFlavors; import org.apache.fop.image.loader.batik.BatikUtil; import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.ps.svg.PSSVGGraphics2D; import org.apache.fop.svg.SVGEventProducer; import org.apache.fop.svg.SVGUserAgent; import org.apache.fop.svg.font.FOPFontFamilyResolverImpl; @@ -48,6 +72,9 @@ import org.apache.fop.svg.font.FOPFontFamilyResolverImpl; */ public class PSImageHandlerSVG implements ImageHandler { + private static final Color FALLBACK_COLOR = new Color(255, 33, 117); + private HashMap<String, String> gradientsFound = new HashMap<String, String>(); + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { BatikImageFlavors.SVG_DOM }; @@ -59,78 +86,262 @@ public class PSImageHandlerSVG implements ImageHandler { PSGenerator gen = psContext.getGenerator(); ImageXMLDOM imageSVG = (ImageXMLDOM)image; - //Controls whether text painted by Batik is generated using text or path operations - boolean strokeText = false; - //TODO Configure text stroking + if (shouldRaster(imageSVG)) { + InputStream is = renderSVGToInputStream(context, imageSVG); + + float x = (float) pos.getX() / 1000f; + float y = (float) pos.getY() / 1000f; + float w = (float) pos.getWidth() / 1000f; + float h = (float) pos.getHeight() / 1000f; + Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h); + + MaskedImage mi = convertToRGB(ImageIO.read(is)); + BufferedImage ri = mi.getImage(); + ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri); + Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight()); + String imgDescription = ri.getClass().getName(); + ImageEncodingHelper helper = new ImageEncodingHelper(ri); + ColorModel cm = helper.getEncodedColorModel(); + PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen, ri, mi.getMaskColor()); + } else { + //Controls whether text painted by Batik is generated using text or path operations + boolean strokeText = false; + //TODO Configure text stroking + + SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(), + new FOPFontFamilyResolverImpl(psContext.getFontInfo()), new AffineTransform()); + + PSSVGGraphics2D graphics = new PSSVGGraphics2D(strokeText, gen); + graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + BridgeContext ctx = new PSBridgeContext(ua, + (strokeText ? null : psContext.getFontInfo()), + context.getUserAgent().getImageManager(), + context.getUserAgent().getImageSessionContext()); + + //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) + //to it. + Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument()); + + GraphicsNode root; + try { + GVTBuilder builder = new GVTBuilder(); + root = builder.build(ctx, clonedDoc); + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI()); + return; + } + // get the 'width' and 'height' attributes of the SVG document + float w = (float)ctx.getDocumentSize().getWidth() * 1000f; + float h = (float)ctx.getDocumentSize().getHeight() * 1000f; - SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(), - new FOPFontFamilyResolverImpl(psContext.getFontInfo()), new AffineTransform()); + float sx = pos.width / w; + float sy = pos.height / h; - PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); - graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + ctx = null; - BridgeContext ctx = new PSBridgeContext(ua, - (strokeText ? null : psContext.getFontInfo()), - context.getUserAgent().getImageManager(), - context.getUserAgent().getImageSessionContext()); + gen.commentln("%FOPBeginSVG"); + gen.saveGraphicsState(); + final boolean clip = false; + if (clip) { + /* + * Clip to the svg area. + * Note: To have the svg overlay (under) a text area then use + * an fo:block-container + */ + gen.writeln("newpath"); + gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f, + pos.width / 1000f, pos.height / 1000f); + gen.writeln("clip"); + } - //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) - //to it. - Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument()); + // transform so that the coordinates (0,0) is from the top left + // and positive is down and to the right. (0,0) is where the + // viewBox puts it. + gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f); - GraphicsNode root; + AffineTransform transform = new AffineTransform(); + // scale to viewbox + transform.translate(pos.getMinX(), pos.getMinY()); + gen.getCurrentState().concatMatrix(transform); + try { + root.paint(graphics); + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); + } + + gen.restoreGraphicsState(); + gen.commentln("%FOPEndSVG"); + } + } + + private InputStream renderSVGToInputStream(RenderingContext context, ImageXMLDOM imageSVG) throws IOException { + PNGTranscoder png = new PNGTranscoder(); + Float width = getDimension(imageSVG.getDocument(), "width") * 8; + png.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, width); + Float height = getDimension(imageSVG.getDocument(), "height") * 8; + png.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, height); + TranscoderInput input = new TranscoderInput(imageSVG.getDocument()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + TranscoderOutput output = new TranscoderOutput(os); try { - GVTBuilder builder = new GVTBuilder(); - root = builder.build(ctx, clonedDoc); - } catch (Exception e) { + png.transcode(input, output); + } catch (TranscoderException ex) { SVGEventProducer eventProducer = SVGEventProducer.Provider.get( context.getUserAgent().getEventBroadcaster()); - eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI()); - return; + eventProducer.svgRenderingError(this, ex, imageSVG.getInfo().getOriginalURI()); + } finally { + os.flush(); + os.close(); + } + return new ByteArrayInputStream(os.toByteArray()); + } + + private MaskedImage convertToRGB(BufferedImage alphaImage) { + int[] red = new int[256]; + int[] green = new int[256]; + int[] blue = new int[256]; + BufferedImage rgbImage = new BufferedImage(alphaImage.getWidth(), + alphaImage.getHeight(), BufferedImage.TYPE_INT_RGB); + //Count occurances of each colour in image + for (int cx = 0; cx < alphaImage.getWidth(); cx++) { + for (int cy = 0; cy < alphaImage.getHeight(); cy++) { + int pixelValue = alphaImage.getRGB(cx, cy); + Color pixelColor = new Color(pixelValue); + red[pixelColor.getRed()]++; + green[pixelColor.getGreen()]++; + blue[pixelColor.getBlue()]++; + } + } + //Find colour not in image + Color alphaSwap = null; + for (int i = 0; i < 256; i++) { + if (red[i] == 0) { + alphaSwap = new Color(i, 0, 0); + break; + } else if (green[i] == 0) { + alphaSwap = new Color(0, i, 0); + break; + } else if (blue[i] == 0) { + alphaSwap = new Color(0, 0, i); + break; + } } - // get the 'width' and 'height' attributes of the SVG document - float w = (float)ctx.getDocumentSize().getWidth() * 1000f; - float h = (float)ctx.getDocumentSize().getHeight() * 1000f; - - float sx = pos.width / w; - float sy = pos.height / h; - - ctx = null; - - gen.commentln("%FOPBeginSVG"); - gen.saveGraphicsState(); - final boolean clip = false; - if (clip) { - /* - * Clip to the svg area. - * Note: To have the svg overlay (under) a text area then use - * an fo:block-container - */ - gen.writeln("newpath"); - gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f, - pos.width / 1000f, pos.height / 1000f); - gen.writeln("clip"); + //Check if all variations are used in all three colours + if (alphaSwap == null) { + //Fallback colour is no unique colour channel can be found + alphaSwap = FALLBACK_COLOR; } + //Replace alpha channel with the new mask colour + for (int cx = 0; cx < alphaImage.getWidth(); cx++) { + for (int cy = 0; cy < alphaImage.getHeight(); cy++) { + int pixelValue = alphaImage.getRGB(cx, cy); + if (pixelValue == 0) { + rgbImage.setRGB(cx, cy, alphaSwap.getRGB()); + } else { + rgbImage.setRGB(cx, cy, alphaImage.getRGB(cx, cy)); + } + } + } + return new MaskedImage(rgbImage, alphaSwap); + } + + private static class MaskedImage { + private Color maskColor = new Color(0, 0, 0); + private BufferedImage image; + + public MaskedImage(BufferedImage image, Color maskColor) { + this.image = image; + this.maskColor = maskColor; + } + + public Color getMaskColor() { + return maskColor; + } + + public BufferedImage getImage() { + return image; + } + } - // transform so that the coordinates (0,0) is from the top left - // and positive is down and to the right. (0,0) is where the - // viewBox puts it. - gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f); + private Float getDimension(Document document, String dimension) { + if (document.getFirstChild().getAttributes().getNamedItem(dimension) != null) { + String width = document.getFirstChild().getAttributes().getNamedItem(dimension).getNodeValue(); + width = width.replaceAll("[^\\d.]", ""); + return Float.parseFloat(width); + } + return null; + } - AffineTransform transform = new AffineTransform(); - // scale to viewbox - transform.translate(pos.getMinX(), pos.getMinY()); - gen.getCurrentState().concatMatrix(transform); + private boolean shouldRaster(ImageXMLDOM image) { + //A list of objects on which to check opacity try { - root.paint(graphics); - } catch (Exception e) { - SVGEventProducer eventProducer = SVGEventProducer.Provider.get( - context.getUserAgent().getEventBroadcaster()); - eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); + List<String> gradMatches = new ArrayList<String>(); + gradMatches.add("radialGradient"); + gradMatches.add("linearGradient"); + return recurseSVGElements(image.getDocument().getChildNodes(), gradMatches, false); + } finally { + gradientsFound.clear(); } + } - gen.restoreGraphicsState(); - gen.commentln("%FOPEndSVG"); + private boolean recurseSVGElements(NodeList childNodes, List<String> gradMatches, boolean isMatched) { + boolean opacityFound = false; + for (int i = 0; i < childNodes.getLength(); i++) { + Node curNode = childNodes.item(i); + if (isMatched && curNode.getLocalName() != null && curNode.getLocalName().equals("stop")) { + if (curNode.getAttributes().getNamedItem("style") != null) { + String[] stylePairs = curNode.getAttributes().getNamedItem("style").getNodeValue() + .split(";"); + for (int styleAtt = 0; styleAtt < stylePairs.length; styleAtt++) { + String[] style = stylePairs[styleAtt].split(":"); + if (style[0].equalsIgnoreCase("stop-opacity")) { + if (Double.parseDouble(style[1]) < 1) { + return true; + } + } + } + } + if (curNode.getAttributes().getNamedItem("stop-opacity") != null) { + String opacityValue = curNode.getAttributes().getNamedItem("stop-opacity").getNodeValue(); + if (Double.parseDouble(opacityValue) < 1) { + return true; + } + } + } + String nodeName = curNode.getLocalName(); + //Special case where rasterization needed for radial gradients + if (nodeName != null && nodeName.equals("ellipse")) { + String found = ""; + String ellipseFill = curNode.getAttributes().getNamedItem("fill").getNodeValue(); + Pattern pattern = Pattern.compile("#(.*?)\\)"); + Matcher matcher = pattern.matcher(ellipseFill); + if (matcher.find()) { + found = matcher.group(1); + } + if (gradientsFound.get(found) != null) { + return true; + } + } + boolean inMatch = false; + if (!isMatched) { + inMatch = nodeName != null && gradMatches.contains(nodeName); + if (inMatch) { + gradientsFound.put(curNode.getAttributes().getNamedItem("id").getNodeValue(), nodeName); + } + } else { + inMatch = true; + } + opacityFound = recurseSVGElements(curNode.getChildNodes(), gradMatches, inMatch); + if (opacityFound) { + return true; + } + } + return opacityFound; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index b0b370c79..547662c47 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -40,6 +40,7 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.xmlgraphics.ps.PSResource; +import org.apache.fop.fonts.EmbeddingMode; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.LazyFont; @@ -115,7 +116,7 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { try { PSGenerator generator = getGenerator(); saveGraphicsState(); @@ -375,7 +376,13 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { } Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints); - useFont(fontKey, sizeMillipoints); + PSFontResource res = getDocumentHandler().getPSResourceForFontKey(fontKey); + if (tf instanceof MultiByteFont && ((MultiByteFont)tf).isOTFFile()) { + generator.writeln("/" + res.getName() + ".0 " + + generator.formatDouble(sizeMillipoints / 1000f) + " F"); + } else { + useFont(fontKey, sizeMillipoints); + } if (dp != null && dp[0] != null) { x += dp[0][0]; @@ -408,7 +415,30 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { } } } else { - useFont(fontKey, sizeMillipoints); + if (tf instanceof MultiByteFont && ((MultiByteFont)tf).isOTFFile()) { + //Analyze string and split up in order to paint in different sub-fonts/encodings + int curEncoding = 0; + for (int i = start; i < textLen; i++) { + char orgChar = text.charAt(i); + + MultiByteFont mbFont = (MultiByteFont)tf; + int origGlyphIdx = mbFont.findGlyphIndex(orgChar); + int newGlyphIdx = mbFont.getUsedGlyphs().get(origGlyphIdx); + int encoding = newGlyphIdx / 256; + if (encoding != curEncoding) { + if (i != 0) { + writeText(text, start, i - start, letterSpacing, wordSpacing, dp, font, tf, + true); + start = i; + } + generator.writeln("/" + res.getName() + "." + encoding + " " + + generator.formatDouble(sizeMillipoints / 1000f) + " F"); + curEncoding = encoding; + } + } + } else { + useFont(fontKey, sizeMillipoints); + } } writeText(text, start, textLen - start, letterSpacing, wordSpacing, dp, font, tf, tf instanceof MultiByteFont); @@ -431,12 +461,14 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { int lineStart = 0; StringBuffer accText = new StringBuffer(initialSize); StringBuffer sb = new StringBuffer(initialSize); + boolean isOTF = multiByte && ((MultiByteFont)tf).isOTFFile(); for (int i = start; i < end; i++) { char orgChar = text.charAt(i); char ch; int cw; int xGlyphAdjust = 0; int yGlyphAdjust = 0; + if (CharUtilities.isFixedWidthSpace(orgChar)) { //Fixed width space are rendered as spaces so copy/paste works in a reader ch = font.mapChar(CharUtilities.SPACE); @@ -460,11 +492,16 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { xGlyphAdjust -= dp[i + 1][0]; yGlyphAdjust += dp[i + 1][1]; } - if (multiByte) { - accText.append(HexEncoder.encode(ch)); - } else { + if (!multiByte || isOTF) { char codepoint = (char)(ch % 256); - PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text + if (isOTF) { + codepoint -= (((MultiByteFont)tf).getEmbeddingMode() == EmbeddingMode.FULL) ? 0 : 1; + accText.append(HexEncoder.encode(codepoint, 2)); + } else { + PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text + } + } else { + accText.append(HexEncoder.encode(ch)); } if (xGlyphAdjust != 0 || yGlyphAdjust != 0) { needTJ = true; diff --git a/src/java/org/apache/fop/render/ps/svg/PSFunction.java b/src/java/org/apache/fop/render/ps/svg/PSFunction.java new file mode 100644 index 000000000..b03e0b590 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSFunction.java @@ -0,0 +1,143 @@ +/* + * 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.render.ps.svg; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.FunctionDelegate; +import org.apache.fop.render.shading.FunctionPattern; + +public class PSFunction implements Function { + + private FunctionDelegate delegate; + + /** + * Creates a Postscript function dictionary + * @param theFunctionType The function type (0 = Sampled, 2 = Exponential + * Interpolation, 3 = Stitching) + * @param theDomain The function domain + * @param theRange Range used for clipping + * @param theFunctions An array of sub-functions such as determining the + * colour values used in a gradient. + * @param theBounds Bounds determines where each boundary exists for whatever + * the function is mean't. In a gradient case, it would be the point between + * colours. + * @param theEncode The function encoding + */ + public PSFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + } + + /** + * Creates a Postscript function dictionary + * @param theFunctionType The function type (0 = Sampled, 2 = Exponential + * Interpolation, 3 = Stitching) + * @param theDomain The function domain + * @param theRange Range used for clipping + * @param theCZero In a gradient, this would be the first colour + * @param theCOne In a gradient, this would be the second colour + * @param theInterpolationExponentN Determines the number of values + * the function returns. + */ + public PSFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + } + + /** + * Outputs the function to a byte array + */ + public byte[] toByteString() { + FunctionPattern pattern = new FunctionPattern(this); + try { + return pattern.toWriteableString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException ex) { + //This should have been made an enum type to avoid throwing exceptions. + return new byte[0]; + } + } + + public int getFunctionType() { + return delegate.getFunctionType(); + } + + public List<Double> getBounds() { + return delegate.getBounds(); + } + + public List<Double> getDomain() { + return delegate.getDomain(); + } + + public List<Double> getSize() { + return delegate.getSize(); + } + + public List<String> getFilter() { + return delegate.getFilter(); + } + + public List<Double> getEncode() { + return delegate.getEncode(); + } + + public List<Function> getFunctions() { + return delegate.getFunctions(); + } + + public int getBitsPerSample() { + return delegate.getBitsPerSample(); + } + + public double getInterpolationExponentN() { + return delegate.getInterpolationExponentN(); + } + + public int getOrder() { + return delegate.getOrder(); + } + + public List<Double> getRange() { + return delegate.getRange(); + } + + public List<Double> getDecode() { + return delegate.getDecode(); + } + + public StringBuffer getDataStream() { + return delegate.getDataStream(); + } + + public List<Double> getCZero() { + return delegate.getCZero(); + } + + public List<Double> getCOne() { + return delegate.getCOne(); + } +} diff --git a/src/java/org/apache/fop/render/ps/svg/PSPattern.java b/src/java/org/apache/fop/render/ps/svg/PSPattern.java new file mode 100644 index 000000000..46f976672 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSPattern.java @@ -0,0 +1,103 @@ +/* + * 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.render.ps.svg; + +import java.util.List; + +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; + +public class PSPattern implements Pattern { + + /** + * Either one (1) for tiling, or two (2) for shading. + */ + protected int patternType = 2; // Default + + /** + * The Shading object comprising the Type 2 pattern + */ + protected PSShading shading = null; + + /** + * List of Integers represetning the Extended unique Identifier + */ + protected List xUID = null; + + /** + * TODO use PDFGState + * String representing the extended Graphics state. + * Probably will never be used like this. + */ + protected StringBuffer extGState = null; + + /** + * Creates a radial or axial shading pattern + * @param thePatternType The pattern type which will be 3 for radial and 2 for axial + * @param theShading The shading object to determine how the gradient + * is drawn + * @param theXUID The XUID + * @param theExtGState The exit state + */ + public PSPattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState) { + this.patternType = 2; // thePatternType; + assert theShading instanceof PSShading; + this.shading = (PSShading)theShading; + this.xUID = theXUID; + this.extGState = theExtGState; // always null + } + + /** + * Outputs the radial or axial pattern as a string dictionary to insert + * into a postscript document. + */ + public String toString() { + int vectorSize = 0; + int tempInt = 0; + StringBuffer p = new StringBuffer(64); + p.append("/Pattern setcolorspace\n"); + p.append("<< \n/Type /Pattern \n"); + + p.append("/PatternType " + this.patternType + " \n"); + + if (this.shading != null) { + p.append("/Shading " + this.shading.toString() + " \n"); + } + + if (this.xUID != null) { + vectorSize = this.xUID.size(); + p.append("/XUID [ "); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append((this.xUID.get(tempInt)) + " "); + } + p.append("] \n"); + } + + if (this.extGState != null) { + p.append("/ExtGState " + this.extGState + " \n"); + } + + p.append(">> \n"); + p.append("matrix makepattern setcolor\n"); + + return p.toString(); + } +} diff --git a/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java b/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java new file mode 100644 index 000000000..1c15e569b --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java @@ -0,0 +1,294 @@ +/* + * 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.render.ps.svg; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Paint; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.batik.ext.awt.LinearGradientPaint; +import org.apache.batik.ext.awt.MultipleGradientPaint; +import org.apache.batik.ext.awt.RadialGradientPaint; + +import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; +import org.apache.xmlgraphics.ps.PSGenerator; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.GradientFactory; +import org.apache.fop.render.shading.GradientRegistrar; +import org.apache.fop.render.shading.PSGradientFactory; +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; + + +public class PSSVGGraphics2D extends PSGraphics2D implements GradientRegistrar { + + private static final Log LOG = LogFactory.getLog(PSSVGGraphics2D.class); + + /** + * Create a new Graphics2D that generates PostScript code. + * @param textAsShapes True if text should be rendered as graphics + * @see org.apache.xmlgraphics.java2d.AbstractGraphics2D#AbstractGraphics2D(boolean) + */ + public PSSVGGraphics2D(boolean textAsShapes) { + super(textAsShapes); + } + + /** + * Create a new Graphics2D that generates PostScript code. + * @param textAsShapes True if text should be rendered as graphics + * @param gen PostScript generator to use for output + * @see org.apache.xmlgraphics.java2d.AbstractGraphics2D#AbstractGraphics2D(boolean) + */ + public PSSVGGraphics2D(boolean textAsShapes, PSGenerator gen) { + super(textAsShapes, gen); + } + + /** + * Constructor for creating copies + * @param g parent PostScript Graphics2D + */ + public PSSVGGraphics2D(PSGraphics2D g) { + super(g); + } + + protected void applyPaint(Paint paint, boolean fill) { + super.applyPaint(paint, fill); + if (paint instanceof RadialGradientPaint) { + RadialGradientPaint rgp = (RadialGradientPaint)paint; + try { + handleRadialGradient(rgp, gen); + } catch (IOException ioe) { + handleIOException(ioe); + } + } else if (paint instanceof LinearGradientPaint) { + LinearGradientPaint lgp = (LinearGradientPaint)paint; + try { + handleLinearGradient(lgp, gen); + } catch (IOException ioe) { + handleIOException(ioe); + } + } + } + + private void handleLinearGradient(LinearGradientPaint lgp, PSGenerator gen) throws IOException { + MultipleGradientPaint.CycleMethodEnum cycle = lgp.getCycleMethod(); + if (cycle != MultipleGradientPaint.NO_CYCLE) { + return; + } + float[] fractions = lgp.getFractions(); + Color[] cols = lgp.getColors(); + + AffineTransform transform = new AffineTransform(getBaseTransform()); + transform.concatenate(getTransform()); + transform.concatenate(lgp.getTransform()); + + List theMatrix = new ArrayList(); + double [] mat = new double[6]; + transform.getMatrix(mat); + for (int idx = 0; idx < mat.length; idx++) { + theMatrix.add(Double.valueOf(mat[idx])); + } + + + List<Double> theCoords = new java.util.ArrayList<Double>(); + + + AffineTransform start = applyTransform(lgp.getTransform(), + lgp.getStartPoint().getX(), lgp.getStartPoint().getY()); + AffineTransform end = applyTransform(lgp.getTransform(), lgp.getEndPoint().getX(), lgp.getEndPoint().getY()); + double startX = start.getTranslateX(); + double startY = start.getTranslateY(); + double endX = end.getTranslateX(); + double endY = end.getTranslateY(); + + double width = endX - startX; + double height = endY - startY; + + startX = startX + width * fractions[0]; + endX = endX - width * (1 - fractions[fractions.length - 1]); + startY = startY + (height * fractions[0]); + endY = endY - height * (1 - fractions[fractions.length - 1]); + + theCoords.add(startX); + theCoords.add(startY); + theCoords.add(endX); + theCoords.add(endY); + + + List<Color> someColors = new java.util.ArrayList<Color>(); + for (int count = 0; count < cols.length; count++) { + Color c1 = cols[count]; + if (c1.getAlpha() != 255) { + LOG.warn("Opacity is not currently supported for Postscript output"); + } + someColors.add(c1); + } + List<Double> theBounds = new java.util.ArrayList<Double>(); + for (int count = 1; count < fractions.length - 1; count++) { + float offset = fractions[count]; + theBounds.add(Double.valueOf(offset)); + } + PDFDeviceColorSpace colSpace; + colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + + PSGradientFactory gradientFactory = (PSGradientFactory)GradientFactory.newInstance(this); + PSPattern myPattern = gradientFactory.createGradient(false, colSpace, + someColors, theBounds, theCoords, theMatrix); + + gen.write(myPattern.toString()); + + } + + + + private void handleRadialGradient(RadialGradientPaint rgp, PSGenerator gen) throws IOException { + MultipleGradientPaint.CycleMethodEnum cycle = rgp.getCycleMethod(); + if (cycle != MultipleGradientPaint.NO_CYCLE) { + return; + } + + AffineTransform transform; + transform = new AffineTransform(getBaseTransform()); + transform.concatenate(getTransform()); + transform.concatenate(rgp.getTransform()); + + AffineTransform resultCentre = applyTransform(rgp.getTransform(), + rgp.getCenterPoint().getX(), rgp.getCenterPoint().getY()); + AffineTransform resultFocus = applyTransform(rgp.getTransform(), + rgp.getFocusPoint().getX(), rgp.getFocusPoint().getY()); + double scale = Math.sqrt(rgp.getTransform().getDeterminant()); + double radius = rgp.getRadius() * scale; + double centreX = resultCentre.getTranslateX(); + double centreY = resultCentre.getTranslateY(); + double focusX = resultFocus.getTranslateX(); + double focusY = resultFocus.getTranslateY(); + + List<Double> theMatrix = new java.util.ArrayList<Double>(); + double [] mat = new double[6]; + transform.getMatrix(mat); + for (int idx = 0; idx < mat.length; idx++) { + theMatrix.add(Double.valueOf(mat[idx])); + } + + List<Double> theCoords = new java.util.ArrayList<Double>(); + float[] fractions = rgp.getFractions(); + + theCoords.add(centreX); + theCoords.add(centreY); + theCoords.add(radius * rgp.getFractions()[0]); + theCoords.add(focusX); + theCoords.add(focusY); + theCoords.add(radius * fractions[fractions.length - 1]); + + Color[] cols = rgp.getColors(); + List<Color> someColors = new java.util.ArrayList<Color>(); + for (int count = 0; count < cols.length; count++) { + Color cc = cols[count]; + if (cc.getAlpha() != 255) { + /* This should never happen because radial gradients with opacity should now + * be rasterized in the PSImageHandlerSVG class. Please see the shouldRaster() + * method for more information. */ + LOG.warn("Opacity is not currently supported for Postscript output"); + } + + someColors.add(cc); + } + + List<Double> theBounds = new java.util.ArrayList<Double>(); + for (int count = 1; count < fractions.length - 1; count++) { + float offset = fractions[count]; + theBounds.add(Double.valueOf(offset)); + } + PDFDeviceColorSpace colSpace; + colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + + PSGradientFactory gradientFactory = (PSGradientFactory)GradientFactory.newInstance(this); + PSPattern myPattern = gradientFactory.createGradient(true, colSpace, + someColors, theBounds, theCoords, theMatrix); + + gen.write(myPattern.toString()); + } + + private AffineTransform applyTransform(AffineTransform base, double posX, double posY) { + AffineTransform result = AffineTransform.getTranslateInstance(posX, posY); + AffineTransform orig = base; + orig.concatenate(result); + return orig; + } + + protected AffineTransform getBaseTransform() { + AffineTransform at = new AffineTransform(this.getTransform()); + return at; + } + + /** + * Creates a new <code>Graphics</code> object that is + * a copy of this <code>Graphics</code> object. + * @return a new graphics context that is a copy of + * this graphics context. + */ + @Override + public Graphics create() { + preparePainting(); + return new PSSVGGraphics2D(this); + } + + /** + * Registers a function object against the output format document + * @param function The function object to register + * @return Returns either the function which has already been registered + * or the current new registered object. + */ + public Function registerFunction(Function function) { + //Objects aren't needed to be registered in Postscript + return function; + } + + /** + * Registers a shading object against the otuput format document + * @param shading The shading object to register + * @return Returs either the shading which has already been registered + * or the current new registered object + */ + public Shading registerShading(Shading shading) { + //Objects aren't needed to be registered in Postscript + return shading; + } + + /** + * Registers a pattern object against the output format document + * @param pattern The pattern object to register + * @return Returns either the pattern which has already been registered + * or the current new registered object + */ + public Pattern registerPattern(Pattern pattern) { + // TODO Auto-generated method stub + return pattern; + } +} diff --git a/src/java/org/apache/fop/render/ps/svg/PSShading.java b/src/java/org/apache/fop/render/ps/svg/PSShading.java new file mode 100644 index 000000000..7465fcadb --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSShading.java @@ -0,0 +1,228 @@ +/* + * 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.render.ps.svg; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.Shading; +import org.apache.fop.render.shading.ShadingPattern; + +public class PSShading implements Shading { + + /** + * Required: The Type of shading (1,2,3,4,5,6,7) + */ + protected int shadingType = 3; // Default + + /** + * A ColorSpace representing the colorspace. "DeviceRGB" is an example. + */ + protected PDFDeviceColorSpace colorSpace = null; + + /** + * The background color. Since shading is opaque, + * this is very rarely used. + */ + protected List background = null; + + /** + * Optional: A List specifying the clipping rectangle + */ + protected List bBox = null; + + /** + * Optional: A flag whether or not to filter the shading function + * to prevent aliasing artifacts. Default is false. + */ + protected boolean antiAlias = false; + + /** + * Optional for Type 1: Array of four numbers, xmin, xmax, ymin, ymax. + * Default is [0 1 0 1] + * Optional for Type 2: An array of two numbers between which the blend + * varies between start and end points. Default is 0, 1. + * Optional for Type 3: An array of two numbers between which the blend + * varies between start and end points. Default is 0, 1. + */ + protected List domain = null; + + /** + * Required for Type 1, 2, and 3: + * The object of the color mapping function (usually type 2 or 3). + * Optional for Type 4,5,6, and 7: When it's nearly the same thing. + */ + protected PSFunction function = null; + + /** + * Required for Type 2: An Array of four numbers specifying + * the starting and ending coordinate pairs + * Required for Type 3: An Array of six numbers [x0,y0,r0,x1,y1,r1] + * specifying the centers and radii of + * the starting and ending circles. + */ + protected List coords = null; + + /** + * Required for Type 2+3: An Array of two boolean values specifying + * whether to extend the start and end colors past the start + * and end points, respectively. + * Default is false, false. + */ + protected List extend = null; + + /** + * Constructor for Type 2 and 3 + * + * @param theShadingType 2 or 3 for axial or radial shading + * @param theColorSpace "DeviceRGB" or similar. + * @param theBackground theBackground An array of color components appropriate to the + * colorspace key specifying a single color value. + * This key is used by the f operator buy ignored by the sh operator. + * @param theBBox List of double's representing a rectangle + * in the coordinate space that is current at the + * time of shading is imaged. Temporary clipping + * boundary. + * @param theAntiAlias Default is false + * @param theCoords List of four (type 2) or 6 (type 3) Double + * @param theDomain List of Doubles specifying the domain + * @param theFunction the Stitching (PDFfunction type 3) function, + * even if it's stitching a single function + * @param theExtend List of Booleans of whether to extend the start + * and end colors past the start and end points + * The default is [false, false] + */ + public PSShading(int theShadingType, PDFDeviceColorSpace theColorSpace, + List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, + List<Double> theDomain, Function theFunction, + List<Integer> theExtend) { + this.shadingType = theShadingType; // 2 or 3 + this.colorSpace = theColorSpace; + this.background = theBackground; + this.bBox = theBBox; + this.antiAlias = theAntiAlias; + + this.coords = theCoords; + this.domain = theDomain; + assert theFunction instanceof PSFunction; + this.function = (PSFunction)theFunction; + this.extend = theExtend; + } + + /** + * represent as PS. Whatever the shadingType is, the correct + * representation spits out. The sets of required and optional + * attributes are different for each type, but if a required + * attribute's object was constructed as null, then no error + * is raised. Instead, the malformed PS that was requested + * by the construction is dutifully output. + * This policy should be reviewed. + * + * @return the PDF string. + */ + public String toString() { + ShadingPattern pattern = new ShadingPattern(this); + return pattern.toString(colorSpace, shadingType, background, bBox, antiAlias); + } + + /** + * A method to write a type 2 or 3 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType2or3(StringBuffer p) { + if (this.coords != null) { + p.append("\t/Coords [ "); + for (int coordIndex = 0; coordIndex < coords.size(); coordIndex++) { + p.append(PDFNumber.doubleOut((Double)this.coords.get(coordIndex)) + + " "); + } + p.append("] \n"); + } + + // DOMAIN + if (this.domain != null) { + p.append("\t/Domain [ "); + for (int domainIndex = 0; domainIndex < domain.size(); domainIndex++) { + p.append(PDFNumber.doubleOut((Double)this.domain.get(domainIndex)) + + " "); + } + p.append("] \n"); + } else { + p.append("\t/Domain [ 0 1 ] \n"); + } + + if (this.extend != null) { + p.append("\t/Extend [ "); + for (int extendIndex = 0; extendIndex < extend.size(); extendIndex++) { + p.append((this.extend.get(extendIndex)) + " "); + } + + p.append("] \n"); + } else { + p.append("\t/Extend [ true true ] \n"); + } + + + if (this.function != null) { + p.append("\t/Function "); + try { + p.append(new String(this.function.toByteString(), "UTF-8") + " \n"); + } catch (UnsupportedEncodingException ex) { + //This should have been made an enum type to avoid throwing exceptions. + } + } + return p; + } + + /** + * A method to write a type 1 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType1(StringBuffer p) { + // TODO Auto-generated method stub + return null; + } + + /** + * A method to write a type 4, 6 or 7 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType4or6or7(StringBuffer p) { + // TODO Auto-generated method stub + return null; + } + + /** + * A method to write a type 5 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType5(StringBuffer p) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java index dfeff65f1..73fa3504c 100644 --- a/src/java/org/apache/fop/render/rtf/RTFHandler.java +++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java @@ -157,6 +157,7 @@ public class RTFHandler extends FOEventHandler { private PercentContext percentManager = new PercentContext(); + /** * Creates a new RTF structure handler. * @param userAgent the FOUserAgent for this process @@ -270,7 +271,7 @@ public class RTFHandler extends FOEventHandler { return; } else { - builderContext.popContainer(); + builderContext.popContainer(RtfSection.class, this); this.pagemaster = null; } } @@ -377,10 +378,10 @@ public class RTFHandler extends FOEventHandler { //just do nothing } else if (regionBefore != null && fl.getFlowName().equals(regionBefore.getRegionName())) { - builderContext.popContainer(); + builderContext.popContainer(RtfBefore.class, this); } else if (regionAfter != null && fl.getFlowName().equals(regionAfter.getRegionName())) { - builderContext.popContainer(); + builderContext.popContainer(RtfAfter.class, this); } } catch (Exception e) { log.error("endFlow: " + e.getMessage()); @@ -571,7 +572,7 @@ public class RTFHandler extends FOEventHandler { nestedTableDepth--; builderContext.popTableContext(); - builderContext.popContainer(); + builderContext.popContainer(RtfTable.class, this); } /** {@inheritDoc} */ @@ -605,21 +606,25 @@ public class RTFHandler extends FOEventHandler { /** {@inheritDoc} */ public void startHeader(TableHeader header) { + builderContext.pushPart(header); startPart(header); } /** {@inheritDoc} */ public void endHeader(TableHeader header) { + builderContext.popPart(header.getClass(), this); endPart(header); } /** {@inheritDoc} */ public void startFooter(TableFooter footer) { + builderContext.pushPart(footer); startPart(footer); } /** {@inheritDoc} */ public void endFooter(TableFooter footer) { + builderContext.popPart(footer.getClass(), this); endPart(footer); } @@ -711,11 +716,13 @@ public class RTFHandler extends FOEventHandler { * {@inheritDoc} */ public void startBody(TableBody body) { + builderContext.pushPart(body); startPart(body); } /** {@inheritDoc} */ public void endBody(TableBody body) { + builderContext.popPart(TableBody.class, this); endPart(body); } @@ -784,7 +791,7 @@ public class RTFHandler extends FOEventHandler { } - builderContext.popContainer(); + builderContext.popContainer(RtfTableRow.class, this); builderContext.getTableContext().decreaseRowSpannings(); } @@ -893,7 +900,7 @@ public class RTFHandler extends FOEventHandler { throw new RuntimeException(e.getMessage()); } - builderContext.popContainer(); + builderContext.popContainer(RtfTableCell.class, this); builderContext.getTableContext().selectNextColumn(); } @@ -929,7 +936,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfList.class, this); } /** {@inheritDoc} */ @@ -976,7 +983,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfListItem.class, this); } /** {@inheritDoc} */ @@ -1005,7 +1012,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfListItemLabel.class, this); } /** {@inheritDoc} */ @@ -1070,7 +1077,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfHyperLink.class, this); } /** {@inheritDoc} */ @@ -1306,7 +1313,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfFootnote.class, this); } /** {@inheritDoc} */ @@ -1688,6 +1695,19 @@ public class RTFHandler extends FOEventHandler { } /** + * Closes any mismatched tags that are detected in the RTF structure. + * @param containerClass The class representing the tag to close. + * @return Determines whether the tag mismatch has been handled. + */ + public boolean endContainer(Class containerClass) { + if (containerClass == RtfTableRow.class) { + endRow(null); + return true; + } + return false; + } + + /** * Calls the event handlers for the passed FONode and all its elements. * * @param foNode FONode object which shall be recursed diff --git a/src/java/org/apache/fop/render/rtf/RTFPlaceHolderHelper.java b/src/java/org/apache/fop/render/rtf/RTFPlaceHolderHelper.java new file mode 100644 index 000000000..e3278ad95 --- /dev/null +++ b/src/java/org/apache/fop/render/rtf/RTFPlaceHolderHelper.java @@ -0,0 +1,74 @@ +/* + * 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.render.rtf; + +import org.apache.fop.render.rtf.rtflib.exceptions.RtfException; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfAttributes; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfContainer; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTable; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTableRow; +import org.apache.fop.render.rtf.rtflib.tools.BuilderContext; + +/** + * This class creates objects which are missing from the XSL:FO but are required + * by the RTF format. + */ +public class RTFPlaceHolderHelper { + /** The context object for building the RTF */ + private BuilderContext builderContext; + + /** + * Creates a new instance for the RTF place holder which attempts to resolve + * mismatches in structure between XSL:FO and RTF. + * @param builderContext The builder context + */ + public RTFPlaceHolderHelper(BuilderContext builderContext) { + this.builderContext = builderContext; + } + + /** + * A method to create an object which is missing and required from the + * RTF structure. + * @param containerClass The class which is missing + * @throws Exception + */ + public void createRTFPlaceholder(Class containerClass) throws RtfException { + if (containerClass == RtfTableRow.class) { + createRtfTableRow(); + } + } + + private void createRtfTableRow() throws RtfException { + try { + RtfContainer element = builderContext.getContainer(RtfTable.class, true, null); + if (element != null && element instanceof RtfTable) { + RtfTable table = (RtfTable)element; + RtfAttributes attribs = new RtfAttributes(); + RtfTableRow newRow = table.newTableRow(attribs); + builderContext.pushContainer(newRow); + builderContext.getTableContext().selectFirstColumn(); + } + } catch (org.apache.fop.apps.FOPException e) { + throw new RtfException(e.getMessage()); + } catch (java.io.IOException e) { + throw new RtfException(e.getMessage()); + } + } +} diff --git a/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java b/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java index f0a29a0ab..4b3f8bd4a 100644 --- a/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java +++ b/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java @@ -21,6 +21,12 @@ package org.apache.fop.render.rtf.rtflib.tools; import java.util.Stack; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fo.FObj; +import org.apache.fop.render.rtf.RTFHandler; +import org.apache.fop.render.rtf.RTFPlaceHolderHelper; import org.apache.fop.render.rtf.rtflib.exceptions.RtfException; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfOptions; import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfContainer; @@ -38,6 +44,10 @@ import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfContainer; */ public class BuilderContext { + + /** Static logging instance */ + protected static final Log LOG = LogFactory.getLog(BuilderContext.class.getName()); + /** stack of RtfContainers */ private final Stack containers = new Stack(); @@ -96,17 +106,22 @@ public class BuilderContext { * @throws RtfException if not caught */ public RtfContainer getContainer(Class containerClass, boolean required, - Object /*IBuilder*/ forWhichBuilder) throws RtfException { + Object forWhichBuilder) throws RtfException { // TODO what to do if the desired container is not at the top of the stack? // close top-of-stack container? - final RtfContainer result = (RtfContainer)getObjectFromStack(containers, + RtfContainer result = (RtfContainer)getObjectFromStack(containers, containerClass); if (result == null && required) { - throw new RtfException( - "No RtfContainer of class '" + containerClass.getName() - + "' available for '" + forWhichBuilder.getClass().getName() + "' builder" - ); + RTFPlaceHolderHelper placeHolderHelper = new RTFPlaceHolderHelper(this); + placeHolderHelper.createRTFPlaceholder(containerClass); + result = getContainer(containerClass, required, forWhichBuilder); + if (result == null) { + throw new RtfException( + "No RtfContainer of class '" + containerClass.getName() + + "' available for '" + forWhichBuilder.getClass().getName() + "' builder" + ); + } } return result; @@ -121,6 +136,14 @@ public class BuilderContext { } /** + * Push a Class representing a non-writeable section of the FO tree + * @param part the part + */ + public void pushPart(FObj part) { + containers.push(part); + } + + /** * In some cases an RtfContainer must be replaced by another one on the * stack. This happens when handling nested fo:blocks for example: after * handling a nested block the enclosing block must switch to a new @@ -142,8 +165,40 @@ public class BuilderContext { } /** pop the topmost RtfContainer from our stack */ - public void popContainer() { - containers.pop(); + public void popContainer(Class containerClass, RTFHandler handler) { + handlePop(containerClass, handler); + } + + /** pop the topmost part class from our stack */ + public void popPart(Class part, RTFHandler handler) { + handlePop(part, handler); + } + + /** + * This method checks for any tag mismatches between what is being closed + * and what exists on the stack. If a mismatch is found, then it will push + * the object back onto the stack and attempt to close the given section + * before retrying with the current pop task. + * @param aClass The class to be popped from the stack + * @param handler The RTF handler class to fix any mismatches + */ + private void handlePop(Class aClass, RTFHandler handler) { + Object object = containers.pop(); + if (object.getClass() != aClass) { + pushAndClose(aClass, object, handler); + } + } + + private void pushAndClose(Class aClass, Object object, RTFHandler handler) { + containers.push(object); + if (handler.endContainer(object.getClass())) { + popContainer(aClass, handler); + } else { + /* This should never happen unless a placeholder is not catered for + * in the RTFHandler.endContainer method. */ + LOG.warn("Unhandled RTF structure tag mismatch detected between " + + aClass.getSimpleName() + " and " + object.getClass().getSimpleName()); + } } /* push an IBuilder to our stack / diff --git a/src/java/org/apache/fop/render/shading/Function.java b/src/java/org/apache/fop/render/shading/Function.java new file mode 100644 index 000000000..5bd44087e --- /dev/null +++ b/src/java/org/apache/fop/render/shading/Function.java @@ -0,0 +1,39 @@ +/* + * 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.render.shading; + +import java.util.List; + +public interface Function { + int getFunctionType(); + List<Double> getBounds(); + List<Double> getDomain(); + List<Double> getSize(); + List<String> getFilter(); + List<Double> getEncode(); + List<Function> getFunctions(); + int getBitsPerSample(); + double getInterpolationExponentN(); + int getOrder(); + List<Double> getRange(); + List<Double> getDecode(); + StringBuffer getDataStream(); + List<Double> getCZero(); + List<Double> getCOne(); + byte[] toByteString(); +} diff --git a/src/java/org/apache/fop/render/shading/FunctionDelegate.java b/src/java/org/apache/fop/render/shading/FunctionDelegate.java new file mode 100644 index 000000000..eea24e0db --- /dev/null +++ b/src/java/org/apache/fop/render/shading/FunctionDelegate.java @@ -0,0 +1,451 @@ +/* + * 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.render.shading; + +import java.util.List; + +public class FunctionDelegate implements Function { + + private Function parentFunction; + + /** + * Required: The Type of function (0,2,3,4) default is 0. + */ + protected int functionType = 0; // Default + + /** + * Required: 2 * m Array of Double numbers which are possible inputs to the function + */ + protected List<Double> domain = null; + + /** + * Required: 2 * n Array of Double numbers which are possible outputs to the function + */ + protected List<Double> range = null; + + /* ********************TYPE 0***************************** */ + // FunctionType 0 specific function guts + + /** + * Required: Array containing the Integer size of the Domain and Range, respectively. + * Note: This is really more like two seperate integers, sizeDomain, and sizeRange, + * but since they're expressed as an array in PDF, my implementation reflects that. + */ + protected List<Double> size = null; + + /** + * Required for Type 0: Number of Bits used to represent each sample value. + * Limited to 1,2,4,8,12,16,24, or 32 + */ + protected int bitsPerSample = 1; + + /** + * Optional for Type 0: order of interpolation between samples. + * Limited to linear (1) or cubic (3). Default is 1 + */ + protected int order = 1; + + /** + * Optional for Type 0: A 2 * m array of Doubles which provides a + * linear mapping of input values to the domain. + * + * Required for Type 3: A 2 * k array of Doubles that, taken + * in pairs, map each subset of the domain defined by Domain + * and the Bounds array to the domain of the corresponding function. + * Should be two values per function, usually (0,1), + * as in [0 1 0 1] for 2 functions. + */ + protected List<Double> encode = null; + + /** + * Optional for Type 0: A 2 * n array of Doubles which provides + * a linear mapping of sample values to the range. Defaults to Range. + */ + protected List<Double> decode = null; + + /** + * Optional For Type 0: A stream of sample values + */ + + /** + * Required For Type 4: Postscript Calculator function + * composed of arithmetic, boolean, and stack operators + boolean constants + */ + protected StringBuffer functionDataStream = null; + + /** + * Required (possibly) For Type 0: A vector of Strings for the + * various filters to be used to decode the stream. + * These are how the string is compressed. Flate, LZW, etc. + */ + protected List<String> filter = null; + /* *************************TYPE 2************************** */ + + /** + * Required For Type 2: An Array of n Doubles defining + * the function result when x=0. Default is [0]. + */ + protected List<Double> cZero = null; + + /** + * Required For Type 2: An Array of n Doubles defining + * the function result when x=1. Default is [1]. + */ + protected List<Double> cOne = null; + + /** + * Required for Type 2: The interpolation exponent. + * Each value x will return n results. + * Must be greater than 0. + */ + protected double interpolationExponentN = 1; + + /* *************************TYPE 3************************** */ + + /** + * Required for Type 3: An vector of PDFFunctions which + * form an array of k single input functions making up + * the stitching function. + */ + protected List<Function> functions = null; + + /** + * Optional for Type 3: An array of (k-1) Doubles that, + * in combination with Domain, define the intervals to which + * each function from the Functions array apply. Bounds + * elements must be in order of increasing magnitude, + * and each value must be within the value of Domain. + * k is the number of functions. + * If you pass null, it will output (1/k) in an array of k-1 elements. + * This makes each function responsible for an equal amount of the stitching function. + * It makes the gradient even. + */ + protected List<Double> bounds = null; + + /** + * create an complete Function object of Type 0, A Sampled function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List objects of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List objects of Double objects. + * This is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theSize A List object of Integer objects. + * This is the number of samples in each input dimension. + * I can't imagine there being more or less than two input dimensions, + * so maybe this should be an array of length 2. + * + * See page 265 of the PDF 1.3 Spec. + * @param theBitsPerSample An int specifying the number of bits + used to represent each sample value. + * Limited to 1,2,4,8,12,16,24 or 32. + * See page 265 of the 1.3 PDF Spec. + * @param theOrder The order of interpolation between samples. Default is 1 (one). Limited + * to 1 (one) or 3, which means linear or cubic-spline interpolation. + * + * This attribute is optional. + * + * See page 265 in the PDF 1.3 spec. + * @param theEncode List objects of Double objects. + * This is the linear mapping of input values intop the domain + * of the function's sample table. Default is hard to represent in + * ascii, but basically [0 (Size0 1) 0 (Size1 1)...]. + * This attribute is optional. + * + * See page 265 in the PDF 1.3 spec. + * @param theDecode List objects of Double objects. + * This is a linear mapping of sample values into the range. + * The default is just the range. + * + * This attribute is optional. + * Read about it on page 265 of the PDF 1.3 spec. + * @param theFunctionDataStream The sample values that specify + * the function are provided in a stream. + * + * This is optional, but is almost always used. + * + * Page 265 of the PDF 1.3 spec has more. + * @param theFilter This is a vector of String objects which are the various filters that + * have are to be applied to the stream to make sense of it. Order matters, + * so watch out. + * + * This is not documented in the Function section of the PDF 1.3 spec, + * it was deduced from samples that this is sometimes used, even if we may never + * use it in FOP. It is added for completeness sake. + * @param theFunctionType This is the type of function (0,2,3, or 4). + * It should be 0 as this is the constructor for sampled functions. + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theSize, int theBitsPerSample, + int theOrder, List<Double> theEncode, List<Double> theDecode, + StringBuffer theFunctionDataStream, List<String> theFilter) { + this.parentFunction = parentFunction; + this.functionType = 0; // dang well better be 0; + this.size = theSize; + this.bitsPerSample = theBitsPerSample; + this.order = theOrder; // int + this.encode = theEncode; // vector of int + this.decode = theDecode; // vector of int + this.functionDataStream = theFunctionDataStream; + this.filter = theFilter; // vector of Strings + + // the domain and range are actually two dimensional arrays. + // so if there's not an even number of items, bad stuff + // happens. + this.domain = theDomain; + this.range = theRange; + } + + /** + * create an complete Function object of Type 2, an Exponential Interpolation function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List objects of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List of Doubles that is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theCZero This is a vector of Double objects which defines the function result + * when x=0. + * + * This attribute is optional. + * It's described on page 268 of the PDF 1.3 spec. + * @param theCOne This is a vector of Double objects which defines the function result + * when x=1. + * + * This attribute is optional. + * It's described on page 268 of the PDF 1.3 spec. + * @param theInterpolationExponentN This is the inerpolation exponent. + * + * This attribute is required. + * PDF Spec page 268 + * @param theFunctionType The type of the function, which should be 2. + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + this.parentFunction = parentFunction; + this.functionType = 2; // dang well better be 2; + + this.cZero = theCZero; + this.cOne = theCOne; + this.interpolationExponentN = theInterpolationExponentN; + + this.domain = theDomain; + this.range = theRange; + + } + + /** + * create an complete Function object of Type 3, a Stitching function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List objects of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List objects of Double objects. + * This is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theFunctions A List of the PDFFunction objects that the stitching function stitches. + * + * This attributed is required. + * It is described on page 269 of the PDF spec. + * @param theBounds This is a vector of Doubles representing the numbers that, + * in conjunction with Domain define the intervals to which each function from + * the 'functions' object applies. It must be in order of increasing magnitude, + * and each must be within Domain. + * + * It basically sets how much of the gradient each function handles. + * + * This attributed is required. + * It's described on page 269 of the PDF 1.3 spec. + * @param theEncode List objects of Double objects. + * This is the linear mapping of input values intop the domain + * of the function's sample table. Default is hard to represent in + * ascii, but basically [0 (Size0 1) 0 (Size1 1)...]. + * This attribute is required. + * + * See page 270 in the PDF 1.3 spec. + * @param theFunctionType This is the function type. It should be 3, + * for a stitching function. + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + this.parentFunction = parentFunction; + this.functionType = 3; // dang well better be 3; + + this.functions = theFunctions; + this.bounds = theBounds; + this.encode = theEncode; + this.domain = theDomain; + this.range = theRange; + + } + + /** + * create an complete Function object of Type 4, a postscript calculator function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List object of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List object of Double objects. + * This is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theFunctionDataStream This is a stream of arithmetic, + * boolean, and stack operators and boolean constants. + * I end up enclosing it in the '{' and '}' braces for you, so don't do it + * yourself. + * + * This attribute is required. + * It's described on page 269 of the PDF 1.3 spec. + * @param theFunctionType The type of function which should be 4, as this is + * a Postscript calculator function + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, StringBuffer theFunctionDataStream) { + this.parentFunction = parentFunction; + this.functionType = 4; // dang well better be 4; + this.functionDataStream = theFunctionDataStream; + + this.domain = theDomain; + + this.range = theRange; + + } + + /** + * Gets the function type + */ + public int getFunctionType() { + return functionType; + } + + /** + * Gets the function bounds + */ + public List<Double> getBounds() { + return bounds; + } + + /** + * The function domain + */ + public List<Double> getDomain() { + return domain; + } + + /** + * The function size + */ + public List<Double> getSize() { + return size; + } + + /** + * Gets the function encoding + */ + public List<Double> getEncode() { + return encode; + } + + /** + * Gets the sub-functions + */ + public List<Function> getFunctions() { + return functions; + } + + /** + * Gets the function filter + */ + public List<String> getFilter() { + return filter; + } + + /** + * Gets the bits per sample of the function + */ + public int getBitsPerSample() { + return bitsPerSample; + } + + /** + * Gets the interpolation exponent of the function + */ + public double getInterpolationExponentN() { + return interpolationExponentN; + } + + /** + * Gets the function order + */ + public int getOrder() { + return order; + } + + /** + * Gets the function range + */ + public List<Double> getRange() { + return range; + } + + /** + * Gets the function decoding + */ + public List<Double> getDecode() { + return decode; + } + + /** + * Gets the function data stream + */ + public StringBuffer getDataStream() { + return functionDataStream; + } + + /** + * Gets the function C0 value (color for gradient) + */ + public List<Double> getCZero() { + return cZero; + } + + /** + * Gets the function C1 value (color for gradient) + */ + public List<Double> getCOne() { + return cOne; + } + + public byte[] toByteString() { + return parentFunction.toByteString(); + } +} diff --git a/src/java/org/apache/fop/render/shading/FunctionPattern.java b/src/java/org/apache/fop/render/shading/FunctionPattern.java new file mode 100644 index 000000000..044053a8b --- /dev/null +++ b/src/java/org/apache/fop/render/shading/FunctionPattern.java @@ -0,0 +1,363 @@ +/* + * 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.render.shading; + +import java.io.UnsupportedEncodingException; + +import org.apache.fop.pdf.PDFFunction; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.render.ps.svg.PSFunction; + +/** + * A class for writing function objects for different output formats + */ +public class FunctionPattern { + + private Function function; + + /** + * Constructor + * @param function The function from which to write the output + */ + public FunctionPattern(Function function) { + this.function = function; + } + + /** + * Outputs the function to a byte array + */ + public String toWriteableString() { + int vectorSize = 0; + int numberOfFunctions = 0; + int tempInt = 0; + StringBuffer p = new StringBuffer(256); + p.append("<< \n/FunctionType " + function.getFunctionType() + " \n"); + + // FunctionType 0 + if (this.function.getFunctionType() == 0) { + if (function.getDomain() != null) { + // DOMAIN + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + // SIZE + if (function.getSize() != null) { + p.append("/Size [ "); + vectorSize = function.getSize().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getSize().get(tempInt)) + + " "); + } + p.append("] \n"); + } + // ENCODE + if (function.getEncode() != null) { + p.append("/Encode [ "); + vectorSize = function.getEncode().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getEncode().get(tempInt)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Encode [ "); + vectorSize = function.getFunctions().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append("0 1 "); + } + p.append("] \n"); + + } + + // BITSPERSAMPLE + p.append("/BitsPerSample " + function.getBitsPerSample()); + + // ORDER (optional) + if (function.getOrder() == 1 || function.getOrder() == 3) { + p.append(" \n/Order " + function.getOrder() + " \n"); + } + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // DECODE + if (function.getDecode() != null) { + p.append("/Decode [ "); + vectorSize = function.getDecode().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDecode().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // LENGTH + if (function.getDataStream() != null) { + p.append("/Length " + (function.getDataStream().length() + 1) + + " \n"); + } + + // FILTER? + if (function.getFilter() != null) { // if there's a filter + vectorSize = function.getFilter().size(); + p.append("/Filter "); + if (vectorSize == 1) { + p.append("/" + (function.getFilter().get(0)) + + " \n"); + } else { + p.append("[ "); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append("/" + (function.getFilter().get(0)) + + " "); + } + p.append("] \n"); + } + } + p.append(">>"); + + // stream representing the function + if (function.getDataStream() != null) { + p.append("\nstream\n" + function.getDataStream() + + "\nendstream"); + } + + // end of if FunctionType 0 + + } else if (function.getFunctionType() == 2) { + // DOMAIN + if (function.getDomain() != null) { + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // FunctionType, C0, C1, N are required in PDF + + // C0 + if (function.getCZero() != null) { + p.append("/C0 [ "); + vectorSize = function.getCZero().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getCZero().get(tempInt)) + + " "); + } + p.append("] \n"); + } + + // C1 + if (function.getCOne() != null) { + p.append("/C1 [ "); + vectorSize = function.getCOne().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getCOne().get(tempInt)) + + " "); + } + p.append("] \n"); + } + + // N: The interpolation Exponent + p.append("/N " + + PDFNumber.doubleOut(Double.valueOf(function.getInterpolationExponentN())) + + " \n"); + + p.append(">>"); + + } else if (function.getFunctionType() + == 3) { // fix this up when my eyes uncross + // DOMAIN + if (function.getDomain() != null) { + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // FUNCTIONS + if (function.getFunctions() != null) { + p.append("/Functions [ "); + numberOfFunctions = function.getFunctions().size(); + for (tempInt = 0; tempInt < numberOfFunctions; tempInt++) { + try { + if (function instanceof PSFunction) { + p.append(new String(function.getFunctions().get(tempInt).toByteString(), "UTF-8") + + " "); + } else { + p.append(((PDFFunction)function.getFunctions().get(tempInt)).referencePDF() + + " "); + } + } catch (UnsupportedEncodingException ex) { + //This should have been made an enum type to avoid throwing exceptions. + } + } + p.append("] \n"); + } + + + // ENCODE + if (function.getEncode() != null) { + p.append("/Encode [ "); + vectorSize = function.getEncode().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getEncode().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Encode [ "); + vectorSize = function.getFunctions().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append("0 1 "); + } + p.append("] \n"); + + } + + + // BOUNDS, required, but can be empty + p.append("/Bounds [ "); + if (function.getBounds() != null) { + + vectorSize = function.getBounds().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getBounds().get(tempInt)) + + " "); + } + + } else { + if (function.getFunctions() != null) { + // if there are n functions, + // there must be n-1 bounds. + // so let each function handle an equal portion + // of the whole. e.g. if there are 4, then [ 0.25 0.25 0.25 ] + + String functionsFraction = PDFNumber.doubleOut(Double.valueOf(1.0 + / (numberOfFunctions))); + + for (tempInt = 0; tempInt + 1 < numberOfFunctions; + tempInt++) { + + p.append(functionsFraction + " "); + } + } + + } + p.append("]\n>>"); + } else if (function.getFunctionType() + == 4) { // fix this up when my eyes uncross + // DOMAIN + if (function.getDomain() != null) { + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // LENGTH + if (function.getDataStream() != null) { + p.append("/Length " + (function.getDataStream().length() + 1) + + " \n"); + } + + p.append(">>"); + + // stream representing the function + if (function.getDataStream() != null) { + p.append("\nstream\n{ " + function.getDataStream() + + " }\nendstream"); + } + } + return p.toString(); + } +} diff --git a/src/java/org/apache/fop/render/shading/GradientFactory.java b/src/java/org/apache/fop/render/shading/GradientFactory.java new file mode 100644 index 000000000..87ac11c83 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/GradientFactory.java @@ -0,0 +1,162 @@ +/* + * 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.render.shading; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + +import org.apache.xmlgraphics.java2d.color.ColorUtil; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.render.ps.svg.PSSVGGraphics2D; + +public abstract class GradientFactory { + + static GradientRegistrar registrar; + + /** + * Constructor + * @param registrar The object used to register new embedded objects in the + * output format. + */ + public static GradientFactory newInstance(GradientRegistrar theRegistrar) { + registrar = theRegistrar; + if (registrar instanceof PSSVGGraphics2D) { + return new PSGradientFactory(); + } else { + return new PDFGradientFactory(); + } + } + + /** + * Creates a new gradient + * @param radial Determines whether the gradient is radial + * @param theColorspace The colorspace used in PDF and Postscript + * @param theColors The colors to be used in the gradient + * @param theBounds The bounds of each color + * @param theCoords The co-ordinates of the gradient + * @param theMatrix The matrix for any transformations + * @return Returns the Pattern object of the gradient + */ + public abstract Pattern createGradient(boolean radial, + PDFDeviceColorSpace theColorspace, List<Color> theColors, List<Double> theBounds, + List<Double> theCoords, List<Double> theMatrix); + + protected Pattern makeGradient(boolean radial, PDFDeviceColorSpace theColorspace, + List<Color> theColors, List<Double> theBounds, + List<Double> theCoords, List<Double> theMatrix) { + Shading myShad; + Function myfunky; + Function myfunc; + List<Double> theCzero; + List<Double> theCone; + double interpolation = 1.000; + List<Function> theFunctions = new ArrayList<Function>(); + + int currentPosition; + int lastPosition = theColors.size() - 1; + + + // if 5 elements, the penultimate element is 3. + // do not go beyond that, because you always need + // to have a next color when creating the function. + + for (currentPosition = 0; currentPosition < lastPosition; + currentPosition++) { // for every consecutive color pair + Color currentColor = theColors.get(currentPosition); + Color nextColor = theColors.get(currentPosition + 1); + + // colorspace must be consistent, so we simply convert to sRGB where necessary + if (!currentColor.getColorSpace().isCS_sRGB()) { + //Convert to sRGB + currentColor = ColorUtil.toSRGBColor(currentColor); + theColors.set(currentPosition, currentColor); + } + if (!nextColor.getColorSpace().isCS_sRGB()) { + //Convert to sRGB + nextColor = ColorUtil.toSRGBColor(nextColor); + theColors.set(currentPosition + 1, nextColor); + } + + theCzero = toColorVector(currentColor); + theCone = toColorVector(nextColor); + + myfunc = makeFunction(2, null, null, theCzero, theCone, + interpolation); + + theFunctions.add(myfunc); + + } // end of for every consecutive color pair + + myfunky = makeFunction(3, null, null, theFunctions, theBounds, + null); + + if (radial) { + if (theCoords.size() == 6) { + // make Shading of Type 2 or 3 + myShad = makeShading(3, theColorspace, null, null, false, theCoords, + null, myfunky, null); + } else { // if the center x, center y, and radius specifiy + // the gradient, then assume the same center x, center y, + // and radius of zero for the other necessary component + List<Double> newCoords = new ArrayList<Double>(); + newCoords.add(theCoords.get(0)); + newCoords.add(theCoords.get(1)); + newCoords.add(theCoords.get(2)); + newCoords.add(theCoords.get(0)); + newCoords.add(theCoords.get(1)); + newCoords.add(Double.valueOf(0.0)); + + myShad = makeShading(3, theColorspace, null, null, false, newCoords, + null, myfunky, null); + } + } else { + myShad = makeShading(2, theColorspace, null, null, false, theCoords, + null, myfunky, null); + } + return makePattern(2, myShad, null, null, theMatrix); + } + + public abstract Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode); + + public abstract Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN); + + public abstract Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain, + Function theFunction, List<Integer> theExtend); + + public abstract Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List<Double> theMatrix); + + private List<Double> toColorVector(Color nextColor) { + List<Double> vector = new java.util.ArrayList<Double>(); + float[] comps = nextColor.getColorComponents(null); + for (int i = 0, c = comps.length; i < c; i++) { + vector.add(Double.valueOf(comps[i])); + } + return vector; + } +} diff --git a/src/java/org/apache/fop/render/shading/GradientRegistrar.java b/src/java/org/apache/fop/render/shading/GradientRegistrar.java new file mode 100644 index 000000000..617fcd4fb --- /dev/null +++ b/src/java/org/apache/fop/render/shading/GradientRegistrar.java @@ -0,0 +1,45 @@ +/* + * 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.render.shading; + +public interface GradientRegistrar { + + /** + * Registers a function object against the output format document + * @param function The function object to register + * @return Returns either the function which has already been registered + * or the current new registered object. + */ + Function registerFunction(Function function); + + /** + * Registers a shading object against the output format document + * @param shading The shading object to register + * @return Returns either the shading which has already been registered + * or the current new registered object + */ + Shading registerShading(Shading shading); + + /** + * Registers a pattern object against the output format document + * @param pattern The pattern object to register + * @return Returns either the pattern which has already been registered + * or the current new registered object + */ + Pattern registerPattern(Pattern pattern); +} diff --git a/src/java/org/apache/fop/render/shading/PDFGradientFactory.java b/src/java/org/apache/fop/render/shading/PDFGradientFactory.java new file mode 100644 index 000000000..3b3dcab75 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/PDFGradientFactory.java @@ -0,0 +1,76 @@ +/* + * 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.render.shading; + +import java.awt.Color; +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFFunction; +import org.apache.fop.pdf.PDFPattern; +import org.apache.fop.pdf.PDFShading; + +public class PDFGradientFactory extends GradientFactory { + + @Override + public PDFPattern createGradient(boolean radial, PDFDeviceColorSpace theColorspace, List<Color> theColors, + List<Double> theBounds, List<Double> theCoords, List<Double> theMatrix) { + return (PDFPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + @Override + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + Function newFunction = new PDFFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + newFunction = registrar.registerFunction(newFunction); + return newFunction; + } + + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + Function newFunction = new PDFFunction(functionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + newFunction = registrar.registerFunction(newFunction); + return newFunction; + } + + @Override + public Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain, + Function theFunction, List<Integer> theExtend) { + Shading newShading = new PDFShading(theShadingType, theColorSpace, theBackground, + theBBox, theAntiAlias, theCoords, theDomain, theFunction, theExtend); + newShading = registrar.registerShading(newShading); + return newShading; + } + + @Override + public Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List<Double> theMatrix) { + Pattern newPattern = new PDFPattern(thePatternType, theShading, theXUID, theExtGState, + theMatrix); + newPattern = registrar.registerPattern(newPattern); + return newPattern; + } + +} diff --git a/src/java/org/apache/fop/render/shading/PSGradientFactory.java b/src/java/org/apache/fop/render/shading/PSGradientFactory.java new file mode 100644 index 000000000..cd47de93a --- /dev/null +++ b/src/java/org/apache/fop/render/shading/PSGradientFactory.java @@ -0,0 +1,70 @@ +/* + * 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.render.shading; + +import java.awt.Color; +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.render.ps.svg.PSFunction; +import org.apache.fop.render.ps.svg.PSPattern; +import org.apache.fop.render.ps.svg.PSShading; + +public class PSGradientFactory extends GradientFactory { + + @Override + public PSPattern createGradient(boolean radial, PDFDeviceColorSpace theColorspace, + List<Color> theColors, List<Double> theBounds, List<Double> theCoords, + List<Double> theMatrix) { + return (PSPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + return newFunction; + } + + @Override + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + return newFunction; + } + + @Override + public Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain, + Function theFunction, List<Integer> theExtend) { + Shading newShading = new PSShading(theShadingType, theColorSpace, theBackground, theBBox, + theAntiAlias, theCoords, theDomain, theFunction, theExtend); + return newShading; + } + + @Override + public Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List<Double> theMatrix) { + return new PSPattern(thePatternType, theShading, theXUID, theExtGState); + } +} diff --git a/src/java/org/apache/fop/render/shading/Pattern.java b/src/java/org/apache/fop/render/shading/Pattern.java new file mode 100644 index 000000000..b66926e53 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/Pattern.java @@ -0,0 +1,22 @@ +/* + * 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.render.shading; + +public interface Pattern { + +} diff --git a/src/java/org/apache/fop/render/shading/Shading.java b/src/java/org/apache/fop/render/shading/Shading.java new file mode 100644 index 000000000..385cb112b --- /dev/null +++ b/src/java/org/apache/fop/render/shading/Shading.java @@ -0,0 +1,26 @@ +/* + * 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.render.shading; + + +public interface Shading { + StringBuffer handleShadingType1(StringBuffer p); + StringBuffer handleShadingType2or3(StringBuffer p); + StringBuffer handleShadingType4or6or7(StringBuffer p); + StringBuffer handleShadingType5(StringBuffer p); +} diff --git a/src/java/org/apache/fop/render/shading/ShadingPattern.java b/src/java/org/apache/fop/render/shading/ShadingPattern.java new file mode 100644 index 000000000..6dac65f55 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/ShadingPattern.java @@ -0,0 +1,105 @@ +/* + * 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.render.shading; + +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFNumber; + +/** + * A class for writing shading objects for different output formats + */ +public class ShadingPattern { + + private Shading shading; + + /** + * Constructor + * @param shading The shading object from which to write the output + */ + public ShadingPattern(Shading shading) { + this.shading = shading; + } + + /** + * Outputs the given shading object to a String + * @param colorSpace The Colospace (PDF and Postscript) + * @param shadingType The shading type + * @param background The background + * @param bBox The bounding box + * @param antiAlias Anti-aliasing + * @return Returns the output string + */ + public String toString(PDFDeviceColorSpace colorSpace, int shadingType, List background, + List bBox, boolean antiAlias) { + StringBuffer p = new StringBuffer(128); + p.append("<<\n/ShadingType " + shadingType + " \n"); + if (colorSpace != null) { + p.append("/ColorSpace /" + + colorSpace.getName() + " \n"); + } + + if (background != null) { + p.append("/Background [ "); + for (int bgIndex = 0; bgIndex < background.size(); bgIndex++) { + p.append(PDFNumber.doubleOut((Double)background.get(bgIndex)) + + " "); + } + p.append("] \n"); + } + + if (bBox + != null) { // I've never seen an example, so I guess this is right. + p.append("/BBox [ "); + for (int bboxIndex = 0; bboxIndex < bBox.size(); bboxIndex++) { + p.append(PDFNumber.doubleOut((Double)bBox.get(bboxIndex)) + + " "); + } + p.append("] \n"); + } + + if (antiAlias) { + p.append("/AntiAlias " + antiAlias + " \n"); + } + + // Here's where we differentiate based on what type it is. + switch (shadingType) { + //Function based shading + case 1: p = shading.handleShadingType1(p); break; + //Axial shading + case 2: + //Radial shading + case 3: p = shading.handleShadingType2or3(p); break; + //Free-form Gouraud-shaded triangle meshes + case 4: + //Coons patch meshes + case 6: + //Tensor product patch meshes + case 7: p = shading.handleShadingType4or6or7(p); break; + //Lattice Free form gouraud-shaded triangle mesh + case 5: p = shading.handleShadingType5(p); break; + default: //Shading pattern outside expecting values + break; + } + + p.append(">>"); + + return (p.toString()); + } +} diff --git a/src/java/org/apache/fop/render/txt/TXTRenderer.java b/src/java/org/apache/fop/render/txt/TXTRenderer.java index bf4a46e19..5b398711f 100644 --- a/src/java/org/apache/fop/render/txt/TXTRenderer.java +++ b/src/java/org/apache/fop/render/txt/TXTRenderer.java @@ -583,6 +583,14 @@ public class TXTRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ + protected void startLayer(String layer) { + } + + /** {@inheritDoc} */ + protected void endLayer() { + } + + /** {@inheritDoc} */ protected void concatenateTransformationMatrix(AffineTransform at) { currentState.push(new CTM(UnitConv.ptToMpt(at))); } diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 2212da434..68e2e3c62 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -545,6 +545,16 @@ public class XMLRenderer extends AbstractXMLRenderer { //only necessary for graphical output } + /** {@inheritDoc} */ + protected void startLayer(String layer) { + //only necessary for graphical output + } + + /** {@inheritDoc} */ + protected void endLayer() { + //only necessary for graphical output + } + /** * {@inheritDoc} * org.apache.fop.area.inline.InlineArea) diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java index e91e2231c..cb2b58c40 100644 --- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java @@ -278,7 +278,6 @@ public class PDFDocumentGraphics2D extends PDFGraphics2D { PDFStream pdfStream = this.pdfDoc.getFactory().makeStream( PDFFilterList.CONTENT_FILTER, false); pdfStream.add(getString()); - currentStream = null; this.pdfDoc.registerObject(pdfStream); pdfContext.getCurrentPage().setContents(pdfStream); PDFAnnotList annots = pdfContext.getCurrentPage().getAnnotations(); diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java index 0c0335a8b..6f2d4429d 100644 --- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java +++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java @@ -88,7 +88,8 @@ public class PDFDocumentGraphics2DConfigurator { final boolean strict = false; if (cfg != null) { URI thisUri = new File(".").getAbsoluteFile().toURI(); - InternalResourceResolver resourceResolver = ResourceResolverFactory.createDefaultInternalResourceResolver(thisUri); + InternalResourceResolver resourceResolver + = ResourceResolverFactory.createDefaultInternalResourceResolver(thisUri); //TODO The following could be optimized by retaining the FontManager somewhere FontManager fontManager = new FontManager(resourceResolver, FontDetectorFactory.createDefault(), FontCacheManagerFactory.createDefault()); diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index 526420d9c..1fcf9f870 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -80,6 +80,7 @@ import org.apache.fop.pdf.PDFColorHandler; import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFDeviceColorSpace; import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFunction; import org.apache.fop.pdf.PDFGState; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFImageXObject; @@ -89,11 +90,18 @@ import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFPattern; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFShading; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.pdf.ImageRawCCITTFaxAdapter; import org.apache.fop.render.pdf.ImageRawJPEGAdapter; import org.apache.fop.render.pdf.ImageRenderedAdapter; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.GradientFactory; +import org.apache.fop.render.shading.GradientRegistrar; +import org.apache.fop.render.shading.PDFGradientFactory; +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; /** * <p>PDF Graphics 2D. @@ -104,7 +112,7 @@ import org.apache.fop.render.pdf.ImageRenderedAdapter; * * @see org.apache.batik.ext.awt.g2d.AbstractGraphics2D */ -public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler { +public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler, GradientRegistrar { private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** The number of decimal places. */ @@ -868,11 +876,10 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand } //Gradients are currently restricted to sRGB - PDFDeviceColorSpace aColorSpace; - aColorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); - PDFPattern myPat = pdfDoc.getFactory().makeGradient( - resourceContext, false, aColorSpace, - someColors, theBounds, theCoords, theMatrix); + PDFDeviceColorSpace colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + PDFGradientFactory gradientFactory = (PDFGradientFactory)GradientFactory.newInstance(this); + PDFPattern myPat = gradientFactory.createGradient(false, colSpace, someColors, theBounds, + theCoords, theMatrix); currentStream.write(myPat.getColorSpaceOut(fill)); return true; @@ -944,13 +951,10 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand float offset = fractions[count]; theBounds.add(new Double(offset)); } - PDFDeviceColorSpace colSpace; - colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); - - PDFPattern myPat = pdfDoc.getFactory().makeGradient( - resourceContext, true, colSpace, - someColors, theBounds, theCoords, theMatrix); - + PDFDeviceColorSpace colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + PDFGradientFactory gradientFactory = (PDFGradientFactory) GradientFactory.newInstance(this); + PDFPattern myPat = gradientFactory.createGradient(true, colSpace, someColors, theBounds, + theCoords, theMatrix); currentStream.write(myPat.getColorSpaceOut(fill)); return true; @@ -1691,11 +1695,15 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand * @param iter PathIterator to process */ public void processPathIterator(PathIterator iter) { + double lastX = 0.0; + double lastY = 0.0; while (!iter.isDone()) { double[] vals = new double[6]; int type = iter.currentSegment(vals); switch (type) { case PathIterator.SEG_CUBICTO: + lastX = vals[4]; + lastY = vals[5]; currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " " + PDFNumber.doubleOut(vals[2], DEC) + " " @@ -1704,18 +1712,30 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand + PDFNumber.doubleOut(vals[5], DEC) + " c\n"); break; case PathIterator.SEG_LINETO: + lastX = vals[0]; + lastY = vals[1]; currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " l\n"); break; case PathIterator.SEG_MOVETO: + lastX = vals[0]; + lastY = vals[1]; currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " m\n"); break; case PathIterator.SEG_QUADTO: - currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " - + PDFNumber.doubleOut(vals[1], DEC) + " " - + PDFNumber.doubleOut(vals[2], DEC) + " " - + PDFNumber.doubleOut(vals[3], DEC) + " y\n"); + double controlPointAX = lastX + ((2.0 / 3.0) * (vals[0] - lastX)); + double controlPointAY = lastY + ((2.0 / 3.0) * (vals[1] - lastY)); + double controlPointBX = vals[2] + ((2.0 / 3.0) * (vals[0] - vals[2])); + double controlPointBY = vals[3] + ((2.0 / 3.0) * (vals[1] - vals[3])); + currentStream.write(PDFNumber.doubleOut(controlPointAX, DEC) + " " + + PDFNumber.doubleOut(controlPointAY, DEC) + " " + + PDFNumber.doubleOut(controlPointBX, DEC) + " " + + PDFNumber.doubleOut(controlPointBY, DEC) + " " + + PDFNumber.doubleOut(vals[2], DEC) + " " + + PDFNumber.doubleOut(vals[3], DEC) + " c\n"); + lastX = vals[2]; + lastY = vals[3]; break; case PathIterator.SEG_CLOSE: currentStream.write("h\n"); @@ -1840,4 +1860,36 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand //NYI } + /** + * Registers a function object against the output format document + * @param function The function object to register + * @return Returns either the function which has already been registered + * or the current new registered object. + */ + public Function registerFunction(Function function) { + return pdfDoc.getFactory().registerFunction((PDFFunction)function); + } + + /** + * Registers a shading object against the otuput format document + * @param shading The shading object to register + * @return Returs either the shading which has already been registered + * or the current new registered object + */ + public Shading registerShading(Shading shading) { + assert shading instanceof PDFShading; + return pdfDoc.getFactory().registerShading(resourceContext, (PDFShading)shading); + } + + /** + * Registers a pattern object against the output format document + * @param pattern The pattern object to register + * @return Returns either the pattern which has already been registered + * or the current new registered object + */ + public Pattern registerPattern(Pattern pattern) { + assert pattern instanceof PDFPattern; + return pdfDoc.getFactory().registerPattern(resourceContext, (PDFPattern)pattern); + } + } diff --git a/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java b/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java index 42a18b17c..60a6020fc 100644 --- a/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java +++ b/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java @@ -23,8 +23,11 @@ import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.awt.geom.Dimension2D; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.SAXException; + import org.apache.batik.bridge.UserAgentAdapter; import org.apache.batik.gvt.font.FontFamilyResolver; @@ -84,17 +87,28 @@ public class SimpleSVGUserAgent extends UserAgentAdapter { return null; // userStyleSheetURI; } + + private static final String XML_PARSER_CLASS_NAME; + + static { + String result; + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + result = factory.newSAXParser().getXMLReader().getClass().getName(); + } catch (SAXException e) { + result = null; + } catch (ParserConfigurationException e) { + result = null; + } + XML_PARSER_CLASS_NAME = result; + } + /** * Returns the class name of the XML parser. * @return the XML parser class name */ public String getXMLParserClassName() { - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - return factory.newSAXParser().getXMLReader().getClass().getName(); - } catch (Exception e) { - return null; - } + return XML_PARSER_CLASS_NAME; } /** diff --git a/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java b/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java index d6e19a97a..6598d1b85 100644 --- a/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java +++ b/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java @@ -29,7 +29,6 @@ package org.apache.fop.text.linebreak; * - commit BOTH changed files */ -// CSOFF: WhitespaceAfterCheck // CSOFF: LineLengthCheck /** Line breaking utilities. */ @@ -694,11 +693,11 @@ public final class LineBreakUtils { "CP", "CR", "EX", "GL", "H2", "H3", "HY", "ID", "IN", "IS", "JL", "JT", "JV", "LF", "NL", "NS", "NU", "OP", "PO", "PR", "QU", "SA", "SG", "SP", "SY", "WJ", "XX", "ZW"}; - private static String[] lineBreakPropertyLongNames = {"Ambiguous","Alphabetic","Break_Both","Break_After","Break_Before", - "Mandatory_Break","Contingent_Break","Close_Punctuation","Combining_Mark","Close_Parenthesis","Carriage_Return", - "Exclamation","Glue","H2","H3","Hyphen","Ideographic","Inseparable","Infix_Numeric","JL","JT","JV","Line_Feed", - "Next_Line","Nonstarter","Numeric","Open_Punctuation","Postfix_Numeric","Prefix_Numeric","Quotation","Complex_Context", - "Surrogate","Space","Break_Symbols","Word_Joiner","Unknown","ZWSpace"}; + private static String[] lineBreakPropertyLongNames = {"Ambiguous", "Alphabetic", "Break_Both", "Break_After", "Break_Before", + "Mandatory_Break", "Contingent_Break", "Close_Punctuation", "Combining_Mark", "Close_Parenthesis", "Carriage_Return", + "Exclamation", "Glue", "H2", "H3", "Hyphen", "Ideographic", "Inseparable", "Infix_Numeric", "JL", "JT", "JV", "Line_Feed", + "Next_Line", "Nonstarter", "Numeric", "Open_Punctuation", "Postfix_Numeric", "Prefix_Numeric", "Quotation", "Complex_Context", + "Surrogate", "Space", "Break_Symbols", "Word_Joiner", "Unknown", "ZWSpace"}; /** * Return the short name for the linebreak property corresponding diff --git a/src/java/org/apache/fop/tools/fontlist/FontListMain.java b/src/java/org/apache/fop/tools/fontlist/FontListMain.java index 659f7ccac..e2f5a651e 100644 --- a/src/java/org/apache/fop/tools/fontlist/FontListMain.java +++ b/src/java/org/apache/fop/tools/fontlist/FontListMain.java @@ -117,7 +117,7 @@ public final class FontListMain { } try { GenerationHelperContentHandler helper = new GenerationHelperContentHandler( - handler, null); + handler, null, null); FontListSerializer serializer = new FontListSerializer(); serializer.generateSAX(fontFamilies, singleFamily, helper); } finally { diff --git a/src/java/org/apache/fop/traits/WritingMode.java b/src/java/org/apache/fop/traits/WritingMode.java index 8718118ef..f8c784941 100644 --- a/src/java/org/apache/fop/traits/WritingMode.java +++ b/src/java/org/apache/fop/traits/WritingMode.java @@ -54,8 +54,9 @@ public final class WritingMode extends TraitEnum { * Assign writing mode traits from this trait to the specified * writing mode traits setter. * @param wms a writing mode traits setter + * @param explicit true if writing mode property explicitly specified */ - public void assignWritingModeTraits(WritingModeTraitsSetter wms) { + public void assignWritingModeTraits(WritingModeTraitsSetter wms, boolean explicit) { Direction inlineProgressionDirection; Direction blockProgressionDirection; Direction columnProgressionDirection; @@ -97,7 +98,7 @@ public final class WritingMode extends TraitEnum { wms.setColumnProgressionDirection(columnProgressionDirection); wms.setRowProgressionDirection(rowProgressionDirection); wms.setShiftDirection(shiftDirection); - wms.setWritingMode(this); + wms.setWritingMode(this, explicit); } /** diff --git a/src/java/org/apache/fop/traits/WritingModeTraits.java b/src/java/org/apache/fop/traits/WritingModeTraits.java index 279326afa..fbd150ca9 100644 --- a/src/java/org/apache/fop/traits/WritingModeTraits.java +++ b/src/java/org/apache/fop/traits/WritingModeTraits.java @@ -31,20 +31,21 @@ public class WritingModeTraits implements WritingModeTraitsSetter { private Direction rowProgressionDirection; private Direction shiftDirection; private WritingMode writingMode; + private boolean explicit; /** * Default writing mode traits constructor. */ public WritingModeTraits() { - this (WritingMode.LR_TB); + this (WritingMode.LR_TB, false); } /** * Construct writing mode traits using the specified writing mode. * @param writingMode a writing mode traits object */ - public WritingModeTraits(WritingMode writingMode) { - assignWritingModeTraits(writingMode); + public WritingModeTraits(WritingMode writingMode, boolean explicit) { + assignWritingModeTraits(writingMode, explicit); } /** @@ -125,17 +126,25 @@ public class WritingModeTraits implements WritingModeTraitsSetter { } /** + * @return the "explicit-writing-mode" trait. + */ + public boolean getExplicitWritingMode() { + return explicit; + } + + /** * @param writingMode the "writing-mode" trait. */ - public void setWritingMode(WritingMode writingMode) { + public void setWritingMode(WritingMode writingMode, boolean explicit) { this.writingMode = writingMode; + this.explicit = explicit; } /** * @param writingMode the "writing-mode" trait. */ - public void assignWritingModeTraits(WritingMode writingMode) { - writingMode.assignWritingModeTraits(this); + public void assignWritingModeTraits(WritingMode writingMode, boolean explicit) { + writingMode.assignWritingModeTraits(this, explicit); } /** diff --git a/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java b/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java index a67e437c9..7340b3e8c 100644 --- a/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java +++ b/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java @@ -55,4 +55,9 @@ public interface WritingModeTraitsGetter { */ WritingMode getWritingMode(); + /** + * @return the "explicit-writing-mode" trait + */ + boolean getExplicitWritingMode(); + } diff --git a/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java b/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java index 6dcf0fb12..b70f73954 100644 --- a/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java +++ b/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java @@ -58,13 +58,14 @@ public interface WritingModeTraitsSetter extends WritingModeTraitsGetter { * Set value of writing-mode trait. * @param writingMode the "writing-mode" trait */ - void setWritingMode(WritingMode writingMode); + void setWritingMode(WritingMode writingMode, boolean explicit); /** * Collectivelly assign values to all writing mode traits based upon a specific * writing mode. * @param writingMode the "writing-mode" trait + * @param explicit true if writing mode explicitly specified */ - void assignWritingModeTraits(WritingMode writingMode); + void assignWritingModeTraits(WritingMode writingMode, boolean explicit); } diff --git a/src/java/org/apache/fop/util/AbstractPaintingState.java b/src/java/org/apache/fop/util/AbstractPaintingState.java index 96c3633e6..3e966d202 100644 --- a/src/java/org/apache/fop/util/AbstractPaintingState.java +++ b/src/java/org/apache/fop/util/AbstractPaintingState.java @@ -24,7 +24,6 @@ import java.awt.geom.AffineTransform; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.Stack; @@ -36,10 +35,10 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { private static final long serialVersionUID = 5998356138437094188L; /** current state data */ - private AbstractData data = null; + private AbstractData data; /** the state stack */ - private StateStack/*<AbstractData>*/ stateStack = new StateStack/*<AbstractData>*/(); + private StateStack<AbstractData> stateStack = new StateStack<AbstractData>(); /** * Instantiates a new state data object @@ -216,8 +215,7 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { */ public AffineTransform getTransform() { AffineTransform at = new AffineTransform(); - for (Iterator iter = stateStack.iterator(); iter.hasNext();) { - AbstractData data = (AbstractData)iter.next(); + for (AbstractData data : stateStack) { AffineTransform stackTrans = data.getTransform(); at.concatenate(stackTrans); } @@ -249,7 +247,7 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { if (stateStack.isEmpty()) { return null; } else { - AbstractData baseData = (AbstractData)stateStack.get(0); + AbstractData baseData = stateStack.get(0); return (AffineTransform) baseData.getTransform().clone(); } } @@ -297,7 +295,7 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { */ public AbstractData restore() { if (!stateStack.isEmpty()) { - setData((AbstractData)stateStack.pop()); + setData(stateStack.pop()); return this.data; } else { return null; @@ -310,12 +308,11 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { * * @param dataList a state data list */ - public void saveAll(List/*<AbstractData>*/ dataList) { - Iterator it = dataList.iterator(); - while (it.hasNext()) { + public void saveAll(List<AbstractData> dataList) { + for (AbstractData data : dataList) { // save current data on stack save(); - setData((AbstractData)it.next()); + setData(data); } } @@ -325,8 +322,8 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { * * @return a list of state data popped from the stack */ - public List/*<AbstractData>*/ restoreAll() { - List/*<AbstractData>*/ dataList = new java.util.ArrayList/*<AbstractData>*/(); + public List<AbstractData> restoreAll() { + List<AbstractData> dataList = new java.util.ArrayList<AbstractData>(); AbstractData data; while (true) { data = getData(); @@ -361,7 +358,7 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { * * @return the state stack */ - protected Stack/*<AbstractData>*/ getStateStack() { + protected Stack<AbstractData> getStateStack() { return this.stateStack; } @@ -369,8 +366,10 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { @Override public Object clone() { AbstractPaintingState state = instantiate(); - state.stateStack = new StateStack(this.stateStack); - state.data = (AbstractData)this.data.clone(); + state.stateStack = new StateStack<AbstractData>(this.stateStack); + if (this.data != null) { + state.data = (AbstractData)this.data.clone(); + } return state; } @@ -385,7 +384,7 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { /** * A stack implementation which holds state objects */ - public class StateStack extends java.util.Stack { + public class StateStack<E> extends java.util.Stack<E> { private static final long serialVersionUID = 4897178211223823041L; @@ -393,7 +392,6 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { * Default constructor */ public StateStack() { - super(); } /** @@ -419,25 +417,28 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { private static final long serialVersionUID = 5208418041189828624L; /** The current color */ - protected Color color = null; + protected Color color; /** The current background color */ - protected Color backColor = null; + protected Color backColor; /** The current font name */ - protected String fontName = null; + protected String fontName; /** The current font size */ - protected int fontSize = 0; + protected int fontSize; /** The current line width */ - protected float lineWidth = 0; + protected float lineWidth; /** The dash array for the current basic stroke (line type) */ - protected float[] dashArray = null; + protected float[] dashArray; /** The current transform */ - protected AffineTransform transform = null; + protected AffineTransform transform; + + /** The current (optional content group) layer. */ + protected String layer; /** * Returns a newly create data object @@ -485,6 +486,18 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { transform = new AffineTransform(); } + public void setLayer(String layer) { + if (layer != null) { + this.layer = layer; + } else { + throw new IllegalArgumentException(); + } + } + + public String getLayer() { + return this.layer; + } + /** * Returns the derived rotation from the current transform * @@ -523,6 +536,7 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { this.transform = new AffineTransform(); } data.transform = new AffineTransform(this.transform); + data.layer = this.layer; return data; } @@ -535,7 +549,8 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { + ", fontSize=" + fontSize + ", lineWidth=" + lineWidth + ", dashArray=" + dashArray - + ", transform=" + transform; + + ", transform=" + transform + + ", layer=" + layer; } } } diff --git a/src/java/org/apache/fop/util/CommandLineLogger.java b/src/java/org/apache/fop/util/CommandLineLogger.java deleted file mode 100644 index a9e59158f..000000000 --- a/src/java/org/apache/fop/util/CommandLineLogger.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * 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.util; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This is a commons-logging logger for command line use. - */ -public class CommandLineLogger implements Log { - /** "Trace" level logging. */ - public static final int LOG_LEVEL_TRACE = 1; - /** "Debug" level logging. */ - public static final int LOG_LEVEL_DEBUG = 2; - /** "Info" level logging. */ - public static final int LOG_LEVEL_INFO = 3; - /** "Warn" level logging. */ - public static final int LOG_LEVEL_WARN = 4; - /** "Error" level logging. */ - public static final int LOG_LEVEL_ERROR = 5; - /** "Fatal" level logging. */ - public static final int LOG_LEVEL_FATAL = 6; - - private int logLevel; - private String logName; - - /** - * Construct the logger with a default log level taken from the LogFactory - * attribute "level". - * @param logName the logger name. - */ - public CommandLineLogger(String logName) { - this.logName = logName; - setLogLevel((String) LogFactory.getFactory().getAttribute("level")); - } - - /** - * Set a log level for the logger. - * @param level the log level - */ - public void setLogLevel(String level) { - if ("fatal".equals(level)) { - logLevel = LOG_LEVEL_FATAL; - } else if ("error".equals(level)) { - logLevel = LOG_LEVEL_ERROR; - } else if ("warn".equals(level)) { - logLevel = LOG_LEVEL_WARN; - } else if ("info".equals(level)) { - logLevel = LOG_LEVEL_INFO; - } else if ("debug".equals(level)) { - logLevel = LOG_LEVEL_DEBUG; - } else if ("trace".equals(level)) { - logLevel = LOG_LEVEL_TRACE; - } else { - logLevel = LOG_LEVEL_INFO; - } - } - - /** - * {@inheritDoc} - */ - public final boolean isTraceEnabled() { - return logLevel <= LOG_LEVEL_TRACE; - } - - /** - * {@inheritDoc} - */ - public final boolean isDebugEnabled() { - return logLevel <= LOG_LEVEL_DEBUG; - } - - /** - * {@inheritDoc} - */ - public final boolean isInfoEnabled() { - return logLevel <= LOG_LEVEL_INFO; - } - - /** - * {@inheritDoc} - */ - public final boolean isWarnEnabled() { - return logLevel <= LOG_LEVEL_WARN; - } - - /** - * {@inheritDoc} - */ - public final boolean isErrorEnabled() { - return logLevel <= LOG_LEVEL_ERROR; - } - - /** - * {@inheritDoc} - */ - public final boolean isFatalEnabled() { - return logLevel <= LOG_LEVEL_FATAL; - } - - /** - * {@inheritDoc} - */ - public final void trace(Object message) { - if (isTraceEnabled()) { - log(LOG_LEVEL_TRACE, message, null); - } - } - - /** - * {@inheritDoc} - */ - public final void trace(Object message, Throwable t) { - if (isTraceEnabled()) { - log(LOG_LEVEL_TRACE, message, t); - } - } - - /** - * {@inheritDoc} - */ - public final void debug(Object message) { - if (isDebugEnabled()) { - log(LOG_LEVEL_DEBUG, message, null); - } - } - - /** - * {@inheritDoc} - */ - public final void debug(Object message, Throwable t) { - if (isDebugEnabled()) { - log(LOG_LEVEL_DEBUG, message, t); - } - } - - /** - * {@inheritDoc} - */ - public final void info(Object message) { - if (isInfoEnabled()) { - log(LOG_LEVEL_INFO, message, null); - } - } - - /** - * {@inheritDoc} - */ - public final void info(Object message, Throwable t) { - if (isInfoEnabled()) { - log(LOG_LEVEL_INFO, message, t); - } - } - - /** - * {@inheritDoc} - */ - public final void warn(Object message) { - if (isWarnEnabled()) { - log(LOG_LEVEL_WARN, message, null); - } - } - - /** - * {@inheritDoc} - */ - public final void warn(Object message, Throwable t) { - if (isWarnEnabled()) { - log(LOG_LEVEL_WARN, message, t); - } - } - - /** - * {@inheritDoc} - */ - public final void error(Object message) { - if (isErrorEnabled()) { - log(LOG_LEVEL_ERROR, message, null); - } - } - - /** - * {@inheritDoc} - */ - public final void error(Object message, Throwable t) { - if (isErrorEnabled()) { - log(LOG_LEVEL_ERROR, message, t); - } - } - - /** - * {@inheritDoc} - */ - public final void fatal(Object message) { - if (isFatalEnabled()) { - log(LOG_LEVEL_FATAL, message, null); - } - } - - /** - * {@inheritDoc} - */ - public final void fatal(Object message, Throwable t) { - if (isFatalEnabled()) { - log(LOG_LEVEL_FATAL, message, t); - } - } - - /** - * Do the actual logging. - * This method assembles the message and prints it to - * and then calls <code>write()</code> to cause it to be written.</p> - * - * @param type One of the LOG_LEVEL_XXX constants defining the log level - * @param message The message itself (typically a String) - * @param t The exception whose stack trace should be logged - */ - protected void log(int type, Object message, Throwable t) { - StringBuffer buf = new StringBuffer(); - // Append the message - buf.append(String.valueOf(message)); - if (t != null) { - buf.append("\n"); - // Append a stack trace or just the stack trace message. - if (!isDebugEnabled()) { - buf.append(t.toString()); - buf.append("\n"); - } else { - java.io.StringWriter sw = new java.io.StringWriter(1024); - java.io.PrintWriter pw = new java.io.PrintWriter(sw); - t.printStackTrace(pw); - pw.close(); - buf.append(sw.toString()); - } - } - - // Print to the appropriate destination - if (type >= LOG_LEVEL_WARN) { - System.err.println(buf); - } else { - System.out.println(buf); - } - - } -} diff --git a/src/java/org/apache/fop/util/GenerationHelperContentHandler.java b/src/java/org/apache/fop/util/GenerationHelperContentHandler.java index 64fabbc8a..68970a7f2 100644 --- a/src/java/org/apache/fop/util/GenerationHelperContentHandler.java +++ b/src/java/org/apache/fop/util/GenerationHelperContentHandler.java @@ -35,6 +35,7 @@ public class GenerationHelperContentHandler extends DelegatingContentHandler { private static final Attributes EMPTY_ATTS = new AttributesImpl(); private String mainNamespace; + private Object contentHandlerContext; /** * Main constructor. If the given handler also implements any of the EntityResolver, @@ -42,10 +43,12 @@ public class GenerationHelperContentHandler extends DelegatingContentHandler { * @param handler the SAX content handler to delegate all calls to * @param mainNamespace the main namespace used for generated XML content when abbreviated * ContentHandler calls are used. + * @param contentHandlerContext additional content handler context state */ - public GenerationHelperContentHandler(ContentHandler handler, String mainNamespace) { + public GenerationHelperContentHandler(ContentHandler handler, String mainNamespace, Object contentHandlerContext) { super(handler); this.mainNamespace = mainNamespace; + this.contentHandlerContext = contentHandlerContext; } /** @@ -66,6 +69,14 @@ public class GenerationHelperContentHandler extends DelegatingContentHandler { } /** + * Returns the context object (may be null). + * @return the context object + */ + public Object getContentHandlerContext() { + return this.contentHandlerContext; + } + + /** * Convenience method to generate a startElement SAX event. * @param localName the local name of the element * @param atts the attributes diff --git a/src/java/org/apache/fop/util/XMLUtil.java b/src/java/org/apache/fop/util/XMLUtil.java index 24c75922c..3f815e59e 100644 --- a/src/java/org/apache/fop/util/XMLUtil.java +++ b/src/java/org/apache/fop/util/XMLUtil.java @@ -300,4 +300,35 @@ public final class XMLUtil implements XMLConstants { } } + /** + * Escape '<', '>' and '&' using NCRs. + * @param unescaped string + * @return escaped string + */ + public static String escape(String unescaped) { + int needsEscape = 0; + for (int i = 0, n = unescaped.length(); i < n; ++i) { + char c = unescaped.charAt(i); + if ((c == '<') || (c == '>') || (c == '&')) { + ++needsEscape; + } + } + if (needsEscape > 0) { + StringBuffer sb = new StringBuffer(unescaped.length() + 6 * needsEscape); + for (int i = 0, n = unescaped.length(); i < n; ++i) { + char c = unescaped.charAt(i); + if ((c == '<') || (c == '>') || (c == '&')) { + sb.append("&#x"); + sb.append(Integer.toString(c, 16)); + sb.append(';'); + } else { + sb.append(c); + } + } + return sb.toString(); + } else { + return unescaped; + } + } + } |