From 21682bdca71d422e03b4452fa841b6b542d0c22f Mon Sep 17 00:00:00 2001 From: Luis Bernardo Date: Wed, 14 Aug 2013 22:35:37 +0000 Subject: FOP-2252: OpenType CFF support for FOP; patch submitted by Robert Meyer git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1514076 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/fop/pdf/PDFCFFStreamType0C.java | 74 ++++++++++++++++ src/java/org/apache/fop/pdf/PDFEncryptionJCE.java | 99 +++++++++++++++++++--- src/java/org/apache/fop/pdf/PDFFactory.java | 57 ++++++++++--- src/java/org/apache/fop/pdf/PDFFontDescriptor.java | 2 + 4 files changed, 210 insertions(+), 22 deletions(-) create mode 100644 src/java/org/apache/fop/pdf/PDFCFFStreamType0C.java (limited to 'src/java/org/apache/fop/pdf') 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/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java index 2fe9f29e0..8fd8d3444 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java +++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java @@ -449,14 +449,13 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { private class Rev5Engine extends InitializationEngine { - // private SecureRandom random = new SecureRandom(); - private byte[] userValidationSalt = new byte[8]; - private byte[] userKeySalt = new byte[8]; - private byte[] ownerValidationSalt = new byte[8]; - private byte[] ownerKeySalt = new byte[8]; - private byte[] ueValue; - private byte[] oeValue; - private final boolean encryptMetadata; + protected byte[] userValidationSalt = new byte[8]; + protected byte[] userKeySalt = new byte[8]; + protected byte[] ownerValidationSalt = new byte[8]; + protected byte[] ownerKeySalt = new byte[8]; + protected byte[] ueValue; + protected byte[] oeValue; + protected final boolean encryptMetadata; Rev5Engine(EncryptionSettings encryptionSettings) { super(encryptionSettings); @@ -563,7 +562,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.8-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - private void computeUEValue() { + protected void computeUEValue() { digest.reset(); byte[] prepared = preparedUserPassword; byte[] concatenated = new byte[prepared.length + 8]; @@ -577,7 +576,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.9-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - private void computeOEValue() { + protected void computeOEValue() { digest.reset(); byte[] prepared = preparedOwnerPassword; byte[] concatenated = new byte[prepared.length + 56]; @@ -615,6 +614,86 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { } } + private class Rev6Engine extends Rev5Engine { + + private MessageDigest digest384; + private MessageDigest digest512; + + Rev6Engine(EncryptionSettings encryptionSettings) { + super(encryptionSettings); + try { + digest384 = MessageDigest.getInstance("SHA-384"); + digest512 = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException(e.getMessage()); + } + } + + @Override + protected void computeUValue() { + byte[] userBytes = new byte[16]; + random.nextBytes(userBytes); + System.arraycopy(userBytes, 0, userValidationSalt, 0, 8); + System.arraycopy(userBytes, 8, userKeySalt, 0, 8); + digest.reset(); + byte[] prepared = preparedUserPassword; + byte[] concatenated = new byte[prepared.length + 8]; + System.arraycopy(prepared, 0, concatenated, 0, prepared.length); + System.arraycopy(userValidationSalt, 0, concatenated, prepared.length, 8); + digest.update(concatenated); + byte[] block = digest.digest(); + int blockSize = 32; + byte[] key = new byte[16]; + byte[] iv = new byte[16]; + int length = prepared.length + blockSize; + byte[] data = new byte[length * 64]; + for (int i = 0; i < 64 || i < data[length * 64 - 1] + 32; i++) { + System.arraycopy(block, 0, key, 0, 16); + System.arraycopy(block, 16, iv, 0, 16); + for (int j = 0; j < 64; j++) { + System.arraycopy(prepared, 0, data, j * length, prepared.length); + System.arraycopy(block, 0, data, j * length + prepared.length, blockSize); + } + try { + final Cipher cipher = PDFEncryptionJCE.initCipher(key, false, iv); + data = cipher.doFinal(data); + } catch (IllegalBlockSizeException e) { + throw new IllegalStateException(e.getMessage()); + } catch (BadPaddingException e) { + throw new IllegalStateException(e.getMessage()); + } + int sum = 0; + for (int k = 0; k < 16; k++) { + sum += data[k]; + } + blockSize = 32 + (sum % 3) * 16; + switch (blockSize) { + case 32: + digest.reset(); + digest.update(data); + block = digest.digest(); + break; + case 48: + digest384.reset(); + digest384.update(data); + block = digest384.digest(); + break; + case 64: + digest512.reset(); + digest512.update(data); + block = digest512.digest(); + break; + default: + // not possible + break; + } + length = prepared.length + blockSize; + data = new byte[length * 64]; + } + } + + } + private class EncryptionFilter extends PDFFilter { private int streamNumber; diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 1756f1d56..631499af1 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; @@ -1387,15 +1389,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 +1405,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 +1520,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 +1567,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 +1581,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 +1628,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) { 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); } -- cgit v1.2.3 From 99e4e25414066866d0f8b3632623576246ae080b Mon Sep 17 00:00:00 2001 From: Luis Bernardo Date: Wed, 14 Aug 2013 22:42:28 +0000 Subject: FOP-2252: OpenType CFF support for FOP; patch submitted by Robert Meyer (eclipse misbehaved and missed a file) git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1514079 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/fop/fonts/cff/CFFDataReader.java | 927 +++++++++++++++++++++ src/java/org/apache/fop/pdf/PDFEncryptionJCE.java | 99 +-- 2 files changed, 937 insertions(+), 89 deletions(-) create mode 100644 src/java/org/apache/fop/fonts/cff/CFFDataReader.java (limited to 'src/java/org/apache/fop/pdf') 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 fdFonts; + + private static final int DOUBLE_BYTE_OPERATOR = 12; + private static final int NUM_STANDARD_STRINGS = 391; + + /** Commonly used parsed dictionaries */ + private LinkedHashMap 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 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 parseDictData(byte[] dictData) throws IOException { + LinkedHashMap dictEntries = new LinkedHashMap(); + List operands = new ArrayList(); + List operandLengths = new ArrayList(); + 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(operands)); + newEntry.setOperatorName(operatorName); + newEntry.setOffset(i - lastOperandLength); + newEntry.setOperandLength(lastOperandLength); + newEntry.setOperandLengths(new ArrayList(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 operands; + private List 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 operands) { + this.operands = operands; + } + + public List 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 operandLengths) { + this.operandLengths = operandLengths; + } + + public List 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 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 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 ranges = new LinkedHashMap(); + 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 ranges = new LinkedHashMap(); + 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 parseCIDData() throws IOException { + ArrayList fdFonts = new ArrayList(); + 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 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 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 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 ranges; + + public void setRanges(LinkedHashMap ranges) { + this.ranges = ranges; + } + + public LinkedHashMap 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 ranges; + private int sentinelGID; + + public void setRangeCount(int rangeCount) { + this.rangeCount = rangeCount; + } + + public int getRangeCount() { + return rangeCount; + } + + public void setRanges(LinkedHashMap ranges) { + this.ranges = ranges; + } + + public LinkedHashMap 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/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java index 8fd8d3444..2fe9f29e0 100644 --- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java +++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java @@ -449,13 +449,14 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { private class Rev5Engine extends InitializationEngine { - protected byte[] userValidationSalt = new byte[8]; - protected byte[] userKeySalt = new byte[8]; - protected byte[] ownerValidationSalt = new byte[8]; - protected byte[] ownerKeySalt = new byte[8]; - protected byte[] ueValue; - protected byte[] oeValue; - protected final boolean encryptMetadata; + // private SecureRandom random = new SecureRandom(); + private byte[] userValidationSalt = new byte[8]; + private byte[] userKeySalt = new byte[8]; + private byte[] ownerValidationSalt = new byte[8]; + private byte[] ownerKeySalt = new byte[8]; + private byte[] ueValue; + private byte[] oeValue; + private final boolean encryptMetadata; Rev5Engine(EncryptionSettings encryptionSettings) { super(encryptionSettings); @@ -562,7 +563,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.8-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - protected void computeUEValue() { + private void computeUEValue() { digest.reset(); byte[] prepared = preparedUserPassword; byte[] concatenated = new byte[prepared.length + 8]; @@ -576,7 +577,7 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { /** * Algorithm 3.9-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3) */ - protected void computeOEValue() { + private void computeOEValue() { digest.reset(); byte[] prepared = preparedOwnerPassword; byte[] concatenated = new byte[prepared.length + 56]; @@ -614,86 +615,6 @@ public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption { } } - private class Rev6Engine extends Rev5Engine { - - private MessageDigest digest384; - private MessageDigest digest512; - - Rev6Engine(EncryptionSettings encryptionSettings) { - super(encryptionSettings); - try { - digest384 = MessageDigest.getInstance("SHA-384"); - digest512 = MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - throw new UnsupportedOperationException(e.getMessage()); - } - } - - @Override - protected void computeUValue() { - byte[] userBytes = new byte[16]; - random.nextBytes(userBytes); - System.arraycopy(userBytes, 0, userValidationSalt, 0, 8); - System.arraycopy(userBytes, 8, userKeySalt, 0, 8); - digest.reset(); - byte[] prepared = preparedUserPassword; - byte[] concatenated = new byte[prepared.length + 8]; - System.arraycopy(prepared, 0, concatenated, 0, prepared.length); - System.arraycopy(userValidationSalt, 0, concatenated, prepared.length, 8); - digest.update(concatenated); - byte[] block = digest.digest(); - int blockSize = 32; - byte[] key = new byte[16]; - byte[] iv = new byte[16]; - int length = prepared.length + blockSize; - byte[] data = new byte[length * 64]; - for (int i = 0; i < 64 || i < data[length * 64 - 1] + 32; i++) { - System.arraycopy(block, 0, key, 0, 16); - System.arraycopy(block, 16, iv, 0, 16); - for (int j = 0; j < 64; j++) { - System.arraycopy(prepared, 0, data, j * length, prepared.length); - System.arraycopy(block, 0, data, j * length + prepared.length, blockSize); - } - try { - final Cipher cipher = PDFEncryptionJCE.initCipher(key, false, iv); - data = cipher.doFinal(data); - } catch (IllegalBlockSizeException e) { - throw new IllegalStateException(e.getMessage()); - } catch (BadPaddingException e) { - throw new IllegalStateException(e.getMessage()); - } - int sum = 0; - for (int k = 0; k < 16; k++) { - sum += data[k]; - } - blockSize = 32 + (sum % 3) * 16; - switch (blockSize) { - case 32: - digest.reset(); - digest.update(data); - block = digest.digest(); - break; - case 48: - digest384.reset(); - digest384.update(data); - block = digest384.digest(); - break; - case 64: - digest512.reset(); - digest512.update(data); - block = digest512.digest(); - break; - default: - // not possible - break; - } - length = prepared.length + blockSize; - data = new byte[length * 64]; - } - } - - } - private class EncryptionFilter extends PDFFilter { private int streamNumber; -- cgit v1.2.3 From 54eec84366117b4705a71810f1d1c87529c32ba5 Mon Sep 17 00:00:00 2001 From: Glenn Adams Date: Fri, 13 Sep 2013 13:56:18 +0000 Subject: FOP-2298: Enable support for PDF Transitions by defining low-level mechanism to augment /Catalog and /Page dictionaries. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1522934 13f79535-47bb-0310-9956-ffa450edef68 --- findbugs-exclude.xml | 20 +++ .../org.apache.fop.util.ContentHandlerFactory | 1 + .../org/apache/fop/fo/extensions/ExtensionObj.java | 65 --------- src/java/org/apache/fop/pdf/PDFNumber.java | 8 ++ .../AbstractXMLWritingIFDocumentHandler.java | 4 +- .../apache/fop/render/intermediate/IFContext.java | 18 +++ .../apache/fop/render/intermediate/IFRenderer.java | 2 + .../fop/render/intermediate/IFSerializer.java | 2 + .../apache/fop/render/pdf/PDFDocumentHandler.java | 16 ++- .../apache/fop/render/pdf/PDFRenderingUtil.java | 52 ++++++- .../extensions/AbstractPDFDictionaryElement.java | 43 ++++++ .../extensions/AbstractPDFExtensionElement.java | 7 +- .../pdf/extensions/PDFDictionaryAttachment.java | 125 +++++++++++++++++ .../pdf/extensions/PDFDictionaryElement.java | 126 +++++++++++++++++ .../pdf/extensions/PDFDictionaryEntryElement.java | 109 ++++++++++++++ .../extensions/PDFDictionaryEntryExtension.java | 110 +++++++++++++++ .../pdf/extensions/PDFDictionaryEntryType.java | 56 ++++++++ .../pdf/extensions/PDFDictionaryExtension.java | 124 ++++++++++++++++ .../render/pdf/extensions/PDFDictionaryType.java | 54 +++++++ .../render/pdf/extensions/PDFElementMapping.java | 36 ++++- .../pdf/extensions/PDFEmbeddedFileAttachment.java | 156 +++++++++++++++++++++ .../pdf/extensions/PDFEmbeddedFileElement.java | 18 +-- .../PDFEmbeddedFileExtensionAttachment.java | 154 -------------------- .../pdf/extensions/PDFExtensionAttachment.java | 35 ++--- .../render/pdf/extensions/PDFExtensionHandler.java | 113 +++++++++++---- .../pdf/extensions/PDFExtensionHandlerFactory.java | 44 ++++++ .../apache/fop/tools/fontlist/FontListMain.java | 2 +- .../fop/util/GenerationHelperContentHandler.java | 13 +- src/java/org/apache/fop/util/XMLUtil.java | 31 ++++ .../apache/fop/render/svg/SVGDocumentHandler.java | 2 +- status.xml | 3 + .../pdf-dictionary-extension_1.xml | 90 ++++++++++++ 32 files changed, 1341 insertions(+), 298 deletions(-) delete mode 100644 src/java/org/apache/fop/fo/extensions/ExtensionObj.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileAttachment.java delete mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandlerFactory.java create mode 100644 test/layoutengine/standard-testcases/pdf-dictionary-extension_1.xml (limited to 'src/java/org/apache/fop/pdf') diff --git a/findbugs-exclude.xml b/findbugs-exclude.xml index 2becac909..46e6e1675 100644 --- a/findbugs-exclude.xml +++ b/findbugs-exclude.xml @@ -5413,4 +5413,24 @@ + + + + + + + + + + + + + + + + + + + + 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/fo/extensions/ExtensionObj.java b/src/java/org/apache/fop/fo/extensions/ExtensionObj.java deleted file mode 100644 index c35dcfc73..000000000 --- a/src/java/org/apache/fop/fo/extensions/ExtensionObj.java +++ /dev/null @@ -1,65 +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.fo.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; - -/** - * Base class for pdf bookmark extension objects. - */ -public abstract class ExtensionObj extends FObj { - - /** - * Create a new extension object. - * - * @param parent the parent formatting object - */ - public ExtensionObj(FONode parent) { - super(parent); - } - - /** - * {@inheritDoc} - */ - public void processNode(String elementName, Locator locator, - Attributes attlist, PropertyList pList) - throws FOPException { - } - - /** - * 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; - } -} - 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/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/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 9e6aae700..30ceda108 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); diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 62e4cc67d..1ffd42863 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); } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index dd9320571..0a3d34ef4 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,22 @@ 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) { + PDFDictionaryAttachment dictionaryExtension = (PDFDictionaryAttachment) extension; + pdfUtil.renderDictionaryExtension(dictionaryExtension, 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/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index c3b46794d..962cd7847 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -56,15 +56,23 @@ import org.apache.fop.pdf.PDFICCBasedColorSpace; import org.apache.fop.pdf.PDFICCStream; import org.apache.fop.pdf.PDFInfo; import org.apache.fop.pdf.PDFMetadata; +import org.apache.fop.pdf.PDFName; import org.apache.fop.pdf.PDFNames; +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.PDFText; 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.PDFDictionaryAttachment; +import org.apache.fop.render.pdf.extensions.PDFDictionaryEntryExtension; +import org.apache.fop.render.pdf.extensions.PDFDictionaryEntryType; +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 static org.apache.fop.render.pdf.PDFEncryptionOption.ENCRYPTION_PARAMS; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ACCESSCONTENT; @@ -250,6 +258,46 @@ class PDFRenderingUtil { } } + public void renderDictionaryExtension(PDFDictionaryAttachment attachment, PDFPage currentPage) { + PDFDictionaryExtension extension = attachment.getExtension(); + if (extension.getDictionaryType() == PDFDictionaryType.Catalog) { + augmentDictionary(pdfDoc.getRoot(), extension); + } else if (extension.getDictionaryType() == PDFDictionaryType.Page) { + if (extension.matchesPageNumber(currentPage.getPageIndex() + 1)) { + augmentDictionary(currentPage, extension); + } + } else { + throw new IllegalStateException(); + } + } + + private PDFDictionary augmentDictionary(PDFDictionary dictionary, PDFDictionaryExtension extension) { + for (PDFDictionaryEntryExtension entry : extension.getEntries()) { + if (entry instanceof PDFDictionaryExtension) { + dictionary.put(entry.getKey(), augmentDictionary(new PDFDictionary(dictionary), (PDFDictionaryExtension) entry)); + } else { + augmentDictionary(dictionary, entry); + } + } + return dictionary; + } + + private void augmentDictionary(PDFDictionary dictionary, PDFDictionaryEntryExtension entry) { + PDFDictionaryEntryType type = entry.getType(); + String key = entry.getKey(); + if (type == PDFDictionaryEntryType.Boolean) { + dictionary.put(key, entry.getValueAsBoolean()); + } else if (type == PDFDictionaryEntryType.Name) { + dictionary.put(key, new PDFName(entry.getValueAsString())); + } else if (type == PDFDictionaryEntryType.Number) { + dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFDictionaryEntryType.String) { + dictionary.put(key, 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 +363,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/extensions/AbstractPDFDictionaryElement.java b/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java new file mode 100644 index 000000000..9de7e95da --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.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; + +import org.apache.fop.fo.FONode; + +// CSOFF: LineLengthCheck + +/** + * Base class for the PDF dictionary related extension elements. + */ +public abstract class AbstractPDFDictionaryElement extends AbstractPDFExtensionElement { + + public static final String ATT_KEY = PDFDictionaryEntryExtension.PROPERTY_KEY; + + /** + * Default constructor + * + * @param parent parent of this node + * @see org.apache.fop.fo.FONode#FONode(FONode) + */ + public AbstractPDFDictionaryElement(FONode parent) { + super(parent); + } +} + 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/PDFDictionaryAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java new file mode 100644 index 000000000..528268c30 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java @@ -0,0 +1,125 @@ +/* + * 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; + } + + @Override + public void toSAX(ContentHandler handler) throws SAXException { + PDFDictionaryType dictionaryType = extension.getDictionaryType(); + int pageNumber = 0; + if (dictionaryType == PDFDictionaryType.Page) { + if (handler instanceof GenerationHelperContentHandler) { + Object context = ((GenerationHelperContentHandler) handler).getContentHandlerContext(); + if (context instanceof IFContext) { + int pageIndex = ((IFContext) context).getPageIndex(); + if ((pageIndex >= 0) && 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 (PDFDictionaryEntryExtension entry : dictionary.getEntries()) { + toSAX(handler, entry); + } + handler.endElement(CATEGORY, ln, qn); + } + + private void toSAX(ContentHandler handler, PDFDictionaryEntryExtension entry) throws SAXException { + if (entry instanceof PDFDictionaryExtension) { + toSAX(handler, (PDFDictionaryExtension) entry); + } else { + AttributesImpl attributes = new AttributesImpl(); + String ln = entry.getElementName(); + String qn = PREFIX + ":" + ln; + attributes = extractIFAttributes(attributes, entry); + handler.startElement(CATEGORY, ln, qn, attributes); + 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 (type == PDFDictionaryType.Catalog) { + // no specific attriburtes + } else if (type == PDFDictionaryType.Page) { + String pageNumbersName = PDFDictionaryExtension.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 = PDFDictionaryEntryExtension.PROPERTY_KEY; + String key = dictionary.getKey(); + if (key != null) { + attributes.addAttribute(null, keyName, keyName, "CDATA", key); + } + } + return attributes; + } + + private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFDictionaryEntryExtension entry) { + String keyName = PDFDictionaryEntryExtension.PROPERTY_KEY; + String key = entry.getKey(); + if (key != null) { + attributes.addAttribute(null, keyName, keyName, "CDATA", key); + } + 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..5a60f20ca --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java @@ -0,0 +1,126 @@ +/* + * 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; +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 AbstractPDFDictionaryElement { + + public static final String ATT_PAGE_NUMBERS = PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS; + + private PDFDictionaryExtension extension; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFDictionaryElement(FONode parent, PDFDictionaryType type) { + super(parent); + this.extension = new PDFDictionaryExtension(type); + } + + public PDFDictionaryExtension getExtension() { + return extension; + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + if (extension.getDictionaryType() == PDFDictionaryType.Catalog) { + // no specific properties + } else if (extension.getDictionaryType() == PDFDictionaryType.Page) { + String pageNumbers = attlist.getValue(ATT_PAGE_NUMBERS); + if (pageNumbers != null) { + extension.setProperty(ATT_PAGE_NUMBERS, pageNumbers); + } + } else if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { + String key = attlist.getValue(ATT_KEY); + if (key == null) { + missingPropertyError(ATT_KEY); + } else if (key.isEmpty()) { + invalidPropertyValueError(ATT_KEY, key, null); + } else { + extension.setKey(key); + } + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + String localName = getLocalName(); + if (localName.equals("catalog")) { + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } else if (localName.equals("page")) { + if (parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfSPM"); + } + } else if (localName.equals("dictionary")) { + if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName())) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), null); + } + } else { + throw new IllegalStateException("unknown name: " + localName); + } + } + + @Override + protected void addChildNode(FONode child) throws FOPException { + if (child instanceof PDFDictionaryElement) { + PDFDictionaryExtension extension = ((PDFDictionaryElement) child).getExtension(); + if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { + this.extension.addEntry(extension); + } + } else if (child instanceof PDFDictionaryEntryElement) { + PDFDictionaryEntryExtension extension = ((PDFDictionaryEntryElement) child).getExtension(); + this.extension.addEntry(extension); + } + } + + @Override + public void endOfNode() throws FOPException { + super.endOfNode(); + } + + @Override + public String getLocalName() { + return extension.getDictionaryType().elementName(); + } + + @Override + protected ExtensionAttachment instantiateExtensionAttachment() { + return new PDFDictionaryAttachment(extension); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java new file mode 100644 index 000000000..d8ef1f04e --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.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.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 dictionary entries: pdf:{boolean,name,number,string}. The specific type + * of entry is established at construction type. + */ +public class PDFDictionaryEntryElement extends AbstractPDFDictionaryElement { + + private PDFDictionaryEntryExtension extension; + private StringBuffer characters; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFDictionaryEntryElement(FONode parent, PDFDictionaryEntryType type) { + super(parent); + this.extension = new PDFDictionaryEntryExtension(type); + } + + public PDFDictionaryEntryExtension getExtension() { + return extension; + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + String key = attlist.getValue("key"); + if (key == null) { + missingPropertyError("key"); + } else if (key.isEmpty()) { + invalidPropertyValueError("key", key, null); + } else { + extension.setKey(key); + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + 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 (characters == null) { + characters = new StringBuffer((length < 16) ? 16 : length); + } + characters.append(data, start, length); + } + + @Override + public void endOfNode() throws FOPException { + String value = (characters != null) ? characters.toString() : ""; + if (extension.getType() == PDFDictionaryEntryType.Boolean) { + if (!value.equals("true") && !value.equals("false")) { + invalidPropertyValueError("", value, null); + } + } else if (extension.getType() == PDFDictionaryEntryType.Name) { + if (value.isEmpty()) { + invalidPropertyValueError("", value, null); + } + } else if (extension.getType() == PDFDictionaryEntryType.Number) { + try { + Double.valueOf(value); + } catch (NumberFormatException e) { + invalidPropertyValueError("", value, null); + } + } else if (extension.getType() != PDFDictionaryEntryType.String) { + throw new IllegalStateException(); + } + extension.setValue(value); + super.endOfNode(); + } + + @Override + public String getLocalName() { + return extension.getType().elementName(); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java new file mode 100644 index 000000000..94d6b5dbf --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java @@ -0,0 +1,110 @@ +/* + * 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 PDFDictionaryEntryExtension { + + public static final String PROPERTY_KEY = "key"; + + private PDFDictionaryEntryType type; + private String key = ""; + private Object value; + + PDFDictionaryEntryExtension() { + } + + PDFDictionaryEntryExtension(PDFDictionaryEntryType type) { + this.type = type; + } + + public PDFDictionaryEntryType getType() { + return type; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public void setValue(String value) { + this.value = value; + } + + public Object getValue() { + return value; + } + + /** + * Obtain entry value as Boolean. + * @return entry value + */ + public Boolean getValueAsBoolean() { + if (value instanceof String) { + return Boolean.valueOf((String)value); + } else { + return false; + } + } + + /** + * Obtain entry value as Number. + * @return entry value + */ + public Number getValueAsNumber() { + if (value instanceof String) { + double d = Double.parseDouble((String) value); + if (Math.floor(d) == d) { + return Long.valueOf((long) d); + } else { + return Double.valueOf(d); + } + } else { + return Integer.valueOf(0); + } + } + + public String getValueAsString() { + if (value instanceof String) { + return (String) value; + } else { + return ""; + } + } + + public String getValueAsXMLEscapedString() { + return XMLUtil.escape(getValueAsString()); + } + + public void setValue(Object value) { + this.value = value; + } + + public String getElementName() { + return type.elementName(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java new file mode 100644 index 000000000..e4b25819a --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java @@ -0,0 +1,56 @@ +/* + * 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 dictionary entry extension elements. + */ +public enum PDFDictionaryEntryType { + Boolean("boolean"), // boolean valued entry + Dictionary("dictionary"), // dictionary valued entry + Name("name"), // name valued entry + Number("number"), // number valued entry + String("string"); // string valued entry + + private String elementName; + PDFDictionaryEntryType(String elementName) { + this.elementName = elementName; + } + public String elementName() { + return elementName; + } + static PDFDictionaryEntryType valueOfElementName(String elementName) { + for (PDFDictionaryEntryType 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/PDFDictionaryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java new file mode 100644 index 000000000..2b851e0bf --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java @@ -0,0 +1,124 @@ +/* + * 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 PDFDictionaryEntryExtension { + + public static final String PROPERTY_PAGE_NUMBERS = "page-numbers"; + + private static final long serialVersionUID = -1L; + + private PDFDictionaryType dictionaryType; + private Map properties; + private List entries; + + PDFDictionaryExtension() { + } + + PDFDictionaryExtension(PDFDictionaryType dictionaryType) { + super(PDFDictionaryEntryType.Dictionary); + this.dictionaryType = dictionaryType; + this.properties = new java.util.HashMap(); + this.entries = new java.util.ArrayList(); + } + + 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); + } + + public void addEntry(PDFDictionaryEntryExtension entry) { + entries.add(entry); + } + + public List getEntries() { + return entries; + } + + public PDFDictionaryEntryExtension getLastEntry() { + if (entries.size() > 0) { + return entries.get(entries.size() - 1); + } else { + return null; + } + } + + /** + * 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) { + if (dictionaryType != PDFDictionaryType.Page) { + return false; + } + String pageNumbers = getProperty(PROPERTY_PAGE_NUMBERS); + if ((pageNumbers == null) || pageNumbers.isEmpty()) { + 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; + } + + @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..edd95160a --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java @@ -0,0 +1,54 @@ +/* + * 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 { + Dictionary("dictionary"), // generic (nested) dictionary element + Catalog("catalog"), // catalog dictionary element + Page("page"); // page dictionary element + + private String elementName; + PDFDictionaryType(String elementName) { + this.elementName = elementName; + } + public String elementName() { + return elementName; + } + 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..3e063e24b 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,43 @@ public class PDFElementMapping extends ElementMapping { protected void initialize() { if (foObjs == null) { foObjs = new java.util.HashMap(); - foObjs.put(PDFEmbeddedFileElement.ELEMENT, new PDFEmbeddedFileMaker()); + // pdf:embedded-file + foObjs.put(PDFEmbeddedFileElement.ELEMENT, new PDFEmbeddedFileElementMaker()); + // pdf:{catalog,page} et al. + for (PDFDictionaryType type : PDFDictionaryType.values()) { + foObjs.put(type.elementName(), new PDFDictionaryElementMaker(type)); + } + for (PDFDictionaryEntryType type : PDFDictionaryEntryType.values()) { + if (type != PDFDictionaryEntryType.Dictionary) { + foObjs.put(type.elementName(), new PDFDictionaryEntryElementMaker(type)); + } + } } } - static class PDFEmbeddedFileMaker extends ElementMapping.Maker { + static class PDFEmbeddedFileElementMaker extends ElementMapping.Maker { public FONode make(FONode parent) { return new PDFEmbeddedFileElement(parent); } } + + static class PDFDictionaryElementMaker extends ElementMapping.Maker { + private PDFDictionaryType dictionaryType; + PDFDictionaryElementMaker(PDFDictionaryType dictionaryType) { + this.dictionaryType = dictionaryType; + } + public FONode make(FONode parent) { + return new PDFDictionaryElement(parent, dictionaryType); + } + } + + static class PDFDictionaryEntryElementMaker extends ElementMapping.Maker { + private PDFDictionaryEntryType entryType; + PDFDictionaryEntryElementMaker(PDFDictionaryEntryType entryType) { + this.entryType = entryType; + } + public FONode make(FONode parent) { + return new PDFDictionaryEntryElement(parent, entryType); + } + } } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileAttachment.java new file mode 100644 index 000000000..a62b35a4b --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileAttachment.java @@ -0,0 +1,156 @@ +/* + * 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; + +/** + * This is the pass-through value object for the PDF extension. + */ +public class PDFEmbeddedFileAttachment extends PDFExtensionAttachment { + + private static final long serialVersionUID = -1L; + + /** element name */ + protected static final String ELEMENT = "embedded-file"; + + /** name of file to be embedded */ + private static final String ATT_NAME = "filename"; + + /** source of file to be embedded (URI) */ + private static final String ATT_SRC = "src"; + + /** a description of the file to be embedded */ + private static final String ATT_DESC = "desc"; + + /** filename attribute */ + private String filename = null; + + /** description attribute (optional) */ + private String desc = null; + + /** source name attribute */ + private String src = null; + + /** + * No-argument contructor. + */ + public PDFEmbeddedFileAttachment() { + super(); + } + + /** + * Default constructor. + * @param filename the name of the file + * @param src the location of the file + * @param desc the description of the file + */ + public PDFEmbeddedFileAttachment(String filename, String src, String desc) { + super(); + this.filename = filename; + this.src = src; + this.desc = desc; + } + + /** + * Returns the file name. + * @return the file name + */ + public String getFilename() { + return filename; + } + + /** + * Sets the file name. + * @param name The file name to set. + */ + public void setFilename(String name) { + this.filename = name; + } + + /** + * Returns the file description. + * @return the description + */ + public String getDesc() { + return desc; + } + + /** + * Sets the description of the file. + * @param desc the description to set + */ + public void setDesc(String desc) { + this.desc = desc; + } + + /** + * Returns the source URI of the file. + * @return the source URI + */ + public String getSrc() { + return src; + } + + /** + * Sets the source URI of the file. + * @param src the source URI + */ + public void setSrc(String src) { + this.src = src; + } + + /** {@inheritDoc} */ + public String getCategory() { + return CATEGORY; + } + + /** {@inheritDoc} */ + public String toString() { + return "PDFEmbeddedFile(name=" + getFilename() + ", " + getSrc() + ")"; + } + + /** + * @return the element name + */ + protected String getElement() { + return ELEMENT; + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (filename != null && filename.length() > 0) { + atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", filename); + } + if (src != null && src.length() > 0) { + atts.addAttribute(null, ATT_SRC, ATT_SRC, "CDATA", src); + } + if (desc != null && desc.length() > 0) { + atts.addAttribute(null, ATT_DESC, ATT_DESC, "CDATA", desc); + } + String element = getElement(); + handler.startElement(CATEGORY, element, element, atts); + handler.endElement(CATEGORY, element, element); + } + +} 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/PDFEmbeddedFileExtensionAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java deleted file mode 100644 index 5f3f16dbb..000000000 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java +++ /dev/null @@ -1,154 +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.render.pdf.extensions; - -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; - -/** - * This is the pass-through value object for the PDF extension. - */ -public class PDFEmbeddedFileExtensionAttachment extends PDFExtensionAttachment { - - /** element name */ - protected static final String ELEMENT = "embedded-file"; - - /** name of file to be embedded */ - private static final String ATT_NAME = "filename"; - - /** source of file to be embedded (URI) */ - private static final String ATT_SRC = "src"; - - /** a description of the file to be embedded */ - private static final String ATT_DESC = "desc"; - - /** filename attribute */ - private String filename = null; - - /** description attribute (optional) */ - private String desc = null; - - /** source name attribute */ - private String src = null; - - /** - * No-argument contructor. - */ - public PDFEmbeddedFileExtensionAttachment() { - super(); - } - - /** - * Default constructor. - * @param filename the name of the file - * @param src the location of the file - * @param desc the description of the file - */ - public PDFEmbeddedFileExtensionAttachment(String filename, String src, String desc) { - super(); - this.filename = filename; - this.src = src; - this.desc = desc; - } - - /** - * Returns the file name. - * @return the file name - */ - public String getFilename() { - return filename; - } - - /** - * Sets the file name. - * @param name The file name to set. - */ - public void setFilename(String name) { - this.filename = name; - } - - /** - * Returns the file description. - * @return the description - */ - public String getDesc() { - return desc; - } - - /** - * Sets the description of the file. - * @param desc the description to set - */ - public void setDesc(String desc) { - this.desc = desc; - } - - /** - * Returns the source URI of the file. - * @return the source URI - */ - public String getSrc() { - return src; - } - - /** - * Sets the source URI of the file. - * @param src the source URI - */ - public void setSrc(String src) { - this.src = src; - } - - /** {@inheritDoc} */ - public String getCategory() { - return CATEGORY; - } - - /** {@inheritDoc} */ - public String toString() { - return "PDFEmbeddedFile(name=" + getFilename() + ", " + getSrc() + ")"; - } - - /** - * @return the element name - */ - protected String getElement() { - return ELEMENT; - } - - /** {@inheritDoc} */ - public void toSAX(ContentHandler handler) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - if (filename != null && filename.length() > 0) { - atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", filename); - } - if (src != null && src.length() > 0) { - atts.addAttribute(null, ATT_SRC, ATT_SRC, "CDATA", src); - } - if (desc != null && desc.length() > 0) { - atts.addAttribute(null, ATT_DESC, ATT_DESC, "CDATA", desc); - } - String element = getElement(); - handler.startElement(CATEGORY, element, element, atts); - handler.endElement(CATEGORY, element, element); - } - -} 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..58664faa0 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,28 @@ 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(); + @Override + 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..2741e64f6 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,127 @@ 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 dictionaries = new Stack(); + 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.hasValueOfElementName(localName)) { + PDFDictionaryExtension dictionary = new PDFDictionaryExtension(PDFDictionaryType.valueOfElementName(localName)); + String key = attributes.getValue(PDFDictionaryEntryExtension.PROPERTY_KEY); + if (key != null) { + dictionary.setKey(key); + } + if (dictionary.getDictionaryType() == PDFDictionaryType.Page) { + String pageNumbers = attributes.getValue(PDFDictionaryElement.ATT_PAGE_NUMBERS); + if (pageNumbers != null) { + dictionary.setProperty(PDFDictionaryElement.ATT_PAGE_NUMBERS, pageNumbers); + } + } + dictionaries.push(dictionary); + } else if (PDFDictionaryEntryType.hasValueOfElementName(localName)) { + PDFDictionaryEntryExtension entry = new PDFDictionaryEntryExtension(PDFDictionaryEntryType.valueOfElementName(localName)); + String key = attributes.getValue(PDFDictionaryEntryElement.ATT_KEY); + if (key != null) { + entry.setKey(key); + } + if (!dictionaries.empty()) { + PDFDictionaryExtension dictionary = dictionaries.peek(); + dictionary.addEntry(entry); + 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.returnedObject = new PDFEmbeddedFileAttachment(name, src, desc); + } else if (PDFDictionaryType.hasValueOfElementName(localName)) { + if (!dictionaries.empty()) { + PDFDictionaryExtension dictionary = dictionaries.pop(); + if ((dictionary.getDictionaryType() == PDFDictionaryType.Catalog) || (dictionary.getDictionaryType() == PDFDictionaryType.Page)) { + this.returnedObject = new PDFDictionaryAttachment(dictionary); + } else if (!dictionaries.empty()) { + PDFDictionaryExtension dictionaryOuter = dictionaries.peek(); + dictionaryOuter.addEntry(dictionary); + } + } else { + throw new SAXException(new IllegalStateException("no active dictionary")); + } + } else if (PDFDictionaryEntryType.hasValueOfElementName(localName)) { + if (!dictionaries.empty()) { + PDFDictionaryExtension dictionary = dictionaries.peek(); + PDFDictionaryEntryExtension entry = dictionary.getLastEntry(); + if (entry != null) { + if (characters != null) { + entry.setValue(characters.toString()); + characters = null; + } + } else { + throw new SAXException(new IllegalStateException("no active entry")); + } + } else { + throw new SAXException(new IllegalStateException("no active dictionary")); + } } } + captureContent = false; } - /** {@inheritDoc} */ + @Override public void endDocument() throws SAXException { if (listener != null) { listener.notifyObjectBuilt(getObject()); } } - /** {@inheritDoc} */ + @Override public Object getObject() { return returnedObject; } - /** {@inheritDoc} */ + @Override 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/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/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; } /** @@ -65,6 +68,14 @@ public class GenerationHelperContentHandler extends DelegatingContentHandler { this.mainNamespace = namespaceURI; } + /** + * 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 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; + } + } + } diff --git a/src/sandbox/org/apache/fop/render/svg/SVGDocumentHandler.java b/src/sandbox/org/apache/fop/render/svg/SVGDocumentHandler.java index 12dd948e5..f7bfbb38e 100644 --- a/src/sandbox/org/apache/fop/render/svg/SVGDocumentHandler.java +++ b/src/sandbox/org/apache/fop/render/svg/SVGDocumentHandler.java @@ -229,7 +229,7 @@ public class SVGDocumentHandler extends AbstractSVGDocumentHandler { } private GenerationHelperContentHandler decorate(ContentHandler contentHandler) { - return new GenerationHelperContentHandler(contentHandler, getMainNamespace()); + return new GenerationHelperContentHandler(contentHandler, getMainNamespace(), getContext()); } private void closeCurrentStream() { diff --git a/status.xml b/status.xml index d17168936..5f29deefb 100644 --- a/status.xml +++ b/status.xml @@ -59,6 +59,9 @@ documents. Example: the fix of marks layering will be such a case when it's done. --> + + Enable support for PDF page transitions. + Fix and improve the handling of temporary files using the new URI resource resolvers diff --git a/test/layoutengine/standard-testcases/pdf-dictionary-extension_1.xml b/test/layoutengine/standard-testcases/pdf-dictionary-extension_1.xml new file mode 100644 index 000000000..ecbf1bafd --- /dev/null +++ b/test/layoutengine/standard-testcases/pdf-dictionary-extension_1.xml @@ -0,0 +1,90 @@ + + + + + +

+ This test checks the PDF dictionary extensions. +

+
+ + + + + + + + + 5 + + + + Trans + 1 + Glitter + 0 + + + + + Trans + 1 + Push + 180 + + + + + Trans + 1 + Cover + 270 + + + + + + + Bar + + + + + SLIDE 1 + SLIDE 2 + SLIDE 3 + + + + + + + + + + + + + + + + + + + +
-- cgit v1.2.3 From 39e331287690a4ba25b19cbf1d9af413c3e81aca Mon Sep 17 00:00:00 2001 From: Luis Bernardo Date: Wed, 16 Oct 2013 23:31:17 +0000 Subject: FOP-2296: SVG fill pattern not visible with encryption; patch submitted by Simon Steiner git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1532940 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/fop/pdf/PDFPattern.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/java/org/apache/fop/pdf') diff --git a/src/java/org/apache/fop/pdf/PDFPattern.java b/src/java/org/apache/fop/pdf/PDFPattern.java index 46a6a7378..b9e042815 100644 --- a/src/java/org/apache/fop/pdf/PDFPattern.java +++ b/src/java/org/apache/fop/pdf/PDFPattern.java @@ -269,13 +269,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 { -- cgit v1.2.3 From 8841c3bdd571ff02ff4e99dbce355b78127e4190 Mon Sep 17 00:00:00 2001 From: Glenn Adams Date: Fri, 1 Nov 2013 14:34:18 +0000 Subject: FOP-2301: Enable support for PDF sub-page transitions. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1537948 13f79535-47bb-0310-9956-ffa450edef68 --- .../fop-intermediate-format-ng-content.xsd | 1 + src/java/org/apache/fop/area/AreaTreeParser.java | 2 +- src/java/org/apache/fop/area/Trait.java | 5 +- src/java/org/apache/fop/fo/Constants.java | 5 +- src/java/org/apache/fop/fo/FOPropertyMapping.java | 7 + src/java/org/apache/fop/fo/FObj.java | 14 +- .../fop/fo/extensions/ExtensionElementMapping.java | 2 + .../fop/layoutmgr/BlockContainerLayoutManager.java | 1 + .../apache/fop/layoutmgr/BlockLayoutManager.java | 1 + src/java/org/apache/fop/layoutmgr/TraitSetter.java | 11 + .../fop/layoutmgr/inline/InlineLayoutManager.java | 1 + src/java/org/apache/fop/pdf/AbstractPDFStream.java | 4 +- src/java/org/apache/fop/pdf/PDFDictionary.java | 2 +- src/java/org/apache/fop/pdf/PDFDocument.java | 54 ++++- src/java/org/apache/fop/pdf/PDFFactory.java | 24 ++ .../apache/fop/pdf/PDFIdentifiedDictionary.java | 42 ++++ src/java/org/apache/fop/pdf/PDFLayer.java | 86 +++++++ src/java/org/apache/fop/pdf/PDFNavigator.java | 93 ++++++++ .../org/apache/fop/pdf/PDFNavigatorAction.java | 28 +++ src/java/org/apache/fop/pdf/PDFPaintingState.java | 34 ++- src/java/org/apache/fop/pdf/PDFResources.java | 34 ++- .../org/apache/fop/pdf/PDFSetOCGStateAction.java | 82 +++++++ .../org/apache/fop/pdf/PDFTransitionAction.java | 79 +++++++ .../org/apache/fop/render/AbstractRenderer.java | 58 +++++ src/java/org/apache/fop/render/afp/AFPPainter.java | 2 +- .../fop/render/intermediate/AbstractIFPainter.java | 4 +- .../fop/render/intermediate/IFGraphicContext.java | 23 +- .../apache/fop/render/intermediate/IFPainter.java | 8 +- .../apache/fop/render/intermediate/IFParser.java | 5 +- .../apache/fop/render/intermediate/IFRenderer.java | 32 ++- .../fop/render/intermediate/IFSerializer.java | 13 +- .../apache/fop/render/java2d/Java2DPainter.java | 2 +- .../apache/fop/render/java2d/Java2DRenderer.java | 8 + src/java/org/apache/fop/render/pcl/PCLPainter.java | 2 +- .../apache/fop/render/pdf/PDFContentGenerator.java | 81 +++++-- .../apache/fop/render/pdf/PDFDocumentHandler.java | 3 +- src/java/org/apache/fop/render/pdf/PDFPainter.java | 4 +- .../apache/fop/render/pdf/PDFRenderingUtil.java | 261 ++++++++++++++++++++- .../extensions/AbstractPDFDictionaryElement.java | 43 ---- .../render/pdf/extensions/PDFActionElement.java | 64 +++++ .../render/pdf/extensions/PDFActionExtension.java | 32 +++ .../fop/render/pdf/extensions/PDFArrayElement.java | 82 +++++++ .../render/pdf/extensions/PDFArrayExtension.java | 84 +++++++ .../render/pdf/extensions/PDFCatalogElement.java | 45 ++++ .../render/pdf/extensions/PDFCatalogExtension.java | 29 +++ .../pdf/extensions/PDFCollectionEntryElement.java | 146 ++++++++++++ .../extensions/PDFCollectionEntryExtension.java | 43 ++++ .../pdf/extensions/PDFCollectionExtension.java | 34 +++ .../pdf/extensions/PDFDictionaryAttachment.java | 70 ++++-- .../pdf/extensions/PDFDictionaryElement.java | 83 ++++--- .../pdf/extensions/PDFDictionaryEntryElement.java | 109 --------- .../extensions/PDFDictionaryEntryExtension.java | 110 --------- .../pdf/extensions/PDFDictionaryEntryType.java | 56 ----- .../pdf/extensions/PDFDictionaryExtension.java | 97 ++++---- .../render/pdf/extensions/PDFDictionaryType.java | 19 +- .../render/pdf/extensions/PDFElementMapping.java | 90 +++++-- .../render/pdf/extensions/PDFExtensionHandler.java | 124 +++++++--- .../fop/render/pdf/extensions/PDFLayerElement.java | 45 ++++ .../render/pdf/extensions/PDFLayerExtension.java | 29 +++ .../render/pdf/extensions/PDFNavigatorElement.java | 45 ++++ .../pdf/extensions/PDFNavigatorExtension.java | 30 +++ .../render/pdf/extensions/PDFObjectExtension.java | 101 ++++++++ .../fop/render/pdf/extensions/PDFObjectType.java | 60 +++++ .../fop/render/pdf/extensions/PDFPageElement.java | 64 +++++ .../render/pdf/extensions/PDFPageExtension.java | 73 ++++++ .../render/pdf/extensions/PDFReferenceElement.java | 58 +++++ .../pdf/extensions/PDFReferenceExtension.java | 61 +++++ src/java/org/apache/fop/render/ps/PSPainter.java | 2 +- .../org/apache/fop/render/txt/TXTRenderer.java | 8 + .../org/apache/fop/render/xml/XMLRenderer.java | 10 + .../org/apache/fop/util/AbstractPaintingState.java | 67 ++++-- .../org/apache/fop/render/svg/SVGPainter.java | 10 +- status.xml | 5 +- .../apache/fop/fonts/EmbedFontInfoTestCase.java | 2 + test/java/org/apache/fop/fonts/FontsTestSuite.java | 2 +- test/java/org/apache/fop/pdf/PDFDestsTestCase.java | 2 +- .../org/apache/fop/pdf/PDFDictionaryTestCase.java | 2 +- .../org/apache/fop/pdf/PDFPageLabelsTestCase.java | 28 +-- .../java/org/apache/fop/pdf/PDFStreamTestCase.java | 2 +- .../fop/pdf/xref/CrossReferenceTableTestCase.java | 2 +- .../intermediate/AbstractIFPainterTestCase.java | 2 +- .../pdf-dictionary-extension_2.xml | 74 ++++++ .../pdf-dictionary-extension_3.xml | 122 ++++++++++ 83 files changed, 2796 insertions(+), 588 deletions(-) create mode 100644 src/java/org/apache/fop/pdf/PDFIdentifiedDictionary.java create mode 100644 src/java/org/apache/fop/pdf/PDFLayer.java create mode 100644 src/java/org/apache/fop/pdf/PDFNavigator.java create mode 100644 src/java/org/apache/fop/pdf/PDFNavigatorAction.java create mode 100644 src/java/org/apache/fop/pdf/PDFSetOCGStateAction.java create mode 100644 src/java/org/apache/fop/pdf/PDFTransitionAction.java delete mode 100644 src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFActionElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFActionExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFArrayElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFArrayExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFCatalogElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFCatalogExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFCollectionExtension.java delete mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java delete mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java delete mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFLayerElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFLayerExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFObjectExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFObjectType.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFPageElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFPageExtension.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFReferenceExtension.java create mode 100644 test/layoutengine/standard-testcases/pdf-dictionary-extension_2.xml create mode 100644 test/layoutengine/standard-testcases/pdf-dictionary-extension_3.xml (limited to 'src/java/org/apache/fop/pdf') diff --git a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd index d6f0c694c..2e58a7f45 100644 --- a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd +++ b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd @@ -38,6 +38,7 @@ + diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index c79f975c1..f6de99dd0 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -1040,7 +1040,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/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/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 b6f2ec83e..0d1a2a84b 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 /** @@ -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; 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/layoutmgr/BlockContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index a23cd28f1..eced48939 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -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); diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java index 55b17e953..b29b95cb1 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java @@ -381,6 +381,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())); 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/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/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/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 launches = new ArrayList(); + private List layers; + + private List navigators; + + private List 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(); + } + this.layers.add((PDFLayer) obj); + } + if (obj instanceof PDFNavigator) { + if (this.navigators == null) { + this.navigators = new ArrayList(); + } + this.navigators.add((PDFNavigator) obj); + } + if (obj instanceof PDFNavigatorAction) { + if (this.navigatorActions == null) { + this.navigatorActions = new ArrayList(); + } + this.navigatorActions.add((PDFNavigatorAction) obj); + } } /** @@ -889,6 +913,34 @@ public class PDFDocument { this.accessibilityEnabled = enableAccessibility; } + /** + * + */ + 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 * @@ -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 631499af1..5af33866e 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -1821,4 +1821,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/PDFIdentifiedDictionary.java b/src/java/org/apache/fop/pdf/PDFIdentifiedDictionary.java new file mode 100644 index 000000000..c2d033aec --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFIdentifiedDictionary.java @@ -0,0 +1,42 @@ +/* + * 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; + +/** + * Identified Dictionary. + */ +public class PDFIdentifiedDictionary extends PDFDictionary { + + private final String id; + + public PDFIdentifiedDictionary(String id) { + this.id = id; + } + + 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/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/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 iccColorSpaces = new LinkedHashMap(); + /** Named properties */ + protected Map properties = new LinkedHashMap(); + /** * 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/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 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(); + } + 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/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 { } /** {@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/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 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/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 30ceda108..e40a8f6b3 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -742,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); @@ -842,6 +848,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { currentBPPosition += bv.getAllocBPD(); } viewportDimensionStack.pop(); + maybeEndLayer(bv, inNewLayer); } /** {@inheritDoc} */ @@ -849,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); @@ -895,10 +902,27 @@ public class IFRenderer extends AbstractPathOrientedRenderer { restoreGraphicsState(); } + /** {@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); } @@ -965,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()); @@ -977,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 1ffd42863..395e79719 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -428,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 { } /** {@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 @@ -475,6 +475,14 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem restoreGraphicsState(); } + /** {@inheritDoc} */ + protected void startLayer(String layer) { + } + + /** {@inheritDoc} */ + protected void endLayer() { + } + /** {@inheritDoc} */ protected List breakOutOfStateStack() { log.debug("Block.FIXED --> break out"); 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 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/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 + " <>\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 0a3d34ef4..648cdce7a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -306,8 +306,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { throw new IFException("Error adding embedded file: " + embeddedFile.getSrc(), ioe); } } else if (extension instanceof PDFDictionaryAttachment) { - PDFDictionaryAttachment dictionaryExtension = (PDFDictionaryAttachment) extension; - pdfUtil.renderDictionaryExtension(dictionaryExtension, currentPage); + pdfUtil.renderDictionaryExtension((PDFDictionaryAttachment) extension, currentPage); } else if (extension != null) { log.debug("Don't know how to handle extension object. Ignoring: " + extension + " (" + extension.getClass().getName() + ")"); 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 { } /** {@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 962cd7847..d27fead12 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,24 +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.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.PDFDictionaryEntryExtension; -import org.apache.fop.render.pdf.extensions.PDFDictionaryEntryType; 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; @@ -260,10 +270,189 @@ class PDFRenderingUtil { public void renderDictionaryExtension(PDFDictionaryAttachment attachment, PDFPage currentPage) { PDFDictionaryExtension extension = attachment.getExtension(); - if (extension.getDictionaryType() == PDFDictionaryType.Catalog) { + 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) 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) 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) 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 (extension.getDictionaryType() == PDFDictionaryType.Page) { - if (extension.matchesPageNumber(currentPage.getPageIndex() + 1)) { + } else if (type == PDFDictionaryType.Page) { + assert extension instanceof PDFPageExtension; + if (((PDFPageExtension) extension).matchesPageNumber(currentPage.getPageIndex() + 1)) { augmentDictionary(currentPage, extension); } } else { @@ -272,9 +461,11 @@ class PDFRenderingUtil { } private PDFDictionary augmentDictionary(PDFDictionary dictionary, PDFDictionaryExtension extension) { - for (PDFDictionaryEntryExtension entry : extension.getEntries()) { + 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); } @@ -282,22 +473,68 @@ class PDFRenderingUtil { return dictionary; } - private void augmentDictionary(PDFDictionary dictionary, PDFDictionaryEntryExtension entry) { - PDFDictionaryEntryType type = entry.getType(); + private void augmentDictionary(PDFDictionary dictionary, PDFCollectionEntryExtension entry) { + PDFObjectType type = entry.getType(); String key = entry.getKey(); - if (type == PDFDictionaryEntryType.Boolean) { + if (type == PDFObjectType.Boolean) { dictionary.put(key, entry.getValueAsBoolean()); - } else if (type == PDFDictionaryEntryType.Name) { + } else if (type == PDFObjectType.Name) { dictionary.put(key, new PDFName(entry.getValueAsString())); - } else if (type == PDFDictionaryEntryType.Number) { + } else if (type == PDFObjectType.Number) { dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); - } else if (type == PDFDictionaryEntryType.String) { + } 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"); diff --git a/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java b/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java deleted file mode 100644 index 9de7e95da..000000000 --- a/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java +++ /dev/null @@ -1,43 +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.render.pdf.extensions; - -import org.apache.fop.fo.FONode; - -// CSOFF: LineLengthCheck - -/** - * Base class for the PDF dictionary related extension elements. - */ -public abstract class AbstractPDFDictionaryElement extends AbstractPDFExtensionElement { - - public static final String ATT_KEY = PDFDictionaryEntryExtension.PROPERTY_KEY; - - /** - * Default constructor - * - * @param parent parent of this node - * @see org.apache.fop.fo.FONode#FONode(FONode) - */ - public AbstractPDFDictionaryElement(FONode parent) { - super(parent); - } -} - 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 properties; + private List entries; + + PDFArrayExtension() { + super(PDFObjectType.Array); + this.properties = new java.util.HashMap(); + this.entries = new java.util.ArrayList(); + } + + @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 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, null); + } + extension.setValue(Boolean.valueOf(value)); + } else if (extension.getType() == PDFObjectType.Name) { + String value = (characters != null) ? characters.toString() : ""; + if (value.length() == 0) { + invalidPropertyValueError("", 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, 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 index 19e5ce07a..3832619ba 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java @@ -43,14 +43,13 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { } public void toSAX(ContentHandler handler) throws SAXException { - PDFDictionaryType dictionaryType = extension.getDictionaryType(); int pageNumber = 0; - if (dictionaryType == PDFDictionaryType.Page) { + 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) && extension.matchesPageNumber(pageIndex + 1)) { + if ((pageIndex >= 0) && ((PDFPageExtension) extension).matchesPageNumber(pageIndex + 1)) { pageNumber = pageIndex + 1; } else { pageNumber = -1; @@ -69,24 +68,40 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { String qn = PREFIX + ":" + ln; attributes = extractIFAttributes(attributes, dictionary); handler.startElement(CATEGORY, ln, qn, attributes); - for (PDFDictionaryEntryExtension entry : dictionary.getEntries()) { + for (PDFCollectionEntryExtension entry : dictionary.getEntries()) { toSAX(handler, entry); } handler.endElement(CATEGORY, ln, qn); } - private void toSAX(ContentHandler handler, PDFDictionaryEntryExtension entry) throws SAXException { + 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); - char[] characters = entry.getValueAsXMLEscapedString().toCharArray(); - if (characters.length > 0) { - handler.characters(characters, 0, characters.length); + if (!(entry instanceof PDFReferenceExtension)) { + char[] characters = entry.getValueAsXMLEscapedString().toCharArray(); + if (characters.length > 0) { + handler.characters(characters, 0, characters.length); + } } handler.endElement(CATEGORY, ln, qn); } @@ -94,16 +109,27 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFDictionaryExtension dictionary) { PDFDictionaryType type = dictionary.getDictionaryType(); - if (type == PDFDictionaryType.Catalog) { - // no specific attriburtes + 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 = PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS; + 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 = PDFDictionaryEntryExtension.PROPERTY_KEY; + String keyName = PDFCollectionEntryElement.ATT_KEY; String key = dictionary.getKey(); if (key != null) { attributes.addAttribute(null, keyName, keyName, "CDATA", key); @@ -112,12 +138,28 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { return attributes; } - private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFDictionaryEntryExtension entry) { - String keyName = PDFDictionaryEntryExtension.PROPERTY_KEY; + 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 index 0920f3a78..9dc127da6 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java @@ -23,7 +23,6 @@ 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; import org.apache.fop.fo.extensions.ExtensionAttachment; @@ -34,38 +33,54 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; * Extension element for dictionaries: pdf:{catalog,page,dictionary}. The specific type * of dictionary is established at construction type. */ -public class PDFDictionaryElement extends AbstractPDFDictionaryElement { +public class PDFDictionaryElement extends PDFCollectionEntryElement { - public static final String ATT_PAGE_NUMBERS = PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS; - - private PDFDictionaryExtension extension; + public static final String ATT_ID = PDFDictionaryExtension.PROPERTY_ID; /** * Main constructor * @param parent parent FO node */ PDFDictionaryElement(FONode parent, PDFDictionaryType type) { - super(parent); - this.extension = new PDFDictionaryExtension(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 getExtension() { - return extension; + public PDFDictionaryExtension getDictionaryExtension() { + assert getExtension() instanceof PDFDictionaryExtension; + return (PDFDictionaryExtension) getExtension(); } @Override public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { - if (extension.getDictionaryType() == PDFDictionaryType.Catalog) { - // no specific properties - } else if (extension.getDictionaryType() == PDFDictionaryType.Page) { - String pageNumbers = attlist.getValue(ATT_PAGE_NUMBERS); - if (pageNumbers != null) { - extension.setProperty(ATT_PAGE_NUMBERS, pageNumbers); + PDFDictionaryExtension extension = getDictionaryExtension(); + if (extension.usesIDAttribute()) { + String id = attlist.getValue(ATT_ID); + if (id != null) { + extension.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); } - } else if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { + } + if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { String key = attlist.getValue(ATT_KEY); if (key == null) { - missingPropertyError(ATT_KEY); + if (parent instanceof PDFDictionaryElement) { + missingPropertyError(ATT_KEY); + } } else if (key.length() == 0) { invalidPropertyValueError(ATT_KEY, key, null); } else { @@ -78,16 +93,18 @@ public class PDFDictionaryElement extends AbstractPDFDictionaryElement { public void startOfNode() throws FOPException { super.startOfNode(); String localName = getLocalName(); - if (localName.equals("catalog")) { - if (parent.getNameId() != Constants.FO_DECLARATIONS) { - invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); - } + 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")) { - if (parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { - invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfSPM"); - } + // handled in PDFPageElement subclass } else if (localName.equals("dictionary")) { - if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName())) { + if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName()) && !PDFObjectType.Array.elementName().equals(parent.getLocalName())) { invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), null); } } else { @@ -97,14 +114,15 @@ public class PDFDictionaryElement extends AbstractPDFDictionaryElement { @Override protected void addChildNode(FONode child) throws FOPException { + PDFDictionaryExtension extension = getDictionaryExtension(); if (child instanceof PDFDictionaryElement) { - PDFDictionaryExtension extension = ((PDFDictionaryElement) child).getExtension(); - if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { - this.extension.addEntry(extension); + PDFDictionaryExtension entry = ((PDFDictionaryElement) child).getDictionaryExtension(); + if (entry.getDictionaryType() == PDFDictionaryType.Dictionary) { + extension.addEntry(entry); } - } else if (child instanceof PDFDictionaryEntryElement) { - PDFDictionaryEntryExtension extension = ((PDFDictionaryEntryElement) child).getExtension(); - this.extension.addEntry(extension); + } else if (child instanceof PDFCollectionEntryElement) { + PDFCollectionEntryExtension entry = ((PDFCollectionEntryElement) child).getExtension(); + extension.addEntry(entry); } } @@ -115,12 +133,13 @@ public class PDFDictionaryElement extends AbstractPDFDictionaryElement { @Override public String getLocalName() { + PDFDictionaryExtension extension = getDictionaryExtension(); return extension.getDictionaryType().elementName(); } @Override protected ExtensionAttachment instantiateExtensionAttachment() { - return new PDFDictionaryAttachment(extension); + return new PDFDictionaryAttachment(getDictionaryExtension()); } } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java deleted file mode 100644 index bcdb90c2c..000000000 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java +++ /dev/null @@ -1,109 +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.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 dictionary entries: pdf:{boolean,name,number,string}. The specific type - * of entry is established at construction type. - */ -public class PDFDictionaryEntryElement extends AbstractPDFDictionaryElement { - - private PDFDictionaryEntryExtension extension; - private StringBuffer characters; - - /** - * Main constructor - * @param parent parent FO node - */ - PDFDictionaryEntryElement(FONode parent, PDFDictionaryEntryType type) { - super(parent); - this.extension = new PDFDictionaryEntryExtension(type); - } - - public PDFDictionaryEntryExtension getExtension() { - return extension; - } - - @Override - public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { - String key = attlist.getValue("key"); - if (key == null) { - missingPropertyError("key"); - } else if (key.length() == 0) { - invalidPropertyValueError("key", key, null); - } else { - extension.setKey(key); - } - } - - @Override - public void startOfNode() throws FOPException { - super.startOfNode(); - 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 (characters == null) { - characters = new StringBuffer((length < 16) ? 16 : length); - } - characters.append(data, start, length); - } - - @Override - public void endOfNode() throws FOPException { - String value = (characters != null) ? characters.toString() : ""; - if (extension.getType() == PDFDictionaryEntryType.Boolean) { - if (!value.equals("true") && !value.equals("false")) { - invalidPropertyValueError("", value, null); - } - } else if (extension.getType() == PDFDictionaryEntryType.Name) { - if (value.length() == 0) { - invalidPropertyValueError("", value, null); - } - } else if (extension.getType() == PDFDictionaryEntryType.Number) { - try { - Double.valueOf(value); - } catch (NumberFormatException e) { - invalidPropertyValueError("", value, null); - } - } else if (extension.getType() != PDFDictionaryEntryType.String) { - throw new IllegalStateException(); - } - extension.setValue(value); - super.endOfNode(); - } - - @Override - public String getLocalName() { - return extension.getType().elementName(); - } -} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java deleted file mode 100644 index 94d6b5dbf..000000000 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java +++ /dev/null @@ -1,110 +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.render.pdf.extensions; - -import org.apache.fop.util.XMLUtil; - -// CSOFF: LineLengthCheck - -public class PDFDictionaryEntryExtension { - - public static final String PROPERTY_KEY = "key"; - - private PDFDictionaryEntryType type; - private String key = ""; - private Object value; - - PDFDictionaryEntryExtension() { - } - - PDFDictionaryEntryExtension(PDFDictionaryEntryType type) { - this.type = type; - } - - public PDFDictionaryEntryType getType() { - return type; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public void setValue(String value) { - this.value = value; - } - - public Object getValue() { - return value; - } - - /** - * Obtain entry value as Boolean. - * @return entry value - */ - public Boolean getValueAsBoolean() { - if (value instanceof String) { - return Boolean.valueOf((String)value); - } else { - return false; - } - } - - /** - * Obtain entry value as Number. - * @return entry value - */ - public Number getValueAsNumber() { - if (value instanceof String) { - double d = Double.parseDouble((String) value); - if (Math.floor(d) == d) { - return Long.valueOf((long) d); - } else { - return Double.valueOf(d); - } - } else { - return Integer.valueOf(0); - } - } - - public String getValueAsString() { - if (value instanceof String) { - return (String) value; - } else { - return ""; - } - } - - public String getValueAsXMLEscapedString() { - return XMLUtil.escape(getValueAsString()); - } - - public void setValue(Object value) { - this.value = value; - } - - public String getElementName() { - return type.elementName(); - } - -} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java deleted file mode 100644 index e4b25819a..000000000 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java +++ /dev/null @@ -1,56 +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.render.pdf.extensions; - -// CSOFF: LineLengthCheck - -/** - * Enumeration type for leaf PDF dictionary entry extension elements. - */ -public enum PDFDictionaryEntryType { - Boolean("boolean"), // boolean valued entry - Dictionary("dictionary"), // dictionary valued entry - Name("name"), // name valued entry - Number("number"), // number valued entry - String("string"); // string valued entry - - private String elementName; - PDFDictionaryEntryType(String elementName) { - this.elementName = elementName; - } - public String elementName() { - return elementName; - } - static PDFDictionaryEntryType valueOfElementName(String elementName) { - for (PDFDictionaryEntryType 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/PDFDictionaryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java index d4b11cdb4..50b6f3a83 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java @@ -24,24 +24,36 @@ import java.util.Map; // CSOFF: LineLengthCheck -public class PDFDictionaryExtension extends PDFDictionaryEntryExtension { +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 properties; - private List entries; + private List entries; PDFDictionaryExtension() { + this(PDFDictionaryType.Dictionary); } PDFDictionaryExtension(PDFDictionaryType dictionaryType) { - super(PDFDictionaryEntryType.Dictionary); + super(PDFObjectType.Dictionary); this.dictionaryType = dictionaryType; this.properties = new java.util.HashMap(); - this.entries = new java.util.ArrayList(); + this.entries = new java.util.ArrayList(); + } + + @Override + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return getEntries(); } public PDFDictionaryType getDictionaryType() { @@ -56,15 +68,40 @@ public class PDFDictionaryExtension extends PDFDictionaryEntryExtension { return properties.get(name); } - public void addEntry(PDFDictionaryEntryExtension entry) { - entries.add(entry); + @Override + public void addEntry(PDFCollectionEntryExtension entry) { + if ((entry.getKey() == null) || (entry.getKey().length() == 0)) { + throw new IllegalArgumentException(); + } else { + entries.add(entry); + } } - public List getEntries() { + public List getEntries() { return entries; } - public PDFDictionaryEntryExtension getLastEntry() { + 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 { @@ -72,48 +109,8 @@ public class PDFDictionaryExtension extends PDFDictionaryEntryExtension { } } - /** - * 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) { - if (dictionaryType != PDFDictionaryType.Page) { - return false; - } - 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; + public boolean usesIDAttribute() { + return dictionaryType.usesIDAttribute(); } @Override diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java index edd95160a..a49a5fc8c 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java @@ -25,17 +25,28 @@ package org.apache.fop.render.pdf.extensions; * Enumeration type for PDF dictionary extension elements. */ public enum PDFDictionaryType { - Dictionary("dictionary"), // generic (nested) dictionary element - Catalog("catalog"), // catalog dictionary element - Page("page"); // page dictionary element + 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; - PDFDictionaryType(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)) { 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 3e063e24b..1fba80796 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java @@ -41,17 +41,56 @@ public class PDFElementMapping extends ElementMapping { protected void initialize() { if (foObjs == null) { foObjs = new java.util.HashMap(); + // 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:{catalog,page} et al. - for (PDFDictionaryType type : PDFDictionaryType.values()) { - foObjs.put(type.elementName(), new PDFDictionaryElementMaker(type)); - } - for (PDFDictionaryEntryType type : PDFDictionaryEntryType.values()) { - if (type != PDFDictionaryEntryType.Dictionary) { - foObjs.put(type.elementName(), new PDFDictionaryEntryElementMaker(type)); - } - } + // 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); } } @@ -61,23 +100,38 @@ public class PDFElementMapping extends ElementMapping { } } - static class PDFDictionaryElementMaker extends ElementMapping.Maker { - private PDFDictionaryType dictionaryType; - PDFDictionaryElementMaker(PDFDictionaryType dictionaryType) { - this.dictionaryType = dictionaryType; + 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 PDFDictionaryElement(parent, dictionaryType); + return new PDFNavigatorElement(parent); } } - static class PDFDictionaryEntryElementMaker extends ElementMapping.Maker { - private PDFDictionaryEntryType entryType; - PDFDictionaryEntryElementMaker(PDFDictionaryEntryType entryType) { + 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 PDFDictionaryEntryElement(parent, entryType); + 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/PDFExtensionHandler.java b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java index f14f1e7d6..2fd14058e 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java @@ -49,7 +49,7 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle private Attributes lastAttributes; // PDFDictionaryAttachment related - private Stack dictionaries = new Stack(); + private Stack collections = new Stack(); private boolean captureContent; private StringBuffer characters; @@ -58,29 +58,78 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle if (PDFExtensionAttachment.CATEGORY.equals(uri)) { if (localName.equals(PDFEmbeddedFileAttachment.ELEMENT)) { lastAttributes = new AttributesImpl(attributes); - } else if (PDFDictionaryType.hasValueOfElementName(localName)) { - PDFDictionaryExtension dictionary = new PDFDictionaryExtension(PDFDictionaryType.valueOfElementName(localName)); - String key = attributes.getValue(PDFDictionaryEntryExtension.PROPERTY_KEY); + } 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); } - if (dictionary.getDictionaryType() == PDFDictionaryType.Page) { - String pageNumbers = attributes.getValue(PDFDictionaryElement.ATT_PAGE_NUMBERS); - if (pageNumbers != null) { - dictionary.setProperty(PDFDictionaryElement.ATT_PAGE_NUMBERS, pageNumbers); - } + 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)); } - dictionaries.push(dictionary); - } else if (PDFDictionaryEntryType.hasValueOfElementName(localName)) { - PDFDictionaryEntryExtension entry = new PDFDictionaryEntryExtension(PDFDictionaryEntryType.valueOfElementName(localName)); - String key = attributes.getValue(PDFDictionaryEntryElement.ATT_KEY); + String key = attributes.getValue(PDFCollectionEntryElement.ATT_KEY); if (key != null) { entry.setKey(key); } - if (!dictionaries.empty()) { - PDFDictionaryExtension dictionary = dictionaries.peek(); - dictionary.addEntry(entry); - captureContent = true; + 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); @@ -107,33 +156,48 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle String name = lastAttributes.getValue("name"); String src = lastAttributes.getValue("src"); String desc = lastAttributes.getValue("description"); + this.lastAttributes = null; this.returnedObject = new PDFEmbeddedFileAttachment(name, src, desc); } else if (PDFDictionaryType.hasValueOfElementName(localName)) { - if (!dictionaries.empty()) { - PDFDictionaryExtension dictionary = dictionaries.pop(); - if ((dictionary.getDictionaryType() == PDFDictionaryType.Catalog) || (dictionary.getDictionaryType() == PDFDictionaryType.Page)) { + 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 if (!dictionaries.empty()) { - PDFDictionaryExtension dictionaryOuter = dictionaries.peek(); - dictionaryOuter.addEntry(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("no active dictionary")); + throw new SAXException(new IllegalStateException("collections stack is empty or not an array")); } - } else if (PDFDictionaryEntryType.hasValueOfElementName(localName)) { - if (!dictionaries.empty()) { - PDFDictionaryExtension dictionary = dictionaries.peek(); - PDFDictionaryEntryExtension entry = dictionary.getLastEntry(); + } 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 active entry")); + throw new SAXException(new IllegalStateException("no current entry")); } } else { - throw new SAXException(new IllegalStateException("no active dictionary")); + throw new SAXException(new IllegalStateException("entry not permitted at outer level")); } } } 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/render/pdf/extensions/PDFReferenceElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java new file mode 100644 index 000000000..37aeeb890 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java @@ -0,0 +1,58 @@ +/* + * 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:reference. + */ +public class PDFReferenceElement extends PDFCollectionEntryElement { + + public static final String ATT_REFID = PDFReferenceExtension.PROPERTY_REFID; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFReferenceElement(FONode parent) { + super(parent, PDFObjectType.Reference); + } + + @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/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index 3d1887f2d..547662c47 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -116,7 +116,7 @@ public class PSPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { try { PSGenerator generator = getGenerator(); saveGraphicsState(); 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 @@ -582,6 +582,14 @@ public class TXTRenderer extends AbstractPathOrientedRenderer { currentState.pop(); } + /** {@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/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/**/ stateStack = new StateStack/**/(); + private StateStack stateStack = new StateStack(); /** * 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/**/ dataList) { - Iterator it = dataList.iterator(); - while (it.hasNext()) { + public void saveAll(List 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/**/ restoreAll() { - List/**/ dataList = new java.util.ArrayList/**/(); + public List restoreAll() { + List dataList = new java.util.ArrayList(); AbstractData data; while (true) { data = getData(); @@ -361,7 +358,7 @@ public abstract class AbstractPaintingState implements Cloneable, Serializable { * * @return the state stack */ - protected Stack/**/ getStateStack() { + protected Stack 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(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 extends java.util.Stack { 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/sandbox/org/apache/fop/render/svg/SVGPainter.java b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java index ebd143a69..fb7ce2677 100644 --- a/src/sandbox/org/apache/fop/render/svg/SVGPainter.java +++ b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java @@ -146,16 +146,16 @@ public class SVGPainter extends AbstractIFPainter } /** {@inheritDoc} */ - public void startGroup(AffineTransform[] transforms) throws IFException { - startGroup(SVGUtil.formatAffineTransformsMptToPt(transforms)); + public void startGroup(AffineTransform[] transforms, String layer) throws IFException { + startGroup(SVGUtil.formatAffineTransformsMptToPt(transforms), layer); } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { - startGroup(SVGUtil.formatAffineTransformMptToPt(transform)); + public void startGroup(AffineTransform transform, String layer) throws IFException { + startGroup(SVGUtil.formatAffineTransformMptToPt(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) { diff --git a/status.xml b/status.xml index 352f487c3..91b9c78d3 100644 --- a/status.xml +++ b/status.xml @@ -58,7 +58,10 @@ users, i.e. when the behaviour changes and could affect the layout of existing documents. Example: the fix of marks layering will be such a case when it's done. --> - + + + Enable support for PDF sub-page transitions. + Fix misplaced table cell border in WM RTL context. diff --git a/test/java/org/apache/fop/fonts/EmbedFontInfoTestCase.java b/test/java/org/apache/fop/fonts/EmbedFontInfoTestCase.java index d156b908c..ee3498a03 100644 --- a/test/java/org/apache/fop/fonts/EmbedFontInfoTestCase.java +++ b/test/java/org/apache/fop/fonts/EmbedFontInfoTestCase.java @@ -35,6 +35,8 @@ import static org.junit.Assert.assertTrue; */ public class EmbedFontInfoTestCase { + public EmbedFontInfoTestCase() {} + private EmbedFontInfo sut; private final URI metricsURI = URI.create("test/resources/fonts/ttf/glb12.ttf.xml"); diff --git a/test/java/org/apache/fop/fonts/FontsTestSuite.java b/test/java/org/apache/fop/fonts/FontsTestSuite.java index ad48c46c4..1baba49df 100644 --- a/test/java/org/apache/fop/fonts/FontsTestSuite.java +++ b/test/java/org/apache/fop/fonts/FontsTestSuite.java @@ -29,7 +29,7 @@ import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ FontManagerConfiguratorTestCase.class, - EmbedFontInfo.class, + EmbedFontInfoTestCase.class, FontEventProcessingTestCase.class, FontManagerConfiguratorTestCase.class }) diff --git a/test/java/org/apache/fop/pdf/PDFDestsTestCase.java b/test/java/org/apache/fop/pdf/PDFDestsTestCase.java index 08d841ede..49c1e6dab 100644 --- a/test/java/org/apache/fop/pdf/PDFDestsTestCase.java +++ b/test/java/org/apache/fop/pdf/PDFDestsTestCase.java @@ -32,7 +32,7 @@ import java.util.List; public class PDFDestsTestCase extends PDFObjectTestCase { private PDFDests dests = new PDFDests(); - private String expectedString = "<< /Names [(number) 10 (name) /Test#20name] >>\n"; + private String expectedString = "<< /Names [(number) 10 (name) /Test#20name] >>"; @Before public void setUp() { diff --git a/test/java/org/apache/fop/pdf/PDFDictionaryTestCase.java b/test/java/org/apache/fop/pdf/PDFDictionaryTestCase.java index 3f84fac2e..00224e93e 100644 --- a/test/java/org/apache/fop/pdf/PDFDictionaryTestCase.java +++ b/test/java/org/apache/fop/pdf/PDFDictionaryTestCase.java @@ -47,7 +47,7 @@ public class PDFDictionaryTestCase extends PDFObjectTestCase { + " /array [1 (two) 20]\n" + " /number 20\n" + " /null null\n" - + ">>\n"; + + ">>"; @Before public void setUp() { diff --git a/test/java/org/apache/fop/pdf/PDFPageLabelsTestCase.java b/test/java/org/apache/fop/pdf/PDFPageLabelsTestCase.java index e982bf246..a8be7ee74 100644 --- a/test/java/org/apache/fop/pdf/PDFPageLabelsTestCase.java +++ b/test/java/org/apache/fop/pdf/PDFPageLabelsTestCase.java @@ -37,11 +37,11 @@ public class PDFPageLabelsTestCase { int index = 0; StringBuilder expected = new StringBuilder(); expected.append("["); - expected.append(index + " << /S /r >>\n"); + expected.append(index + " << /S /r >>"); pageLabels.addPageLabel(index++, "i"); pageLabels.addPageLabel(index++, "ii"); pageLabels.addPageLabel(index++, "iii"); - expected.append(" " + index + " << /S /D >>\n"); + expected.append(" " + index + " << /S /D >>"); pageLabels.addPageLabel(index++, "1"); pageLabels.addPageLabel(index++, "2"); pageLabels.addPageLabel(index++, "3"); @@ -52,33 +52,33 @@ public class PDFPageLabelsTestCase { pageLabels.addPageLabel(index++, "8"); pageLabels.addPageLabel(index++, "9"); pageLabels.addPageLabel(index++, "10"); - expected.append(" " + index + " << /S /A >>\n"); + expected.append(" " + index + " << /S /A >>"); pageLabels.addPageLabel(index++, "A"); pageLabels.addPageLabel(index++, "B"); - expected.append(" " + index + " << /S /R /St 100 >>\n"); + expected.append(" " + index + " << /S /R /St 100 >>"); pageLabels.addPageLabel(index++, "C"); - expected.append(" " + index + " << /S /R /St 500 >>\n"); + expected.append(" " + index + " << /S /R /St 500 >>"); pageLabels.addPageLabel(index++, "D"); - expected.append(" " + index + " << /S /A /St 5 >>\n"); + expected.append(" " + index + " << /S /A /St 5 >>"); pageLabels.addPageLabel(index++, "E"); pageLabels.addPageLabel(index++, "F"); pageLabels.addPageLabel(index++, "G"); - expected.append(" " + index + " << /P (aa) >>\n"); + expected.append(" " + index + " << /P (aa) >>"); pageLabels.addPageLabel(index++, "aa"); - expected.append(" " + index + " << /P (ab) >>\n"); + expected.append(" " + index + " << /P (ab) >>"); pageLabels.addPageLabel(index++, "ab"); - expected.append(" " + index + " << /P (ac) >>\n"); + expected.append(" " + index + " << /P (ac) >>"); pageLabels.addPageLabel(index++, "ac"); - expected.append(" " + index + " << /S /a >>\n"); + expected.append(" " + index + " << /S /a >>"); pageLabels.addPageLabel(index++, "a"); pageLabels.addPageLabel(index++, "b"); - expected.append(" " + index + " << /S /R /St 2 >>\n"); + expected.append(" " + index + " << /S /R /St 2 >>"); pageLabels.addPageLabel(index++, "II"); - expected.append(" " + index + " << /S /R /St 12 >>\n"); + expected.append(" " + index + " << /S /R /St 12 >>"); pageLabels.addPageLabel(index++, "XII"); - expected.append(" " + index + " <<\n /P (00)\n /S /D\n /St 9\n>>\n"); + expected.append(" " + index + " <<\n /P (00)\n /S /D\n /St 9\n>>"); pageLabels.addPageLabel(index++, "009"); - expected.append(" " + index + " <<\n /P (0)\n /S /D\n /St 10\n>>\n"); + expected.append(" " + index + " <<\n /P (0)\n /S /D\n /St 10\n>>"); pageLabels.addPageLabel(index++, "010"); pageLabels.addPageLabel(index++, "011"); expected.append("]"); diff --git a/test/java/org/apache/fop/pdf/PDFStreamTestCase.java b/test/java/org/apache/fop/pdf/PDFStreamTestCase.java index 93dcea511..20e38a600 100644 --- a/test/java/org/apache/fop/pdf/PDFStreamTestCase.java +++ b/test/java/org/apache/fop/pdf/PDFStreamTestCase.java @@ -118,7 +118,7 @@ public class PDFStreamTestCase { private byte[] createSampleStreamData() throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); - stream.write("stream\n".getBytes("US-ASCII")); + stream.write("\nstream\n".getBytes("US-ASCII")); stream.write(createSampleData()); stream.write("\nendstream".getBytes("US-ASCII")); return stream.toByteArray(); diff --git a/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java b/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java index ceff96a91..12f6e3c1b 100644 --- a/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java +++ b/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java @@ -73,7 +73,7 @@ public class CrossReferenceTableTestCase extends CrossReferenceObjectTest { .append(" /Info 2 0 R\n") .append(" /ID [<0123456789ABCDEF> <0123456789ABCDEF>]\n") .append(" /Size ").append(Integer.toString(offsets.size() + 1)).append('\n') - .append(">>\n"); + .append(">>"); return getBytes(expected); } diff --git a/test/java/org/apache/fop/render/intermediate/AbstractIFPainterTestCase.java b/test/java/org/apache/fop/render/intermediate/AbstractIFPainterTestCase.java index 592335648..be8b5d718 100644 --- a/test/java/org/apache/fop/render/intermediate/AbstractIFPainterTestCase.java +++ b/test/java/org/apache/fop/render/intermediate/AbstractIFPainterTestCase.java @@ -51,7 +51,7 @@ public class AbstractIFPainterTestCase { public void endViewport() throws IFException { } - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { } public void endGroup() throws IFException { diff --git a/test/layoutengine/standard-testcases/pdf-dictionary-extension_2.xml b/test/layoutengine/standard-testcases/pdf-dictionary-extension_2.xml new file mode 100644 index 000000000..486b8601c --- /dev/null +++ b/test/layoutengine/standard-testcases/pdf-dictionary-extension_2.xml @@ -0,0 +1,74 @@ + + + + + +

+ This test checks the PDF dictionary extensions related to optional content groups (layers). +

+
+ + + + + + + + + + + + + Bullet 1 + + + Bullet 2 + + + + + + + + + + + + Default + OFF + + + + + + + + BULLET 1A + BULLET 1B + + BULLET 2 + + + + + + + + +
diff --git a/test/layoutengine/standard-testcases/pdf-dictionary-extension_3.xml b/test/layoutengine/standard-testcases/pdf-dictionary-extension_3.xml new file mode 100644 index 000000000..ee5061497 --- /dev/null +++ b/test/layoutengine/standard-testcases/pdf-dictionary-extension_3.xml @@ -0,0 +1,122 @@ + + + + + +

+ This test checks the PDF dictionary extensions related to optional content groups (layers), + including navigator and action dictionaries. +

+
+ + + + + + + + + + + + + + + + + Bullet 1 + + + Bullet 2 + + + + + OFF + + + + + + + OFF + + ON + + + + + + OFF + + ON + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + OFF + + + + + + + + BULLET 1A + BULLET 1B + + BULLET 2 + + + + + + + + +
-- cgit v1.2.3 From 7d3dba6c3c2fc6caeeeaccbc8df159b87934e44a Mon Sep 17 00:00:00 2001 From: Luis Bernardo Date: Wed, 13 Nov 2013 15:24:26 +0000 Subject: FOP-2313: add support for svg gradients when generating PostScript; most code authored by Robert Meyer with a small contribution by Athanasios Giannimaras. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1541551 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 1 + lib/xmlgraphics-commons-svn-trunk.jar | Bin 629507 -> 629955 bytes src/java/org/apache/fop/pdf/PDFFactory.java | 106 ++-- src/java/org/apache/fop/pdf/PDFFunction.java | 651 ++++----------------- src/java/org/apache/fop/pdf/PDFPattern.java | 14 +- src/java/org/apache/fop/pdf/PDFShading.java | 373 ++++++------ .../apache/fop/render/ps/PSImageHandlerSVG.java | 327 +++++++++-- .../org/apache/fop/render/ps/svg/PSFunction.java | 143 +++++ .../org/apache/fop/render/ps/svg/PSPattern.java | 103 ++++ .../apache/fop/render/ps/svg/PSSVGGraphics2D.java | 291 +++++++++ .../org/apache/fop/render/ps/svg/PSShading.java | 228 ++++++++ .../org/apache/fop/render/shading/Function.java | 39 ++ .../fop/render/shading/FunctionDelegate.java | 451 ++++++++++++++ .../apache/fop/render/shading/FunctionPattern.java | 363 ++++++++++++ .../apache/fop/render/shading/GradientFactory.java | 162 +++++ .../fop/render/shading/GradientRegistrar.java | 45 ++ .../fop/render/shading/PDFGradientFactory.java | 76 +++ .../fop/render/shading/PSGradientFactory.java | 70 +++ .../org/apache/fop/render/shading/Pattern.java | 22 + .../org/apache/fop/render/shading/Shading.java | 26 + .../apache/fop/render/shading/ShadingPattern.java | 105 ++++ src/java/org/apache/fop/svg/PDFGraphics2D.java | 62 +- .../fop/render/ps/svg/PSSVGGraphics2DTestCase.java | 71 +++ .../ps/svg/PSSVGLinearGraphics2DTestCase.java | 70 +++ .../fop/render/ps/svg/axial-shading-expected.dat | 26 + test/java/org/apache/fop/render/ps/svg/expected.ps | 38 ++ 26 files changed, 3009 insertions(+), 854 deletions(-) create mode 100644 src/java/org/apache/fop/render/ps/svg/PSFunction.java create mode 100644 src/java/org/apache/fop/render/ps/svg/PSPattern.java create mode 100644 src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java create mode 100644 src/java/org/apache/fop/render/ps/svg/PSShading.java create mode 100644 src/java/org/apache/fop/render/shading/Function.java create mode 100644 src/java/org/apache/fop/render/shading/FunctionDelegate.java create mode 100644 src/java/org/apache/fop/render/shading/FunctionPattern.java create mode 100644 src/java/org/apache/fop/render/shading/GradientFactory.java create mode 100644 src/java/org/apache/fop/render/shading/GradientRegistrar.java create mode 100644 src/java/org/apache/fop/render/shading/PDFGradientFactory.java create mode 100644 src/java/org/apache/fop/render/shading/PSGradientFactory.java create mode 100644 src/java/org/apache/fop/render/shading/Pattern.java create mode 100644 src/java/org/apache/fop/render/shading/Shading.java create mode 100644 src/java/org/apache/fop/render/shading/ShadingPattern.java create mode 100644 test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java create mode 100644 test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.java create mode 100644 test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat create mode 100644 test/java/org/apache/fop/render/ps/svg/expected.ps (limited to 'src/java/org/apache/fop/pdf') diff --git a/build.xml b/build.xml index 97503f208..f25239bc0 100644 --- a/build.xml +++ b/build.xml @@ -579,6 +579,7 @@ list of possible build targets. + diff --git a/lib/xmlgraphics-commons-svn-trunk.jar b/lib/xmlgraphics-commons-svn-trunk.jar index 22b0f5d52..fad312940 100644 Binary files a/lib/xmlgraphics-commons-svn-trunk.jar and b/lib/xmlgraphics-commons-svn-trunk.jar differ diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 5af33866e..070630274 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -310,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); } @@ -352,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); } @@ -407,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); } @@ -434,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 ================================== */ @@ -481,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); } @@ -534,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); } @@ -591,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); } @@ -645,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); @@ -652,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 ================================== */ @@ -707,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 * 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 theDomain, + List theRange, List theSize, int theBitsPerSample, + int theOrder, List theEncode, List theDecode, + StringBuffer theFunctionDataStream, List 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 theDomain, + List theRange, List theCZero, List 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 theDomain, + List theRange, List theFunctions, + List theBounds, List 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 theDomain, + List 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 getBounds() { + return delegate.getBounds(); + } + + public List getDomain() { + return delegate.getDomain(); + } + + public List getSize() { + return delegate.getSize(); + } + + public List getFilter() { + return delegate.getFilter(); + } + + public List getEncode() { + return delegate.getEncode(); + } + + public List getFunctions() { + return delegate.getFunctions(); + } + + public int getBitsPerSample() { + return delegate.getBitsPerSample(); + } + + public double getInterpolationExponentN() { + return delegate.getInterpolationExponentN(); + } + + public int getOrder() { + return delegate.getOrder(); + } + + public List getRange() { + return delegate.getRange(); + } + + public List getDecode() { + return delegate.getDecode(); + } + + public StringBuffer getDataStream() { + return delegate.getDataStream(); + } + + public List getCZero() { + return delegate.getCZero(); + } + + public List getCOne() { + return delegate.getCOne(); + } } diff --git a/src/java/org/apache/fop/pdf/PDFPattern.java b/src/java/org/apache/fop/pdf/PDFPattern.java index b9e042815..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"); } @@ -290,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/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/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java index 3ade34522..cadc28267 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; @@ -47,6 +71,9 @@ import org.apache.fop.svg.SVGUserAgent; */ public class PSImageHandlerSVG implements ImageHandler { + private static final Color FALLBACK_COLOR = new Color(255, 33, 117); + private HashMap gradientsFound = new HashMap(); + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { BatikImageFlavors.SVG_DOM }; @@ -58,78 +85,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 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 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 gradMatches = new ArrayList(); + 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 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/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 theDomain, + List theRange, List theFunctions, + List theBounds, List 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 theDomain, + List theRange, List theCZero, List 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 getBounds() { + return delegate.getBounds(); + } + + public List getDomain() { + return delegate.getDomain(); + } + + public List getSize() { + return delegate.getSize(); + } + + public List getFilter() { + return delegate.getFilter(); + } + + public List getEncode() { + return delegate.getEncode(); + } + + public List getFunctions() { + return delegate.getFunctions(); + } + + public int getBitsPerSample() { + return delegate.getBitsPerSample(); + } + + public double getInterpolationExponentN() { + return delegate.getInterpolationExponentN(); + } + + public int getOrder() { + return delegate.getOrder(); + } + + public List getRange() { + return delegate.getRange(); + } + + public List getDecode() { + return delegate.getDecode(); + } + + public StringBuffer getDataStream() { + return delegate.getDataStream(); + } + + public List getCZero() { + return delegate.getCZero(); + } + + public List 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..3b6b5490c --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java @@ -0,0 +1,291 @@ +/* + * 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 theCoords = new java.util.ArrayList(); + + + 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 someColors = new java.util.ArrayList(); + 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 theBounds = new java.util.ArrayList(); + 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 theMatrix = new java.util.ArrayList(); + double [] mat = new double[6]; + transform.getMatrix(mat); + for (int idx = 0; idx < mat.length; idx++) { + theMatrix.add(Double.valueOf(mat[idx])); + } + + List theCoords = new java.util.ArrayList(); + 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 someColors = new java.util.ArrayList(); + 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 theBounds = new java.util.ArrayList(); + 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 Graphics object that is + * a copy of this Graphics 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 theBackground, List theBBox, + boolean theAntiAlias, List theCoords, + List theDomain, Function theFunction, + List 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/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 getBounds(); + List getDomain(); + List getSize(); + List getFilter(); + List getEncode(); + List getFunctions(); + int getBitsPerSample(); + double getInterpolationExponentN(); + int getOrder(); + List getRange(); + List getDecode(); + StringBuffer getDataStream(); + List getCZero(); + List 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 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; + + /** + * 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; + + /** + * 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 theDomain, + List theRange, List theSize, int theBitsPerSample, + int theOrder, List theEncode, List theDecode, + StringBuffer theFunctionDataStream, List 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 theDomain, + List theRange, List theCZero, List 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 theDomain, + List theRange, List theFunctions, + List theBounds, List 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 theDomain, + List 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 getBounds() { + return bounds; + } + + /** + * The function domain + */ + public List getDomain() { + return domain; + } + + /** + * The function size + */ + public List getSize() { + return size; + } + + /** + * Gets the function encoding + */ + public List getEncode() { + return encode; + } + + /** + * Gets the sub-functions + */ + public List getFunctions() { + return functions; + } + + /** + * Gets the function filter + */ + public List 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 getRange() { + return range; + } + + /** + * Gets the function decoding + */ + public List getDecode() { + return decode; + } + + /** + * Gets the function data stream + */ + public StringBuffer getDataStream() { + return functionDataStream; + } + + /** + * Gets the function C0 value (color for gradient) + */ + public List getCZero() { + return cZero; + } + + /** + * Gets the function C1 value (color for gradient) + */ + public List 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 theColors, List theBounds, + List theCoords, List theMatrix); + + protected Pattern makeGradient(boolean radial, PDFDeviceColorSpace theColorspace, + List theColors, List theBounds, + List theCoords, List theMatrix) { + Shading myShad; + Function myfunky; + Function myfunc; + List theCzero; + List theCone; + double interpolation = 1.000; + List theFunctions = new ArrayList(); + + 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 newCoords = new ArrayList(); + 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 theDomain, + List theRange, List theFunctions, + List theBounds, List theEncode); + + public abstract Function makeFunction(int functionType, List theDomain, + List theRange, List theCZero, List theCOne, + double theInterpolationExponentN); + + public abstract Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, + boolean theAntiAlias, List theCoords, List theDomain, + Function theFunction, List theExtend); + + public abstract Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List theMatrix); + + private List toColorVector(Color nextColor) { + List vector = new java.util.ArrayList(); + 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 theColors, + List theBounds, List theCoords, List theMatrix) { + return (PDFPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + @Override + public Function makeFunction(int functionType, List theDomain, + List theRange, List theFunctions, + List theBounds, List theEncode) { + Function newFunction = new PDFFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + newFunction = registrar.registerFunction(newFunction); + return newFunction; + } + + public Function makeFunction(int functionType, List theDomain, + List theRange, List theCZero, List 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 theBackground, List theBBox, + boolean theAntiAlias, List theCoords, List theDomain, + Function theFunction, List 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 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 theColors, List theBounds, List theCoords, + List theMatrix) { + return (PSPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + public Function makeFunction(int functionType, List theDomain, + List theRange, List theFunctions, + List theBounds, List theEncode) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + return newFunction; + } + + @Override + public Function makeFunction(int functionType, List theDomain, + List theRange, List theCZero, List theCOne, + double theInterpolationExponentN) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + return newFunction; + } + + @Override + public Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, + boolean theAntiAlias, List theCoords, List theDomain, + Function theFunction, List 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 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/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index efa71a225..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; /** *

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; @@ -1856,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/test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java b/test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java new file mode 100644 index 000000000..b48da4186 --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java @@ -0,0 +1,71 @@ +/* + * 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.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Rectangle2D.Float; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +import org.junit.Test; + +import org.apache.commons.io.FileUtils; + +import org.apache.batik.ext.awt.RadialGradientPaint; + +import org.apache.xmlgraphics.java2d.GraphicContext; +import org.apache.xmlgraphics.ps.PSGenerator; + +import static org.junit.Assert.assertEquals; + +public class PSSVGGraphics2DTestCase { + + float cx = 841.891f; + float cy = 178.583f; + float r = 16.4331f; + float[] fractions = {0.2f, 0.6012f, 0.8094f, 1.0f}; + + /** + * Tests a radial gradient generated pattern with certain inputs against + * an expected output. + * @throws IOException + */ + @Test + public void testApplyPaint() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PSGenerator gen = new PSGenerator(os); + PSSVGGraphics2D svgGraphics2D = new PSSVGGraphics2D(false, gen); + svgGraphics2D.setGraphicContext(new GraphicContext()); + svgGraphics2D.setTransform(new AffineTransform()); + Color[] colors = {new Color(255, 255, 255), new Color(200, 200, 200), + new Color(170, 170, 170), new Color(140, 140, 140)}; + RadialGradientPaint paint = new RadialGradientPaint(cx, cy, r, fractions, colors); + Float s = new Rectangle2D.Float(7.0f, 3.0f, 841.891f, 178.583f); + svgGraphics2D.applyPaint(paint, true); + byte[] test = os.toByteArray(); + + byte[] expected = FileUtils.readFileToByteArray( + new File("test/java/org/apache/fop/render/ps/svg/expected.ps")); + assertEquals(new String(test), new String(expected)); + } +} diff --git a/test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.java b/test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.java new file mode 100644 index 000000000..283d3f4ad --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.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. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps.svg; + +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Rectangle2D.Float; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +import org.junit.Test; + +import org.apache.commons.io.FileUtils; + +import org.apache.batik.ext.awt.LinearGradientPaint; + +import org.apache.xmlgraphics.java2d.GraphicContext; +import org.apache.xmlgraphics.ps.PSGenerator; + +import static org.junit.Assert.assertEquals; + +public class PSSVGLinearGraphics2DTestCase { + float startX = 115f; + float endX = 15f; + float startY = 285f; + float endY=15f; + float[] fractions = {0.0f, 1.0f}; + + /** + * Tests a linear gradient generated pattern with certain inputs against + * an expected output. + * @throws IOException + */ + @Test + public void testApplyPaint() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PSGenerator gen = new PSGenerator(os); + PSSVGGraphics2D svgGraphics2D = new PSSVGGraphics2D(false, gen); + svgGraphics2D.setGraphicContext(new GraphicContext()); + svgGraphics2D.setTransform(new AffineTransform()); + Color[] colors = {new Color(255, 255, 0), new Color(255, 0, 0)}; + LinearGradientPaint paint = new LinearGradientPaint(startX, startY, endX, endY, fractions, colors); + Float s = new Rectangle2D.Float(115.0f, 15.0f, 170f, 110f); + svgGraphics2D.applyPaint(paint, true); + byte[] test = os.toByteArray(); + + byte[] expected = FileUtils.readFileToByteArray( + new File("test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat")); + assertEquals(new String(test), new String(expected)); + } +} diff --git a/test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat b/test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat new file mode 100644 index 000000000..fdf92cda5 --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat @@ -0,0 +1,26 @@ +/Pattern setcolorspace +<< +/Type /Pattern +/PatternType 2 +/Shading << +/ShadingType 2 +/ColorSpace /DeviceRGB + /Coords [ 115 285 15 15 ] + /Domain [ 0 1 ] + /Extend [ true true ] + /Function << +/FunctionType 3 +/Domain [ 0 1 ] +/Functions [ << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 1 1 0 ] +/C1 [ 1 0 0 ] +/N 1 +>> ] +/Encode [ 0 1 ] +/Bounds [ ] +>> +>> +>> +matrix makepattern setcolor diff --git a/test/java/org/apache/fop/render/ps/svg/expected.ps b/test/java/org/apache/fop/render/ps/svg/expected.ps new file mode 100644 index 000000000..9977070c3 --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/expected.ps @@ -0,0 +1,38 @@ +/Pattern setcolorspace +<< +/Type /Pattern +/PatternType 2 +/Shading << +/ShadingType 3 +/ColorSpace /DeviceRGB + /Coords [ 841.890991 178.582993 3.28662 841.890991 178.582993 16.4331 ] + /Domain [ 0 1 ] + /Extend [ true true ] + /Function << +/FunctionType 3 +/Domain [ 0 1 ] +/Functions [ << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 1 1 1 ] +/C1 [ 0.784314 0.784314 0.784314 ] +/N 1 +>> << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 0.784314 0.784314 0.784314 ] +/C1 [ 0.666667 0.666667 0.666667 ] +/N 1 +>> << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 0.666667 0.666667 0.666667 ] +/C1 [ 0.54902 0.54902 0.54902 ] +/N 1 +>> ] +/Encode [ 0 1 0 1 0 1 ] +/Bounds [ 0.6012 0.8094 ] +>> +>> +>> +matrix makepattern setcolor -- cgit v1.2.3