]> _ Git - cubeextranet.git/commitdiff
(no commit message)
authorvincent@cubedesigners.com <vincent@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Thu, 18 Aug 2011 12:23:24 +0000 (12:23 +0000)
committervincent@cubedesigners.com <vincent@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Thu, 18 Aug 2011 12:23:24 +0000 (12:23 +0000)
539 files changed:
fluidbook/tools/fwstk/nbproject/configs/Make_images.properties [new file with mode: 0644]
fluidbook/tools/fwstk/nbproject/private/config.properties
fluidbook/tools/fwstk/nbproject/private/configs/Extract_Layout.properties
fluidbook/tools/fwstk/nbproject/private/configs/Make_images.properties [new file with mode: 0644]
fluidbook/tools/fwstk/nbproject/private/private.xml
fluidbook/tools/fwstk/nbproject/project.properties
fluidbook/tools/fwstk/src/org/apache/pdfbox/ConvertColorspace.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/Decrypt.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/Encrypt.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/ExportFDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/ExportXFDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/ExtractImages.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/ExtractText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/ImportFDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/ImportXFDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/Overlay.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFBox.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFDebugger.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFMerger.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFReader.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFSplit.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFToImage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PdfDecompressor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/PrintPDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/TextToPDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/Version.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/WriteDecodedDoc.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSArray.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSBase.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSBoolean.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSDocument.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSFloat.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSInteger.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSName.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSNull.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSNumber.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSString.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/ICOSVisitor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/AFMEncoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/DictionaryEncoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/Encoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/EncodingManager.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/MacRomanEncoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/PdfDocEncoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/StandardEncoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/Type1Encoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/WinAnsiEncoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CJKConverter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CJKEncoding.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CMapSubstitution.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/EncodingConversionManager.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/EncodingConverter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/ARCFour.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/DocumentEncryption.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/PDFEncryption.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/AbstractExample.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/PrintFields.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/SetField.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddImageToPDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddJavascript.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddMessageToEachPage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddMetadataFromDocInfo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/Annotation.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateBlankPDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateBookmarks.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateLandscapePDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/EmbeddedFiles.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ExtractMetadata.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/GoToSecondBookmarkOnOpen.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorld.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorldType1AfmPfb.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ImageToPDF.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintBookmarks.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintDocumentMetaData.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintURLs.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RemoveFirstPage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ReplaceString.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ReplaceURLs.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RubberStamp.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RubberStampWithImage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ShowColorBoxes.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/UsingTextMatrix.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/persistence/CopyDoc.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/persistence/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/signature/ShowSignature.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/signature/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/ExtractTextByArea.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/PrintImageLocations.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/PrintTextLocations.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/RemoveAllText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/COSVisitorException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/CryptographyException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/InvalidPasswordException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/OutlineNotLocalException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/SignatureException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/WrappedException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/WrappedIOException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/ASCII85Filter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/ASCIIHexFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/CCITTFaxDecodeFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/CryptFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/DCTFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/Filter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/FilterManager.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/FlateFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/IdentityFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/JBIG2Filter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/JPXFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/RunLengthDecodeFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/TIFFFaxDecoder.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ASCII85InputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ASCII85OutputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ByteArrayPushBackInputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/FastByteArrayOutputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/NBitInputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/NBitOutputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/PushBackInputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccess.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessBuffer.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFile.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFileInputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFileOutputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/io/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/BaseParser.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFObjectStreamParser.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFParser.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFStreamParser.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/VisualSignatureParser.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/ArrayEntry.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/MapEntry.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFPagePanel.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFTreeCellRenderer.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFTreeModel.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PageDrawer.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PageWrapper.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/ReaderBottomPanel.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSFilterInputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSStandardOutputStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSWriter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSWriterXRefEntry.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/ContentStreamWriter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDestinationNameTreeNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocument.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentInformation.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentNameDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDEmbeddedFilesNameTreeNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDJavascriptNameTreeNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPageNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPageable.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDResources.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSArrayList.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSDictionaryMap.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSObjectable.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSStreamArray.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/DualCOSObjectable.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDDestinationOrAction.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDDictionaryWrapper.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMatrix.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMemoryStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMetadata.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNameTreeNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNamedTextStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNumberTreeNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDObjectStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDPageLabelRange.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDPageLabels.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDRange.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDRectangle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDTextStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDTypedDictionaryWrapper.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDComplexFileSpecification.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDEmbeddedFile.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDFileSpecification.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDSimpleFileSpecification.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunction.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType0.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType2.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType3.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType4.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDDefaultAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkInfo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkedContentReference.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDObjectReference.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureElement.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserProperty.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/Revisions.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/markedcontent/PDMarkedContent.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/markedcontent/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/prepress/PDBoxStyle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/prepress/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDArtifactMarkedContent.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDExportFormatAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDFourColours.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDLayoutAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDListAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDPrintFieldAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDStandardAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDTableAttributeObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/StandardStructureTypes.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/edit/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/AccessPermission.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/BadSecurityHandlerException.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/DecryptionMaterial.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDEncryptionDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDEncryptionManager.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDStandardEncryption.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/ProtectionPolicy.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyDecryptionMaterial.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyProtectionPolicy.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyRecipient.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/SecurityHandlersManager.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardDecryptionMaterial.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardProtectionPolicy.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotation.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationCaret.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationCircle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationFileAttachment.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationFreeText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationHighlight.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationInk.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationLine.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationPolygon.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationPolyline.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSound.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSquare.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSquiggly.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStrikeOut.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationUnderline.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFCatalog.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFField.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFIconFit.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFJavaScript.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFNamedPageReference.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFOptionElement.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFPage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFPageInfo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFTemplate.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/FontManager.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFont.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFontType0Font.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Font.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFont.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptorAFM.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptorDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontFactory.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDMMType1Font.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType0Font.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1CFont.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1Font.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType3Font.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/Type3StreamParser.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDExtendedGraphicsState.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDFontSetting.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDGraphicsState.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDLineDashPattern.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDShading.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceCMYK.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceCalRGB.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDCalGray.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDCalRGB.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpace.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpaceFactory.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorState.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceN.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceNAttributes.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDGamma.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDICCBased.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDPattern.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDSeparation.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDTristimulus.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Average.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/None.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Optimum.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Paeth.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/PredictorAlgorithm.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Sub.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Up.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/CompositeImage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDCcitt.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDInlinedImage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDJpeg.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDActionFactory.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDAdditionalActions.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDAnnotationAdditionalActions.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDDocumentCatalogAdditionalActions.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDFormFieldAdditionalActions.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDPageAdditionalActions.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDAction.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionGoTo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionJavaScript.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionLaunch.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionRemoteGoTo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionURI.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDURIDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDWindowsLaunchParams.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationFileAttachment.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationLine.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationLink.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationPopup.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationRubberStamp.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationUnknown.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationWidget.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceCharacteristicsDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDBorderEffectDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDBorderStyleDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDExternalDataDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureInterface.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDNamedDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitHeightDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitRectangleDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitWidthDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageXYZDestination.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDDocumentOutline.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineItem.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineNode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDAppearance.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDCheckbox.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDChoiceButton.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDChoiceField.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDField.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDFieldFactory.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDPushButton.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDRadioCollection.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDSignature.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDSignatureField.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDTextbox.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDUnknownField.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDVariableText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDXFA.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDMeasureDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDNumberFormatDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDRectlinearMeasureDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDViewportDictionary.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/PDThread.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/PDThreadBead.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/viewerpreferences/PDViewerPreferences.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/viewerpreferences/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/markedcontent/PDPropertyList.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/text/PDTextState.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/text/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/COSHEXTable.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/COSObjectKey.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/BitFlagHelper.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/DateConverter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ErrorLogger.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ExtensionFileFilter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ICU4JImpl.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ImageParameters.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/LayerUtility.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/MapUtil.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/Matrix.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFCloneUtility.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFHighlighter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFImageWriter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFMarkedContentExtractor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFMergerUtility.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFOperator.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFStreamEngine.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFText2HTML.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFTextStripper.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFTextStripperByArea.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PageExtractor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PositionWrapper.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ResourceLoader.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/Splitter.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/StringUtil.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextNormalize.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextPosition.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextPositionComparator.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/XMLUtil.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginMarkedContentSequence.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginMarkedContentSequenceWithProperties.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/CloseAndStrokePath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/Concatenate.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/EndMarkedContentSequence.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/EndText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/GRestore.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/GSave.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/Invoke.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveAndShow.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveTextSetLeading.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/NextLine.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/OperatorProcessor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetCharSpacing.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetGraphicsStateParameters.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetHorizontalTextScaling.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineCapStyle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineDashPattern.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineJoinStyle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineMiterLimit.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineWidth.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetMatrix.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetMoveAndShow.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingCMYKColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingCalRGBColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingColorSpace.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingDeviceN.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingGrayColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingICCBasedColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingRGBColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingSeparation.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingCMYKColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingCalRGBColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingColorSpace.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingDeviceN.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingGrayColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingICCBasedColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingRGBColor.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingSeparation.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextFont.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextLeading.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextRenderingMode.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextRise.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetWordSpacing.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/ShowText.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/ShowTextGlyph.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/AppendRectangleToPath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/BeginInlineImage.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClipEvenOddRule.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClipNonZeroRule.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CloseFillEvenOddAndStrokePath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CloseFillNonZeroAndStrokePath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClosePath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveTo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveToReplicateFinalPoint.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveToReplicateInitialPoint.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/EndPath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillEvenOddAndStrokePath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillEvenOddRule.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillNonZeroAndStrokePath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillNonZeroRule.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/Invoke.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/LineTo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/MoveTo.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SHFill.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineCapStyle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineDashPattern.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineJoinStyle.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineMiterLimit.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineWidth.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/StrokePath.java [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/package.html [new file with mode: 0644]
fluidbook/tools/fwstk/src/org/apache/pdfbox/util/package.html [new file with mode: 0644]

diff --git a/fluidbook/tools/fwstk/nbproject/configs/Make_images.properties b/fluidbook/tools/fwstk/nbproject/configs/Make_images.properties
new file mode 100644 (file)
index 0000000..3f861c9
--- /dev/null
@@ -0,0 +1 @@
+$label=Make images\r
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2f416298f3dff6b408835db087e4ba6024a05d3d 100644 (file)
@@ -0,0 +1 @@
+config=Make_images\r
index a2d8bcded51fac19a945c087b0b283276727ec98..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1 +0,0 @@
-application.args=--input C:\\Users\\Vincent\\Documents\\layout\\document.pdf --layout C:\\Users\\Vincent\\Documents\\layout\\p%d.layout\r
diff --git a/fluidbook/tools/fwstk/nbproject/private/configs/Make_images.properties b/fluidbook/tools/fwstk/nbproject/private/configs/Make_images.properties
new file mode 100644 (file)
index 0000000..38d1316
--- /dev/null
@@ -0,0 +1 @@
+application.args=--input C:\\Users\\Vincent\\Documents\\layout\\document.pdf --image C:\\Users\\Vincent\\Documents\\layout\\p\r
index b70f99168b5db76034d9cbd276b9bf984e16bcc4..cc2c0e57c4f9286a6ee78a9b3557c26caca57415 100644 (file)
@@ -1,10 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>\r
 <project-private xmlns="http://www.netbeans.org/ns/project-private/1">\r
     <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>\r
-    <open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">\r
-        <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/com/fluidbook/fwstk/Main.java</file>\r
-        <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/cube/util/StringUtil.java</file>\r
-        <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/cube/files/FileIO.java</file>\r
-        <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/com/fluidbook/fwstk/LayoutStripper.java</file>\r
-    </open-files>\r
 </project-private>\r
index ce694421c4cfe39b659f746f815e782629a7e8f6..21c54bd72bfed9c7ed29f9429323332c5d6acc2b 100644 (file)
@@ -34,7 +34,6 @@ file.reference.fontbox-1.6.0.jar=H:\\Works\\Java\\jar\\fontbox-1.6.0.jar
 file.reference.icu4j-4_6_1.jar=H:\\Works\\Java\\jar\\icu4j-4_6_1.jar\r
 file.reference.Java-resources=../../../../Java/resources\r
 file.reference.jempbox-1.6.0.jar=H:\\Works\\Java\\jar\\jempbox-1.6.0.jar\r
-file.reference.pdfbox-1.6.0.jar=H:\\Works\\Java\\jar\\pdfbox-1.6.0.jar\r
 file.reference.Works-Java=../../../../Java\r
 includes=**\r
 jar.archive.disabled=${jnlp.enabled}\r
@@ -48,7 +47,7 @@ javac.classpath=\
     ${file.reference.commons-logging-1.1.1.jar}:\\r
     ${file.reference.fontbox-1.6.0.jar}:\\r
     ${file.reference.jempbox-1.6.0.jar}:\\r
-    ${file.reference.pdfbox-1.6.0.jar}\r
+    ${libs.PDFBox.classpath}\r
 # Space-separated list of extra javac options\r
 javac.compilerargs=\r
 javac.deprecation=false\r
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/ConvertColorspace.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/ConvertColorspace.java
new file mode 100644 (file)
index 0000000..3e2c238
--- /dev/null
@@ -0,0 +1,470 @@
+/*
+ * 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.pdfbox;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * This is the main program that simply parses the pdf document and replace
+ * change a PDF to use a specific colorspace.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Pierre-Yves Landuré (pierre-yves@landure.org)
+ * @version $Revision: 1.5 $
+ */
+public class ConvertColorspace
+{
+
+    private static final String PASSWORD = "-password";
+    private static final String CONVERSION = "-equiv";
+    private static final String DEST_COLORSPACE = "-toColorspace";
+
+    /**
+     * private constructor.
+    */
+    private ConvertColorspace()
+    {
+        //static class
+    }
+
+    /**
+     * The method that replace RGB colors by CMYK ones.
+     *
+     * @param inputFile input file name.
+     * @param colorEquivalents a dictionnary for the color equivalents.
+     * @param destColorspace The destination colorspace, currently CMYK is supported.
+     *
+     * @throws IOException If there is an error parsing the document.
+     */
+    private void replaceColors( PDDocument inputFile,
+                               Hashtable colorEquivalents,
+                               String destColorspace ) throws IOException
+    {
+        if( !destColorspace.equals( "CMYK" ) )
+        {
+            throw new IOException( "Error: Unknown colorspace " + destColorspace );
+        }
+        List pagesList =  inputFile.getDocumentCatalog().getAllPages();
+
+        PDPage currentPage = null;
+        PDFStreamParser parser = null;
+        List pageTokens = null;
+        List editedPageTokens = null;
+
+        for(int pageCounter = 0; pageCounter < pagesList.size(); pageCounter++) // For each document page
+        {
+            currentPage = (PDPage)pagesList.get( pageCounter );
+
+            parser = new PDFStreamParser(currentPage.getContents().getStream());
+            parser.parse();
+            pageTokens = parser.getTokens();
+            editedPageTokens = new ArrayList();
+
+            for( int counter = 0; counter < pageTokens.size(); counter++) // For each page token
+            {
+                Object token = pageTokens.get( counter );
+                if( token instanceof PDFOperator ) // Test if PDFOperator
+                {
+                    PDFOperator tokenOperator = (PDFOperator)token;
+
+                    if(tokenOperator.getOperation().equals("rg")) // Test if "rg" Operator.
+                    {
+                        if( destColorspace.equals( "CMYK" ) )
+                        {
+                            replaceRGBTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+                            editedPageTokens.add( PDFOperator.getOperator( "k" ));
+                        }
+                    }
+                    else if(tokenOperator.getOperation().equals("RG")) // Test if "rg" Operator.
+                    {
+                        if( destColorspace.equals( "CMYK" ) )
+                        {
+                            replaceRGBTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+                            editedPageTokens.add( PDFOperator.getOperator( "K" ));
+                        }
+                    }
+                    else if(tokenOperator.getOperation().equals("g")) // Test if "rg" Operator.
+                    {
+                        if( destColorspace.equals( "CMYK" ) )
+                        {
+                            replaceGrayTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+                            editedPageTokens.add( PDFOperator.getOperator( "k" ));
+                        }
+                    }
+                    else if(tokenOperator.getOperation().equals("G")) // Test if "rg" Operator.
+                    {
+                        if( destColorspace.equals( "CMYK" ) )
+                        {
+                            replaceGrayTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+                            editedPageTokens.add( PDFOperator.getOperator( "K" ));
+                        }
+                    }
+                    else
+                    {
+                        editedPageTokens.add( token );
+                    }
+                }
+                else // Test if PDFOperator
+                {
+                    editedPageTokens.add( token );
+                }
+            } // For each page token
+
+            // We replace original page content by the edited one.
+            PDStream updatedPageContents = new PDStream(inputFile);
+            ContentStreamWriter contentWriter = new ContentStreamWriter( updatedPageContents.createOutputStream() );
+            contentWriter.writeTokens( editedPageTokens );
+            currentPage.setContents( updatedPageContents );
+
+        } // For each document page
+    }
+
+    private void replaceRGBTokensWithCMYKTokens( List editedPageTokens,
+                                                 List pageTokens,
+                                                 int counter,
+                                                 Hashtable colorEquivalents )
+    {
+//      Get current RGB color.
+        float red = ((COSNumber)pageTokens.get( counter - 3 )).floatValue();
+        float green = ((COSNumber)pageTokens.get( counter - 2 )).floatValue();
+        float blue = ((COSNumber)pageTokens.get( counter - 1 )).floatValue();
+
+        int intRed = Math.round(red * 255.0f);
+        int intGreen = Math.round(green * 255.0f);
+        int intBlue = Math.round(blue * 255.0f);
+
+        ColorSpaceInstance rgbColor = new ColorSpaceInstance();
+        rgbColor.colorspace = "RGB";
+        rgbColor.colorspaceValues = new int[] { intRed, intGreen, intBlue };
+        ColorSpaceInstance cmykColor = (ColorSpaceInstance)colorEquivalents.get(rgbColor);
+        float[] cmyk = null;
+
+        if( cmykColor != null )
+        {
+            cmyk = new float[] {
+                cmykColor.colorspaceValues[0] / 100.0f,
+                cmykColor.colorspaceValues[1] / 100.0f,
+                cmykColor.colorspaceValues[2] / 100.0f,
+                cmykColor.colorspaceValues[3] / 100.0f
+            };
+        }
+        else
+        {
+            cmyk = convertRGBToCMYK( red, green, blue );
+        }
+
+        //remove the RGB components that are already part of the editedPageTokens list
+        editedPageTokens.remove( editedPageTokens.size() -1 );
+        editedPageTokens.remove( editedPageTokens.size() -1 );
+        editedPageTokens.remove( editedPageTokens.size() -1 );
+
+        // Add the new CMYK color
+        editedPageTokens.add( new COSFloat( cmyk[0] ) );
+        editedPageTokens.add( new COSFloat( cmyk[1] ) );
+        editedPageTokens.add( new COSFloat( cmyk[2] ) );
+        editedPageTokens.add( new COSFloat( cmyk[3] ) );
+    }
+
+    private void replaceGrayTokensWithCMYKTokens( List editedPageTokens,
+                                                  List pageTokens,
+                                                  int counter,
+                                                  Hashtable colorEquivalents )
+    {
+//      Get current RGB color.
+        float gray = ((COSNumber)pageTokens.get( counter - 1 )).floatValue();
+
+        ColorSpaceInstance grayColor = new ColorSpaceInstance();
+        grayColor.colorspace = "Grayscale";
+        grayColor.colorspaceValues = new int[] { Math.round( gray * 100 ) };
+        ColorSpaceInstance cmykColor = (ColorSpaceInstance)colorEquivalents.get(grayColor);
+        float[] cmyk = null;
+
+        if( cmykColor != null )
+        {
+            cmyk = new float[] {
+                cmykColor.colorspaceValues[0] / 100.0f,
+                cmykColor.colorspaceValues[1] / 100.0f,
+                cmykColor.colorspaceValues[2] / 100.0f,
+                cmykColor.colorspaceValues[3] / 100.0f
+            };
+        }
+        else
+        {
+            cmyk = new float[] {0,0,0,gray};
+        }
+
+        //remove the Gray components that are already part of the editedPageTokens list
+        editedPageTokens.remove( editedPageTokens.size() -1 );
+
+        // Add the new CMYK color
+        editedPageTokens.add( new COSFloat( cmyk[0] ) );
+        editedPageTokens.add( new COSFloat( cmyk[1] ) );
+        editedPageTokens.add( new COSFloat( cmyk[2] ) );
+        editedPageTokens.add( new COSFloat( cmyk[3] ) );
+    }
+
+    private static float[] convertRGBToCMYK( float red, float green, float blue )
+    {
+        //
+        // RGB->CMYK from From
+        // http://en.wikipedia.org/wiki/Talk:CMYK_color_model
+        //
+        float c = 1.0f - red;
+        float m = 1.0f - green;
+        float y = 1.0f - blue;
+        float k = 1.0f;
+
+        k = Math.min( Math.min( Math.min( c,k ), m), y );
+
+        c = ( c - k ) / ( 1 - k );
+        m = ( m - k ) / ( 1 - k );
+        y = ( y - k ) / ( 1 - k );
+        return new float[] { c,m,y,k};
+    }
+
+    private static int[] stringToIntArray( String string )
+    {
+        String[] ints = string.split( "," );
+        int[] retval = new int[ints.length];
+        for( int i=0; i<ints.length; i++ )
+        {
+            retval[i] = Integer.parseInt( ints[i] );
+        }
+        return retval;
+    }
+
+    /**
+     * Infamous main method.
+     *
+     * @param args Command line arguments, should be one and a reference to a file.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        String password = "";
+        String inputFile = null;
+        String outputFile = null;
+        String destColorspace = "CMYK";
+
+        Pattern colorEquivalentPattern = Pattern.compile(
+            "^(.*):\\((.*)\\)" +
+            "=(.*):\\((.*)\\)$");
+        Matcher colorEquivalentMatcher = null;
+
+        //key= value=java.awt.Color
+        Hashtable colorEquivalents = new Hashtable();
+
+        for( int i=0; i<args.length; i++ )
+        {
+            if( args[i].equals( PASSWORD ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                password = args[i];
+            }
+            if( args[i].equals( DEST_COLORSPACE ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                destColorspace = args[i];
+            }
+            if(args[i].equals( CONVERSION ) )
+            {
+              i++;
+              if( i >= args.length )
+              {
+                  usage();
+              }
+
+              colorEquivalentMatcher = colorEquivalentPattern.matcher(args[i]);
+              if(!colorEquivalentMatcher.matches())
+              {
+                  usage();
+              }
+              String srcColorSpace = colorEquivalentMatcher.group(1);
+              String srcColorvalues = colorEquivalentMatcher.group(2);
+              String destColorSpace = colorEquivalentMatcher.group(3);
+              String destColorvalues = colorEquivalentMatcher.group(4);
+
+              ConvertColorspace.ColorSpaceInstance source = new ColorSpaceInstance();
+              source.colorspace = srcColorSpace;
+              source.colorspaceValues = stringToIntArray( srcColorvalues );
+
+              ColorSpaceInstance dest = new ColorSpaceInstance();
+              dest.colorspace = destColorSpace;
+              dest.colorspaceValues = stringToIntArray( destColorvalues );
+
+              colorEquivalents.put(source, dest);
+
+            }
+            else
+            {
+                if( inputFile == null )
+                {
+                    inputFile = args[i];
+                }
+                else
+                {
+                    outputFile = args[i];
+                }
+            }
+        }
+
+        if( inputFile == null )
+        {
+            usage();
+        }
+
+        if( outputFile == null || outputFile.equals(inputFile))
+        {
+            usage();
+        }
+
+        PDDocument doc = null;
+        try
+        {
+            doc = PDDocument.load( inputFile );
+            if( doc.isEncrypted() )
+            {
+                try
+                {
+                    doc.decrypt( password );
+                }
+                catch( InvalidPasswordException e )
+                {
+                    if( !password.equals( "" ) )//they supplied the wrong password
+                    {
+                        System.err.println( "Error: The supplied password is incorrect." );
+                        System.exit( 2 );
+                    }
+                    else
+                    {
+                        //they didn't suppply a password and the default of "" was wrong.
+                        System.err.println( "Error: The document is encrypted." );
+                        usage();
+                    }
+                }
+            }
+            ConvertColorspace converter = new ConvertColorspace();
+            converter.replaceColors(doc, colorEquivalents, destColorspace );
+            doc.save( outputFile );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+
+
+    }
+
+
+    /**
+     * This will print the usage requirements and exit.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.ConvertColorspace [OPTIONS] <PDF Input file> "
+            +"<PDF Output File>\n" +
+            "  -password  <password>                Password to decrypt document\n" +
+            "  -equiv <color equivalent>            Color equivalent to use for conversion.\n" +
+            "  -destColorspace <color equivalent>   The destination colorspace, CMYK is the only '" +
+            "supported colorspace." +
+            "  \n" +
+            " The equiv format is : <source colorspace>:(colorspace value)=<dest colorspace>:(colorspace value)" +
+            " This option can be used as many times as necessary\n" +
+            " The supported equiv colorspaces are RGB and CMYK.\n" +
+            " RGB color values are integers between 0 and 255" +
+            " CMYK color values are integer between 0 and 100.\n" +
+            " Example: java org.apache.pdfbox.ConvertColorspace -equiv RGB:(255,0,0)=CMYK(0,99,100,0)" +
+            " input.pdf output.pdf\n" +
+            "  <PDF Input file>             The PDF document to use\n" +
+            "  <PDF Output file>            The PDF file to write the result to. Must be different of input file\n"
+            );
+        System.exit( 1 );
+    }
+
+    /**
+     *
+     *
+     */
+    private static class ColorSpaceInstance
+    {
+        private String colorspace = null;
+        private int[] colorspaceValues = null;
+
+        /**
+         * {@inheritDoc}
+         */
+        public int hashCode()
+        {
+            int code = colorspace.hashCode();
+            for( int i=0; i<colorspaceValues.length; i++ )
+            {
+                code += colorspaceValues[i];
+            }
+            return code;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean equals( Object o )
+        {
+            boolean retval = false;
+            if( o instanceof ColorSpaceInstance )
+            {
+                ColorSpaceInstance other = (ColorSpaceInstance)o;
+                if( this.colorspace.equals( other.colorspace ) &&
+                         colorspaceValues.length == other.colorspaceValues.length )
+                {
+                    retval = true;
+                    for( int i=0; i<colorspaceValues.length && retval; i++ )
+                    {
+                        retval = retval && colorspaceValues[i] == other.colorspaceValues[i];
+                    }
+                }
+            }
+            return retval;
+        }
+    }
+}
+
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/Decrypt.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/Decrypt.java
new file mode 100644 (file)
index 0000000..6a5d53e
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * 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.pdfbox;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.DecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.PublicKeyDecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+
+/**
+ * This will read a document from the filesystem, decrypt it and and then write
+ * the results to the filesystem. <br/><br/>
+ *
+ * usage: java org.apache.pdfbox.Decrypt &lt;password&gt; &lt;inputfile&gt; &lt;outputfile&gt;
+ *
+ * @author  <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class Decrypt
+{
+    private static final String ALIAS = "-alias";
+    private static final String PASSWORD = "-password";
+    private static final String KEYSTORE = "-keyStore";
+
+    private Decrypt()
+    {
+    }
+    /**
+     * This is the entry point for the application.
+     *
+     * @param args The command-line arguments.
+     *
+     * @throws Exception If there is an error decrypting the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        Decrypt decrypt = new Decrypt();
+        decrypt.decrypt( args );
+    }
+
+    private void decrypt( String[] args ) throws Exception
+    {
+        if( args.length < 2 || args.length > 5 )
+        {
+            usage();
+        }
+        else
+        {
+            String password = null;
+            String infile = null;
+            String outfile = null;
+            String alias = null;
+            String keyStore = null;
+            for( int i=0; i<args.length; i++ )
+            {
+                if( args[i].equals( ALIAS ) )
+                {
+                    i++;
+                    if( i >= args.length )
+                    {
+                        usage();
+                    }
+                    alias = args[i];
+                }
+                else if( args[i].equals( KEYSTORE ) )
+                {
+                    i++;
+                    if( i >= args.length )
+                    {
+                        usage();
+                    }
+                    keyStore = args[i];
+                }
+                else if( args[i].equals( PASSWORD ) )
+                {
+                    i++;
+                    if( i >= args.length )
+                    {
+                        usage();
+                    }
+                    password = args[i];
+                }
+                else if( infile == null )
+                {
+                    infile = args[i];
+                }
+                else if( outfile == null )
+                {
+                    outfile = args[i];
+                }
+                else
+                {
+                    usage();
+                }
+            }
+            if( infile == null )
+            {
+                usage();
+            }
+            if( outfile == null )
+            {
+                outfile = infile;
+            }
+            if( password == null )
+            {
+                password = "";
+            }
+
+
+            PDDocument document = null;
+
+            try
+            {
+                document = PDDocument.load( infile );
+
+                if( document.isEncrypted() )
+                {
+                    DecryptionMaterial decryptionMaterial = null;
+                    if( keyStore != null )
+                    {
+                        KeyStore ks = KeyStore.getInstance("PKCS12");
+                        ks.load(new FileInputStream(keyStore), password.toCharArray());
+
+                        decryptionMaterial = new PublicKeyDecryptionMaterial(ks, alias, password);
+                    }
+                    else
+                    {
+                        decryptionMaterial = new StandardDecryptionMaterial(password);
+                    }
+                    document.openProtection(decryptionMaterial);
+                    AccessPermission ap = document.getCurrentAccessPermission();
+                    if(ap.isOwnerPermission())
+                    {
+                        document.save( outfile );
+                    }
+                    else
+                    {
+                        throw new IOException(
+                        "Error: You are only allowed to decrypt a document with the owner password." );
+                    }
+                }
+                else
+                {
+                    System.err.println( "Error: Document is not encrypted." );
+                }
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print a usage message.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar Decrypt " +
+                            "[options] <inputfile> [outputfile]" );
+        System.err.println( "-alias      The alias of the key in the certificate file " +
+                                         "(mandatory if several keys are available)");
+        System.err.println( "-password   The password to open the certificate and extract the private key from it." );
+        System.err.println( "-keyStore   The KeyStore that holds the certificate." );
+        System.exit( -1 );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/Encrypt.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/Encrypt.java
new file mode 100644 (file)
index 0000000..5822161
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * 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.pdfbox;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.PublicKeyProtectionPolicy;
+import org.apache.pdfbox.pdmodel.encryption.PublicKeyRecipient;
+import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
+
+/**
+ * This will read a document from the filesystem, encrypt it and and then write
+ * the results to the filesystem. <br/><br/>
+ *
+ * @author  <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class Encrypt
+{
+    private Encrypt()
+    {
+    }
+
+    /**
+     * This is the entry point for the application.
+     *
+     * @param args The command-line arguments.
+     *
+     * @throws Exception If there is an error decrypting the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        Encrypt encrypt = new Encrypt();
+        encrypt.encrypt( args );
+    }
+
+    private void encrypt( String[] args ) throws Exception
+    {
+        if( args.length < 1 )
+        {
+            usage();
+        }
+        else
+        {
+            AccessPermission ap = new AccessPermission();
+
+            String infile = null;
+            String outfile = null;
+            String certFile = null;
+            String userPassword = "";
+            String ownerPassword = "";
+
+            int keyLength = 40;
+
+            PDDocument document = null;
+
+            try
+            {
+                for( int i=0; i<args.length; i++ )
+                {
+                    String key = args[i];
+                    if( key.equals( "-O" ) )
+                    {
+                        ownerPassword = args[++i];
+                    }
+                    else if( key.equals( "-U" ) )
+                    {
+                        userPassword = args[++i];
+                    }
+                    else if( key.equals( "-canAssemble" ) )
+                    {
+                        ap.setCanAssembleDocument(args[++i].equalsIgnoreCase( "true" ));
+                    }
+                    else if( key.equals( "-canExtractContent" ) )
+                    {
+                        ap.setCanExtractContent( args[++i].equalsIgnoreCase( "true" ) );
+                    }
+                    else if( key.equals( "-canExtractForAccessibility" ) )
+                    {
+                        ap.setCanExtractForAccessibility( args[++i].equalsIgnoreCase( "true" ) );
+                    }
+                    else if( key.equals( "-canFillInForm" ) )
+                    {
+                        ap.setCanFillInForm( args[++i].equalsIgnoreCase( "true" ) );
+                    }
+                    else if( key.equals( "-canModify" ) )
+                    {
+                        ap.setCanModify( args[++i].equalsIgnoreCase( "true" ) );
+                    }
+                    else if( key.equals( "-canModifyAnnotations" ) )
+                    {
+                        ap.setCanModifyAnnotations( args[++i].equalsIgnoreCase( "true" ) );
+                    }
+                    else if( key.equals( "-canPrint" ) )
+                    {
+                        ap.setCanPrint( args[++i].equalsIgnoreCase( "true" ) );
+                    }
+                    else if( key.equals( "-canPrintDegraded" ) )
+                    {
+                        ap.setCanPrintDegraded( args[++i].equalsIgnoreCase( "true" ) );
+                    }
+                    else if( key.equals( "-certFile" ) )
+                    {
+                        certFile = args[++i];
+                    }
+                    else if( key.equals( "-keyLength" ) )
+                    {
+                        try
+                        {
+                            keyLength = Integer.parseInt( args[++i] );
+                        }
+                        catch( NumberFormatException e )
+                        {
+                            throw new NumberFormatException(
+                                "Error: -keyLength is not an integer '" + args[i] + "'" );
+                        }
+                    }
+                    else if( infile == null )
+                    {
+                        infile = key;
+                    }
+                    else if( outfile == null )
+                    {
+                        outfile = key;
+                    }
+                    else
+                    {
+                        usage();
+                    }
+                }
+                if( infile == null )
+                {
+                    usage();
+                }
+                if( outfile == null )
+                {
+                    outfile = infile;
+                }
+                document = PDDocument.load( infile );
+
+                if( !document.isEncrypted() )
+                {
+                    if( certFile != null )
+                    {
+                        PublicKeyProtectionPolicy ppp = new PublicKeyProtectionPolicy();
+                        PublicKeyRecipient recip = new PublicKeyRecipient();
+                        recip.setPermission(ap);
+
+
+                        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                        InputStream inStream = new FileInputStream(certFile);
+                        X509Certificate certificate = (X509Certificate)cf.generateCertificate(inStream);
+                        inStream.close();
+
+                        recip.setX509(certificate);
+
+                        ppp.addRecipient(recip);
+
+                        ppp.setEncryptionKeyLength(keyLength);
+
+                        document.protect(ppp);
+                    }
+                    else
+                    {
+                        StandardProtectionPolicy spp =
+                            new StandardProtectionPolicy(ownerPassword, userPassword, ap);
+                        spp.setEncryptionKeyLength(keyLength);
+                        document.protect(spp);
+                    }
+                    document.save( outfile );
+                }
+                else
+                {
+                    System.err.println( "Error: Document is already encrypted." );
+                }
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print a usage message.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar Encrypt [options] <inputfile> [outputfile]" );
+        System.err.println( "   -O <password>                            " +
+                                            "Set the owner password(ignored if cert is set)" );
+        System.err.println( "   -U <password>                            " +
+                                            "Set the user password(ignored if cert is set)" );
+        System.err.println( "   -certFile <path to cert>                 Path to X.509 certificate" );
+        System.err.println( "   -canAssemble <true|false>                Set the assemble permission" );
+        System.err.println( "   -canExtractContent <true|false>          Set the extraction permission" );
+        System.err.println( "   -canExtractForAccessibility <true|false> Set the extraction permission" );
+        System.err.println( "   -canFillInForm <true|false>              Set the fill in form permission" );
+        System.err.println( "   -canModify <true|false>                  Set the modify permission" );
+        System.err.println( "   -canModifyAnnotations <true|false>       Set the modify annots permission" );
+        System.err.println( "   -canPrint <true|false>                   Set the print permission" );
+        System.err.println( "   -canPrintDegraded <true|false>           Set the print degraded permission" );
+        System.err.println( "   -keyLength <length>                      The length of the key in bits(40)" );
+        System.err.println( "\nNote: By default all permissions are set to true!" );
+        System.exit( 1 );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExportFDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExportFDF.java
new file mode 100644 (file)
index 0000000..cc1bece
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.pdfbox;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * FDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ExportFDF
+{
+    /**
+     * Creates a new instance of ImportFDF.
+     */
+    public ExportFDF()
+    {
+    }
+
+    /**
+     * This will import an fdf document and write out another pdf.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     *
+     * @throws Exception If there is an error importing the FDF document.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        ExportFDF exporter = new ExportFDF();
+        exporter.exportFDF( args );
+    }
+
+    private void exportFDF( String[] args ) throws Exception
+    {
+        PDDocument pdf = null;
+        FDFDocument fdf = null;
+
+        try
+        {
+            if( args.length != 1 && args.length != 2 )
+            {
+                usage();
+            }
+            else
+            {
+                pdf = PDDocument.load( args[0] );
+                PDAcroForm form = pdf.getDocumentCatalog().getAcroForm();
+                if( form == null )
+                {
+                    System.err.println( "Error: This PDF does not contain a form." );
+                }
+                else
+                {
+                    String fdfName = null;
+                    if( args.length == 2 )
+                    {
+                        fdfName = args[1];
+                    }
+                    else
+                    {
+                        if( args[0].length() > 4 )
+                        {
+                            fdfName = args[0].substring( 0, args[0].length() -4 ) + ".fdf";
+                        }
+                    }
+                    fdf = form.exportFDF();
+                    fdf.save( fdfName );
+                }
+            }
+        }
+        finally
+        {
+            close( fdf );
+            close( pdf );
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: org.apache.pdfbox.ExortFDF <pdf-file> [output-fdf-file]" );
+        System.err.println( "    [output-fdf-file] - Default is pdf name, test.pdf->test.fdf" );
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( FDFDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( PDDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExportXFDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExportXFDF.java
new file mode 100644 (file)
index 0000000..3d2c222
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.pdfbox;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * FDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ExportXFDF
+{
+    /**
+     * Creates a new instance of ImportFDF.
+     */
+    public ExportXFDF()
+    {
+    }
+
+    /**
+     * This will import an fdf document and write out another pdf.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     *
+     * @throws Exception If there is an error importing the FDF document.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        ExportXFDF exporter = new ExportXFDF();
+        exporter.exportXFDF( args );
+    }
+
+    private void exportXFDF( String[] args ) throws Exception
+    {
+        PDDocument pdf = null;
+        FDFDocument fdf = null;
+
+        try
+        {
+            if( args.length != 1 && args.length != 2 )
+            {
+                usage();
+            }
+            else
+            {
+                pdf = PDDocument.load( args[0] );
+                PDAcroForm form = pdf.getDocumentCatalog().getAcroForm();
+                if( form == null )
+                {
+                    System.err.println( "Error: This PDF does not contain a form." );
+                }
+                else
+                {
+                    String fdfName = null;
+                    if( args.length == 2 )
+                    {
+                        fdfName = args[1];
+                    }
+                    else
+                    {
+                        if( args[0].length() > 4 )
+                        {
+                            fdfName = args[0].substring( 0, args[0].length() -4 ) + ".xfdf";
+                        }
+                    }
+                    fdf = form.exportFDF();
+                    fdf.saveXFDF( fdfName );
+                }
+            }
+        }
+        finally
+        {
+            close( fdf );
+            close( pdf );
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: org.apache.pdfbox.ExortXFDF <pdf-file> [output-xfdf-file]" );
+        System.err.println( "    [output-xfdf-file] - Default is pdf name, test.pdf->test.xfdf" );
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( FDFDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( PDDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExtractImages.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExtractImages.java
new file mode 100644 (file)
index 0000000..55fc23c
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * 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.pdfbox;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+
+/**
+ * This will read a read pdf and extract images. <br/><br/>
+ *
+ * usage: java org.apache.pdfbox.ExtractImages &lt;pdffile&gt; &lt;password&gt; [imageprefix]
+ *
+ * @author  <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class ExtractImages
+{
+    private int imageCounter = 1;
+
+    private static final String PASSWORD = "-password";
+    private static final String PREFIX = "-prefix";
+    private static final String ADDKEY = "-addkey";
+
+    private ExtractImages()
+    {
+    }
+
+    /**
+     * This is the entry point for the application.
+     *
+     * @param args The command-line arguments.
+     *
+     * @throws Exception If there is an error decrypting the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        ExtractImages extractor = new ExtractImages();
+        extractor.extractImages( args );
+    }
+
+    private void extractImages( String[] args ) throws Exception
+    {
+        if( args.length < 1 || args.length > 3 )
+        {
+            usage();
+        }
+        else
+        {
+            String pdfFile = null;
+            String password = "";
+            String prefix = null;
+            boolean addKey = false;
+            for( int i=0; i<args.length; i++ )
+            {
+                if( args[i].equals( PASSWORD ) )
+                {
+                    i++;
+                    if( i >= args.length )
+                    {
+                        usage();
+                    }
+                    password = args[i];
+                }
+                else if( args[i].equals( PREFIX ) )
+                {
+                    i++;
+                    if( i >= args.length )
+                    {
+                        usage();
+                    }
+                    prefix = args[i];
+                }
+                else if( args[i].equals( ADDKEY ) )
+                {
+                    addKey = true;
+                }
+                else
+                {
+                    if( pdfFile == null )
+                    {
+                        pdfFile = args[i];
+                    }
+                }
+            }
+            if(pdfFile == null)
+            {
+                usage();
+            }
+            else
+            {
+                if( prefix == null && pdfFile.length() >4 )
+                {
+                    prefix = pdfFile.substring( 0, pdfFile.length() -4 );
+                }
+
+                PDDocument document = null;
+
+                try
+                {
+                    document = PDDocument.load( pdfFile );
+
+                    if( document.isEncrypted() )
+                    {
+
+                        StandardDecryptionMaterial spm = new StandardDecryptionMaterial(password);
+                        document.openProtection(spm);
+                        AccessPermission ap = document.getCurrentAccessPermission();
+
+
+                        if( ! ap.canExtractContent() )
+                        {
+                            throw new IOException(
+                                "Error: You do not have permission to extract images." );
+                        }
+                    }
+
+                    List pages = document.getDocumentCatalog().getAllPages();
+                    Iterator iter = pages.iterator();
+                    while( iter.hasNext() )
+                    {
+                        PDPage page = (PDPage)iter.next();
+                        PDResources resources = page.getResources();
+                        Map images = resources.getImages();
+                        if( images != null )
+                        {
+                            Iterator imageIter = images.keySet().iterator();
+                            while( imageIter.hasNext() )
+                            {
+                                String key = (String)imageIter.next();
+                                PDXObjectImage image = (PDXObjectImage)images.get( key );
+                                String name = null;
+                                if (addKey) 
+                                {
+                                    name = getUniqueFileName( prefix + "_" + key, image.getSuffix() );
+                                }
+                                else 
+                                {
+                                    name = getUniqueFileName( prefix, image.getSuffix() );
+                                }
+                                System.out.println( "Writing image:" + name );
+                                image.write2file( name );
+                            }
+                        }
+                    }
+                }
+                finally
+                {
+                    if( document != null )
+                    {
+                        document.close();
+                    }
+                }
+            }
+        }
+    }
+
+    private String getUniqueFileName( String prefix, String suffix )
+    {
+        String uniqueName = null;
+        File f = null;
+        while( f == null || f.exists() )
+        {
+            uniqueName = prefix + "-" + imageCounter;
+            f = new File( uniqueName + "." + suffix );
+            imageCounter++;
+        }
+        return uniqueName;
+    }
+
+    /**
+     * This will print the usage requirements and exit.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.ExtractImages [OPTIONS] <PDF file>\n" +
+            "  -password  <password>        Password to decrypt document\n" +
+            "  -prefix  <image-prefix>      Image prefix(default to pdf name)\n" +
+            "  -addkey                      add the internal image key to the file name\n" +
+            "  <PDF file>                   The PDF document to use\n"
+            );
+        System.exit( 1 );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExtractText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/ExtractText.java
new file mode 100644 (file)
index 0000000..5ccdb09
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * 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.pdfbox;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+import org.apache.pdfbox.util.PDFText2HTML;
+import org.apache.pdfbox.util.PDFTextStripper;
+
+/**
+ * This is the main program that simply parses the pdf document and transforms it
+ * into text.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class ExtractText
+{
+    private static final String PASSWORD = "-password";
+    private static final String ENCODING = "-encoding";
+    private static final String CONSOLE = "-console";
+    private static final String START_PAGE = "-startPage";
+    private static final String END_PAGE = "-endPage";
+    private static final String SORT = "-sort";
+    private static final String IGNORE_BEADS = "-ignoreBeads";
+    private static final String DEBUG = "-debug";
+    private static final String HTML = "-html";  // jjb - added simple HTML output
+    private static final String FORCE = "-force"; //enables pdfbox to skip corrupt objects
+
+    /*
+     * debug flag
+     */
+    private boolean debug = false;
+
+    /**
+     * private constructor.
+    */
+    private ExtractText()
+    {
+        //static class
+    }
+
+    /**
+     * Infamous main method.
+     *
+     * @param args Command line arguments, should be one and a reference to a file.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        ExtractText extractor = new ExtractText();
+        extractor.startExtraction(args);
+    }
+
+    public void startExtraction( String[] args ) throws Exception
+    {
+        boolean toConsole = false;
+        boolean toHTML = false;
+        boolean force = false;
+        boolean sort = false;
+        boolean separateBeads = true;
+        String password = "";
+        String encoding = null;
+        String pdfFile = null;
+        String outputFile = null;
+        // Defaults to text files
+        String ext = ".txt";
+        int startPage = 1;
+        int endPage = Integer.MAX_VALUE;
+        for( int i=0; i<args.length; i++ )
+        {
+            if( args[i].equals( PASSWORD ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                password = args[i];
+            }
+            else if( args[i].equals( ENCODING ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                encoding = args[i];
+            }
+            else if( args[i].equals( START_PAGE ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                startPage = Integer.parseInt( args[i] );
+            }
+            else if( args[i].equals( HTML ) )
+            {
+                toHTML = true;
+                ext = ".html";
+            }
+            else if( args[i].equals( SORT ) )
+            {
+                sort = true;
+            }
+            else if( args[i].equals( IGNORE_BEADS ) )
+            {
+                separateBeads = false;
+            }
+            else if( args[i].equals( DEBUG ) )
+            {
+                debug = true;
+            }
+            else if( args[i].equals( END_PAGE ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                endPage = Integer.parseInt( args[i] );
+            }
+            else if( args[i].equals( CONSOLE ) )
+            {
+                toConsole = true;
+            }
+            else if( args[i].equals( FORCE ) )
+            {
+                force = true;
+            }
+            else
+            {
+                if( pdfFile == null )
+                {
+                    pdfFile = args[i];
+                }
+                else
+                {
+                    outputFile = args[i];
+                }
+            }
+        }
+
+        if( pdfFile == null )
+        {
+            usage();
+        }
+        else
+        {
+
+            Writer output = null;
+            PDDocument document = null;
+            try
+            {
+                long startTime = startProcessing("Loading PDF "+pdfFile);
+                try
+                {
+                    //basically try to load it from a url first and if the URL
+                    //is not recognized then try to load it from the file system.
+                    URL url = new URL( pdfFile );
+                    document = PDDocument.load(url, force);
+                    String fileName = url.getFile();
+                    if( outputFile == null && fileName.length() >4 )
+                    {
+                        outputFile = new File( fileName.substring( 0, fileName.length() -4 ) + ext ).getName();
+                    }
+                }
+                catch( MalformedURLException e )
+                {
+                    document = PDDocument.load(pdfFile, force);
+                    if( outputFile == null && pdfFile.length() >4 )
+                    {
+                        outputFile = pdfFile.substring( 0, pdfFile.length() -4 ) + ext;
+                    }
+                }
+                stopProcessing("Time for loading: ", startTime);
+
+                if( document.isEncrypted() )
+                {
+                    StandardDecryptionMaterial sdm = new StandardDecryptionMaterial( password );
+                    document.openProtection( sdm );
+                    AccessPermission ap = document.getCurrentAccessPermission();
+
+                    if( ! ap.canExtractContent() )
+                    {
+                        throw new IOException( "You do not have permission to extract text" );
+                    }
+                }
+
+                if ((encoding == null) && (toHTML))
+                {
+                    encoding = "UTF-8";
+                }
+
+                if( toConsole )
+                {
+                    output = new OutputStreamWriter( System.out );
+                }
+                else
+                {
+                    if( encoding != null )
+                    {
+                        output = new OutputStreamWriter(
+                                new FileOutputStream( outputFile ), encoding );
+                    }
+                    else
+                    {
+                        //use default encoding
+                        output = new OutputStreamWriter(
+                                new FileOutputStream( outputFile ) );
+                    }
+                }
+
+                PDFTextStripper stripper = null;
+                if(toHTML)
+                {
+                    stripper = new PDFText2HTML(encoding);
+                }
+                else
+                {
+                    stripper = new PDFTextStripper(encoding);
+                }
+                stripper.setForceParsing( force );
+                stripper.setSortByPosition( sort );
+                stripper.setShouldSeparateByBeads( separateBeads );
+                stripper.setStartPage( startPage );
+                stripper.setEndPage( endPage );
+
+                startTime = startProcessing("Starting text extraction");
+                stripper.writeText( document, output );
+                stopProcessing("Time for extraction: ", startTime);
+            }
+            finally
+            {
+                if( output != null )
+                {
+                    output.close();
+                }
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    private long startProcessing(String message) {
+        if (debug) 
+        {
+            System.err.println(message);
+        }
+        return System.currentTimeMillis();
+    }
+    
+    private void stopProcessing(String message, long startTime) {
+        if (debug)
+        {
+            long stopTime = System.currentTimeMillis();
+            float elapsedTime = ((float)(stopTime - startTime))/1000;
+            System.err.println(message + elapsedTime + " seconds");
+        }
+    }
+
+    /**
+     * This will print the usage requirements and exit.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar ExtractText [OPTIONS] <PDF file> [Text File]\n" +
+            "  -password  <password>        Password to decrypt document\n" +
+            "  -encoding  <output encoding> (ISO-8859-1,UTF-16BE,UTF-16LE,...)\n" +
+            "  -console                     Send text to console instead of file\n" +
+            "  -html                        Output in HTML format instead of raw text\n" +
+            "  -sort                        Sort the text before writing\n" +
+            "  -ignoreBeads                 Disables the separation by beads\n" +
+            "  -force                       Enables pdfbox to ignore corrupt objects\n" +
+            "  -debug                       Enables debug output about the time consumption of every stage\n" +
+            "  -startPage <number>          The first page to start extraction(1 based)\n" +
+            "  -endPage <number>            The last page to extract(inclusive)\n" +
+            "  <PDF file>                   The PDF document to use\n" +
+            "  [Text File]                  The file to write the text to\n"
+            );
+        System.exit( 1 );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/ImportFDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/ImportFDF.java
new file mode 100644 (file)
index 0000000..3bc79fe
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.pdfbox;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * FDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ImportFDF
+{
+    /**
+     * Creates a new instance of ImportFDF.
+     */
+    public ImportFDF()
+    {
+    }
+
+    /**
+     * This will takes the values from the fdf document and import them into the
+     * PDF document.
+     *
+     * @param pdfDocument The document to put the fdf data into.
+     * @param fdfDocument The FDF document to get the data from.
+     *
+     * @throws IOException If there is an error setting the data in the field.
+     */
+    public void importFDF( PDDocument pdfDocument, FDFDocument fdfDocument ) throws IOException
+    {
+        PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+        PDAcroForm acroForm = docCatalog.getAcroForm();
+        acroForm.setCacheFields( true );
+        acroForm.importFDF( fdfDocument );
+    }
+
+    /**
+     * This will import an fdf document and write out another pdf.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     *
+     * @throws Exception If there is an error importing the FDF document.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        ImportFDF importer = new ImportFDF();
+        importer.importFDF( args );
+    }
+
+    private void importFDF( String[] args ) throws Exception
+    {
+        PDDocument pdf = null;
+        FDFDocument fdf = null;
+
+        try
+        {
+            if( args.length != 3 )
+            {
+                usage();
+            }
+            else
+            {
+                ImportFDF importer = new ImportFDF();
+
+                pdf = PDDocument.load( args[0] );
+                fdf = FDFDocument.load( args[1] );
+                importer.importFDF( pdf, fdf );
+
+                pdf.save( args[2] );
+            }
+        }
+        finally
+        {
+            close( fdf );
+            close( pdf );
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: org.apache.pdfbox.ImportFDF <pdf-file> <fdf-file> <output-file>" );
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( FDFDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( PDDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/ImportXFDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/ImportXFDF.java
new file mode 100644 (file)
index 0000000..3cf0ea3
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+import java.io.IOException;
+
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * XFDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ImportXFDF
+{
+    /**
+     * Creates a new instance of ImportFDF.
+     */
+    public ImportXFDF()
+    {
+    }
+
+    /**
+     * This will takes the values from the fdf document and import them into the
+     * PDF document.
+     *
+     * @param pdfDocument The document to put the fdf data into.
+     * @param fdfDocument The FDF document to get the data from.
+     *
+     * @throws IOException If there is an error setting the data in the field.
+     */
+    public void importFDF( PDDocument pdfDocument, FDFDocument fdfDocument ) throws IOException
+    {
+        PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+        PDAcroForm acroForm = docCatalog.getAcroForm();
+        acroForm.setCacheFields( true );
+        acroForm.importFDF( fdfDocument );
+    }
+
+    /**
+     * This will import an fdf document and write out another pdf.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     *
+     * @throws Exception If there is an error importing the FDF document.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        ImportXFDF importer = new ImportXFDF();
+        importer.importXFDF( args );
+    }
+
+    private void importXFDF( String[] args ) throws Exception
+    {
+        PDDocument pdf = null;
+        FDFDocument fdf = null;
+
+        try
+        {
+            if( args.length != 3 )
+            {
+                usage();
+            }
+            else
+            {
+                ImportFDF importer = new ImportFDF();
+                pdf = PDDocument.load( args[0] );
+                fdf = FDFDocument.loadXFDF( args[1] );
+
+                importer.importFDF( pdf, fdf );
+                pdf.save( args[2] );
+                fdf.save( "tmp/outputXFDFtoPDF.fdf");
+            }
+        }
+        finally
+        {
+            close( fdf );
+            close( pdf );
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: org.apache.pdfbox.ImportXFDF <pdf-file> <fdf-file> <output-file>" );
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( FDFDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( PDDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/Overlay.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/Overlay.java
new file mode 100644 (file)
index 0000000..bb0b255
--- /dev/null
@@ -0,0 +1,504 @@
+/*
+ * 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.pdfbox;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdfparser.PDFParser;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.Map;
+
+/**
+ * Overlay on document with another one.<br>
+ * e.g. Overlay an invoice with your company layout<br>
+ * <br>
+ * How it (should) work:<br>
+ * If the document has 10 pages, and the layout 2 the following is the result:<br>
+ * <pre>
+ * Document: 1234567890
+ * Layout  : 1212121212
+ * </pre>
+ * <br>
+ *
+ * @author Mario Ivankovits (mario@ops.co.at)
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ *
+ * @version $Revision: 1.7 $
+ */
+public class Overlay
+{
+    /**
+     * @deprecated use the {@link COSName#XOBJECT} constant instead
+     */
+    public static final COSName XOBJECT = COSName.XOBJECT;
+
+    /**
+     * @deprecated use the {@link COSName#PROC_SET} constant instead
+     */
+    public static final COSName PROC_SET = COSName.PROC_SET;
+
+    /**
+     * @deprecated use the {@link COSName#EXT_G_STATE} constant instead
+     */
+    public static final COSName EXT_G_STATE = COSName.EXT_G_STATE;
+
+    private List layoutPages = new ArrayList(10);
+
+    private PDDocument pdfOverlay;
+    private PDDocument pdfDocument;
+    private int pageCount = 0;
+    private COSStream saveGraphicsStateStream;
+    private COSStream restoreGraphicsStateStream;
+
+    /**
+     * This will overlay a document and write out the results.<br/><br/>
+     *
+     * usage: java org.apache.pdfbox.Overlay &lt;overlay.pdf&gt; &lt;document.pdf&gt; &lt;result.pdf&gt;
+     *
+     * @param args The command line arguments.
+     *
+     * @throws IOException If there is an error reading/writing the document.
+     * @throws COSVisitorException If there is an error writing the document.
+     */
+    public static void main( String[] args ) throws IOException, COSVisitorException
+    {
+        if( args.length != 3 )
+        {
+            usage();
+            System.exit(1);
+        }
+        else
+        {
+            PDDocument overlay = null;
+            PDDocument pdf = null;
+
+            try
+            {
+                overlay = getDocument( args[0] );
+                pdf = getDocument( args[1] );
+                Overlay overlayer = new Overlay();
+                overlayer.overlay( overlay, pdf );
+                writeDocument( pdf, args[2] );
+            }
+            finally
+            {
+                if( overlay != null )
+                {
+                    overlay.close();
+                }
+                if( pdf != null )
+                {
+                    pdf.close();
+                }
+            }
+        }
+    }
+
+    private static void writeDocument( PDDocument pdf, String filename ) throws IOException, COSVisitorException
+    {
+        FileOutputStream output = null;
+        COSWriter writer = null;
+        try
+        {
+            output = new FileOutputStream( filename );
+            writer = new COSWriter( output );
+            writer.write( pdf );
+        }
+        finally
+        {
+            if( writer != null )
+            {
+                writer.close();
+            }
+            if( output != null )
+            {
+                output.close();
+            }
+        }
+    }
+
+    private static PDDocument getDocument( String filename ) throws IOException
+    {
+        FileInputStream input = null;
+        PDFParser parser = null;
+        PDDocument result = null;
+        try
+        {
+            input = new FileInputStream( filename );
+            parser = new PDFParser( input );
+            parser.parse();
+            result = parser.getPDDocument();
+        }
+        finally
+        {
+            if( input != null )
+            {
+                input.close();
+            }
+        }
+        return result;
+    }
+
+    private static void usage()
+    {
+        System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar Overlay <overlay.pdf> <document.pdf> <result.pdf>" );
+    }
+
+    /**
+     * Private class.
+     */
+    private static class LayoutPage
+    {
+        private final COSBase contents;
+        private final COSDictionary res;
+        private final Map objectNameMap;
+
+        /**
+         * Constructor.
+         *
+         * @param contentsValue The contents.
+         * @param resValue The resource dictionary
+         * @param objectNameMapValue The map
+         */
+        public LayoutPage(COSBase contentsValue, COSDictionary resValue, Map objectNameMapValue)
+        {
+            contents = contentsValue;
+            res = resValue;
+            objectNameMap = objectNameMapValue;
+        }
+    }
+
+    /**
+     * This will overlay two documents onto each other.  The overlay document is
+     * repeatedly overlayed onto the destination document for every page in the
+     * destination.
+     *
+     * @param overlay The document to copy onto the destination
+     * @param destination The file that the overlay should be placed on.
+     *
+     * @return The destination pdf, same as argument passed in.
+     *
+     * @throws IOException If there is an error accessing data.
+     */
+    public PDDocument overlay( PDDocument overlay, PDDocument destination ) throws IOException
+    {
+        pdfOverlay = overlay;
+        pdfDocument = destination;
+
+        PDDocumentCatalog overlayCatalog = pdfOverlay.getDocumentCatalog();
+        collectLayoutPages( overlayCatalog.getAllPages() );
+
+        COSDictionary saveGraphicsStateDic = new COSDictionary();
+        saveGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() );
+        OutputStream saveStream = saveGraphicsStateStream.createUnfilteredStream();
+        saveStream.write( " q\n".getBytes("ISO-8859-1") );
+        saveStream.flush();
+
+        restoreGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() );
+        OutputStream restoreStream = restoreGraphicsStateStream.createUnfilteredStream();
+        restoreStream.write( " Q\n".getBytes("ISO-8859-1") );
+        restoreStream.flush();
+
+
+        PDDocumentCatalog pdfCatalog = pdfDocument.getDocumentCatalog();
+        processPages( pdfCatalog.getAllPages() );
+
+        return pdfDocument;
+    }
+
+    private void collectLayoutPages( List pages) throws IOException
+    {
+        Iterator pagesIter = pages.iterator();
+        while( pagesIter.hasNext() )
+        {
+            PDPage page = (PDPage)pagesIter.next();
+            COSBase contents = page.getCOSDictionary().getDictionaryObject( COSName.CONTENTS );
+            PDResources resources = page.findResources();
+            if( resources == null )
+            {
+                resources = new PDResources();
+                page.setResources( resources );
+            }
+            COSDictionary res = resources.getCOSDictionary();
+
+            if( contents instanceof COSStream )
+            {
+                COSStream stream = (COSStream) contents;
+                Map objectNameMap = new TreeMap();
+                stream = makeUniqObjectNames(objectNameMap, stream);
+
+                layoutPages.add(new LayoutPage(stream, res, objectNameMap));
+            }
+            else if( contents instanceof COSArray )
+            {
+                throw new UnsupportedOperationException("Layout pages with COSArray currently not supported.");
+                // layoutPages.add(new LayoutPage(contents, res));
+            }
+            else
+            {
+                throw new IOException( "Contents are unknown type:" + contents.getClass().getName() );
+            }
+        }
+    }
+
+    private COSStream makeUniqObjectNames(Map objectNameMap, COSStream stream) throws IOException
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(10240);
+
+        byte[] buf = new byte[10240];
+        int read;
+        InputStream is = stream.getUnfilteredStream();
+        while ((read = is.read(buf)) > -1)
+        {
+            baos.write(buf, 0, read);
+        }
+
+        buf = baos.toByteArray();
+        baos = new ByteArrayOutputStream(buf.length + 100);
+        StringBuffer sbObjectName = new StringBuffer(10);
+        boolean bInObjectIdent = false;
+        boolean bInText = false;
+        boolean bInEscape = false;
+        for (int i = 0; i<buf.length; i++)
+        {
+            byte b = buf[i];
+
+            if (!bInEscape)
+            {
+                if (!bInText && b == '(')
+                {
+                    bInText = true;
+                }
+                if (bInText && b == ')')
+                {
+                    bInText = false;
+                }
+                if (b == '\\')
+                {
+                    bInEscape = true;
+                }
+
+                if (!bInText && !bInEscape)
+                {
+                    if (b == '/')
+                    {
+                        bInObjectIdent = true;
+                    }
+                    else if (bInObjectIdent && Character.isWhitespace((char) b))
+                    {
+                        bInObjectIdent = false;
+
+                        // System.err.println(sbObjectName);
+                        // String object = sbObjectName.toString();
+
+                        String objectName = sbObjectName.toString().substring(1);
+                        String newObjectName = objectName + "overlay";
+                        baos.write('/');
+                        baos.write(newObjectName.getBytes("ISO-8859-1"));
+
+                        objectNameMap.put(objectName, COSName.getPDFName(newObjectName));
+
+                        sbObjectName.delete(0, sbObjectName.length());
+                    }
+                }
+
+                if (bInObjectIdent)
+                {
+                    sbObjectName.append((char) b);
+                    continue;
+                }
+            }
+            else
+            {
+                bInEscape = false;
+            }
+
+            baos.write(b);
+        }
+
+        COSDictionary streamDict = new COSDictionary();
+        streamDict.setInt(COSName.LENGTH, baos.size());
+        COSStream output = new COSStream(streamDict, pdfDocument.getDocument().getScratchFile());
+        output.setFilters(stream.getFilters());
+        OutputStream os = output.createUnfilteredStream();
+        baos.writeTo(os);
+        os.close();
+
+        return output;
+    }
+
+    private void processPages( List pages ) throws IOException
+    {
+        Iterator pageIter = pages.iterator();
+        while( pageIter.hasNext() )
+        {
+            PDPage page = (PDPage)pageIter.next();
+            COSDictionary pageDictionary = page.getCOSDictionary();
+            COSBase contents = pageDictionary.getDictionaryObject( COSName.CONTENTS );
+            if( contents instanceof COSStream )
+            {
+                COSStream contentsStream = (COSStream)contents;
+                // System.err.println("stream");
+
+                COSArray array = new COSArray();
+
+                array.add(contentsStream);
+
+                mergePage( array, page );
+
+                pageDictionary.setItem(COSName.CONTENTS, array);
+            }
+            else if( contents instanceof COSArray )
+            {
+                COSArray contentsArray = (COSArray)contents;
+
+                mergePage( contentsArray, page );
+            }
+            else
+            {
+                throw new IOException( "Contents are unknown type:" + contents.getClass().getName() );
+            }
+            pageCount++;
+        }
+    }
+
+    private void mergePage(COSArray array, PDPage page )
+    {
+        int layoutPageNum = pageCount % layoutPages.size();
+        LayoutPage layoutPage = (LayoutPage) layoutPages.get(layoutPageNum);
+        PDResources resources = page.findResources();
+        if( resources == null )
+        {
+            resources = new PDResources();
+            page.setResources( resources );
+        }
+        COSDictionary docResDict = resources.getCOSDictionary();
+        COSDictionary layoutResDict = layoutPage.res;
+        mergeArray(COSName.PROC_SET, docResDict, layoutResDict);
+        mergeDictionary(COSName.FONT, docResDict, layoutResDict, layoutPage.objectNameMap);
+        mergeDictionary(COSName.XOBJECT, docResDict, layoutResDict, layoutPage.objectNameMap);
+        mergeDictionary(COSName.EXT_G_STATE, docResDict, layoutResDict, layoutPage.objectNameMap);
+
+        //we are going to wrap the existing content around some save/restore
+        //graphics state, so the result is
+        //
+        //<save graphics state>
+        //<all existing content streams>
+        //<restore graphics state>
+        //<overlay content>
+        array.add(0, saveGraphicsStateStream );
+        array.add( restoreGraphicsStateStream );
+        array.add(layoutPage.contents);
+    }
+
+    /**
+     * merges two dictionaries.
+     *
+     * @param dest
+     * @param source
+     */
+    private void mergeDictionary(COSName name, COSDictionary dest, COSDictionary source, Map objectNameMap)
+    {
+        COSDictionary destDict = (COSDictionary) dest.getDictionaryObject(name);
+        COSDictionary sourceDict = (COSDictionary) source.getDictionaryObject(name);
+
+        if (destDict == null)
+        {
+            destDict = new COSDictionary();
+            dest.setItem(name, destDict);
+        }
+        if( sourceDict != null )
+        {
+
+            for (Map.Entry<COSName, COSBase> entry : sourceDict.entrySet())
+            {
+                COSName mappedKey = (COSName) objectNameMap.get(entry.getKey().getName());
+                if (mappedKey != null)
+                {
+                    destDict.setItem(mappedKey, entry.getValue());
+                }
+            }
+        }
+    }
+
+    /**
+     * merges two arrays.
+     *
+     * @param dest
+     * @param source
+     */
+    private void mergeArray(COSName name, COSDictionary dest, COSDictionary source)
+    {
+        COSArray destDict = (COSArray) dest.getDictionaryObject(name);
+        COSArray sourceDict = (COSArray) source.getDictionaryObject(name);
+
+        if (destDict == null)
+        {
+            destDict = new COSArray();
+            dest.setItem(name, destDict);
+        }
+
+        for (int sourceDictIdx = 0; sourceDict != null && sourceDictIdx<sourceDict.size(); sourceDictIdx++)
+        {
+            COSBase key = sourceDict.get(sourceDictIdx);
+            if (key instanceof COSName)
+            {
+                COSName keyname = (COSName) key;
+
+                boolean bFound = false;
+                for (int destDictIdx = 0; destDictIdx<destDict.size(); destDictIdx++)
+                {
+                    COSBase destkey = destDict.get(destDictIdx);
+                    if (destkey instanceof COSName)
+                    {
+                        COSName destkeyname = (COSName) destkey;
+                        if (destkeyname.equals(keyname))
+                        {
+                            bFound = true;
+                            break;
+                        }
+                    }
+                }
+                if (!bFound)
+                {
+                    destDict.add(keyname);
+                }
+            }
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFBox.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFBox.java
new file mode 100644 (file)
index 0000000..91a0d6f
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.pdfbox;
+
+
+/**
+ * Simple wrapper around all the command line utilities included in PDFBox.
+ * Used as the main class in the runnable standalone PDFBox jar.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-687">PDFBOX-687</a>
+ */
+public class PDFBox {
+
+    public static void main(String[] args) {
+        if (args.length > 0) {
+            String command = args[0];
+            String[] arguments = new String[args.length - 1];
+            System.arraycopy(args, 1, arguments, 0, arguments.length);
+            boolean exitAfterCallingMain = true;
+            try {
+                if (command.equals("ConvertColorspace")) {
+                    ConvertColorspace.main(arguments);
+                } else if (command.equals("Decrypt")) {
+                    Decrypt.main(arguments);
+                } else if (command.equals("Encrypt")) {
+                    Encrypt.main(arguments);
+                } else if (command.equals("ExtractText")) {
+                    ExtractText.main(arguments);
+                } else if (command.equals("Overlay")) {
+                    Overlay.main(arguments);
+                } else if (command.equals("PrintPDF")) {
+                    PrintPDF.main(arguments);
+                } else if (command.equals("PDFDebugger")) {
+                    PDFDebugger.main(arguments);
+                    exitAfterCallingMain = false;
+                } else if (command.equals("PDFMerger")) {
+                    PDFMerger.main(arguments);
+                } else if (command.equals("PDFReader")) {
+                    PDFReader.main(arguments);
+                    exitAfterCallingMain = false;
+                } else if (command.equals("PDFSplit")) {
+                    PDFSplit.main(arguments);
+                } else if (command.equals("PDFToImage")) {
+                    PDFToImage.main(arguments);
+                } else if (command.equals("TextToPDF")) {
+                    TextToPDF.main(arguments);
+                } else if (command.equals("WriteDecodedDoc")) {
+                    WriteDecodedDoc.main(arguments);
+                }
+                else {
+                    showMessageAndExit();
+                }
+                if (exitAfterCallingMain) {
+                    System.exit(0);
+                }
+            } catch (Exception e) {
+                System.err.println(
+                        command + " failed with the following exception:");
+                e.printStackTrace();
+                System.exit(1);
+            }
+        }
+        else {
+            showMessageAndExit();
+        }
+    }
+
+    private static void showMessageAndExit() {
+        System.err.println(
+                "usage: java pdfbox-app-x.y.z.jar <command> <args..>");
+        System.exit(1);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFDebugger.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFDebugger.java
new file mode 100644 (file)
index 0000000..cce922b
--- /dev/null
@@ -0,0 +1,435 @@
+/*
+ * 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.pdfbox;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdfviewer.PDFTreeModel;
+import org.apache.pdfbox.pdfviewer.PDFTreeCellRenderer;
+import org.apache.pdfbox.pdfviewer.ArrayEntry;
+import org.apache.pdfbox.pdfviewer.MapEntry;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.util.ExtensionFileFilter;
+
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+//import javax.swing.tree.*;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.JFileChooser;
+import javax.swing.JScrollPane;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ *
+ * @author  wurtz
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFDebugger extends javax.swing.JFrame
+{
+    private File currentDir=new File(".");
+    private PDDocument document = null;
+
+    /**
+     * Constructor.
+     */
+    public PDFDebugger()
+    {
+        initComponents();
+    }
+
+    /**
+     * This method is called from within the constructor to
+     * initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is
+     * always regenerated by the Form Editor.
+     */
+    private void initComponents()
+    {
+        jSplitPane1 = new javax.swing.JSplitPane();
+        jScrollPane1 = new javax.swing.JScrollPane();
+        jTree1 = new javax.swing.JTree();
+        jScrollPane2 = new javax.swing.JScrollPane();
+        jTextPane1 = new javax.swing.JTextPane();
+        menuBar = new javax.swing.JMenuBar();
+        fileMenu = new javax.swing.JMenu();
+        openMenuItem = new javax.swing.JMenuItem();
+        saveMenuItem = new javax.swing.JMenuItem();
+        saveAsMenuItem = new javax.swing.JMenuItem();
+        exitMenuItem = new javax.swing.JMenuItem();
+        editMenu = new javax.swing.JMenu();
+        cutMenuItem = new javax.swing.JMenuItem();
+        copyMenuItem = new javax.swing.JMenuItem();
+        pasteMenuItem = new javax.swing.JMenuItem();
+        deleteMenuItem = new javax.swing.JMenuItem();
+        helpMenu = new javax.swing.JMenu();
+        contentsMenuItem = new javax.swing.JMenuItem();
+        aboutMenuItem = new javax.swing.JMenuItem();
+
+        jTree1.setCellRenderer( new PDFTreeCellRenderer() );
+        jTree1.setModel( null );
+
+        setTitle("PDFBox - PDF Viewer");
+        addWindowListener(new java.awt.event.WindowAdapter()
+        {
+            public void windowClosing(java.awt.event.WindowEvent evt)
+            {
+                exitForm(evt);
+            }
+        });
+
+
+        jScrollPane1.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+        jScrollPane1.setPreferredSize(new java.awt.Dimension(300, 500));
+        jTree1.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener()
+        {
+            public void valueChanged(javax.swing.event.TreeSelectionEvent evt)
+            {
+                jTree1ValueChanged(evt);
+            }
+        });
+
+        jScrollPane1.setViewportView(jTree1);
+
+        jSplitPane1.setRightComponent(jScrollPane2);
+
+        jScrollPane2.setPreferredSize(new java.awt.Dimension(300, 500));
+        jScrollPane2.setViewportView(jTextPane1);
+
+        jSplitPane1.setLeftComponent(jScrollPane1);
+
+        JScrollPane documentScroller = new JScrollPane();
+        //documentScroller.setPreferredSize( new Dimension( 300, 500 ) );
+        documentScroller.setViewportView( documentPanel );
+
+        getContentPane().add( jSplitPane1, java.awt.BorderLayout.CENTER );
+
+        fileMenu.setText("File");
+        openMenuItem.setText("Open");
+        openMenuItem.setToolTipText("Open PDF file");
+        openMenuItem.addActionListener(new java.awt.event.ActionListener()
+        {
+            public void actionPerformed(java.awt.event.ActionEvent evt)
+            {
+                openMenuItemActionPerformed(evt);
+            }
+        });
+
+        fileMenu.add(openMenuItem);
+
+        saveMenuItem.setText("Save");
+        //fileMenu.add(saveMenuItem);
+
+        saveAsMenuItem.setText("Save As ...");
+        //fileMenu.add(saveAsMenuItem);
+
+        exitMenuItem.setText("Exit");
+        exitMenuItem.addActionListener(new java.awt.event.ActionListener()
+        {
+            public void actionPerformed(java.awt.event.ActionEvent evt)
+            {
+                exitMenuItemActionPerformed(evt);
+            }
+        });
+
+        fileMenu.add(exitMenuItem);
+
+        menuBar.add(fileMenu);
+
+        editMenu.setText("Edit");
+        cutMenuItem.setText("Cut");
+        editMenu.add(cutMenuItem);
+
+        copyMenuItem.setText("Copy");
+        editMenu.add(copyMenuItem);
+
+        pasteMenuItem.setText("Paste");
+        editMenu.add(pasteMenuItem);
+
+        deleteMenuItem.setText("Delete");
+        editMenu.add(deleteMenuItem);
+
+        //menuBar.add(editMenu);
+
+        helpMenu.setText("Help");
+        contentsMenuItem.setText("Contents");
+        helpMenu.add(contentsMenuItem);
+
+        aboutMenuItem.setText("About");
+        helpMenu.add(aboutMenuItem);
+
+        //menuBar.add(helpMenu);
+
+        setJMenuBar(menuBar);
+
+
+        java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
+        setBounds((screenSize.width-700)/2, (screenSize.height-600)/2, 700, 600);
+    }//GEN-END:initComponents
+
+    private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt)
+    {
+        JFileChooser chooser = new JFileChooser();
+        chooser.setCurrentDirectory(currentDir);
+
+        ExtensionFileFilter pdfFilter = new ExtensionFileFilter(new String[] {"pdf", "PDF"}, "PDF Files");
+        chooser.setFileFilter(pdfFilter);
+        int result = chooser.showOpenDialog(PDFDebugger.this);
+        if (result == JFileChooser.APPROVE_OPTION)
+        {
+            String name = chooser.getSelectedFile().getPath();
+            currentDir = new File(name).getParentFile();
+            try
+            {
+                readPDFFile(name);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }//GEN-LAST:event_openMenuItemActionPerformed
+
+    private void jTree1ValueChanged(javax.swing.event.TreeSelectionEvent evt)
+    {
+        TreePath path = jTree1.getSelectionPath();
+        if (path != null)
+        {
+            try
+            {
+                Object selectedNode = path.getLastPathComponent();
+                String data=convertToString(selectedNode);
+
+
+
+                if (data != null)
+                {
+                    jTextPane1.setText(data);
+                }
+                else
+                {
+                    jTextPane1.setText( "" );
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }//GEN-LAST:event_jTree1ValueChanged
+
+    private String convertToString( Object selectedNode )
+    {
+        String data = null;
+        if(selectedNode instanceof COSBoolean)
+        {
+            data = "" + ((COSBoolean)selectedNode).getValue();
+        }
+        else if( selectedNode instanceof COSFloat )
+        {
+            data = "" + ((COSFloat)selectedNode).floatValue();
+        }
+        else if( selectedNode instanceof COSNull )
+        {
+            data = "null";
+        }
+        else if( selectedNode instanceof COSInteger )
+        {
+            data = "" + ((COSInteger)selectedNode).intValue();
+        }
+        else if( selectedNode instanceof COSName )
+        {
+            data = "" + ((COSName)selectedNode).getName();
+        }
+        else if( selectedNode instanceof COSString )
+        {
+            data = "" + ((COSString)selectedNode).getString();
+        }
+        else if( selectedNode instanceof COSStream )
+        {
+            try
+            {
+                COSStream stream = (COSStream)selectedNode;
+                InputStream ioStream = stream.getUnfilteredStream();
+                ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
+                byte[] buffer = new byte[1024];
+                int amountRead = 0;
+                while( (amountRead = ioStream.read( buffer, 0, buffer.length ) ) != -1 )
+                {
+                    byteArray.write( buffer, 0, amountRead );
+                }
+                data = byteArray.toString();
+            }
+            catch( IOException e )
+            {
+                e.printStackTrace();
+            }
+        }
+        else if( selectedNode instanceof MapEntry )
+        {
+            data = convertToString( ((MapEntry)selectedNode).getValue() );
+        }
+        else if( selectedNode instanceof ArrayEntry )
+        {
+            data = convertToString( ((ArrayEntry)selectedNode).getValue() );
+        }
+        return data;
+    }
+
+    private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt)
+    {
+        if( document != null )
+        {
+            try
+            {
+                document.close();
+            }
+            catch( IOException io )
+            {
+                io.printStackTrace();
+            }
+        }
+        System.exit(0);
+    }
+
+    /**
+     * Exit the Application.
+     */
+    private void exitForm(java.awt.event.WindowEvent evt)
+    {
+        if( document != null )
+        {
+            try
+            {
+                document.close();
+            }
+            catch( IOException io )
+            {
+                io.printStackTrace();
+            }
+        }
+        System.exit(0);
+    }
+
+    /**
+     * @param args the command line arguments
+     *
+     * @throws Exception If anything goes wrong.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        PDFDebugger viewer = new PDFDebugger();
+        if( args.length >0 )
+        {
+            viewer.readPDFFile( args[0] );
+        }
+        viewer.setVisible(true);
+    }
+
+    private void readPDFFile(String file) throws Exception
+    {
+        if( document != null )
+        {
+            document.close();
+        }
+        InputStream input = null;
+        File f = new File( file );
+        input = new FileInputStream(f);
+        document = parseDocument( input );
+        TreeModel model=new PDFTreeModel(document);
+        jTree1.setModel(model);
+        setTitle( "PDFBox - " + f.getAbsolutePath() );
+        /*
+        List pages = document.getDocumentCatalog().getAllPages();
+        for( int i=0; i<pages.size(); i++ )
+        {
+            PageWrapper wrapper = new PageWrapper();
+            wrapper.displayPage( (PDPage)pages.get(i) );
+            documentPanel.add( wrapper.getPanel() );
+        }*/
+    }
+        /**
+     * This will parse a document.
+     *
+     * @param input The input stream for the document.
+     *
+     * @return The document.
+     *
+     * @throws IOException If there is an error parsing the document.
+     */
+    private static PDDocument parseDocument( InputStream input )throws IOException
+    {
+        PDDocument document = PDDocument.load( input );
+        if( document.isEncrypted() )
+        {
+            try
+            {
+                document.decrypt( "" );
+            }
+            catch( InvalidPasswordException e )
+            {
+                System.err.println( "Error: The document is encrypted." );
+            }
+            catch( org.apache.pdfbox.exceptions.CryptographyException e )
+            {
+                e.printStackTrace();
+            }
+        }
+
+        return document;
+    }
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JMenuItem aboutMenuItem;
+    private javax.swing.JMenuItem contentsMenuItem;
+    private javax.swing.JMenuItem copyMenuItem;
+    private javax.swing.JMenuItem cutMenuItem;
+    private javax.swing.JMenuItem deleteMenuItem;
+    private javax.swing.JMenu editMenu;
+    private javax.swing.JMenuItem exitMenuItem;
+    private javax.swing.JMenu fileMenu;
+    private javax.swing.JMenu helpMenu;
+    private javax.swing.JScrollPane jScrollPane1;
+    private javax.swing.JScrollPane jScrollPane2;
+    private javax.swing.JSplitPane jSplitPane1;
+    private javax.swing.JTextPane jTextPane1;
+    private javax.swing.JTree jTree1;
+    private javax.swing.JMenuBar menuBar;
+    private javax.swing.JMenuItem openMenuItem;
+    private javax.swing.JMenuItem pasteMenuItem;
+    private javax.swing.JMenuItem saveAsMenuItem;
+    private javax.swing.JMenuItem saveMenuItem;
+    private JPanel documentPanel = new JPanel();
+    // End of variables declaration//GEN-END:variables
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFMerger.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFMerger.java
new file mode 100644 (file)
index 0000000..767c6bf
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox;
+
+import org.apache.pdfbox.util.PDFMergerUtility;
+
+/**
+ * This is the main program that will take a list of pdf documents and merge them,
+ * saving the result in a new document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFMerger
+{
+    
+    private PDFMerger()
+    {
+    }
+    /**
+     * Infamous main method.
+     *
+     * @param args Command line arguments, should be at least 3.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        PDFMerger merge = new PDFMerger();
+        merge.merge( args );
+    }
+
+    private void merge( String[] args ) throws Exception
+    {
+        String destinationFileName = "";
+        String sourceFileName = null;
+
+        if ( args.length < 3 )
+        {
+            usage();
+        }
+
+        PDFMergerUtility merger = new PDFMergerUtility();
+        for( int i=0; i<args.length-1; i++ )
+        {
+            sourceFileName = args[i];
+            merger.addSource(sourceFileName);
+        }
+
+        destinationFileName = args[args.length-1];
+
+        merger.setDestinationFileName(destinationFileName);
+
+        merger.mergeDocuments();
+    }
+
+    /**
+     * This will print the usage requirements and exit.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PDFMerger <Source PDF File 2..n> <Destination PDF File>\n" +
+            "  <Source PDF File 2..n>       2 or more source PDF documents to merge\n" +
+            "  <Destination PDF File>       The PDF document to save the merged documents to\n"
+            );
+        System.exit( 1 );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFReader.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFReader.java
new file mode 100644 (file)
index 0000000..fb8da6d
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ * 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.pdfbox;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdfviewer.PageWrapper;
+import org.apache.pdfbox.pdfviewer.ReaderBottomPanel;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.util.ExtensionFileFilter;
+
+import javax.swing.JFileChooser;
+import javax.swing.JScrollPane;
+import javax.swing.JPanel;
+import javax.swing.KeyStroke;
+
+import java.awt.print.PrinterException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * An application to read PDF documents.  This will provide Acrobat Reader like
+ * funtionality.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDFReader extends javax.swing.JFrame
+{
+    private File currentDir=new File(".");
+    private javax.swing.JMenuItem aboutMenuItem;
+    private javax.swing.JMenuItem contentsMenuItem;
+    private javax.swing.JMenuItem exitMenuItem;
+    private javax.swing.JMenu fileMenu;
+    private javax.swing.JMenu helpMenu;
+    private javax.swing.JMenuBar menuBar;
+    private javax.swing.JMenuItem openMenuItem;
+    private javax.swing.JMenuItem printMenuItem;
+    private javax.swing.JMenu viewMenu;
+    private javax.swing.JMenuItem nextPageItem;
+    private javax.swing.JMenuItem previousPageItem;
+    private JPanel documentPanel = new JPanel();
+    private ReaderBottomPanel bottomStatusPanel = new ReaderBottomPanel();
+
+    private PDDocument document = null;
+    private List pages= null;
+    
+    private int currentPage = 0;
+    private int numberOfPages = 0;
+    private String currentFilename = null;
+    
+    /**
+     * Constructor.
+     */
+    public PDFReader()
+    {
+        initComponents();
+    }
+
+    /**
+     * This method is called from within the constructor to
+     * initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is
+     * always regenerated by the Form Editor.
+     */
+    private void initComponents()
+    {
+        menuBar = new javax.swing.JMenuBar();
+        fileMenu = new javax.swing.JMenu();
+        openMenuItem = new javax.swing.JMenuItem();
+        exitMenuItem = new javax.swing.JMenuItem();
+        helpMenu = new javax.swing.JMenu();
+        contentsMenuItem = new javax.swing.JMenuItem();
+        aboutMenuItem = new javax.swing.JMenuItem();
+        printMenuItem = new javax.swing.JMenuItem();
+        viewMenu = new javax.swing.JMenu();
+        nextPageItem = new javax.swing.JMenuItem();
+        previousPageItem = new javax.swing.JMenuItem();
+
+
+        setTitle("PDFBox - PDF Reader");
+        addWindowListener(new java.awt.event.WindowAdapter()
+        {
+            public void windowClosing(java.awt.event.WindowEvent evt)
+            {
+                exitApplication();
+            }
+        });
+
+
+        JScrollPane documentScroller = new JScrollPane();
+        documentScroller.setViewportView( documentPanel );
+
+
+        getContentPane().add( documentScroller, java.awt.BorderLayout.CENTER );
+        getContentPane().add( bottomStatusPanel, java.awt.BorderLayout.SOUTH );
+
+        fileMenu.setText("File");
+        openMenuItem.setText("Open");
+        openMenuItem.setToolTipText("Open PDF file");
+        openMenuItem.addActionListener(new java.awt.event.ActionListener()
+        {
+            public void actionPerformed(java.awt.event.ActionEvent evt)
+            {
+                openMenuItemActionPerformed(evt);
+            }
+        });
+
+        fileMenu.add(openMenuItem);
+
+        printMenuItem.setText( "Print" );
+        printMenuItem.addActionListener(new java.awt.event.ActionListener()
+        {
+            public void actionPerformed(java.awt.event.ActionEvent evt)
+            {
+                try
+                {
+                    if (document != null) 
+                    {
+                        document.print();
+                    }
+                }
+                catch( PrinterException e )
+                {
+                    e.printStackTrace();
+                }
+            }
+        });
+        fileMenu.add( printMenuItem );
+
+        exitMenuItem.setText("Exit");
+        exitMenuItem.addActionListener(new java.awt.event.ActionListener()
+        {
+            public void actionPerformed(java.awt.event.ActionEvent evt)
+            {
+                exitApplication();
+            }
+        });
+
+        fileMenu.add(exitMenuItem);
+
+        menuBar.add(fileMenu);
+
+        helpMenu.setText("Help");
+        contentsMenuItem.setText("Contents");
+        helpMenu.add(contentsMenuItem);
+
+        aboutMenuItem.setText("About");
+        helpMenu.add(aboutMenuItem);
+
+        //menuBar.add(helpMenu);
+
+        viewMenu.setText("View");
+        nextPageItem.setText("Next page");
+        nextPageItem.setAccelerator(KeyStroke.getKeyStroke('+'));
+        nextPageItem.addActionListener(new java.awt.event.ActionListener()
+        {
+            public void actionPerformed(java.awt.event.ActionEvent evt)
+            {
+                nextPage();
+            }
+        });
+        viewMenu.add(nextPageItem);
+
+        previousPageItem.setText("Previous page");
+        previousPageItem.setAccelerator(KeyStroke.getKeyStroke('-'));
+        previousPageItem.addActionListener(new java.awt.event.ActionListener()
+        {
+            public void actionPerformed(java.awt.event.ActionEvent evt)
+            {
+                previousPage();
+            }
+        });
+        viewMenu.add(previousPageItem);
+
+        menuBar.add(viewMenu);
+
+        setJMenuBar(menuBar);
+
+
+        java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
+        setBounds((screenSize.width-700)/2, (screenSize.height-600)/2, 700, 600);
+    }
+
+
+    private void updateTitle() {
+        setTitle( "PDFBox - " + currentFilename + " ("+(currentPage+1)+"/"+numberOfPages+")");
+    }
+    
+    private void nextPage()
+    {
+        if (currentPage < numberOfPages-1) 
+        {
+            currentPage++;
+            updateTitle();
+            showPage(currentPage);
+        }
+    }
+    
+    private void previousPage()
+    {
+        if (currentPage > 0 ) 
+        {
+            currentPage--;
+            updateTitle();
+            showPage(currentPage);
+        }
+    }
+
+    private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt)
+    {
+        JFileChooser chooser = new JFileChooser();
+        chooser.setCurrentDirectory(currentDir);
+
+        ExtensionFileFilter pdfFilter = new ExtensionFileFilter(new String[] {"PDF"}, "PDF Files");
+        chooser.setFileFilter(pdfFilter);
+        int result = chooser.showOpenDialog(PDFReader.this);
+        if (result == JFileChooser.APPROVE_OPTION)
+        {
+            String name = chooser.getSelectedFile().getPath();
+            currentDir = new File(name).getParentFile();
+            try
+            {
+                openPDFFile(name);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void exitApplication()
+    {
+        try
+        {
+            if( document != null )
+            {
+                document.close();
+            }
+        }
+        catch( IOException io )
+        {
+            //do nothing because we are closing the application
+        }
+        this.setVisible( false );
+        this.dispose();
+    }
+
+    /**
+     * @param args the command line arguments
+     *
+     * @throws Exception If anything goes wrong.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        PDFReader viewer = new PDFReader();
+        if( args.length >0 )
+        {
+            viewer.openPDFFile( args[0] );
+        }
+        viewer.setVisible(true);
+    }
+
+    private void openPDFFile(String file) throws Exception
+    {
+        if( document != null )
+        {
+            document.close();
+            documentPanel.removeAll();
+        }
+        InputStream input = null;
+        File f = new File( file );
+        input = new FileInputStream(f);
+        document = parseDocument( input );
+        pages = document.getDocumentCatalog().getAllPages();
+        numberOfPages = pages.size();
+        currentFilename = f.getAbsolutePath();
+        currentPage = 0;
+        updateTitle();
+        showPage(0);
+    }
+    
+    private void showPage(int pageNumber)
+    {
+        try 
+        {
+            PageWrapper wrapper = new PageWrapper( this );
+            wrapper.displayPage( (PDPage)pages.get(pageNumber) );
+            if (documentPanel.getComponentCount() > 0)
+            {
+                documentPanel.remove(0);
+            }
+            documentPanel.add( wrapper.getPanel() );
+            pack();
+        }
+        catch (IOException exception)
+        {
+            exception.printStackTrace();
+        }
+    }
+    /**
+     * This will parse a document.
+     *
+     * @param input The input stream for the document.
+     *
+     * @return The document.
+     *
+     * @throws IOException If there is an error parsing the document.
+     */
+    private static PDDocument parseDocument( InputStream input )throws IOException
+    {
+        PDDocument document = PDDocument.load( input );
+        if( document.isEncrypted() )
+        {
+            try
+            {
+                document.decrypt( "" );
+            }
+            catch( InvalidPasswordException e )
+            {
+                System.err.println( "Error: The document is encrypted." );
+            }
+            catch( org.apache.pdfbox.exceptions.CryptographyException e )
+            {
+                e.printStackTrace();
+            }
+        }
+
+        return document;
+    }
+
+    /**
+     * Get the bottom status panel.
+     *
+     * @return The bottom status panel.
+     */
+    public ReaderBottomPanel getBottomStatusPanel()
+    {
+        return bottomStatusPanel;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFSplit.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFSplit.java
new file mode 100644 (file)
index 0000000..a746f05
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * 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.pdfbox;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+import org.apache.pdfbox.util.Splitter;
+
+/**
+ * This is the main program that will take a pdf document and split it into
+ * a number of other documents.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFSplit
+{
+    private static final String PASSWORD = "-password";
+    private static final String SPLIT = "-split";
+
+    private PDFSplit()
+    {
+    }
+    /**
+     * Infamous main method.
+     *
+     * @param args Command line arguments, should be one and a reference to a file.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        PDFSplit split = new PDFSplit();
+        split.split( args );
+    }
+
+    private void split( String[] args ) throws Exception
+    {
+        String password = "";
+        String split = "1";
+
+        Splitter splitter = new Splitter();
+        String pdfFile = null;
+        for( int i=0; i<args.length; i++ )
+        {
+            if( args[i].equals( PASSWORD ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                password = args[i];
+            }
+            else if( args[i].equals( SPLIT ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                split = args[i];
+            }
+            else
+            {
+                if( pdfFile == null )
+                {
+                    pdfFile = args[i];
+                }
+            }
+        }
+
+        if( pdfFile == null )
+        {
+            usage();
+        }
+        else
+        {
+
+            InputStream input = null;
+            PDDocument document = null;
+            List documents = null;
+            try
+            {
+                input = new FileInputStream( pdfFile );
+                document = parseDocument( input );
+
+                if( document.isEncrypted() )
+                {
+                    try
+                    {
+                        document.decrypt( password );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        if( args.length == 4 )//they supplied the wrong password
+                        {
+                            System.err.println( "Error: The supplied password is incorrect." );
+                            System.exit( 2 );
+                        }
+                        else
+                        {
+                            //they didn't suppply a password and the default of "" was wrong.
+                            System.err.println( "Error: The document is encrypted." );
+                            usage();
+                        }
+                    }
+                }
+
+                splitter.setSplitAtPage( Integer.parseInt( split ) );
+                documents = splitter.split( document );
+                for( int i=0; i<documents.size(); i++ )
+                {
+                    PDDocument doc = (PDDocument)documents.get( i );
+                    String fileName = pdfFile.substring(0, pdfFile.length()-4 ) + "-" + i + ".pdf";
+                    writeDocument( doc, fileName );
+                    doc.close();
+                }
+
+            }
+            finally
+            {
+                if( input != null )
+                {
+                    input.close();
+                }
+                if( document != null )
+                {
+                    document.close();
+                }
+                for( int i=0; documents != null && i<documents.size(); i++ )
+                {
+                    PDDocument doc = (PDDocument)documents.get( i );
+                    doc.close();
+                }
+            }
+        }
+    }
+
+    private static final void writeDocument( PDDocument doc, String fileName ) throws IOException, COSVisitorException
+    {
+        FileOutputStream output = null;
+        COSWriter writer = null;
+        try
+        {
+            output = new FileOutputStream( fileName );
+            writer = new COSWriter( output );
+            writer.write( doc );
+        }
+        finally
+        {
+            if( output != null )
+            {
+                output.close();
+            }
+            if( writer != null )
+            {
+                writer.close();
+            }
+        }
+    }
+
+    /**
+     * This will parse a document.
+     *
+     * @param input The input stream for the document.
+     *
+     * @return The document.
+     *
+     * @throws IOException If there is an error parsing the document.
+     */
+    private static PDDocument parseDocument( InputStream input )throws IOException
+    {
+        PDFParser parser = new PDFParser( input );
+        parser.parse();
+        return parser.getPDDocument();
+    }
+
+    /**
+     * This will print the usage requirements and exit.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PDFSplit [OPTIONS] <PDF file>\n" +
+            "  -password  <password>        Password to decrypt document\n" +
+            "  -split     <integer>         split after this many pages\n" +
+            "  <PDF file>                   The PDF document to use\n"
+            );
+        System.exit( 1 );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFToImage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PDFToImage.java
new file mode 100644 (file)
index 0000000..c14da54
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * 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.pdfbox;
+
+import java.awt.HeadlessException;
+import java.awt.Toolkit;
+import java.awt.image.BufferedImage;
+
+import javax.imageio.ImageIO;
+
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.util.PDFImageWriter;
+
+/**
+ * Convert a PDF document to an image.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFToImage
+{
+
+    private static final String PASSWORD = "-password";
+    private static final String START_PAGE = "-startPage";
+    private static final String END_PAGE = "-endPage";
+    private static final String IMAGE_FORMAT = "-imageType";
+    private static final String OUTPUT_PREFIX = "-outputPrefix";
+    private static final String COLOR = "-color";
+    private static final String RESOLUTION = "-resolution";
+    private static final String CROPBOX = "-cropbox";
+
+    /**
+     * private constructor.
+    */
+    private PDFToImage()
+    {
+        //static class
+    }
+
+    /**
+     * Infamous main method.
+     *
+     * @param args Command line arguments, should be one and a reference to a file.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        String password = "";
+        String pdfFile = null;
+        String outputPrefix = null;
+        String imageFormat = "jpg";
+        int startPage = 1;
+        int endPage = Integer.MAX_VALUE;
+        String color = "rgb";
+        int resolution;
+        float cropBoxLowerLeftX = 0;
+        float cropBoxLowerLeftY = 0;
+        float cropBoxUpperRightX = 0;
+           float cropBoxUpperRightY = 0;
+        try
+        {
+            resolution = Toolkit.getDefaultToolkit().getScreenResolution();
+        }
+        catch( HeadlessException e )
+        {
+            resolution = 96;
+        }
+        for( int i = 0; i < args.length; i++ )
+        {
+            if( args[i].equals( PASSWORD ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                password = args[i];
+            }
+            else if( args[i].equals( START_PAGE ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                startPage = Integer.parseInt( args[i] );
+            }
+            else if( args[i].equals( END_PAGE ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                endPage = Integer.parseInt( args[i] );
+            }
+            else if( args[i].equals( IMAGE_FORMAT ) )
+            {
+                i++;
+                imageFormat = args[i];
+            }
+            else if( args[i].equals( OUTPUT_PREFIX ) )
+            {
+                i++;
+                outputPrefix = args[i];
+            }
+            else if( args[i].equals( COLOR ) )
+            {
+                i++;
+                color = args[i];
+            }
+            else if( args[i].equals( RESOLUTION ) )
+            {
+                i++;
+                resolution = Integer.parseInt(args[i]);
+            }
+            else if( args[i].equals( CROPBOX ) )
+            {
+                i++;
+               cropBoxLowerLeftX = Float.valueOf(args[i]).floatValue();
+                i++;
+               cropBoxLowerLeftY = Float.valueOf(args[i]).floatValue();
+                i++;
+               cropBoxUpperRightX = Float.valueOf(args[i]).floatValue();
+                i++;
+               cropBoxUpperRightY = Float.valueOf(args[i]).floatValue();
+            }
+            else
+            {
+                if( pdfFile == null )
+                {
+                    pdfFile = args[i];
+                }
+            }
+        }
+        if( pdfFile == null )
+        {
+            usage();
+        }
+        else
+        {
+            if(outputPrefix == null)
+            {
+                outputPrefix = pdfFile.substring( 0, pdfFile.lastIndexOf( '.' ));
+            }
+
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( pdfFile );
+
+
+                //document.print();
+                if( document.isEncrypted() )
+                {
+                    try
+                    {
+                        document.decrypt( password );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        if( args.length == 4 )//they supplied the wrong password
+                        {
+                            System.err.println( "Error: The supplied password is incorrect." );
+                            System.exit( 2 );
+                        }
+                        else
+                        {
+                            //they didn't supply a password and the default of "" was wrong.
+                            System.err.println( "Error: The document is encrypted." );
+                            usage();
+                        }
+                    }
+                }
+                int imageType = 24;
+                if ("bilevel".equalsIgnoreCase(color))
+                {
+                    imageType = BufferedImage.TYPE_BYTE_BINARY;
+                }
+                else if ("indexed".equalsIgnoreCase(color))
+                {
+                    imageType = BufferedImage.TYPE_BYTE_INDEXED;
+                }
+                else if ("gray".equalsIgnoreCase(color))
+                {
+                    imageType = BufferedImage.TYPE_BYTE_GRAY;
+                }
+                else if ("rgb".equalsIgnoreCase(color))
+                {
+                    imageType = BufferedImage.TYPE_INT_RGB;
+                }
+                else if ("rgba".equalsIgnoreCase(color))
+                {
+                    imageType = BufferedImage.TYPE_INT_ARGB;
+                }
+                else
+                {
+                    System.err.println( "Error: the number of bits per pixel must be 1, 8 or 24." );
+                    System.exit( 2 );
+                }
+
+                //si une cropBox a ete specifier, appeler la methode de modification de cropbox
+               //changeCropBoxes(PDDocument document,float a, float b, float c,float d)
+               if ( cropBoxLowerLeftX!=0 || cropBoxLowerLeftY!=0 || cropBoxUpperRightX!=0 || cropBoxUpperRightY!=0 )
+               {
+                 changeCropBoxes(document,cropBoxLowerLeftX, cropBoxLowerLeftY, cropBoxUpperRightX, cropBoxUpperRightY);
+               }
+
+                //Make the call
+                PDFImageWriter imageWriter = new PDFImageWriter();
+                boolean success = imageWriter.writeImage(document, imageFormat, password,
+                        startPage, endPage, outputPrefix, imageType, resolution);
+                if (!success)
+                {
+                    System.err.println( "Error: no writer found for image format '"
+                            + imageFormat + "'" );
+                    System.exit(1);
+                }
+            }
+            catch (Exception e)
+            {
+                System.err.println(e);
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage requirements and exit.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PDFToImage [OPTIONS] <PDF file>\n" +
+            "  -password  <password>          Password to decrypt document\n" +
+            "  -imageType <image type>        (" + getImageFormats() + ")\n" +
+            "  -outputPrefix <output prefix>  Filename prefix for image files\n" +
+            "  -startPage <number>            The first page to start extraction(1 based)\n" +
+            "  -endPage <number>              The last page to extract(inclusive)\n" +
+            "  -color <string>                The color depth (valid: bilevel, indexed, gray, rgb, rgba)\n" +
+            "  -resolution <number>           The bitmap resolution in dpi\n" +
+            "  -cropbox <number> <number> <number> <number> The page area to export\n" +
+            "  <PDF file>                     The PDF document to use\n"
+            );
+        System.exit( 1 );
+    }
+
+    private static String getImageFormats()
+    {
+        StringBuffer retval = new StringBuffer();
+        String[] formats = ImageIO.getReaderFormatNames();
+        for( int i = 0; i < formats.length; i++ )
+        {
+            retval.append( formats[i] );
+            if( i + 1 < formats.length )
+            {
+                retval.append( "," );
+            }
+        }
+        return retval.toString();
+    }
+
+    private static void changeCropBoxes(PDDocument document,float a, float b, float c,float d)
+    {
+      List pages = document.getDocumentCatalog().getAllPages();
+      for( int i = 0; i < pages.size(); i++ )
+      {
+              System.out.println("resizing page");
+             PDPage page = (PDPage)pages.get( i );
+             PDRectangle rectangle = new PDRectangle();
+              rectangle.setLowerLeftX(a);
+              rectangle.setLowerLeftY(b);
+              rectangle.setUpperRightX(c);
+              rectangle.setUpperRightY(d);
+             page.setMediaBox(rectangle);
+             page.setCropBox(rectangle);
+
+      }
+
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PdfDecompressor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PdfDecompressor.java
new file mode 100644 (file)
index 0000000..c2a353c
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ *  Copyright 2010 adam.
+ * 
+ *  Licensed 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.
+ *  under the License.
+ */
+
+package org.apache.pdfbox;
+
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdfparser.PDFObjectStreamParser;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This program will just take all of the stream objects in a PDF and dereference
+ * them.  The streams will be gone in the resulting file and the objects will be
+ * present.  This is very helpful when trying to debug problems as it'll make
+ * it possible to easily look through a PDF using a text editor.  It also exposes
+ * problems which stem from objects inside object streams overwriting other
+ * objects.
+ * @author <a href="adam@apache.org">Adam Nichols</a>
+ */
+public class PdfDecompressor {
+
+    /**
+     * This is a very simple program, so everything is in the main method.
+     * @param args arguments to the program
+     */
+    public static void main(String[] args) {
+        if(args.length < 1)
+            usage();
+
+        String inputFilename = args[0];
+        String outputFilename;
+        if(args.length > 1) {
+            outputFilename = args[1];
+        } else {
+            if(inputFilename.matches(".*\\.[pP][dD][fF]$"))
+                outputFilename = inputFilename.replaceAll("\\.[pP][dD][fF]$", ".unc.pdf");
+            else
+                outputFilename = inputFilename + ".unc.pdf";
+        }
+
+        PDDocument doc = null;
+        try {
+            doc = PDDocument.load(inputFilename);
+            for(COSObject objStream : doc.getDocument().getObjectsByType("ObjStm")) {
+                COSStream stream = (COSStream)objStream.getObject();
+                PDFObjectStreamParser sp = new PDFObjectStreamParser(stream, doc.getDocument());
+                sp.parse();
+                for(COSObject next : sp.getObjects()) {
+                    COSObjectKey key = new COSObjectKey(next);
+                    COSObject obj = doc.getDocument().getObjectFromPool(key);
+                    obj.setObject(next.getObject());
+                }
+                doc.getDocument().removeObject(new COSObjectKey(objStream));
+            }
+            doc.save(outputFilename);
+        } catch(Exception e) {
+            System.out.println("Error processing file: " + e.getMessage());
+        } finally {
+            if(doc != null)
+                try { doc.close(); } catch(Exception e) { }
+        }
+    }
+
+    /**
+     * Explains how to use the program.
+     */
+    private static void usage() {
+        System.err.println( "Usage: java -cp /path/to/pdfbox.jar;/path/to/commons-logging-api.jar "
+                + "org.apache.pdfbox.PdfDecompressor <input PDF File> [<Output PDF File>]\n"
+                + "  <input PDF File>       The PDF document to decompress\n"
+                + "  <output PDF File>      The output filename (default is to replace .pdf with .unc.pdf)");
+        System.exit(1);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/PrintPDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/PrintPDF.java
new file mode 100644 (file)
index 0000000..0cfacb1
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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.pdfbox;
+
+import java.awt.print.PrinterJob;
+
+import javax.print.PrintService;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import java.io.File;
+
+/**
+ * This is a command line program that will print a PDF document.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PrintPDF
+{
+
+    private static final String PASSWORD     = "-password";
+    private static final String SILENT       = "-silentPrint";
+    private static final String PRINTER_NAME = "-printerName";
+
+    /**
+     * private constructor.
+    */
+    private PrintPDF()
+    {
+        //static class
+    }
+
+    /**
+     * Infamous main method.
+     *
+     * @param args Command line arguments, should be one and a reference to a file.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        String password = "";
+        String pdfFile = null;
+        boolean silentPrint = false;
+        String printerName = null;
+        for( int i=0; i<args.length; i++ )
+        {
+            if( args[i].equals( PASSWORD ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                password = args[i];
+            }
+            else if( args[i].equals( PRINTER_NAME ) )
+            {
+                i++;
+                if( i >= args.length )
+                {
+                    usage();
+                }
+                printerName = args[i];
+            }
+            else if( args[i].equals( SILENT ) )
+            {
+                silentPrint = true;
+            }
+            else
+            {
+                pdfFile = args[i];
+            }
+        }
+
+        if( pdfFile == null )
+        {
+            usage();
+        }
+
+        PDDocument document = null;
+        try
+        {
+            document = PDDocument.load( pdfFile );
+
+            if( document.isEncrypted() )
+            {
+                document.decrypt( password );
+            }
+
+            PrinterJob printJob = PrinterJob.getPrinterJob();
+            printJob.setJobName(new File(pdfFile).getName());
+
+            if(printerName != null )
+            {
+                PrintService[] printService = PrinterJob.lookupPrintServices();
+                boolean printerFound = false;
+                for(int i = 0; !printerFound && i < printService.length; i++)
+                {
+                    if(printService[i].getName().indexOf(printerName) != -1)
+                    {
+                        printJob.setPrintService(printService[i]);
+                        printerFound = true;
+                    }
+                }
+            }
+
+            if( silentPrint )
+            {
+                document.silentPrint( printJob );
+            }
+            else
+            {
+                document.print( printJob );
+            }
+        }
+        finally
+        {
+            if( document != null )
+            {
+                document.close();
+            }
+        }
+    }
+
+    /**
+     * This will print the usage requirements and exit.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PrintPDF [OPTIONS] <PDF file>\n" +
+            "  -password  <password>        Password to decrypt document\n" +
+            "  -silentPrint                 Print without prompting for printer info\n"
+            );
+        System.exit( 1 );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/TextToPDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/TextToPDF.java
new file mode 100644 (file)
index 0000000..767ecf8
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * 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.pdfbox;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
+import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This will take a text file and ouput a pdf with that text.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class TextToPDF
+{
+    private int fontSize = 10;
+    private PDSimpleFont font = PDType1Font.HELVETICA;
+
+    /**
+     * Create a PDF document with some text.
+     *
+     * @param text The stream of text data.
+     *
+     * @return The document with the text in it.
+     *
+     * @throws IOException If there is an error writing the data.
+     */
+    public PDDocument createPDFFromText( Reader text ) throws IOException
+    {
+        PDDocument doc = null;
+        try
+        {
+               
+            final int margin = 40;
+            float height = font.getFontDescriptor().getFontBoundingBox().getHeight()/1000;
+
+            //calculate font height and increase by 5 percent.
+            height = height*fontSize*1.05f;
+            doc = new PDDocument();
+            BufferedReader data = new BufferedReader( text );
+            String nextLine = null;
+            PDPage page = new PDPage();
+            PDPageContentStream contentStream = null;
+            float y = -1;
+            float maxStringLength = page.getMediaBox().getWidth() - 2*margin;
+            
+            // There is a special case of creating a PDF document from an empty string.
+            boolean textIsEmpty = true;
+            
+            while( (nextLine = data.readLine()) != null )
+            {
+               
+               // The input text is nonEmpty. New pages will be created and added
+               // to the PDF document as they are needed, depending on the length of
+               // the text.
+               textIsEmpty = false;
+
+                String[] lineWords = nextLine.trim().split( " " );
+                int lineIndex = 0;
+                while( lineIndex < lineWords.length )
+                {
+                    StringBuffer nextLineToDraw = new StringBuffer();
+                    float lengthIfUsingNextWord = 0;
+                    do
+                    {
+                        nextLineToDraw.append( lineWords[lineIndex] );
+                        nextLineToDraw.append( " " );
+                        lineIndex++;
+                        if( lineIndex < lineWords.length )
+                        {
+                            String lineWithNextWord = nextLineToDraw.toString() + lineWords[lineIndex];
+                            lengthIfUsingNextWord =
+                                (font.getStringWidth( lineWithNextWord )/1000) * fontSize;
+                        }
+                    }
+                    while( lineIndex < lineWords.length &&
+                           lengthIfUsingNextWord < maxStringLength );
+                    if( y < margin )
+                    {
+                       // We have crossed the end-of-page boundary and need to extend the
+                       // document by another page.
+                        page = new PDPage();
+                        doc.addPage( page );
+                        if( contentStream != null )
+                        {
+                            contentStream.endText();
+                            contentStream.close();
+                        }
+                        contentStream = new PDPageContentStream(doc, page);
+                        contentStream.setFont( font, fontSize );
+                        contentStream.beginText();
+                        y = page.getMediaBox().getHeight() - margin + height;
+                        contentStream.moveTextPositionByAmount(
+                            margin, y );
+
+                    }
+                    //System.out.println( "Drawing string at " + x + "," + y );
+
+                    if( contentStream == null )
+                    {
+                        throw new IOException( "Error:Expected non-null content stream." );
+                    }
+                    contentStream.moveTextPositionByAmount( 0, -height);
+                    y -= height;
+                    contentStream.drawString( nextLineToDraw.toString() );
+                }
+
+
+            }
+            
+            // If the input text was the empty string, then the above while loop will have short-circuited
+            // and we will not have added any PDPages to the document.
+            // So in order to make the resultant PDF document readable by Adobe Reader etc, we'll add an empty page.
+            if (textIsEmpty)
+            {
+               doc.addPage(page);
+            }
+            
+            if( contentStream != null )
+            {
+                contentStream.endText();
+                contentStream.close();
+            }
+        }
+        catch( IOException io )
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+            throw io;
+        }
+        return doc;
+    }
+
+    /**
+     * This will create a PDF document with some text in it.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     *
+     * @throws IOException If there is an error with the PDF.
+     */
+    public static void main(String[] args) throws IOException
+    {
+        TextToPDF app = new TextToPDF();
+        PDDocument doc = null;
+        try
+        {
+            if( args.length < 2 )
+            {
+                app.usage();
+            }
+            else
+            {
+                for( int i=0; i<args.length-2; i++ )
+                {
+                    if( args[i].equals( "-standardFont" ))
+                    {
+                        i++;
+                        app.setFont( PDType1Font.getStandardFont( args[i] ));
+                    }
+                    else if( args[i].equals( "-ttf" ))
+                    {
+                        i++;
+                        PDTrueTypeFont font = PDTrueTypeFont.loadTTF( doc, new File( args[i]));
+                        app.setFont( font );
+                    }
+                    else if( args[i].equals( "-fontSize" ))
+                    {
+                        i++;
+                        app.setFontSize( Integer.parseInt( args[i] ) );
+                    }
+                    else
+                    {
+                        throw new IOException( "Unknown argument:" + args[i] );
+                    }
+                }
+                doc = app.createPDFFromText( new FileReader( args[args.length-1] ) );
+                doc.save( args[args.length-2] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        String[] std14 = PDType1Font.getStandard14Names();
+        System.err.println( "usage: jar -jar pdfbox-app-x.y.z.jar TextToPDF [options] <output-file> <text-file>" );
+        System.err.println( "    -standardFont <name>    default:" + PDType1Font.HELVETICA.getBaseFont() );
+        for( int i=0; i<std14.length; i++ )
+        {
+            System.err.println( "                                    " + std14[i] );
+        }
+        System.err.println( "    -ttf <ttf file>         The TTF font to use.");
+        System.err.println( "    -fontSize <fontSize>    default:10" );
+
+
+    }
+    /**
+     * @return Returns the font.
+     */
+    public PDSimpleFont getFont()
+    {
+        return font;
+    }
+    /**
+     * @param aFont The font to set.
+     */
+    public void setFont(PDSimpleFont aFont)
+    {
+        this.font = aFont;
+    }
+    /**
+     * @return Returns the fontSize.
+     */
+    public int getFontSize()
+    {
+        return fontSize;
+    }
+    /**
+     * @param aFontSize The fontSize to set.
+     */
+    public void setFontSize(int aFontSize)
+    {
+        this.fontSize = aFontSize;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/Version.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/Version.java
new file mode 100644 (file)
index 0000000..6234de8
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.pdfbox;
+
+import java.io.IOException;
+
+import java.util.Properties;
+
+import org.apache.pdfbox.util.ResourceLoader;
+
+
+/**
+ * A simple command line utility to get the version of PDFBox.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class Version
+{
+    private static final String PDFBOX_VERSION_PROPERTIES =
+        "org/apache/pdfbox/resources/pdfbox.properties";
+
+    private Version()
+    {
+        //should not be constructed.
+    }
+
+    /**
+     * Get the version of PDFBox or unknown if it is not known.
+     *
+     * @return The version of pdfbox that is being used.
+     */
+    public static String getVersion()
+    {
+        String version = "unknown";
+        try
+        {
+            Properties props = ResourceLoader.loadProperties( PDFBOX_VERSION_PROPERTIES, false );
+            version = props.getProperty( "pdfbox.version", version );
+        }
+        catch( IOException io )
+        {
+            //if there is a problem loading the properties then don't throw an
+            //exception, 'unknown' will be returned instead.
+            io.printStackTrace();
+        }
+        return version;
+    }
+
+    /**
+     * This will print out the version of PDF to System.out.
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        if( args.length != 0 )
+        {
+            usage();
+            return;
+        }
+        System.out.println( "Version:" + getVersion() );
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: " + Version.class.getName() );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/WriteDecodedDoc.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/WriteDecodedDoc.java
new file mode 100644 (file)
index 0000000..0b33a23
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * 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.pdfbox;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+/**
+ * load document and write with all streams decoded.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.8 $
+ */
+public class WriteDecodedDoc
+{
+
+    /**
+     * Constructor.
+     */
+    public WriteDecodedDoc()
+    {
+        super();
+    }
+
+    /**
+     * This will perform the document reading, decoding and writing.
+     *
+     * @param in The filename used for input.
+     * @param out The filename used for output.
+     *
+     * @throws IOException If there is an error parsing the document.
+     * @throws COSVisitorException If there is an error while copying the document.
+     */
+    public void doIt(String in, String out) throws IOException, COSVisitorException
+    {
+        PDDocument doc = null;
+        try
+        {
+            doc = PDDocument.load( in );
+            if( doc.isEncrypted() )
+            {
+                try
+                {
+                    doc.decrypt( "" );
+                }
+                catch( InvalidPasswordException e )
+                {
+                    System.err.println( "Error: The document is encrypted." );
+                }
+                catch( org.apache.pdfbox.exceptions.CryptographyException e )
+                {
+                    e.printStackTrace();
+                }
+            }
+
+            for (Iterator<COSObject> i = doc.getDocument().getObjects().iterator(); i.hasNext();)
+            {
+                COSBase base = ((COSObject) i.next()).getObject();
+                if (base instanceof COSStream)
+                {
+                    // just kill the filters
+                    COSStream cosStream = (COSStream)base;
+                    cosStream.getUnfilteredStream();
+                    cosStream.setFilters(null);
+                }
+            }
+            doc.save( out );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will write a PDF document with completely decoded streams.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     */
+    public static void main(String[] args)
+    {
+        WriteDecodedDoc app = new WriteDecodedDoc();
+        try
+        {
+            if( args.length != 2 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0], args[1]);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar WriteDecodedDoc <input-file> <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSArray.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSArray.java
new file mode 100644 (file)
index 0000000..9f942b6
--- /dev/null
@@ -0,0 +1,556 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * An array of PDFBase objects as part of the PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.24 $
+ */
+public class COSArray extends COSBase implements Iterable<COSBase>
+{
+    private List<COSBase> objects = new ArrayList<COSBase>();
+
+    /**
+     * Constructor.
+     */
+    public COSArray()
+    {
+        //default constructor
+    }
+
+    /**
+     * This will add an object to the array.
+     *
+     * @param object The object to add to the array.
+     */
+    public void add( COSBase object )
+    {
+        objects.add( object );
+    }
+
+    /**
+     * This will add an object to the array.
+     *
+     * @param object The object to add to the array.
+     */
+    public void add( COSObjectable object )
+    {
+        objects.add( object.getCOSObject() );
+    }
+
+    /**
+     * Add the specified object at the ith location and push the rest to the
+     * right.
+     *
+     * @param i The index to add at.
+     * @param object The object to add at that index.
+     */
+    public void add( int i, COSBase object)
+    {
+        objects.add( i, object );
+    }
+
+    /**
+     * This will remove all of the objects in the collection.
+     */
+    public void clear()
+    {
+        objects.clear();
+    }
+
+    /**
+     * This will remove all of the objects in the collection.
+     *
+     * @param objectsList The list of objects to remove from the collection.
+     */
+    public void removeAll( Collection<COSBase> objectsList )
+    {
+        objects.removeAll( objectsList );
+    }
+
+    /**
+     * This will retain all of the objects in the collection.
+     *
+     * @param objectsList The list of objects to retain from the collection.
+     */
+    public void retainAll( Collection<COSBase> objectsList )
+    {
+        objects.retainAll( objectsList );
+    }
+
+    /**
+     * This will add an object to the array.
+     *
+     * @param objectsList The object to add to the array.
+     */
+    public void addAll( Collection<COSBase> objectsList )
+    {
+        objects.addAll( objectsList );
+    }
+
+    /**
+     * This will add all objects to this array.
+     *
+     * @param objectList The objects to add.
+     */
+    public void addAll( COSArray objectList )
+    {
+        if( objectList != null )
+        {
+            objects.addAll( objectList.objects );
+        }
+    }
+
+    /**
+     * Add the specified object at the ith location and push the rest to the
+     * right.
+     *
+     * @param i The index to add at.
+     * @param objectList The object to add at that index.
+     */
+    public void addAll( int i, Collection<COSBase> objectList )
+    {
+        objects.addAll( i, objectList );
+    }
+
+    /**
+     * This will set an object at a specific index.
+     *
+     * @param index zero based index into array.
+     * @param object The object to set.
+     */
+    public void set( int index, COSBase object )
+    {
+        objects.set( index, object );
+    }
+
+    /**
+     * This will set an object at a specific index.
+     *
+     * @param index zero based index into array.
+     * @param intVal The object to set.
+     */
+    public void set( int index, int intVal )
+    {
+        objects.set( index, COSInteger.get(intVal) );
+    }
+
+    /**
+     * This will set an object at a specific index.
+     *
+     * @param index zero based index into array.
+     * @param object The object to set.
+     */
+    public void set( int index, COSObjectable object )
+    {
+        COSBase base = null;
+        if( object != null )
+        {
+            base = object.getCOSObject();
+        }
+        objects.set( index, base );
+    }
+
+    /**
+     * This will get an object from the array.  This will dereference the object.
+     * If the object is COSNull then null will be returned.
+     *
+     * @param index The index into the array to get the object.
+     *
+     * @return The object at the requested index.
+     */
+    public COSBase getObject( int index )
+    {
+        Object obj = objects.get( index );
+        if( obj instanceof COSObject )
+        {
+            obj = ((COSObject)obj).getObject();
+        }
+        else if( obj instanceof COSNull )
+        {
+            obj = null;
+        }
+        return (COSBase)obj;
+    }
+
+    /**
+     * This will get an object from the array.  This will NOT derefernce
+     * the COS object.
+     *
+     * @param index The index into the array to get the object.
+     *
+     * @return The object at the requested index.
+     */
+    public COSBase get( int index )
+    {
+        return objects.get( index );
+    }
+
+    /**
+     * Get the value of the array as an integer.
+     *
+     * @param index The index into the list.
+     *
+     * @return The value at that index or -1 if it is null.
+     */
+    public int getInt( int index )
+    {
+        return getInt( index, -1 );
+    }
+
+    /**
+     * Get the value of the array as an integer, return the default if it does
+     * not exist.
+     *
+     * @param index The value of the array.
+     * @param defaultValue The value to return if the value is null.
+     * @return The value at the index or the defaultValue.
+     */
+    public int getInt( int index, int defaultValue )
+    {
+        int retval = defaultValue;
+        if ( index < size() )
+        {
+            Object obj = objects.get( index );
+            if( obj instanceof COSNumber )
+            {
+                retval = ((COSNumber)obj).intValue();
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Set the value in the array as an integer.
+     *
+     * @param index The index into the array.
+     * @param value The value to set.
+     */
+    public void setInt( int index, int value )
+    {
+        set( index, COSInteger.get( value ) );
+    }
+
+    /**
+     * Set the value in the array as a name.
+     * @param index The index into the array.
+     * @param name The name to set in the array.
+     */
+    public void setName( int index, String name )
+    {
+        set( index, COSName.getPDFName( name ) );
+    }
+
+    /**
+     * Get the value of the array as a string.
+     *
+     * @param index The index into the array.
+     * @return The name converted to a string or null if it does not exist.
+     */
+    public String getName( int index )
+    {
+        return getName( index, null );
+    }
+
+    /**
+     * Get an entry in the array that is expected to be a COSName.
+     * @param index The index into the array.
+     * @param defaultValue The value to return if it is null.
+     * @return The value at the index or defaultValue if none is found.
+     */
+    public String getName( int index, String defaultValue )
+    {
+        String retval = defaultValue;
+        if( index < size() )
+        {
+            Object obj = objects.get( index );
+            if( obj instanceof COSName )
+            {
+                retval = ((COSName)obj).getName();
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Set the value in the array as a string.
+     * @param index The index into the array.
+     * @param string The string to set in the array.
+     */
+    public void setString( int index, String string )
+    {
+        set( index, new COSString( string ) );
+    }
+
+    /**
+     * Get the value of the array as a string.
+     *
+     * @param index The index into the array.
+     * @return The string or null if it does not exist.
+     */
+    public String getString( int index )
+    {
+        return getString( index, null );
+    }
+
+    /**
+     * Get an entry in the array that is expected to be a COSName.
+     * @param index The index into the array.
+     * @param defaultValue The value to return if it is null.
+     * @return The value at the index or defaultValue if none is found.
+     */
+    public String getString( int index, String defaultValue )
+    {
+        String retval = defaultValue;
+        if( index < size() )
+        {
+            Object obj = objects.get( index );
+            if( obj instanceof COSString )
+            {
+                retval = ((COSString)obj).getString();
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the size of this array.
+     *
+     * @return The number of elements in the array.
+     */
+    public int size()
+    {
+        return objects.size();
+    }
+
+    /**
+     * This will remove an element from the array.
+     *
+     * @param i The index of the object to remove.
+     *
+     * @return The object that was removed.
+     */
+    public COSBase remove( int i )
+    {
+        return objects.remove( i );
+    }
+
+    /**
+     * This will remove an element from the array.
+     *
+     * @param o The object to remove.
+     *
+     * @return <code>true</code> if the object was removed, <code>false</code>
+     *  otherwise
+     */
+    public boolean remove( COSBase o )
+    {
+        return objects.remove( o );
+    }
+
+    /**
+     * This will remove an element from the array.
+     * This method will also remove a reference to the object.
+     *
+     * @param o The object to remove.
+     * @return <code>true</code> if the object was removed, <code>false</code>
+     *  otherwise
+     */
+    public boolean removeObject(COSBase o)
+    {
+        boolean removed = this.remove(o);
+        if (!removed)
+        {
+            for (int i = 0; i < this.size(); i++)
+            {
+                COSBase entry = this.get(i);
+                if (entry instanceof COSObject)
+                {
+                    COSObject objEntry = (COSObject) entry;
+                    if (objEntry.getObject().equals(o))
+                    {
+                        return this.remove(entry);
+                    }
+                }
+            }
+        }
+        return removed;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString()
+    {
+        return "COSArray{" + objects + "}";
+    }
+
+    /**
+     * Get access to the list.
+     *
+     * @return an iterator over the array elements
+     */
+    public Iterator<COSBase> iterator()
+    {
+        return objects.iterator();
+    }
+
+    /**
+     * This will return the index of the entry or -1 if it is not found.
+     *
+     * @param object The object to search for.
+     * @return The index of the object or -1.
+     */
+    public int indexOf( COSBase object )
+    {
+        int retval = -1;
+        for( int i=0; retval < 0 && i<size(); i++ )
+        {
+            if( get( i ).equals( object ) )
+            {
+                retval = i;
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will return the index of the entry or -1 if it is not found.
+     * This method will also find references to indirect objects.
+     *
+     * @param object The object to search for.
+     * @return The index of the object or -1.
+     */
+    public int indexOfObject(COSBase object)
+    {
+        int retval = -1;
+        for (int i = 0; retval < 0 && i < this.size(); i++)
+        {
+            COSBase item = this.get(i);
+            if (item.equals(object))
+            {
+                retval = i;
+                break;
+            }
+            else if (item instanceof COSObject)
+            {
+                if (((COSObject) item).getObject().equals(object))
+                {
+                    retval = i;
+                    break;
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will add null values until the size of the array is at least
+     * as large as the parameter.  If the array is already larger than the
+     * parameter then nothing is done.
+     *
+     * @param size The desired size of the array.
+     */
+    public void growToSize( int size )
+    {
+        growToSize( size, null );
+    }
+
+    /**
+     * This will add the object until the size of the array is at least
+     * as large as the parameter.  If the array is already larger than the
+     * parameter then nothing is done.
+     *
+     * @param size The desired size of the array.
+     * @param object The object to fill the array with.
+     */
+    public void growToSize( int size, COSBase object )
+    {
+        while( size() < size )
+        {
+            add( object );
+        }
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    @Override
+    public Object accept(ICOSVisitor visitor) throws COSVisitorException
+    {
+        return visitor.visitFromArray(this);
+    }
+
+    /**
+     * This will take an COSArray of numbers and convert it to a float[].
+     *
+     * @return This COSArray as an array of float numbers.
+     */
+    public float[] toFloatArray()
+    {
+        float[] retval = new float[size()];
+        for( int i=0; i<size(); i++ )
+        {
+            retval[i] = ((COSNumber)getObject( i )).floatValue();
+        }
+        return retval;
+    }
+
+    /**
+     * Clear the current contents of the COSArray and set it with the float[].
+     *
+     * @param value The new value of the float array.
+     */
+    public void setFloatArray( float[] value )
+    {
+        this.clear();
+        for( int i=0; i<value.length; i++ )
+        {
+            add( new COSFloat( value[i] ) );
+        }
+    }
+
+    /**
+     *  Return contents of COSArray as a Java List.
+     *
+     *  @return the COSArray as List
+     */
+    public List<COSBase> toList()
+    {
+        ArrayList<COSBase> retList = new ArrayList<COSBase>(size());
+        for (int i = 0; i < size(); i++)
+        {
+            retList.add(get(i));
+        }
+        return retList;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSBase.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSBase.java
new file mode 100644 (file)
index 0000000..fa87be0
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.cos;
+
+import org.apache.pdfbox.filter.FilterManager;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * The base object that all objects in the PDF document will extend.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public abstract class COSBase implements COSObjectable
+{
+    /**
+     * Constructor.
+     */
+  
+    private boolean needToBeUpdate;
+    
+    private boolean direct;
+  
+    public COSBase()
+    {
+      needToBeUpdate = false;
+    }
+
+    /**
+     * This will get the filter manager to use to filter streams.
+     *
+     * @return The filter manager.
+     */
+    public FilterManager getFilterManager()
+    {
+        /**
+         * @todo move this to PDFdocument or something better
+         */
+        return new FilterManager();
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return this;
+    }
+
+
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public abstract Object accept(ICOSVisitor visitor) throws COSVisitorException;
+    
+    public void setNeedToBeUpdate(boolean flag) 
+    {
+      needToBeUpdate = flag;
+    }
+    
+    /**
+     * If the state is set true, the dictionary will be written direct into the called object. 
+     * This means, no indirect object will be created.
+     * 
+     * @return the state
+     */
+    public boolean isDirect() 
+    {
+        return direct;
+    }
+    
+    /**
+     * Set the state true, if the dictionary should be written as a direct object and not indirect.
+     * 
+     * @param direct set it true, for writting direct object
+     */
+    public void setDirect(boolean direct)
+    {
+      this.direct = direct;
+    }
+    
+    public boolean isNeedToBeUpdate() 
+    {
+      return needToBeUpdate;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSBoolean.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSBoolean.java
new file mode 100644 (file)
index 0000000..04b39ca
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents a boolean value in the PDF document.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class COSBoolean extends COSBase
+{
+    /**
+     * The true boolean token.
+     */
+    public static final byte[] TRUE_BYTES = new byte[]{ 116, 114, 117, 101 }; //"true".getBytes( "ISO-8859-1" );
+    /**
+     * The false boolean token.
+     */
+    public static final byte[] FALSE_BYTES = new byte[]{ 102, 97, 108, 115, 101 }; //"false".getBytes( "ISO-8859-1" );
+
+    /**
+     * The PDF true value.
+     */
+    public static final COSBoolean TRUE = new COSBoolean( true );
+
+    /**
+     * The PDF false value.
+     */
+    public static final COSBoolean FALSE = new COSBoolean( false );
+
+    private boolean value;
+
+    /**
+     * Constructor.
+     *
+     * @param aValue The boolean value.
+     */
+    private COSBoolean(boolean aValue )
+    {
+        value = aValue;
+    }
+
+    /**
+     * This will get the value that this object wraps.
+     *
+     * @return The boolean value of this object.
+     */
+    public boolean getValue()
+    {
+        return value;
+    }
+
+    /**
+     * This will get the value that this object wraps.
+     *
+     * @return The boolean value of this object.
+     */
+    public Boolean getValueAsObject()
+    {
+        return (value?Boolean.TRUE:Boolean.FALSE);
+    }
+
+    /**
+     * This will get the boolean value.
+     *
+     * @param value Parameter telling which boolean value to get.
+     *
+     * @return The single boolean instance that matches the parameter.
+     */
+    public static COSBoolean getBoolean( boolean value )
+    {
+        return (value?TRUE:FALSE);
+    }
+
+    /**
+     * This will get the boolean value.
+     *
+     * @param value Parameter telling which boolean value to get.
+     *
+     * @return The single boolean instance that matches the parameter.
+     */
+    public static COSBoolean getBoolean( Boolean value )
+    {
+        return getBoolean( value.booleanValue() );
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept(ICOSVisitor  visitor) throws COSVisitorException
+    {
+        return visitor.visitFromBoolean(this);
+    }
+
+    /**
+     * Return a string representation of this object.
+     *
+     * @return The string value of this object.
+     */
+    public String toString()
+    {
+        return String.valueOf( value );
+    }
+
+    /**
+     * This will write this object out to a PDF stream.
+     *
+     * @param output The stream to write this object out to.
+     *
+     * @throws IOException If an error occurs while writing out this object.
+     */
+    public void writePDF( OutputStream output ) throws IOException
+    {
+        if( value )
+        {
+            output.write( TRUE_BYTES );
+        }
+        else
+        {
+            output.write( FALSE_BYTES );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSDictionary.java
new file mode 100644 (file)
index 0000000..168cb93
--- /dev/null
@@ -0,0 +1,1425 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.util.DateConverter;
+
+/**
+ * This class represents a dictionary where name/value pairs reside.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.32 $
+ */
+public class COSDictionary extends COSBase
+{
+    private static final String PATH_SEPARATOR = "/";
+
+    /**
+     * The name-value pairs of this dictionary. The pairs are kept in the
+     * order they were added to the dictionary.
+     */
+    private final Map<COSName, COSBase> items =
+        new LinkedHashMap<COSName, COSBase>();
+
+    /**
+     * Constructor.
+     */
+    public COSDictionary()
+    {
+        //default constructor
+    }
+
+    /**
+     * Copy Constructor.  This will make a shallow copy of this dictionary.
+     *
+     * @param dict The dictionary to copy.
+     */
+    public COSDictionary( COSDictionary dict )
+    {
+        items.putAll( dict.items );
+    }
+
+    /**
+     * @see java.util.Map#containsValue(java.lang.Object)
+     *
+     * @param value The value to find in the map.
+     *
+     * @return true if the map contains this value.
+     */
+    public boolean containsValue( Object value )
+    {
+        boolean contains = items.containsValue( value );
+        if( !contains && value instanceof COSObject )
+        {
+            contains = items.containsValue( ((COSObject)value).getObject());
+        }
+        return contains;
+    }
+
+    /**
+     * Search in the map for the value that matches the parameter
+     * and return the first key that maps to that value.
+     *
+     * @param value The value to search for in the map.
+     * @return The key for the value in the map or null if it does not exist.
+     */
+    public COSName getKeyForValue( Object value )
+    {
+        for( Map.Entry<COSName, COSBase> entry : items.entrySet() )
+        {
+            Object nextValue = entry.getValue();
+            if( nextValue.equals( value ) ||
+                (nextValue instanceof COSObject &&
+                 ((COSObject)nextValue).getObject().equals( value))
+                )
+            {
+                return entry.getKey();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * This will return the number of elements in this dictionary.
+     *
+     * @return The number of elements in the dictionary.
+     */
+    public int size()
+    {
+        return items.size();
+    }
+
+    /**
+     * This will clear all items in the map.
+     */
+    public void clear()
+    {
+        items.clear();
+    }
+
+    /**
+     * This will get an object from this dictionary.  If the object is a reference then it will
+     * dereference it and get it from the document.  If the object is COSNull then
+     * null will be returned.
+     *
+     * @param key The key to the object that we are getting.
+     *
+     * @return The object that matches the key.
+     */
+    public COSBase getDictionaryObject( String key )
+    {
+        return getDictionaryObject( COSName.getPDFName( key ) );
+    }
+
+    /**
+     * This is a special case of getDictionaryObject that takes multiple keys, it will handle
+     * the situation where multiple keys could get the same value, ie if either CS or ColorSpace
+     * is used to get the colorspace.
+     * This will get an object from this dictionary.  If the object is a reference then it will
+     * dereference it and get it from the document.  If the object is COSNull then
+     * null will be returned.
+     *
+     * @param firstKey The first key to try.
+     * @param secondKey The second key to try.
+     *
+     * @return The object that matches the key.
+     * 
+     * @deprecated  use {@link #getDictionaryObject(COSName, COSName)} using COSName constants instead
+     */
+    public COSBase getDictionaryObject( String firstKey, String secondKey )
+    {
+        COSBase retval = getDictionaryObject( COSName.getPDFName( firstKey ) );
+        if( retval == null )
+        {
+            retval = getDictionaryObject( COSName.getPDFName( secondKey ) );
+        }
+        return retval;
+    }
+
+    /**
+     * This is a special case of getDictionaryObject that takes multiple keys, it will handle
+     * the situation where multiple keys could get the same value, ie if either CS or ColorSpace
+     * is used to get the colorspace.
+     * This will get an object from this dictionary.  If the object is a reference then it will
+     * dereference it and get it from the document.  If the object is COSNull then
+     * null will be returned.
+     *
+     * @param firstKey The first key to try.
+     * @param secondKey The second key to try.
+     *
+     * @return The object that matches the key.
+     */
+    public COSBase getDictionaryObject( COSName firstKey, COSName secondKey )
+    {
+        COSBase retval = getDictionaryObject( firstKey );
+        if( retval == null && secondKey != null)
+        {
+            retval = getDictionaryObject( secondKey );
+        }
+        return retval;
+    }
+    /**
+     * This is a special case of getDictionaryObject that takes multiple keys, it will handle
+     * the situation where multiple keys could get the same value, ie if either CS or ColorSpace
+     * is used to get the colorspace.
+     * This will get an object from this dictionary.  If the object is a reference then it will
+     * dereference it and get it from the document.  If the object is COSNull then
+     * null will be returned.
+     *
+     * @param keyList The list of keys to find a value.
+     *
+     * @return The object that matches the key.
+     */
+    public COSBase getDictionaryObject( String[] keyList )
+    {
+        COSBase retval = null;
+        for( int i=0; i<keyList.length && retval == null; i++ )
+        {
+            retval = getDictionaryObject( COSName.getPDFName( keyList[i] ) );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get an object from this dictionary.  If the object is a reference then it will
+     * dereference it and get it from the document.  If the object is COSNull then
+     * null will be returned.
+     *
+     * @param key The key to the object that we are getting.
+     *
+     * @return The object that matches the key.
+     */
+    public COSBase getDictionaryObject( COSName key )
+    {
+        COSBase retval = items.get( key );
+        if( retval instanceof COSObject )
+        {
+            retval = ((COSObject)retval).getObject();
+        }
+        if( retval instanceof COSNull )
+        {
+            retval = null;
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an item in the dictionary.  If value is null then the result
+     * will be the same as removeItem( key ).
+     *
+     * @param key The key to the dictionary object.
+     * @param value The value to the dictionary object.
+     */
+    public void setItem( COSName key, COSBase value )
+    {
+        if( value == null )
+        {
+            removeItem( key );
+        }
+        else
+        {
+            items.put( key, value );
+        }
+    }
+
+    /**
+     * This will set an item in the dictionary.  If value is null then the result
+     * will be the same as removeItem( key ).
+     *
+     * @param key The key to the dictionary object.
+     * @param value The value to the dictionary object.
+     */
+    public void setItem( COSName key, COSObjectable value )
+    {
+        COSBase base = null;
+        if( value != null )
+        {
+            base = value.getCOSObject();
+        }
+        setItem( key, base );
+    }
+
+    /**
+     * This will set an item in the dictionary.  If value is null then the result
+     * will be the same as removeItem( key ).
+     *
+     * @param key The key to the dictionary object.
+     * @param value The value to the dictionary object.
+     */
+    public void setItem( String key, COSObjectable value )
+    {
+        setItem( COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This will set an item in the dictionary.
+     *
+     * @param key The key to the dictionary object.
+     * @param value The value to the dictionary object.
+     */
+    public void setBoolean( String key, boolean value )
+    {
+        setItem( COSName.getPDFName( key ), COSBoolean.getBoolean( value ) );
+    }
+
+    /**
+     * This will set an item in the dictionary.
+     *
+     * @param key The key to the dictionary object.
+     * @param value The value to the dictionary object.
+     */
+    public void setBoolean( COSName key, boolean value )
+    {
+        setItem( key , COSBoolean.getBoolean( value ) );
+    }
+
+    /**
+     * This will set an item in the dictionary.  If value is null then the result
+     * will be the same as removeItem( key ).
+     *
+     * @param key The key to the dictionary object.
+     * @param value The value to the dictionary object.
+     */
+    public void setItem( String key, COSBase value )
+    {
+        setItem( COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSName
+     * object.  If it is null then the object will be removed.
+     *
+     * @param key The key to the object,
+     * @param value The string value for the name.
+     */
+    public void setName( String key, String value )
+    {
+        setName( COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSName
+     * object.  If it is null then the object will be removed.
+     *
+     * @param key The key to the object,
+     * @param value The string value for the name.
+     */
+    public void setName( COSName key, String value )
+    {
+        COSName name = null;
+        if( value != null )
+        {
+            name = COSName.getPDFName( value );
+        }
+        setItem( key, name );
+    }
+
+    /**
+     * Set the value of a date entry in the dictionary.
+     *
+     * @param key The key to the date value.
+     * @param date The date value.
+     */
+    public void setDate( String key, Calendar date )
+    {
+        setDate( COSName.getPDFName( key ), date );
+    }
+
+    /**
+     * Set the date object.
+     *
+     * @param key The key to the date.
+     * @param date The date to set.
+     */
+    public void setDate( COSName key, Calendar date )
+    {
+        setString( key, DateConverter.toString( date ) );
+    }
+
+    /**
+     * Set the value of a date entry in the dictionary.
+     *
+     * @param embedded The embedded dictionary.
+     * @param key The key to the date value.
+     * @param date The date value.
+     */
+    public void setEmbeddedDate( String embedded, String key, Calendar date )
+    {
+        setEmbeddedDate( embedded, COSName.getPDFName( key ), date );
+    }
+
+    /**
+     * Set the date object.
+     *
+     * @param embedded The embedded dictionary.
+     * @param key The key to the date.
+     * @param date The date to set.
+     */
+    public void setEmbeddedDate( String embedded, COSName key, Calendar date )
+    {
+        COSDictionary dic = (COSDictionary)getDictionaryObject( embedded );
+        if( dic == null && date != null )
+        {
+            dic = new COSDictionary();
+            setItem( embedded, dic );
+        }
+        if( dic != null )
+        {
+            dic.setDate( key, date );
+        }
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSString
+     * object.  If it is null then the object will be removed.
+     *
+     * @param key The key to the object,
+     * @param value The string value for the name.
+     */
+    public void setString( String key, String value )
+    {
+        setString( COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSString
+     * object.  If it is null then the object will be removed.
+     *
+     * @param key The key to the object,
+     * @param value The string value for the name.
+     */
+    public void setString( COSName key, String value )
+    {
+        COSString name = null;
+        if( value != null )
+        {
+            name = new COSString( value );
+        }
+        setItem( key, name );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSString
+     * object.  If it is null then the object will be removed.
+     *
+     * @param embedded The embedded dictionary to set the item in.
+     * @param key The key to the object,
+     * @param value The string value for the name.
+     */
+    public void setEmbeddedString( String embedded, String key, String value )
+    {
+        setEmbeddedString( embedded, COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSString
+     * object.  If it is null then the object will be removed.
+     *
+     * @param embedded The embedded dictionary to set the item in.
+     * @param key The key to the object,
+     * @param value The string value for the name.
+     */
+    public void setEmbeddedString( String embedded, COSName key, String value )
+    {
+        COSDictionary dic = (COSDictionary)getDictionaryObject( embedded );
+        if( dic == null && value != null )
+        {
+            dic = new COSDictionary();
+            setItem( embedded, dic );
+        }
+        if( dic != null )
+        {
+            dic.setString( key, value );
+        }
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSInteger
+     * object.
+     *
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setInt( String key, int value )
+    {
+        setInt( COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSInteger
+     * object.
+     *
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setInt( COSName key, int value )
+    {
+        setItem( key, COSInteger.get(value) );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSInteger
+     * object.
+     *
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setLong( String key, long value )
+    {
+        setLong( COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSInteger
+     * object.
+     *
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setLong( COSName key, long value )
+    {
+        COSInteger intVal = null;
+        intVal = COSInteger.get(value);
+        setItem( key, intVal );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSInteger
+     * object.
+     *
+     * @param embeddedDictionary The embedded dictionary.
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setEmbeddedInt( String embeddedDictionary, String key, int value )
+    {
+        setEmbeddedInt( embeddedDictionary, COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSInteger
+     * object.
+     *
+     * @param embeddedDictionary The embedded dictionary.
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setEmbeddedInt( String embeddedDictionary, COSName key, int value )
+    {
+        COSDictionary embedded = (COSDictionary)getDictionaryObject( embeddedDictionary );
+        if( embedded == null )
+        {
+            embedded = new COSDictionary();
+            setItem( embeddedDictionary, embedded );
+        }
+        embedded.setInt( key, value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSFloat
+     * object.
+     *
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setFloat( String key, float value )
+    {
+        setFloat( COSName.getPDFName( key ), value );
+    }
+
+    /**
+     * This is a convenience method that will convert the value to a COSFloat
+     * object.
+     *
+     * @param key The key to the object,
+     * @param value The int value for the name.
+     */
+    public void setFloat( COSName key, float value )
+    {
+        COSFloat fltVal = new COSFloat( value );
+        setItem( key, fltVal );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     */
+    public String getNameAsString( String key )
+    {
+        return getNameAsString( COSName.getPDFName( key ) );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     */
+    public String getNameAsString( COSName key )
+    {
+        String retval = null;
+        COSBase name = getDictionaryObject( key );
+        if( name != null )
+        {
+            if ( name instanceof COSName)
+            {
+                retval = ((COSName)name).getName();
+            }
+            else if ( name instanceof COSString)
+            {
+                retval = ((COSString)name).getString();
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The name converted to a string.
+     */
+    public String getNameAsString( String key, String defaultValue )
+    {
+        return getNameAsString( COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The name converted to a string.
+     */
+    public String getNameAsString( COSName key, String defaultValue )
+    {
+        String retval = getNameAsString( key );
+        if( retval == null )
+        {
+            retval = defaultValue;
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     */
+    public String getString( String key )
+    {
+        return getString( COSName.getPDFName( key ) );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     */
+    public String getString( COSName key )
+    {
+        String retval = null;
+        COSBase value = getDictionaryObject( key );
+        if( value != null && value instanceof COSString)
+        {
+            retval = ((COSString)value).getString();
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     */
+    public String getString( String key, String defaultValue )
+    {
+        return getString( COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     */
+    public String getString( COSName key, String defaultValue )
+    {
+        String retval = getString( key );
+        if( retval == null )
+        {
+            retval = defaultValue;
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary.
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     */
+    public String getEmbeddedString( String embedded, String key )
+    {
+        return getEmbeddedString( embedded, COSName.getPDFName( key ), null );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary.
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     */
+    public String getEmbeddedString( String embedded, COSName key )
+    {
+        return getEmbeddedString( embedded, key, null );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary.
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     */
+    public String getEmbeddedString( String embedded, String key, String defaultValue )
+    {
+        return getEmbeddedString( embedded, COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary.
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     */
+    public String getEmbeddedString( String embedded, COSName key, String defaultValue )
+    {
+        String retval = defaultValue;
+        COSDictionary dic = (COSDictionary)getDictionaryObject( embedded );
+        if( dic != null )
+        {
+            retval = dic.getString( key, defaultValue );
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getDate( String key ) throws IOException
+    {
+        return getDate( COSName.getPDFName( key ) );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     *
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getDate( COSName key ) throws IOException
+    {
+        COSString date = (COSString)getDictionaryObject( key );
+        return DateConverter.toCalendar( date );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a date.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getDate( String key, Calendar defaultValue ) throws IOException
+    {
+        return getDate( COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a date.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getDate( COSName key, Calendar defaultValue ) throws IOException
+    {
+        Calendar retval = getDate( key );
+        if( retval == null )
+        {
+            retval = defaultValue;
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary to get.
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getEmbeddedDate( String embedded, String key ) throws IOException
+    {
+        return getEmbeddedDate( embedded, COSName.getPDFName( key ), null );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a name and convert it to a string.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary to get.
+     * @param key The key to the item in the dictionary.
+     * @return The name converted to a string.
+     *
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getEmbeddedDate( String embedded, COSName key ) throws IOException
+    {
+        return getEmbeddedDate( embedded, key, null );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a date.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary to get.
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getEmbeddedDate( String embedded, String key, Calendar defaultValue ) throws IOException
+    {
+        return getEmbeddedDate( embedded, COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a date.  Null is returned
+     * if the entry does not exist in the dictionary.
+     *
+     * @param embedded The embedded dictionary to get.
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The default value to return.
+     * @return The name converted to a string.
+     * @throws IOException If there is an error converting to a date.
+     */
+    public Calendar getEmbeddedDate( String embedded, COSName key, Calendar defaultValue ) throws IOException
+    {
+        Calendar retval = defaultValue;
+        COSDictionary eDic = (COSDictionary)getDictionaryObject( embedded );
+        if( eDic != null )
+        {
+            retval = eDic.getDate( key, defaultValue );
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a cos boolean and convert it to a primitive boolean.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value returned if the entry is null.
+     *
+     * @return The value converted to a boolean.
+     */
+    public boolean getBoolean( String key, boolean defaultValue )
+    {
+        return getBoolean( COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a COSBoolean and convert it to a primitive boolean.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value returned if the entry is null.
+     *
+     * @return The entry converted to a boolean.
+     */
+    public boolean getBoolean( COSName key, boolean defaultValue )
+    {
+        return getBoolean( key, null, defaultValue);
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a COSBoolean and convert it to a primitive boolean.
+     *
+     * @param firstKey The first key to the item in the dictionary.
+     * @param secondKey The second key to the item in the dictionary.
+     * @param defaultValue The value returned if the entry is null.
+     *
+     * @return The entry converted to a boolean.
+     */
+    public boolean getBoolean( COSName firstKey, COSName secondKey, boolean defaultValue )
+    {
+        boolean retval = defaultValue;
+        COSBase bool = getDictionaryObject( firstKey, secondKey );
+        if( bool != null && bool instanceof COSBoolean)
+        {
+            retval = ((COSBoolean)bool).getValue();
+        }
+        return retval;
+    }
+
+    /**
+     * Get an integer from an embedded dictionary.  Useful for 1-1 mappings.  default:-1
+     *
+     * @param embeddedDictionary The name of the embedded dictionary.
+     * @param key The key in the embedded dictionary.
+     *
+     * @return The value of the embedded integer.
+     */
+    public int getEmbeddedInt( String embeddedDictionary, String key )
+    {
+        return getEmbeddedInt( embeddedDictionary, COSName.getPDFName( key ) );
+    }
+
+    /**
+     * Get an integer from an embedded dictionary.  Useful for 1-1 mappings.  default:-1
+     *
+     * @param embeddedDictionary The name of the embedded dictionary.
+     * @param key The key in the embedded dictionary.
+     *
+     * @return The value of the embedded integer.
+     */
+    public int getEmbeddedInt( String embeddedDictionary, COSName key )
+    {
+        return getEmbeddedInt( embeddedDictionary, key, -1 );
+    }
+
+    /**
+     * Get an integer from an embedded dictionary.  Useful for 1-1 mappings.
+     *
+     * @param embeddedDictionary The name of the embedded dictionary.
+     * @param key The key in the embedded dictionary.
+     * @param defaultValue The value if there is no embedded dictionary or it does not contain the key.
+     *
+     * @return The value of the embedded integer.
+     */
+    public int getEmbeddedInt( String embeddedDictionary, String key, int defaultValue )
+    {
+        return getEmbeddedInt( embeddedDictionary, COSName.getPDFName( key ), defaultValue );
+    }
+
+
+    /**
+     * Get an integer from an embedded dictionary.  Useful for 1-1 mappings.
+     *
+     * @param embeddedDictionary The name of the embedded dictionary.
+     * @param key The key in the embedded dictionary.
+     * @param defaultValue The value if there is no embedded dictionary or it does not contain the key.
+     *
+     * @return The value of the embedded integer.
+     */
+    public int getEmbeddedInt( String embeddedDictionary, COSName key, int defaultValue )
+    {
+        int retval = defaultValue;
+        COSDictionary embedded = (COSDictionary)getDictionaryObject( embeddedDictionary );
+        if( embedded != null )
+        {
+            retval = embedded.getInt( key, defaultValue );
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an int.  -1 is returned if there is no value.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The integer value.
+     */
+    public int getInt( String key )
+    {
+        return getInt( COSName.getPDFName( key ), -1 );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an int.  -1 is returned if there is no value.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The integer value..
+     */
+    public int getInt( COSName key )
+    {
+        return getInt( key, -1 );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an integer.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param keyList The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The integer value.
+     */
+    public int getInt( String[] keyList, int defaultValue )
+    {
+        int retval = defaultValue;
+        COSBase obj = getDictionaryObject( keyList );
+        if( obj != null && obj instanceof COSNumber)
+        {
+            retval = ((COSNumber)obj).intValue();
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an integer.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The integer value.
+     */
+    public int getInt( String key, int defaultValue )
+    {
+        return getInt( COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an integer.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The integer value.
+     */
+    public int getInt( COSName key, int defaultValue )
+    {
+        return getInt( key, null, defaultValue);
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an integer.  If the dictionary value is null then the
+     * default Value -1 will be returned.
+     *
+     * @param firstKey The first key to the item in the dictionary.
+     * @param secondKey The second key to the item in the dictionary.
+     * @return The integer value.
+     */
+    public int getInt( COSName firstKey, COSName secondKey )
+    {
+        return getInt( firstKey, secondKey, -1 );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an integer.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param firstKey The first key to the item in the dictionary.
+     * @param secondKey The second key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The integer value.
+     */
+    public int getInt( COSName firstKey, COSName secondKey, int defaultValue )
+    {
+        int retval = defaultValue;
+        COSBase obj = getDictionaryObject( firstKey, secondKey );
+        if( obj != null && obj instanceof COSNumber)
+        {
+            retval = ((COSNumber)obj).intValue();
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an long.  -1 is returned if there is no value.
+     *
+     * @param key The key to the item in the dictionary.
+     *
+     * @return The long value.
+     */
+    public long getLong( String key )
+    {
+        return getLong( COSName.getPDFName( key ), -1L );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an long.  -1 is returned if there is no value.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The long value.
+     */
+    public long getLong( COSName key )
+    {
+        return getLong( key, -1L );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an long.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param keyList The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The long value.
+     */
+    public long getLong( String[] keyList, long defaultValue )
+    {
+        long retval = defaultValue;
+        COSBase obj = getDictionaryObject( keyList );
+        if( obj != null && obj instanceof COSNumber)
+        {
+            retval = ((COSNumber)obj).longValue();
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an integer.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The integer value.
+     */
+    public long getLong( String key, long defaultValue )
+    {
+        return getLong( COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an integer.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The integer value.
+     */
+    public long getLong( COSName key, long defaultValue )
+    {
+        long retval = defaultValue;
+        COSBase obj = getDictionaryObject( key );
+        if( obj != null && obj instanceof COSNumber)
+        {
+            retval = ((COSNumber)obj).longValue();
+        }
+        return retval;
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an float.  -1 is returned if there is no value.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The float value.
+     */
+    public float getFloat( String key )
+    {
+        return getFloat( COSName.getPDFName( key ), -1 );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an float.  -1 is returned if there is no value.
+     *
+     * @param key The key to the item in the dictionary.
+     * @return The float value.
+     */
+    public float getFloat( COSName key )
+    {
+        return getFloat( key, -1 );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be a float.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The float value.
+     */
+    public float getFloat( String key, float defaultValue )
+    {
+        return getFloat( COSName.getPDFName( key ), defaultValue );
+    }
+
+    /**
+     * This is a convenience method that will get the dictionary object that
+     * is expected to be an float.  If the dictionary value is null then the
+     * default Value will be returned.
+     *
+     * @param key The key to the item in the dictionary.
+     * @param defaultValue The value to return if the dictionary item is null.
+     * @return The float value.
+     */
+    public float getFloat( COSName key, float defaultValue )
+    {
+        float retval = defaultValue;
+        COSBase obj = getDictionaryObject( key );
+        if( obj != null && obj instanceof COSNumber)
+        {
+            retval = ((COSNumber)obj).floatValue();
+        }
+        return retval;
+    }
+
+    /**
+     * This will remove an item for the dictionary.  This
+     * will do nothing of the object does not exist.
+     *
+     * @param key The key to the item to remove from the dictionary.
+     */
+    public void removeItem( COSName key )
+    {
+        items.remove( key );
+    }
+
+    /**
+     * This will do a lookup into the dictionary.
+     *
+     * @param key The key to the object.
+     *
+     * @return The item that matches the key.
+     */
+    public COSBase getItem( COSName key )
+    {
+        return items.get( key );
+    }
+
+    /**
+     * This will get the keys for all objects in the dictionary in the sequence that
+     * they were added.
+     *
+     * @deprecated Use the {@link #entrySet()} method instead.
+     * @return a list of the keys in the sequence of insertion
+     */
+    public List<COSName> keyList()
+    {
+        return new ArrayList<COSName>(items.keySet());
+    }
+
+    /**
+     * Returns the names of the entries in this dictionary. The returned
+     * set is in the order the entries were added to the dictionary.
+     *
+     * @since Apache PDFBox 1.1.0
+     * @return names of the entries in this dictionary
+     */
+    public Set<COSName> keySet()
+    {
+        return items.keySet();
+    }
+
+    /**
+     * Returns the name-value entries in this dictionary. The returned
+     * set is in the order the entries were added to the dictionary.
+     *
+     * @since Apache PDFBox 1.1.0
+     * @return name-value entries in this dictionary
+     */
+    public Set<Map.Entry<COSName, COSBase>> entrySet()
+    {
+        return items.entrySet();
+    }
+
+    /**
+     * This will get all of the values for the dictionary.
+     *
+     * @return All the values for the dictionary.
+     */
+    public Collection<COSBase> getValues()
+    {
+        return items.values();
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return The object that the visitor returns.
+     *
+     * @throws COSVisitorException If there is an error visiting this object.
+     */
+    public Object accept(ICOSVisitor  visitor) throws COSVisitorException
+    {
+        return visitor.visitFromDictionary(this);
+    }
+
+    /**
+     * This will add all of the dictionarys keys/values to this dictionary.
+     * Only called when adding keys to a trailer that already exists.
+     *
+     * @param dic The dic to get the keys from.
+     */
+    public void addAll( COSDictionary dic )
+    {
+        for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+        {
+            /*
+             * If we're at a second trailer, we have a linearized
+             * pdf file, meaning that the first Size entry represents
+             * all of the objects so we don't need to grab the second.
+             */
+            if(!entry.getKey().getName().equals("Size")
+                    || !items.containsKey(COSName.getPDFName("Size")))
+            {
+                setItem( entry.getKey(), entry.getValue() );
+            }
+        }
+    }
+
+    /**
+     * This will add all of the dictionarys keys/values to this dictionary, but only
+     * if they don't already exist.  If a key already exists in this dictionary then
+     * nothing is changed.
+     *
+     * @param dic The dic to get the keys from.
+     */
+    public void mergeInto( COSDictionary dic )
+    {
+        for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+        {
+            if( getItem( entry.getKey() ) == null )
+            {
+                setItem( entry.getKey(), entry.getValue() );
+            }
+        }
+    }
+
+    /**
+     * Nice method, gives you every object you want
+     * Arrays works properly too. Try "P/Annots/[k]/Rect"
+     * where k means the index of the Annotsarray.
+     *
+     * @param objPath the relative path to the object.
+     * @return the object
+     */
+    public COSBase getObjectFromPath(String objPath)
+    {
+        COSBase retval = null;
+        String[] path = objPath.split(PATH_SEPARATOR);
+        retval = this;
+
+        for (int i = 0; i < path.length; i++)
+        {
+            if(retval instanceof COSArray)
+            {
+                int idx = new Integer(path[i].replaceAll("\\[","").replaceAll("\\]","")).intValue();
+                retval = ((COSArray)retval).getObject(idx);
+            }
+            else if (retval instanceof COSDictionary)
+            {
+                retval = ((COSDictionary)retval).getDictionaryObject( path[i] );
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        StringBuilder retVal = new StringBuilder("COSDictionary{");
+        for( COSName key : items.keySet() )
+        {
+            retVal.append("(" + key + ":" + getDictionaryObject(key).toString() + ") ");
+        }
+        retVal.append("}");
+        return retVal.toString();
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSDocument.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSDocument.java
new file mode 100644 (file)
index 0000000..e66a371
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.io.RandomAccessBuffer;
+import org.apache.pdfbox.io.RandomAccessFile;
+import org.apache.pdfbox.pdfparser.PDFObjectStreamParser;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This is the in-memory representation of the PDF document.  You need to call
+ * close() on this object when you are done using it!!
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.28 $
+ */
+public class COSDocument extends COSBase
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(COSDocument.class);
+
+    private float version;
+
+    /**
+     * Maps ObjectKeys to a COSObject. Note that references to these objects
+     * are also stored in COSDictionary objects that map a name to a specific object.
+     */
+    private final Map<COSObjectKey, COSObject> objectPool =
+        new HashMap<COSObjectKey, COSObject>();
+
+    /**
+     * Maps object and generation ids to object byte offsets.
+     */
+    private final Map<COSObjectKey, Integer> xrefTable =
+        new HashMap<COSObjectKey, Integer>();
+
+    /**
+     * Document trailer dictionary.
+     */
+    private COSDictionary trailer;
+    
+    /**
+     * Document signature dictionary
+     */
+    private COSDictionary signDictionary = null;
+    
+    /**
+     * Some doc
+     */
+    private SignatureInterface signatureInterface;
+
+    /**
+     * This file will store the streams in order to conserve memory.
+     */
+    private final RandomAccess scratchFile;
+
+    private final File tmpFile;
+
+    private String headerString = "%PDF-1.4";
+
+    private boolean warnMissingClose = true;
+    
+    private int startXref;
+    
+    private boolean closed = false;
+
+    /**
+     * Flag to skip malformed or otherwise unparseable input where possible.
+     */
+    private final boolean forceParsing;
+
+    /**
+     * Constructor that will use the given random access file for storage
+     * of the PDF streams. The client of this method is responsible for
+     * deleting the storage if necessary that this file will write to. The
+     * close method will close the file though.
+     *
+     * @param scratchFile the random access file to use for storage
+     * @param forceParsing flag to skip malformed or otherwise unparseable
+     *                     document content where possible
+     */
+    public COSDocument(RandomAccess scratchFile, boolean forceParsing) {
+        this.scratchFile = scratchFile;
+        this.tmpFile = null;
+        this.forceParsing = forceParsing;
+    }
+
+    /**
+     * Constructor that will use a temporary file in the given directory
+     * for storage of the PDF streams. The temporary file is automatically
+     * removed when this document gets closed.
+     *
+     * @param scratchDir directory for the temporary file,
+     *                   or <code>null</code> to use the system default
+     * @param forceParsing flag to skip malformed or otherwise unparseable
+     *                     document content where possible
+     */
+    public COSDocument(File scratchDir, boolean forceParsing)
+            throws IOException {
+        this.tmpFile = File.createTempFile("pdfbox-", ".tmp", scratchDir);
+        this.scratchFile = new RandomAccessFile(tmpFile, "rw");
+        this.forceParsing = forceParsing;
+    }
+
+    /**
+     * Constructor.  Uses memory to store stream.
+     *
+     *  @throws IOException If there is an error creating the tmp file.
+     */
+    public COSDocument() throws IOException {
+        this(new RandomAccessBuffer(), false);
+    }
+
+    /**
+     * Constructor that will create a create a scratch file in the
+     * following directory.
+     *
+     * @param scratchDir The directory to store a scratch file.
+     *
+     * @throws IOException If there is an error creating the tmp file.
+     */
+    public COSDocument(File scratchDir) throws IOException {
+        this(scratchDir, false);
+    }
+
+    /**
+     * Constructor that will use the following random access file for storage
+     * of the PDF streams.  The client of this method is responsible for deleting
+     * the storage if necessary that this file will write to.  The close method
+     * will close the file though.
+     *
+     * @param file The random access file to use for storage.
+     */
+    public COSDocument(RandomAccess file) {
+        this(file, false);
+    }
+
+    /**
+     * This will get the scratch file for this document.
+     *
+     * @return The scratch file.
+     */
+    public RandomAccess getScratchFile()
+    {
+        return scratchFile;
+    }
+
+    /**
+     * This will get the first dictionary object by type.
+     *
+     * @param type The type of the object.
+     *
+     * @return This will return an object with the specified type.
+     * @throws IOException If there is an error getting the object
+     */
+    public COSObject getObjectByType( String type ) throws IOException
+    {
+        return getObjectByType( COSName.getPDFName( type ) );
+    }
+
+    /**
+     * This will get the first dictionary object by type.
+     *
+     * @param type The type of the object.
+     *
+     * @return This will return an object with the specified type.
+     * @throws IOException If there is an error getting the object
+     */
+    public COSObject getObjectByType( COSName type ) throws IOException
+    {
+        for( COSObject object : objectPool.values() )
+        {
+
+            COSBase realObject = object.getObject();
+            if( realObject instanceof COSDictionary )
+            {
+                try
+                {
+                    COSDictionary dic = (COSDictionary)realObject;
+                    COSName objectType = (COSName)dic.getItem( COSName.TYPE );
+                    if( objectType != null && objectType.equals( type ) )
+                    {
+                        return object;
+                    }
+                }
+                catch (ClassCastException e)
+                {
+                    log.warn(e, e);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This will get all dictionary objects by type.
+     *
+     * @param type The type of the object.
+     *
+     * @return This will return an object with the specified type.
+     * @throws IOException If there is an error getting the object
+     */
+    public List<COSObject> getObjectsByType( String type ) throws IOException
+    {
+        return getObjectsByType( COSName.getPDFName( type ) );
+    }
+
+    /**
+     * This will get a dictionary object by type.
+     *
+     * @param type The type of the object.
+     *
+     * @return This will return an object with the specified type.
+     * @throws IOException If there is an error getting the object
+     */
+    public List<COSObject> getObjectsByType( COSName type ) throws IOException
+    {
+        List<COSObject> retval = new ArrayList<COSObject>();
+        for( COSObject object : objectPool.values() )
+        {
+            COSBase realObject = object.getObject();
+            if( realObject instanceof COSDictionary )
+            {
+                try
+                {
+                    COSDictionary dic = (COSDictionary)realObject;
+                    COSName objectType = (COSName)dic.getItem( COSName.TYPE );
+                    if( objectType != null && objectType.equals( type ) )
+                    {
+                        retval.add( object );
+                    }
+                }
+                catch (ClassCastException e)
+                {
+                    log.warn(e, e);
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will print contents to stdout.
+     */
+    public void print()
+    {
+        for( COSObject object : objectPool.values() )
+        {
+            System.out.println( object);
+        }
+    }
+
+    /**
+     * This will set the version of this PDF document.
+     *
+     * @param versionValue The version of the PDF document.
+     */
+    public void setVersion( float versionValue )
+    {
+        version = versionValue;
+    }
+
+    /**
+     * This will get the version of this PDF document.
+     *
+     * @return This documents version.
+     */
+    public float getVersion()
+    {
+        return version;
+    }
+
+    /**
+     * This will tell if this is an encrypted document.
+     *
+     * @return true If this document is encrypted.
+     */
+    public boolean isEncrypted()
+    {
+        boolean encrypted = false;
+        if( trailer != null )
+        {
+            encrypted = trailer.getDictionaryObject( COSName.ENCRYPT ) != null;
+        }
+        return encrypted;
+    }
+
+    /**
+     * This will get the encryption dictionary if the document is encrypted or null
+     * if the document is not encrypted.
+     *
+     * @return The encryption dictionary.
+     */
+    public COSDictionary getEncryptionDictionary()
+    {
+        return (COSDictionary)trailer.getDictionaryObject( COSName.ENCRYPT );
+    }
+
+    public SignatureInterface getSignatureInterface() {
+        return signatureInterface;
+    }
+    
+    /**
+     * This will set the encryption dictionary, this should only be called when
+     * encrypting the document.
+     *
+     * @param encDictionary The encryption dictionary.
+     */
+    public void setEncryptionDictionary( COSDictionary encDictionary )
+    {
+        trailer.setItem( COSName.ENCRYPT, encDictionary );
+    }
+
+    public COSDictionary getLastSignatureDictionary() throws IOException {
+      if (signDictionary == null)
+      {
+        COSObject documentCatalog = getCatalog();
+        if (documentCatalog != null)
+        {
+          COSDictionary acroForm = (COSDictionary)documentCatalog.getDictionaryObject(COSName.ACRO_FORM);
+          if (acroForm !=null)
+          {
+            COSArray fields = (COSArray)acroForm.getDictionaryObject(COSName.FIELDS);
+            for ( Object object : fields )
+            {
+              COSObject dict = (COSObject)object;
+              if(dict.getItem(COSName.FT).equals(COSName.SIG))
+              {
+                COSBase dictionaryObject = dict.getDictionaryObject(COSName.V);
+                
+                if (dictionaryObject != null) 
+                {
+                  signDictionary = (COSDictionary)dictionaryObject;
+                }
+              }
+            }
+          }
+        }
+      }
+      return signDictionary;
+    }
+    
+    /**
+     * This will get the document ID.
+     *
+     * @return The document id.
+     */
+    public COSArray getDocumentID()
+    {
+        return (COSArray) getTrailer().getItem(COSName.ID);
+    }
+
+    /**
+     * This will set the document ID.
+     *
+     * @param id The document id.
+     */
+    public void setDocumentID( COSArray id )
+    {
+        getTrailer().setItem(COSName.ID, id);
+    }
+    
+    public void setSignatureInterface(SignatureInterface signatureInterface) {
+      this.signatureInterface = signatureInterface;
+    }
+
+    /**
+     * This will get the document catalog.
+     *
+     * Maybe this should move to an object at PDFEdit level
+     *
+     * @return catalog is the root of all document activities
+     *
+     * @throws IOException If no catalog can be found.
+     */
+    public COSObject getCatalog() throws IOException
+    {
+        COSObject catalog = getObjectByType( COSName.CATALOG );
+        if( catalog == null )
+        {
+            throw new IOException( "Catalog cannot be found" );
+        }
+        return catalog;
+    }
+
+    /**
+     * This will get a list of all available objects.
+     *
+     * @return A list of all objects.
+     */
+    public List<COSObject> getObjects()
+    {
+        return new ArrayList<COSObject>(objectPool.values());
+    }
+
+    /**
+     * This will get the document trailer.
+     *
+     * @return the document trailer dict
+     */
+    public COSDictionary getTrailer()
+    {
+        return trailer;
+    }
+
+    /**
+     * // MIT added, maybe this should not be supported as trailer is a persistence construct.
+     * This will set the document trailer.
+     *
+     * @param newTrailer the document trailer dictionary
+     */
+    public void setTrailer(COSDictionary newTrailer)
+    {
+        trailer = newTrailer;
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    @Override
+    public Object accept(ICOSVisitor visitor) throws COSVisitorException
+    {
+        return visitor.visitFromDocument( this );
+    }
+
+    /**
+     * This will close all storage and delete the tmp files.
+     *
+     *  @throws IOException If there is an error close resources.
+     */
+    public void close() throws IOException
+    {
+        if (!closed) {
+            scratchFile.close();
+            if (tmpFile != null) {
+                tmpFile.delete();
+            }
+            closed = true;
+        }
+    }
+
+    /**
+     * Warn the user in the finalizer if he didn't close the PDF document. The method also
+     * closes the document just in case, to avoid abandoned temporary files. It's still a good
+     * idea for the user to close the PDF document at the earliest possible to conserve resources.
+     * @throws IOException if an error occurs while closing the temporary files
+     */
+    @Override
+    protected void finalize() throws IOException
+    {
+        if (!closed) {
+            if (warnMissingClose) {
+                log.warn( "Warning: You did not close a PDF Document" );
+            }
+            close();
+        }
+    }
+
+    /**
+     * Controls whether this instance shall issue a warning if the PDF document wasn't closed
+     * properly through a call to the {@link #close()} method. If the PDF document is held in
+     * a cache governed by soft references it is impossible to reliably close the document
+     * before the warning is raised. By default, the warning is enabled.
+     * @param warn true enables the warning, false disables it.
+     */
+    public void setWarnMissingClose(boolean warn)
+    {
+        this.warnMissingClose = warn;
+    }
+
+    /**
+     * @return Returns the headerString.
+     */
+    public String getHeaderString()
+    {
+        return headerString;
+    }
+    /**
+     * @param header The headerString to set.
+     */
+    public void setHeaderString(String header)
+    {
+        headerString = header;
+    }
+
+    /**
+     * This method will search the list of objects for types of ObjStm.  If it finds
+     * them then it will parse out all of the objects from the stream that is contains.
+     *
+     * @throws IOException If there is an error parsing the stream.
+     */
+    public void dereferenceObjectStreams() throws IOException
+    {
+        for( COSObject objStream : getObjectsByType( COSName.OBJ_STM ) )
+        {
+            COSStream stream = (COSStream)objStream.getObject();
+            PDFObjectStreamParser parser =
+                new PDFObjectStreamParser(stream, this, forceParsing);
+            parser.parse();
+            for( COSObject next : parser.getObjects() )
+            {
+                COSObjectKey key = new COSObjectKey( next );
+                if(objectPool.get(key) == null || objectPool.get(key).getObject() == null)
+                {
+                    COSObject obj = getObjectFromPool(key);
+                    obj.setObject(next.getObject());
+                }
+            }
+        }
+    }
+
+    /**
+     * This will get an object from the pool.
+     *
+     * @param key The object key.
+     *
+     * @return The object in the pool or a new one if it has not been parsed yet.
+     *
+     * @throws IOException If there is an error getting the proxy object.
+     */
+    public COSObject getObjectFromPool(COSObjectKey key) throws IOException
+    {
+        COSObject obj = null;
+        if( key != null )
+        {
+            obj = objectPool.get(key);
+        }
+        if (obj == null)
+        {
+            // this was a forward reference, make "proxy" object
+            obj = new COSObject(null);
+            if( key != null )
+            {
+                obj.setObjectNumber( COSInteger.get( key.getNumber() ) );
+                obj.setGenerationNumber( COSInteger.get( key.getGeneration() ) );
+                objectPool.put(key, obj);
+            }
+        }
+        return obj;
+    }
+
+    /**
+     * Removes an object from the object pool.
+     * @param key the object key
+     * @return the object that was removed or null if the object was not found
+     */
+    public COSObject removeObject(COSObjectKey key)
+    {
+        COSObject obj = objectPool.remove(key);
+        return obj;
+    }
+
+    /**
+     * Populate XRef HashMap with given values.
+     * Each entry maps ObjectKeys to byte offsets in the file.
+     * @param _xrefTable  xref table entries to be added
+     */
+    public void addXRefTable( Map<COSObjectKey, Integer> xrefTable )
+    {
+        this.xrefTable.putAll( xrefTable );
+    }
+
+    /**
+     * Returns the xrefTable which is a mapping of ObjectKeys
+     * to byte offsets in the file.
+     * @return mapping of ObjectsKeys to byte offsets
+     */
+    public Map<COSObjectKey, Integer> getXrefTable()
+    {
+        return xrefTable;
+    }
+
+    /**
+     * This method set the startxref value of the document. This will only 
+     * be needed for incremental updates.
+     * 
+     * @param readInt
+     */
+    public void setStartXref(int startXref)
+    {
+      this.startXref = startXref;
+    }
+
+    /**
+     * Return the startXref Position of the parsed document. This will only be needed for incremental updates.
+     * 
+     * @return a int with the old position of the startxref
+     */
+    public int getStartXref()
+    {
+      return startXref;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSFloat.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSFloat.java
new file mode 100644 (file)
index 0000000..cc7d03c
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents a floating point number in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.17 $
+ */
+public class COSFloat extends COSNumber
+{
+    private float value;
+
+    /**
+     * Constructor.
+     *
+     * @param aFloat The primitive float object that this object wraps.
+     */
+    public COSFloat( float aFloat )
+    {
+        value = aFloat;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param aFloat The primitive float object that this object wraps.
+     *
+     * @throws IOException If aFloat is not a float.
+     */
+    public COSFloat( String aFloat ) throws IOException
+    {
+        try
+        {
+            value = Float.parseFloat( aFloat );
+        }
+        catch( NumberFormatException e )
+        {
+            throw new IOException( "Error expected floating point number actual='" +aFloat + "'" );
+        }
+    }
+
+    /**
+     * Set the value of the float object.
+     *
+     * @param floatValue The new float value.
+     */
+    public void setValue( float floatValue )
+    {
+        value = floatValue;
+    }
+
+    /**
+     * The value of the float object that this one wraps.
+     *
+     * @return The value of this object.
+     */
+    public float floatValue()
+    {
+        return value;
+    }
+
+    /**
+     * The value of the double object that this one wraps.
+     *
+     * @return The double of this object.
+     */
+    public double doubleValue()
+    {
+        return value;
+    }
+
+    /**
+     * This will get the integer value of this object.
+     *
+     * @return The int value of this object,
+     */
+    public long longValue()
+    {
+        return (long)value;
+    }
+
+    /**
+     * This will get the integer value of this object.
+     *
+     * @return The int value of this object,
+     */
+    public int intValue()
+    {
+        return (int)value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals( Object o )
+    {
+        return o instanceof COSFloat && Float.floatToIntBits(((COSFloat)o).value) == Float.floatToIntBits(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return Float.floatToIntBits(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "COSFloat{" + value + "}";
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept(ICOSVisitor visitor) throws COSVisitorException
+    {
+        return visitor.visitFromFloat(this);
+    }
+
+    /**
+     * This will output this string as a PDF object.
+     *
+     * @param output The stream to write to.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void writePDF( OutputStream output ) throws IOException
+    {
+        DecimalFormat formatDecimal = (DecimalFormat)NumberFormat.getNumberInstance();
+        formatDecimal.setMaximumFractionDigits( 10 );
+        formatDecimal.setGroupingUsed( false );
+        DecimalFormatSymbols symbols = formatDecimal.getDecimalFormatSymbols();
+        symbols.setDecimalSeparator( '.' );
+        formatDecimal.setDecimalFormatSymbols( symbols );
+        output.write(formatDecimal.format( value ).getBytes("ISO-8859-1"));
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSInteger.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSInteger.java
new file mode 100644 (file)
index 0000000..6793162
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents an integer number in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class COSInteger extends COSNumber
+{
+
+    /**
+     * The lowest integer to be kept in the {@link #STATIC} array.
+     */
+    private static int LOW = -100;
+
+    /**
+     * The highest integer to be kept in the {@link #STATIC} array.
+     */
+    private static int HIGH = 256;
+
+    /**
+     * Static instances of all COSIntegers in the range from {@link #LOW}
+     * to {@link #HIGH}.
+     */
+    private static final COSInteger[] STATIC = new COSInteger[HIGH - LOW + 1];
+
+    /**
+     * Constant for the number zero.
+     * @since Apache PDFBox 1.1.0
+     */
+    public static final COSInteger ZERO = get(0); 
+
+    /**
+     * Constant for the number one.
+     * @since Apache PDFBox 1.1.0
+     */
+    public static final COSInteger ONE = get(1); 
+
+    /**
+     * Constant for the number two.
+     * @since Apache PDFBox 1.1.0
+     */
+    public static final COSInteger TWO = get(2); 
+
+    /**
+     * Constant for the number three.
+     * @since Apache PDFBox 1.1.0
+     */
+    public static final COSInteger THREE = get(3); 
+
+    /**
+     * Returns a COSInteger instance with the given value.
+     *
+     * @param val integer value
+     * @return COSInteger instance
+     */
+    public static COSInteger get(long val) {
+        if (LOW <= val && val <= HIGH) {
+            int index = (int) val - LOW;
+            // no synchronization needed
+            if (STATIC[index] == null) {
+                STATIC[index] = new COSInteger(val);
+            }
+            return STATIC[index];
+        } else {
+            return new COSInteger(val);
+        }
+    }
+
+    private long value;
+
+    /**
+     * constructor.
+     *
+     * @deprecated use the static {@link #get(long)} method instead
+     * @param val The integer value of this object.
+     */
+    public COSInteger( long val )
+    {
+        value = val;
+    }
+
+    /**
+     * constructor.
+     *
+     * @deprecated use the static {@link #get(long)} method instead
+     * @param val The integer value of this object.
+     */
+    public COSInteger( int val )
+    {
+        this( (long)val );
+    }
+
+    /**
+     * This will create a new PDF Int object using a string.
+     *
+     * @param val The string value of the integer.
+     * @deprecated use the static {@link #get(long)} method instead
+     * @throws IOException If the val is not an integer type.
+     */
+    public COSInteger( String val ) throws IOException
+    {
+        try
+        {
+            value = Long.parseLong( val );
+        }
+        catch( NumberFormatException e )
+        {
+            throw new IOException( "Error: value is not an integer type actual='" + val + "'" );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object o)
+    {
+        return o instanceof COSInteger && ((COSInteger)o).intValue() == intValue();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        //taken from java.lang.Long
+        return (int)(value ^ (value >> 32));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "COSInt{" + value + "}";
+    }
+
+    /**
+     * Change the value of this reference.
+     *
+     * @param newValue The new value.
+     */
+    public void setValue( long newValue )
+    {
+        value = newValue;
+    }
+
+    /**
+     * polymorphic access to value as float.
+     *
+     * @return The float value of this object.
+     */
+    public float floatValue()
+    {
+        return value;
+    }
+
+    /**
+     * polymorphic access to value as float.
+     *
+     * @return The double value of this object.
+     */
+    public double doubleValue()
+    {
+        return value;
+    }
+
+    /**
+     * Polymorphic access to value as int
+     * This will get the integer value of this object.
+     *
+     * @return The int value of this object,
+     */
+    public int intValue()
+    {
+        return (int)value;
+    }
+
+    /**
+     * Polymorphic access to value as int
+     * This will get the integer value of this object.
+     *
+     * @return The int value of this object,
+     */
+    public long longValue()
+    {
+        return value;
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept(ICOSVisitor visitor) throws COSVisitorException
+    {
+        return visitor.visitFromInt(this);
+    }
+
+    /**
+     * This will output this string as a PDF object.
+     *
+     * @param output The stream to write to.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void writePDF( OutputStream output ) throws IOException
+    {
+        output.write(String.valueOf(value).getBytes("ISO-8859-1"));
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSName.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSName.java
new file mode 100644 (file)
index 0000000..d883c51
--- /dev/null
@@ -0,0 +1,1382 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.persistence.util.COSHEXTable;
+
+
+/**
+ * This class represents a PDF named object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.42 $
+ */
+public final class COSName extends COSBase implements Comparable<COSName>
+{
+    /**
+     * Note: This is a ConcurrentHashMap because a HashMap must be synchronized if accessed by
+     * multiple threads.
+     */
+    private static Map<String, COSName> nameMap = new ConcurrentHashMap<String, COSName>(8192);
+
+    /**
+     * All common COSName values are stored in a simple HashMap. They are already defined as
+     * static constants and don't need to be synchronized for multithreaded environments.
+     */
+    private static Map<String, COSName> commonNameMap =
+        new HashMap<String, COSName>();
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName A = new COSName( "A" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName AA = new COSName( "AA" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName ACRO_FORM = new COSName( "AcroForm" );
+    /**
+     * "ActualText"
+     */
+    public static final COSName ACTUAL_TEXT = new COSName("ActualText");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName AIS = new COSName( "AIS" );
+    /**
+     * "Alt"
+     */
+    public static final COSName ALT = new COSName("Alt");
+    /**
+     * "Alt"
+     */
+    public static final COSName ALTERNATE = new COSName("Alternate");
+    /**
+    * A common COSName value.
+    */
+    public static final COSName ANNOT = new COSName( "Annot" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ANNOTS = new COSName( "Annots" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ANTI_ALIAS = new COSName( "AntiAlias" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName AP_REF = new COSName( "APRef" );
+    /**
+     * "Artifact"
+     */
+    public static final COSName ARTIFACT = new COSName("Artifact");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ART_BOX = new COSName("ArtBox" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName AS = new COSName( "AS" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ASCII85_DECODE = new COSName( "ASCII85Decode" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName ASCII85_DECODE_ABBREVIATION = new COSName( "A85" );
+    /**
+     * "Attached"
+     */
+    public static final COSName ATTACHED = new COSName("Attached");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ASCENT = new COSName( "Ascent" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ASCII_HEX_DECODE = new COSName( "ASCIIHexDecode" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName ASCII_HEX_DECODE_ABBREVIATION = new COSName( "AHx" );
+
+    /** "AP" */
+    public static final COSName AP = new COSName( "AP" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName AUTHOR = new COSName( "Author" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName AVG_WIDTH = new COSName( "AvgWidth" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName B = new COSName( "B" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName BASE_ENCODING = new COSName( "BaseEncoding" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName BASE_FONT = new COSName( "BaseFont" );
+
+    /** the COSName for "BaseState". */
+    public static final COSName BASE_STATE = new COSName( "BaseState" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BBOX = new COSName( "BBox" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BLACK_IS_1 = new COSName( "BlackIs1" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BLACK_POINT = new COSName( "BlackPoint" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BLEED_BOX = new COSName("BleedBox" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BITS_PER_COMPONENT = new COSName("BitsPerComponent" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BITS_PER_SAMPLE = new COSName("BitsPerSample" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BOUNDS = new COSName("Bounds" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BPC = new COSName("BPC" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CATALOG = new COSName( "Catalog" );
+    /**
+     * "C"
+     */
+    public static final COSName C = new COSName("C");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName C0 = new COSName( "C0" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName C1 = new COSName( "C1" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CA = new COSName( "CA" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CA_NS = new COSName( "ca" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CALGRAY = new COSName( "CalGray" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CALRGB = new COSName( "CalRGB" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CAP_HEIGHT = new COSName( "CapHeight" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CCITTFAX_DECODE = new COSName( "CCITTFaxDecode" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CCITTFAX_DECODE_ABBREVIATION = new COSName( "CCF" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CF = new COSName( "CF" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CFM = new COSName( "CFM" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CHAR_PROCS = new COSName( "CharProcs" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CHAR_SET = new COSName( "CharSet" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CID_FONT_TYPE0 = new COSName( "CIDFontType0" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CID_FONT_TYPE2 = new COSName( "CIDFontType2" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName CIDSYSTEMINFO = new COSName( "CIDSystemInfo" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName COLORANTS = new COSName( "Colorants" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName COLORS = new COSName( "Colors" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName COLORSPACE = new COSName( "ColorSpace" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName COLUMNS = new COSName( "Columns" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CONTENTS = new COSName( "Contents" );
+     /**
+      * A common COSName value.
+      */
+    public static final COSName COORDS = new COSName( "Coords" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName COUNT = new COSName( "Count" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CLR_F = new COSName( "ClrF" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CLR_FF = new COSName( "ClrFf" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CREATION_DATE = new COSName( "CreationDate" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CREATOR = new COSName( "Creator" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CROP_BOX = new COSName(  "CropBox" );
+    /**
+     * The Crypt filter.
+     */
+    public static final COSName CRYPT = new COSName( "Crypt" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName CS = new COSName(  "CS" );
+
+    /** "D" */
+    public static final COSName D = new COSName( "D" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DA = new COSName( "DA" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DCT_DECODE = new COSName( "DCTDecode" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DCT_DECODE_ABBREVIATION = new COSName( "DCT" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DECODE = new COSName( "Decode" );
+    /** "DecodeParms" */
+    public static final COSName DECODE_PARMS = new COSName( "DecodeParms" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DESCENT = new COSName(  "Descent" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DESCENDANT_FONTS = new COSName(  "DescendantFonts" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DEST = new COSName(  "Dest" );
+
+    /** "Dests" */
+    public static final COSName DESTS = new COSName( "Dests" );
+
+    /**
+    * A common COSName value.
+    */
+    public static final COSName DEVICECMYK = new COSName( "DeviceCMYK" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName DEVICEGRAY = new COSName( "DeviceGray" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName DEVICEN = new COSName( "DeviceN" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName DEVICERGB = new COSName( "DeviceRGB" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DIFFERENCES = new COSName( "Differences" );
+
+    /** "DL" */
+    public static final COSName DL = new COSName( "DL" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DOMAIN = new COSName( "Domain" );
+
+     /** "DP" */
+    public static final COSName DP = new COSName( "DP" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DR = new COSName( "DR" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DV = new COSName( "DV" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName DW = new COSName( "DW" );
+
+    /**
+     * "E"
+     */
+    public static final COSName E = new COSName("E");
+
+    /** "EmbeddedFiles" */
+    public static final COSName EMBEDDED_FILES = new COSName( "EmbeddedFiles" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ENCODE = new COSName( "Encode" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ENCODING = new COSName( "Encoding" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ENCODING_90MS_RKSJ_H = new COSName( "90ms-RKSJ-H" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ENCODING_90MS_RKSJ_V = new COSName( "90ms-RKSJ-V" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ENCODING_ETEN_B5_H = new COSName( "ETen?B5?H" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ENCODING_ETEN_B5_V = new COSName( "ETen?B5?V" );
+
+    /** "Encrypt" */
+    public static final COSName ENCRYPT = new COSName( "Encrypt" );
+
+    /** "EncryptMetaData" */
+    public static final COSName ENCRYPT_META_DATA = new COSName( "EncryptMetadata" );
+    
+    /** "ExtGState" */
+    public static final COSName EXT_G_STATE = new COSName( "ExtGState" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName EXTEND = new COSName( "Extend" );
+
+    /** "Extends" */
+    public static final COSName EXTENDS = new COSName( "Extends" );
+
+    /** "F" */
+    public static final COSName F = new COSName( "F" );
+
+    /** "FDecodeParms" */
+    public static final COSName F_DECODE_PARMS = new COSName( "FDecodeParms" );
+
+    /** "FFilter" */
+    public static final COSName F_FILTER = new COSName( "FFilter" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FF = new COSName( "Ff" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FIELDS = new COSName( "Fields" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FILTER = new COSName( "Filter" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FIRST_CHAR = new COSName( "FirstChar" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FL = new COSName( "FL" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FLAGS = new COSName( "Flags" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FLATE_DECODE = new COSName( "FlateDecode" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FLATE_DECODE_ABBREVIATION = new COSName( "Fl" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FONT = new COSName( "Font" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_BBOX = new COSName( "FontBBox" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_FAMILY = new COSName("FontFamily");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_FILE = new COSName("FontFile");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_FILE2 = new COSName("FontFile2");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_FILE3 = new COSName("FontFile3");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_DESC = new COSName("FontDescriptor");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_MATRIX = new COSName("FontMatrix" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FONT_NAME = new COSName("FontName" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FONT_STRETCH = new COSName("FontStretch" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FONT_WEIGHT = new COSName("FontWeight" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName FORM = new COSName( "Form" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FORMTYPE = new COSName( "FormType" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FRM = new COSName( "FRM" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FT = new COSName( "FT" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FUNCTION = new COSName( "Function" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FUNCTION_TYPE = new COSName( "FunctionType" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName FUNCTIONS = new COSName( "Functions" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName GAMMA = new COSName( "Gamma" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName H = new COSName( "H" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName HEIGHT = new COSName( "Height" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName ICCBASED = new COSName( "ICCBased" );
+
+    /** "ID" */
+    public static final COSName ID = new COSName("ID");
+
+    /** "IDTree" */
+    public static final COSName ID_TREE = new COSName("IDTree");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName IDENTITY = new COSName( "Identity" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName IDENTITY_H = new COSName( "Identity-H" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName IMAGE = new COSName( "Image" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName IMAGE_MASK = new COSName( "ImageMask" );
+
+    /** "Index" */
+    public static final COSName INDEX = new COSName( "Index" );
+
+    /**
+    * A common COSName value.
+    */
+    public static final COSName INDEXED = new COSName( "Indexed" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName INFO = new COSName( "Info" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ITALIC_ANGLE = new COSName( "ItalicAngle" );
+
+    /** "JavaScript" */
+    public static final COSName JAVA_SCRIPT = new COSName( "JavaScript" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName JBIG2_DECODE = new COSName( "JBIG2Decode" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName JPX_DECODE = new COSName( "JPXDecode" );
+
+    /** "K" */
+    public static final COSName K = new COSName("K");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName KEYWORDS = new COSName( "Keywords" );
+
+    /** "Kids" */
+    public static final COSName KIDS = new COSName( "Kids" );
+
+    /**
+    * A common COSName value.
+    */
+    public static final COSName LAB = new COSName( "Lab" );
+
+    /** "Lang" */
+    public static final COSName LANG = new COSName("Lang");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName LAST_CHAR = new COSName( "LastChar" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName LC = new COSName( "LC" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName LEADING = new COSName( "Leading" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName LENGTH = new COSName( "Length" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName LENGTH1 = new COSName( "Length1" );
+
+    /** "Limits" */
+    public static final COSName LIMITS = new COSName( "Limits" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName LJ = new COSName( "LJ" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName LW = new COSName( "LW" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName LZW_DECODE = new COSName( "LZWDecode" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName LZW_DECODE_ABBREVIATION = new COSName( "LZW" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName M = new COSName( "M" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName MAC_ROMAN_ENCODING = new COSName( "MacRomanEncoding" );
+
+    /** "MarkInfo" */
+    public static final COSName MARK_INFO = new COSName("MarkInfo");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName MASK = new COSName( "Mask" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName MATRIX = new COSName( "Matrix" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName MAX_WIDTH = new COSName( "MaxWidth" );
+    /**
+     * "MCID"
+     */
+    public static final COSName MCID = new COSName("MCID");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName MEDIA_BOX = new COSName(  "MediaBox" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName METADATA = new COSName(  "Metadata" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName MISSING_WIDTH = new COSName( "MissingWidth" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ML = new COSName( "ML" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName MM_TYPE1 = new COSName(  "MMType1" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName MOD_DATE = new COSName(  "ModDate" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName N = new COSName( "N" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName NAME = new COSName( "Name" );
+
+    /** "Names" */
+    public static final COSName NAMES = new COSName( "Names" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName NEXT = new COSName( "Next" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName NM = new COSName( "NM" );
+    /** "Nums" */
+    public static final COSName NUMS = new COSName( "Nums" );
+
+    /**
+     * "O"
+     */
+    public static final COSName O = new COSName("O");
+
+    /**
+     * "Obj"
+     */
+    public static final COSName OBJ = new COSName("Obj");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName OBJ_STM = new COSName( "ObjStm" );
+
+    /** the COSName for the content group tag. */
+    public static final COSName OC = new COSName("OC");
+    /** the COSName for an optional content group. */
+    public static final COSName OCG = new COSName("OCG");
+    /** the COSName for the optional content group list. */
+    public static final COSName OCGS = new COSName("OCGs");
+    /** the COSName for the optional content properties. */
+    public static final COSName OCPROPERTIES = new COSName("OCProperties");
+
+    /** the COSName for the "OFF" value. */
+    public static final COSName OFF = new COSName("OFF");
+    /** the COSName for the "ON" value. */
+    public static final COSName ON = new COSName("ON");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName OP = new COSName( "OP" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName OP_NS = new COSName( "op" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName OPM = new COSName( "OPM" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName OPT = new COSName( "Opt" );
+    /** "Outlines" */
+    public static final COSName OUTLINES = new COSName("Outlines");
+
+    /** "OpenAction" */
+    public static final COSName OPEN_ACTION = new COSName("OpenAction");
+
+    /** A common COSName value. */
+     public static final COSName ORDER = new COSName( "Order" );
+
+    /**
+     * A common COSName value.
+     */
+     public static final COSName ORDERING = new COSName( "Ordering" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName P = new COSName( "P" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName PAGE = new COSName( "Page" );
+
+    /** "PageLabels" */
+    public static final COSName PAGE_LABELS = new COSName("PageLabels");
+
+    /** "PageLayout" */
+    public static final COSName PAGE_LAYOUT = new COSName("PageLayout");
+
+    /** "PageMode" */
+    public static final COSName PAGE_MODE = new COSName("PageMode");
+
+    /**
+    * A common COSName value.
+    */
+    public static final COSName PAGES = new COSName( "Pages" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName PARENT = new COSName( "Parent" );
+    /**
+     * "ParentTreeNextKey"
+     */
+    public static final COSName PARENT_TREE_NEXT_KEY = new COSName("ParentTreeNextKey");
+    /**
+    * A common COSName value.
+    */
+    public static final COSName PATTERN = new COSName( "Pattern" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName PDF_DOC_ENCODING = new COSName( "PDFDocEncoding" );
+    /**
+     * "Pg"
+     */
+    public static final COSName PG = new COSName("Pg");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName PREDICTOR = new COSName( "Predictor" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName PREV = new COSName( "Prev" );
+
+    /** The COSName value for "ProcSet". */
+    public static final COSName PROC_SET = new COSName( "ProcSet" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName PRODUCER = new COSName( "Producer" );
+
+    /** The COSName value for "Properties". */
+    public static final COSName PROPERTIES = new COSName( "Properties" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName Q = new COSName( "Q" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName R = new COSName( "R" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName RANGE = new COSName( "Range" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName RECIPIENTS = new COSName( "Recipients" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName RECT = new COSName( "Rect" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName REGISTRY = new COSName( "Registry" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName RESOURCES = new COSName( "Resources" );
+    /**
+     * "RoleMap"
+     */
+    public static final COSName RI = new COSName( "RI" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ROLE_MAP = new COSName("RoleMap");
+    /**
+    * A common COSName value.
+    */
+    public static final COSName ROOT = new COSName( "Root" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ROTATE = new COSName(  "Rotate" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName ROWS = new COSName(  "Rows" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName RUN_LENGTH_DECODE = new COSName( "RunLengthDecode" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName RUN_LENGTH_DECODE_ABBREVIATION = new COSName( "RL" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName RV = new COSName( "RV" );
+    /**
+     * "S"
+     */
+    public static final COSName S = new COSName("S");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SA = new COSName( "SA" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SE = new COSName( "SE" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SEPARATION = new COSName( "Separation" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SET_F = new COSName( "SetF" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SET_FF = new COSName( "SetFf" );
+
+    /** "Shading" */
+    public static final COSName SHADING = new COSName( "Shading" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SHADING_TYPE = new COSName( "ShadingType" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SM = new COSName( "SM" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SMASK = new COSName( "SMask" );
+    /** "Size" */
+    public static final COSName SIZE = new COSName( "Size" );
+
+    /**
+    * A common COSName value.
+    */
+    public static final COSName STANDARD_ENCODING = new COSName( "StandardEncoding" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName STD_CF = new COSName( "StdCF" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName STEM_H = new COSName( "StemH" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName STEM_V = new COSName( "StemV" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName STM_F = new COSName( "StmF" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName STR_F = new COSName( "StrF" );
+
+    /** "StructTreeRoot" */
+    public static final COSName STRUCT_TREE_ROOT = new COSName("StructTreeRoot");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SUB_FILTER = new COSName( "SubFilter" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName SUBJ = new COSName( "Subj" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SUBJECT = new COSName( "Subject" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SUPPLEMENT = new COSName( "Supplement" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SUBTYPE = new COSName( "Subtype" );
+
+    /**
+     * "T"
+     */
+    public static final COSName T = new COSName("T");
+
+    /** "Threads" */
+    public static final COSName THREADS = new COSName("Threads");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName TITLE = new COSName( "Title" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName TK = new COSName( "TK" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName TRAPPED = new COSName("Trapped" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName TRIM_BOX = new COSName("TrimBox" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName TRUE_TYPE = new COSName("TrueType" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName TO_UNICODE = new COSName( "ToUnicode" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName TU = new COSName("TU" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName TYPE = new COSName( "Type" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName TYPE0 = new COSName(  "Type0" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName TYPE1 = new COSName(  "Type1" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName TYPE3 = new COSName(  "Type3" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName U = new COSName( "U" );
+    /** the COSName for the "Unchanged" value. */
+    public static final COSName UNCHANGED = new COSName("Unchanged");
+    /** "URI" */
+    public static final COSName URI = new COSName("URI");
+
+    /**
+    * A common COSName value.
+    */
+    public static final COSName V = new COSName( "V" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName VERSION = new COSName( "Version" );
+
+    /** "ViewerPreferences" */
+    public static final COSName VIEWER_PREFERENCES = new COSName("ViewerPreferences");
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName W = new COSName( "W" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName WIDTH = new COSName( "Width" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName WIDTHS = new COSName( "Widths" );
+    /**
+    * A common COSName value.
+    */
+    public static final COSName WIN_ANSI_ENCODING = new COSName( "WinAnsiEncoding" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName WHITE_POINT = new COSName( "WhitePoint" );
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName XHEIGHT = new COSName( "XHeight" );
+
+    /** "XObject" */
+    public static final COSName XOBJECT = new COSName( "XObject" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName XREF = new COSName( "XRef" );
+    /**
+     * The prefix to a PDF name.
+     */
+    public static final byte[] NAME_PREFIX = new byte[] { 47  }; // The / character
+    /**
+     * The escape character for a name.
+     */
+    public static final byte[] NAME_ESCAPE = new byte[] { 35  };  //The # character
+
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SUBFILTER = new COSName("SubFilter");
+    /**
+     * A signature filter value.
+     */
+    public static final COSName ADOBE_PPKLITE = new COSName("Adobe.PPKLite");
+    /**
+     * A signature filter value.
+     */
+    public static final COSName ENTRUST_PPKEF = new COSName("Entrust.PPKEF");
+    /**
+     * A signature filter value.
+     */
+    public static final COSName CICI_SIGNIT = new COSName("CICI.SignIt");
+    /**
+     * A signature filter value.
+     */
+    public static final COSName VERISIGN_PPKVS = new COSName("VeriSign.PPKVS");
+    /**
+     * A signature subfilter value.
+     */
+    public static final COSName ADBE_X509_RSA_SHA1 = new COSName("adbe.x509.rsa_sha1");
+    /**
+     * A signature subfilter value.
+     */
+    public static final COSName ADBE_PKCS7_DETACHED = new COSName("adbe.pkcs7.detached");
+    /**
+     * A signature subfilter value.www
+     */
+    public static final COSName ADBE_PKCS7_SHA1 = new COSName("adbe.pkcs7.sha1");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName LOCATION = new COSName("Location");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName REASON = new COSName("Reason");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName BYTERANGE = new COSName("ByteRange");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SIG = new COSName("Sig");
+    /**
+     * A common COSName value.
+     */
+    public static final COSName SIG_FLAGS = new COSName("SigFlags");
+
+    
+    private String name;
+    private int hashCode;
+
+    /**
+     * This will get a COSName object with that name.
+     *
+     * @param aName The name of the object.
+     *
+     * @return A COSName with the specified name.
+     */
+    public static final COSName getPDFName( String aName )
+    {
+        COSName name = null;
+        if( aName != null )
+        {
+            // Is it a common COSName ??
+            name = commonNameMap.get( aName );
+            if( name == null )
+            {
+                // It seems to be a document specific COSName
+                name = nameMap.get( aName );
+                if( name == null )
+                {
+                    //name is added to the synchronized map in the constructor
+                    name = new COSName( aName, false );
+                }
+            }
+        }
+        return name;
+    }
+
+    /**
+     * Private constructor.  This will limit the number of COSName objects.
+     * that are created.
+     *
+     * @param aName The name of the COSName object.
+     * @param staticValue Indicates if the COSName object is static so that it can
+     *        be stored in the HashMap without synchronizing.
+     */
+    private COSName( String aName, boolean staticValue )
+    {
+        name = aName;
+        if ( staticValue )
+        {
+            commonNameMap.put( aName, this);
+        }
+        else
+        {
+            nameMap.put( aName, this );
+        }
+        hashCode = name.hashCode();
+    }
+
+    /**
+     * Private constructor.  This will limit the number of COSName objects.
+     * that are created.
+     *
+     * @param aName The name of the COSName object.
+     */
+    private COSName( String aName )
+    {
+        this( aName, true );
+    }
+
+    /**
+     * This will get the name of this COSName object.
+     *
+     * @return The name of the object.
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "COSName{" + name + "}";
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals( Object o )
+    {
+        boolean retval = this == o;
+        if( !retval && o instanceof COSName )
+        {
+            COSName other = (COSName)o;
+            retval = name == other.name || name.equals( other.name );
+        }
+        return retval;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return hashCode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(COSName other)
+    {
+        return this.name.compareTo( other.name );
+    }
+
+
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept(ICOSVisitor  visitor) throws COSVisitorException
+    {
+        return visitor.visitFromName(this);
+    }
+
+    /**
+     * This will output this string as a PDF object.
+     *
+     * @param output The stream to write to.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void writePDF( OutputStream output ) throws IOException
+    {
+        output.write(NAME_PREFIX);
+        byte[] bytes = getName().getBytes("ISO-8859-1");
+        for (int i = 0; i < bytes.length;i++)
+        {
+            int current = ((bytes[i]+256)%256);
+
+            if(current <= 32 || current >= 127 ||
+               current == '(' ||
+               current == ')' ||
+               current == '[' ||
+               current == ']' ||
+               current == '/' ||
+               current == '%' ||
+               current == '<' ||
+               current == '>' ||
+               current == NAME_ESCAPE[0] )
+            {
+                output.write(NAME_ESCAPE);
+                output.write(COSHEXTable.TABLE[current]);
+            }
+            else
+            {
+                output.write(current);
+            }
+        }
+    }
+
+     /**
+      * Not usually needed except if resources need to be reclaimed in a ong
+      * running process.
+      * Patch provided by flester@GMail.com
+      * incorporated 5/23/08, Danielwilson@users.SourceForge.net
+      */
+     public static synchronized void clearResources()
+     {
+         // Clear them all
+         nameMap.clear();
+     }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSNull.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSNull.java
new file mode 100644 (file)
index 0000000..0e27b56
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.cos;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents a null PDF object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ */
+public class COSNull extends COSBase
+{
+    /**
+     * The null token.
+     */
+    public static final byte[] NULL_BYTES = new byte[] {110, 117, 108, 108}; //"null".getBytes( "ISO-8859-1" );
+
+    /**
+     * The one null object in the system.
+     */
+    public static final COSNull NULL = new COSNull();
+
+    /**
+     * Constructor.
+     */
+    private COSNull()
+    {
+        //limit creation to one instance.
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept( ICOSVisitor  visitor ) throws COSVisitorException
+    {
+        return visitor.visitFromNull( this );
+    }
+
+    /**
+     * This will output this string as a PDF object.
+     *
+     * @param output The stream to write to.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void writePDF( OutputStream output ) throws IOException
+    {
+        output.write(NULL_BYTES);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSNumber.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSNumber.java
new file mode 100644 (file)
index 0000000..03d44c8
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.IOException;
+
+/**
+ * This class represents an abstract number in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public abstract class COSNumber extends COSBase
+{
+
+    /**
+     * @deprecated Use the {@link COSInteger#ZERO} constant instead
+     */
+    public static final COSInteger ZERO = COSInteger.ZERO;
+
+    /**
+     * @deprecated Use the {@link COSInteger#ONE} constant instead
+     */
+    public static final COSInteger ONE = COSInteger.ONE;
+
+    /**
+     * This will get the float value of this number.
+     *
+     * @return The float value of this object.
+     */
+    public abstract float floatValue();
+
+    /**
+     * This will get the double value of this number.
+     *
+     * @return The double value of this number.
+     */
+    public abstract double doubleValue();
+
+    /**
+     * This will get the integer value of this number.
+     *
+     * @return The integer value of this number.
+     */
+    public abstract int intValue();
+
+    /**
+     * This will get the long value of this number.
+     *
+     * @return The long value of this number.
+     */
+    public abstract long longValue();
+
+    /**
+     * This factory method will get the appropriate number object.
+     *
+     * @param number The string representation of the number.
+     *
+     * @return A number object, either float or int.
+     *
+     * @throws IOException If the string is not a number.
+     */
+    public static COSNumber get( String number ) throws IOException
+    {
+        if (number.length() == 1) {
+            char digit = number.charAt(0);
+            if ('0' <= digit && digit <= '9') {
+                return COSInteger.get(digit - '0');
+            } else if (digit == '-' || digit == '.') {
+                // See https://issues.apache.org/jira/browse/PDFBOX-592
+                return COSInteger.ZERO;
+            } else {
+                throw new IOException("Not a number: " + number);
+            }
+        } else if (number.indexOf('.') == -1) {
+            try
+            {
+                return COSInteger.get( Long.parseLong( number ) );
+            }
+            catch( NumberFormatException e )
+            {
+                throw new IOException( "Value is not an integer: " + number );
+            }
+        } else {
+            return new COSFloat(number);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSObject.java
new file mode 100644 (file)
index 0000000..18ac8d1
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * 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.pdfbox.cos;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import java.io.IOException;
+
+/**
+ * This class represents a PDF object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.37 $
+ */
+public class COSObject extends COSBase
+{
+    private COSBase baseObject;
+    private COSInteger objectNumber;
+    private COSInteger generationNumber;
+
+    /**
+     * Constructor.
+     *
+     * @param object The object that this encapsulates.
+     *
+     * @throws IOException If there is an error with the object passed in.
+     */
+    public COSObject( COSBase object ) throws IOException
+    {
+        setObject( object );
+    }
+
+    /**
+     * This will get the dictionary object in this object that has the name key and
+     * if it is a pdfobjref then it will dereference that and return it.
+     *
+     * @param key The key to the value that we are searching for.
+     *
+     * @return The pdf object that matches the key.
+     */
+    public COSBase getDictionaryObject( COSName key )
+    {
+        COSBase retval =null;
+        if( baseObject instanceof COSDictionary )
+        {
+            retval = ((COSDictionary)baseObject).getDictionaryObject( key );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the dictionary object in this object that has the name key.
+     *
+     * @param key The key to the value that we are searching for.
+     *
+     * @return The pdf object that matches the key.
+     */
+    public COSBase getItem( COSName key )
+    {
+        COSBase retval =null;
+        if( baseObject instanceof COSDictionary )
+        {
+            retval = ((COSDictionary)baseObject).getItem( key );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the object that this object encapsulates.
+     *
+     * @return The encapsulated object.
+     */
+    public COSBase getObject()
+    {
+        return baseObject;
+    }
+
+    /**
+     * This will set the object that this object encapsulates.
+     *
+     * @param object The new object to encapsulate.
+     *
+     * @throws IOException If there is an error setting the updated object.
+     */
+    public void setObject( COSBase object ) throws IOException
+    {
+        baseObject = object;
+        /*if( baseObject == null )
+        {
+            baseObject = object;
+        }
+        else
+        {
+            //This is for when an object appears twice in the
+            //pdf file we really want to replace it such that
+            //object references still work correctly.
+            //see owcp-as-received.pdf for an example
+            if( baseObject instanceof COSDictionary )
+            {
+                COSDictionary dic = (COSDictionary)baseObject;
+                COSDictionary dicObject = (COSDictionary)object;
+                dic.clear();
+                dic.addAll( dicObject );
+            }
+            else if( baseObject instanceof COSArray )
+            {
+                COSArray array = (COSArray)baseObject;
+                COSArray arrObject = (COSArray)object;
+                array.clear();
+                for( int i=0; i<arrObject.size(); i++ )
+                {
+                    array.add( arrObject.get( i ) );
+                }
+            }
+            else if( baseObject instanceof COSStream )
+            {
+                COSStream oldStream = (COSStream)baseObject;
+                System.out.println( "object:" +  object.getClass().getName() );
+                COSStream newStream = (COSStream)object;
+                oldStream.replaceWithStream( newStream );
+            }
+            else if( baseObject instanceof COSInteger )
+            {
+                COSInteger oldInt = (COSInteger)baseObject;
+                COSInteger newInt = (COSInteger)object;
+                oldInt.setValue( newInt.longValue() );
+            }
+            else if( baseObject == null )
+            {
+                baseObject = object;
+            }
+            else
+            {
+                throw new IOException( "Unknown object substitution type:" + baseObject );
+            }
+        }*/
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "COSObject{" +
+            (objectNumber == null ? "unknown" : "" + objectNumber.intValue() ) + ", " +
+            (generationNumber == null ? "unknown" : "" + generationNumber.intValue() ) +
+            "}";
+    }
+
+    /** Getter for property objectNumber.
+     * @return Value of property objectNumber.
+     */
+    public COSInteger getObjectNumber()
+    {
+        return objectNumber;
+    }
+
+    /** Setter for property objectNumber.
+     * @param objectNum New value of property objectNumber.
+     */
+    public void setObjectNumber(COSInteger objectNum)
+    {
+        objectNumber = objectNum;
+    }
+
+    /** Getter for property generationNumber.
+     * @return Value of property generationNumber.
+     */
+    public COSInteger getGenerationNumber()
+    {
+        return generationNumber;
+    }
+
+    /** Setter for property generationNumber.
+     * @param generationNumberValue New value of property generationNumber.
+     */
+    public void setGenerationNumber(COSInteger generationNumberValue)
+    {
+        generationNumber = generationNumberValue;
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept( ICOSVisitor visitor ) throws COSVisitorException
+    {
+        return getObject() != null ? getObject().accept( visitor ) : COSNull.NULL.accept( visitor );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSStream.java
new file mode 100644 (file)
index 0000000..7d619be
--- /dev/null
@@ -0,0 +1,445 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.util.List;
+
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterManager;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.io.RandomAccessFileInputStream;
+import org.apache.pdfbox.io.RandomAccessFileOutputStream;
+
+/**
+ * This class represents a stream object in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.41 $
+ */
+public class COSStream extends COSDictionary
+{
+    private static final int BUFFER_SIZE=16384;
+
+    private RandomAccess file;
+    /**
+     * The stream with all of the filters applied.
+     */
+    private RandomAccessFileOutputStream filteredStream;
+
+    /**
+     * The stream with no filters, this contains the useful data.
+     */
+    private RandomAccessFileOutputStream unFilteredStream;
+
+    /**
+     * Constructor.  Creates a new stream with an empty dictionary.
+     *
+     * @param storage The intermediate storage for the stream.
+     */
+    public COSStream( RandomAccess storage )
+    {
+        super();
+        file = storage;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dictionary The dictionary that is associated with this stream.
+     * @param storage The intermediate storage for the stream.
+     */
+    public COSStream( COSDictionary dictionary, RandomAccess storage )
+    {
+        super( dictionary );
+        file = storage;
+    }
+
+    /**
+     * This will replace this object with the data from the new object.  This
+     * is used to easily maintain referential integrity when changing references
+     * to new objects.
+     *
+     * @param stream The stream that have the new values in it.
+     */
+    public void replaceWithStream( COSStream stream )
+    {
+        this.clear();
+        this.addAll( stream );
+        file = stream.file;
+        filteredStream = stream.filteredStream;
+        unFilteredStream = stream.unFilteredStream;
+    }
+
+    /**
+     * This will get the scratch file associated with this stream.
+     *
+     * @return The scratch file where this stream is being stored.
+     */
+    public RandomAccess getScratchFile()
+    {
+        return file;
+    }
+
+    /**
+     * This will get all the tokens in the stream.
+     *
+     * @return All of the tokens in the stream.
+     *
+     * @throws IOException If there is an error parsing the stream.
+     */
+    public List<Object> getStreamTokens() throws IOException
+    {
+        PDFStreamParser parser = new PDFStreamParser( this );
+        parser.parse();
+        return parser.getTokens();
+    }
+
+    /**
+     * This will get the stream with all of the filters applied.
+     *
+     * @return the bytes of the physical (endoced) stream
+     *
+     * @throws IOException when encoding/decoding causes an exception
+     */
+    public InputStream getFilteredStream() throws IOException
+    {
+        if( filteredStream == null )
+        {
+            doEncode();
+        }
+        long position = filteredStream.getPosition();
+        long length = filteredStream.getLength();
+
+        RandomAccessFileInputStream input =
+            new RandomAccessFileInputStream( file, position, length );
+        return new BufferedInputStream( input, BUFFER_SIZE );
+    }
+
+    /**
+     * This will get the logical content stream with none of the filters.
+     *
+     * @return the bytes of the logical (decoded) stream
+     *
+     * @throws IOException when encoding/decoding causes an exception
+     */
+    public InputStream getUnfilteredStream() throws IOException
+    {
+        InputStream retval = null;
+        if( unFilteredStream == null )
+        {
+            doDecode();
+        }
+
+        //if unFilteredStream is still null then this stream has not been
+        //created yet, so we should return null.
+        if( unFilteredStream != null )
+        {
+            long position = unFilteredStream.getPosition();
+            long length = unFilteredStream.getLength();
+            RandomAccessFileInputStream input =
+                new RandomAccessFileInputStream( file, position, length );
+            retval = new BufferedInputStream( input, BUFFER_SIZE );
+        }
+        else
+        {
+            // We should check if the COSStream contains data, maybe it
+            // has been created with a RandomAccessFile - which is not
+            // necessary empty.
+            // In this case, the creation was been done as an input, this should
+            // be the unfiltered file, since no filter has been applied yet.
+//            if ( (file != null) &&
+//                    (file.length() > 0) )
+//            {
+//                retval = new RandomAccessFileInputStream( file,
+//                                                          0,
+//                                                          file.length() );
+//            }
+//            else
+//            {
+                //if there is no stream data then simply return an empty stream.
+                retval = new ByteArrayInputStream( new byte[0] );
+//            }
+        }
+        return retval;
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept(ICOSVisitor visitor) throws COSVisitorException
+    {
+        return visitor.visitFromStream(this);
+    }
+
+    /**
+     * This will decode the physical byte stream applying all of the filters to the stream.
+     *
+     * @throws IOException If there is an error applying a filter to the stream.
+     */
+    private void doDecode() throws IOException
+    {
+// FIXME: We shouldn't keep the same reference?
+        unFilteredStream = filteredStream;
+
+        COSBase filters = getFilters();
+        if( filters == null )
+        {
+            //then do nothing
+        }
+        else if( filters instanceof COSName )
+        {
+            doDecode( (COSName)filters, 0 );
+        }
+        else if( filters instanceof COSArray )
+        {
+            COSArray filterArray = (COSArray)filters;
+            for( int i=0; i<filterArray.size(); i++ )
+            {
+                COSName filterName = (COSName)filterArray.get( i );
+                doDecode( filterName, i );
+            }
+        }
+        else
+        {
+            throw new IOException( "Error: Unknown filter type:" + filters );
+        }
+    }
+
+    /**
+     * This will decode applying a single filter on the stream.
+     *
+     * @param filterName The name of the filter.
+     * @param filterIndex The index of the current filter.
+     *
+     * @throws IOException If there is an error parsing the stream.
+     */
+    private void doDecode( COSName filterName, int filterIndex ) throws IOException
+    {
+        FilterManager manager = getFilterManager();
+        Filter filter = manager.getFilter( filterName );
+        InputStream input;
+
+        boolean done = false;
+        IOException exception = null;
+        long position = unFilteredStream.getPosition();
+        long length = unFilteredStream.getLength();
+        // in case we need it later
+        long writtenLength = unFilteredStream.getLengthWritten();  
+
+        if( length == 0 )
+        {
+            //if the length is zero then don't bother trying to decode
+            //some filters don't work when attempting to decode
+            //with a zero length stream.  See zlib_error_01.pdf
+            unFilteredStream = new RandomAccessFileOutputStream( file );
+            done = true;
+        }
+        else
+        {
+            //ok this is a simple hack, sometimes we read a couple extra
+            //bytes that shouldn't be there, so we encounter an error we will just
+            //try again with one less byte.
+            for( int tryCount=0; !done && tryCount<5; tryCount++ )
+            {
+                try
+                {
+                    input = new BufferedInputStream(
+                        new RandomAccessFileInputStream( file, position, length ), BUFFER_SIZE );
+                    unFilteredStream = new RandomAccessFileOutputStream( file );
+                    filter.decode( input, unFilteredStream, this, filterIndex );
+                    done = true;
+                }
+                catch( IOException io )
+                {
+                    length--;
+                    exception = io;
+                }
+            }
+            if( !done )
+            {
+                //if no good stream was found then lets try again but with the
+                //length of data that was actually read and not length
+                //defined in the dictionary
+                length = writtenLength;
+                for( int tryCount=0; !done && tryCount<5; tryCount++ )
+                {
+                    try
+                    {
+                        input = new BufferedInputStream(
+                            new RandomAccessFileInputStream( file, position, length ), BUFFER_SIZE );
+                        unFilteredStream = new RandomAccessFileOutputStream( file );
+                        filter.decode( input, unFilteredStream, this, filterIndex );
+                        done = true;
+                    }
+                    catch( IOException io )
+                    {
+                        length--;
+                        exception = io;
+                    }
+                }
+            }
+        }
+        if( !done )
+        {
+            throw exception;
+        }
+    }
+
+    /**
+     * This will encode the logical byte stream applying all of the filters to the stream.
+     *
+     * @throws IOException If there is an error applying a filter to the stream.
+     */
+    private void doEncode() throws IOException
+    {
+        filteredStream = unFilteredStream;
+
+        COSBase filters = getFilters();
+        if( filters == null )
+        {
+            //there is no filter to apply
+        }
+        else if( filters instanceof COSName )
+        {
+            doEncode( (COSName)filters, 0 );
+        }
+        else if( filters instanceof COSArray )
+        {
+            // apply filters in reverse order
+            COSArray filterArray = (COSArray)filters;
+            for( int i=filterArray.size()-1; i>=0; i-- )
+            {
+                COSName filterName = (COSName)filterArray.get( i );
+                doEncode( filterName, i );
+            }
+        }
+    }
+
+    /**
+     * This will encode applying a single filter on the stream.
+     *
+     * @param filterName The name of the filter.
+     * @param filterIndex The index to the filter.
+     *
+     * @throws IOException If there is an error parsing the stream.
+     */
+    private void doEncode( COSName filterName, int filterIndex ) throws IOException
+    {
+        FilterManager manager = getFilterManager();
+        Filter filter = manager.getFilter( filterName );
+        InputStream input;
+
+        input = new BufferedInputStream(
+            new RandomAccessFileInputStream( file, filteredStream.getPosition(),
+                                                   filteredStream.getLength() ), BUFFER_SIZE );
+        filteredStream = new RandomAccessFileOutputStream( file );
+        filter.encode( input, filteredStream, this, filterIndex );
+    }
+
+    /**
+     * This will return the filters to apply to the byte stream.
+     * The method will return
+     * - null if no filters are to be applied
+     * - a COSName if one filter is to be applied
+     * - a COSArray containing COSNames if multiple filters are to be applied
+     *
+     * @return the COSBase object representing the filters
+     */
+    public COSBase getFilters()
+    {
+        return getDictionaryObject(COSName.FILTER);
+    }
+
+    /**
+     * This will create a new stream for which filtered byte should be
+     * written to.  You probably don't want this but want to use the
+     * createUnfilteredStream, which is used to write raw bytes to.
+     *
+     * @return A stream that can be written to.
+     *
+     * @throws IOException If there is an error creating the stream.
+     */
+    public OutputStream createFilteredStream() throws IOException
+    {
+        filteredStream = new RandomAccessFileOutputStream( file );
+        unFilteredStream = null;
+        return new BufferedOutputStream( filteredStream, BUFFER_SIZE );
+    }
+
+    /**
+     * This will create a new stream for which filtered byte should be
+     * written to.  You probably don't want this but want to use the
+     * createUnfilteredStream, which is used to write raw bytes to.
+     *
+     * @param expectedLength An entry where a length is expected.
+     *
+     * @return A stream that can be written to.
+     *
+     * @throws IOException If there is an error creating the stream.
+     */
+    public OutputStream createFilteredStream( COSBase expectedLength ) throws IOException
+    {
+        filteredStream = new RandomAccessFileOutputStream( file );
+        filteredStream.setExpectedLength( expectedLength );
+        unFilteredStream = null;
+        return new BufferedOutputStream( filteredStream, BUFFER_SIZE );
+    }
+
+    /**
+     * set the filters to be applied to the stream.
+     *
+     * @param filters The filters to set on this stream.
+     *
+     * @throws IOException If there is an error clearing the old filters.
+     */
+    public void setFilters(COSBase filters) throws IOException
+    {
+        setItem(COSName.FILTER, filters);
+        // kill cached filtered streams
+        filteredStream = null;
+    }
+
+    /**
+     * This will create an output stream that can be written to.
+     *
+     * @return An output stream which raw data bytes should be written to.
+     *
+     * @throws IOException If there is an error creating the stream.
+     */
+    public OutputStream createUnfilteredStream() throws IOException
+    {
+        unFilteredStream = new RandomAccessFileOutputStream( file );
+        filteredStream = null;
+        return new BufferedOutputStream( unFilteredStream, BUFFER_SIZE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSString.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/COSString.java
new file mode 100644 (file)
index 0000000..b10e04d
--- /dev/null
@@ -0,0 +1,459 @@
+/*
+ * 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.pdfbox.cos;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.persistence.util.COSHEXTable;
+
+/**
+ * This represents a string object in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.30 $
+ */
+public class COSString extends COSBase
+{
+    /**
+     * One of the open string tokens.
+     */
+    public static final byte[] STRING_OPEN = new byte[]{ 40 }; //"(".getBytes();
+    /**
+     * One of the close string tokens.
+     */
+    public static final byte[] STRING_CLOSE = new byte[]{ 41 }; //")".getBytes( "ISO-8859-1" );
+    /**
+     * One of the open string tokens.
+     */
+    public static final byte[] HEX_STRING_OPEN = new byte[]{ 60 }; //"<".getBytes( "ISO-8859-1" );
+    /**
+     * One of the close string tokens.
+     */
+    public static final byte[] HEX_STRING_CLOSE = new byte[]{ 62 }; //">".getBytes( "ISO-8859-1" );
+    /**
+     * the escape character in strings.
+     */
+    public static final byte[] ESCAPE = new byte[]{ 92 }; //"\\".getBytes( "ISO-8859-1" );
+
+    /**
+     * CR escape characters.
+     */
+    public static final byte[] CR_ESCAPE = new byte[]{ 92, 114 }; //"\\r".getBytes( "ISO-8859-1" );
+    /**
+     * LF escape characters.
+     */
+    public static final byte[] LF_ESCAPE = new byte[]{ 92, 110 }; //"\\n".getBytes( "ISO-8859-1" );
+    /**
+     * HT escape characters.
+     */
+    public static final byte[] HT_ESCAPE = new byte[]{ 92, 116 }; //"\\t".getBytes( "ISO-8859-1" );
+    /**
+     * BS escape characters.
+     */
+    public static final byte[] BS_ESCAPE = new byte[]{ 92, 98 }; //"\\b".getBytes( "ISO-8859-1" );
+    /**
+     * FF escape characters.
+     */
+    public static final byte[] FF_ESCAPE = new byte[]{ 92, 102 }; //"\\f".getBytes( "ISO-8859-1" );
+
+    private ByteArrayOutputStream out = null;
+    private String str = null;
+
+    /**
+     * Forces the string to be serialized in hex form but not literal form, the default is to stream
+     * in literal form.
+     */
+    private boolean forceHexForm = false;
+
+    /**
+     * Constructor.
+     */
+    public COSString()
+    {
+        out = new ByteArrayOutputStream();
+    }
+
+    /**
+     * Explicit constructor for ease of manual PDF construction.
+     *
+     * @param value The string value of the object.
+     */
+    public COSString( String value )
+    {
+        try
+        {
+            boolean unicode16 = false;
+            char[] chars = value.toCharArray();
+            int length = chars.length;
+            for( int i=0; i<length; i++ )
+            {
+                if( chars[i] > 255 )
+                {
+                    unicode16 = true;
+                    break;
+                }
+            }
+            if( unicode16 )
+            {
+                byte[] data = value.getBytes( "UTF-16BE" );
+                out = new ByteArrayOutputStream( data.length +2);
+                out.write( 0xFE );
+                out.write( 0xFF );
+                out.write( data );
+            }
+            else
+            {
+                byte[] data = value.getBytes("ISO-8859-1");
+                out = new ByteArrayOutputStream( data.length );
+                out.write( data );
+            }
+        }
+        catch (IOException ignore)
+        {
+            ignore.printStackTrace();
+            //should never happen
+        }
+    }
+
+    /**
+     * Explicit constructor for ease of manual PDF construction.
+     *
+     * @param value The string value of the object.
+     */
+    public COSString( byte[] value )
+    {
+        try
+        {
+            out = new ByteArrayOutputStream( value.length );
+            out.write( value );
+        }
+        catch (IOException ignore)
+        {
+            ignore.printStackTrace();
+            //should never happen
+        }
+    }
+
+    /**
+     * Forces the string to be written in literal form instead of hexadecimal form.
+     *
+     * @param v if v is true the string will be written in literal form, otherwise it will
+     * be written in hexa if necessary.
+     */
+
+    public void setForceLiteralForm(boolean v)
+    {
+        forceHexForm = !v;
+    }
+
+    /**
+     * Forces the string to be written in hexadecimal form instead of literal form.
+     * 
+     * @param v if v is true the string will be written in hexadecimal form otherwise it will be written in literal if
+     *          necessary.
+     */
+
+    public void setForceHexForm(boolean v)
+    {
+      forceHexForm = v;
+    }
+    
+    /**
+     * This will create a COS string from a string of hex characters.
+     *
+     * @param hex A hex string.
+     * @return A cos string with the hex characters converted to their actual bytes.
+     * @throws IOException If there is an error with the hex string.
+     */
+    public static COSString createFromHexString(String hex)
+            throws IOException {
+        return createFromHexString(hex, false);
+    }
+
+    /**
+     * Creates a COS string from a string of hex characters, optionally
+     * ignoring malformed input.
+     *
+     * @param hex A hex string.
+     * @param force flag to ignore malformed input
+     * @return A cos string with the hex characters converted to their actual bytes.
+     * @throws IOException If there is an error with the hex string.
+     */
+    public static COSString createFromHexString(String hex, boolean force)
+            throws IOException {
+        COSString retval = new COSString();
+        StringBuilder hexBuffer = new StringBuilder( hex.trim() );
+        //if odd number then the last hex digit is assumed to be 0
+        if( hexBuffer.length() % 2 != 0 )
+        {
+            hexBuffer.append( '0' );
+        }
+        int length = hexBuffer.length(); 
+        for (int i = 0; i < length; i += 2) {
+            try {
+                retval.append(
+                        Integer.parseInt(hexBuffer.substring(i, i + 2), 16));
+            } catch (NumberFormatException e) {
+                if (force) {
+                    retval.append('?');
+                } else {
+                    IOException exception =
+                        new IOException("Invalid hex string: " + hex);
+                    exception.initCause(e);
+                    throw exception;
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will take this string and create a hex representation of the bytes that make the string.
+     *
+     * @return A hex string representing the bytes in this string.
+     */
+    public String getHexString()
+    {
+        StringBuilder retval = new StringBuilder( out.size() * 2 );
+        byte[] data = getBytes();
+        int length = data.length;
+        for( int i=0; i<length; i++ )
+        {
+            retval.append( COSHEXTable.HEX_TABLE[ (data[i]+256)%256 ] );
+        }
+
+        return retval.toString();
+    }
+
+    /**
+     * This will get the string that this object wraps.
+     *
+     * @return The wrapped string.
+     */
+    public String getString()
+    {
+        if (this.str != null)
+        {
+            return this.str;
+        }
+        String retval;
+        String encoding = "ISO-8859-1";
+        byte[] data = getBytes();
+        int start = 0;
+        if( data.length > 2 )
+        {
+            if( data[0] == (byte)0xFF && data[1] == (byte)0xFE )
+            {
+                encoding = "UTF-16LE";
+                start=2;
+            }
+            else if( data[0] == (byte)0xFE && data[1] == (byte)0xFF )
+            {
+                encoding = "UTF-16BE";
+                start=2;
+            }
+        }
+        try
+        {
+            retval = new String( getBytes(), start, data.length-start, encoding );
+        }
+        catch( UnsupportedEncodingException e )
+        {
+            //should never happen
+            e.printStackTrace();
+            retval = new String( getBytes() );
+        }
+        this.str = retval;
+        return retval;
+    }
+
+    /**
+     * This will append a byte[] to the string.
+     *
+     * @param data The byte[] to add to this string.
+     *
+     * @throws IOException If an IO error occurs while writing the byte.
+     */
+    public void append( byte[] data ) throws IOException
+    {
+        out.write( data );
+        this.str = null;
+    }
+
+    /**
+     * This will append a byte to the string.
+     *
+     * @param in The byte to add to this string.
+     *
+     * @throws IOException If an IO error occurs while writing the byte.
+     */
+    public void append( int in ) throws IOException
+    {
+        out.write( in );
+        this.str = null;
+    }
+
+    /**
+     * This will reset the internal buffer.
+     */
+    public void reset()
+    {
+        out.reset();
+        this.str = null;
+    }
+
+    /**
+     * This will get the bytes of the string.
+     *
+     * @return A byte array that represents the string.
+     */
+    public byte[] getBytes()
+    {
+        return out.toByteArray();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString()
+    {
+        return "COSString{" + this.getString() + "}";
+    }
+
+    /**
+     * This will output this string as a PDF object.
+     *
+     * @param output The stream to write to.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void writePDF( OutputStream output ) throws IOException
+    {
+        boolean outsideASCII = false;
+        //Lets first check if we need to escape this string.
+        byte[] bytes = getBytes();
+        int length = bytes.length; 
+        for( int i=0; i<length && !outsideASCII; i++ )
+        {
+            //if the byte is negative then it is an eight bit byte and is
+            //outside the ASCII range.
+            outsideASCII = bytes[i] <0;
+        }
+        if (!outsideASCII && !forceHexForm)
+        {
+            output.write(STRING_OPEN);
+            for( int i=0; i<length; i++ )
+            {
+                int b = (bytes[i]+256)%256;
+                switch( b )
+                {
+                    case '(':
+                    case ')':
+                    case '\\':
+                    {
+                        output.write(ESCAPE);
+                        output.write((byte)b);
+                        break;
+                    }
+                    case 10: //LF
+                    {
+                        output.write( LF_ESCAPE );
+                        break;
+                    }
+                    case 13: // CR
+                    {
+                        output.write( CR_ESCAPE );
+                        break;
+                    }
+                    case '\t':
+                    {
+                        output.write( HT_ESCAPE );
+                        break;
+                    }
+                    case '\b':
+                    {
+                        output.write( BS_ESCAPE );
+                        break;
+                    }
+                    case '\f':
+                    {
+                        output.write( FF_ESCAPE );
+                        break;
+                    }
+                    default:
+                    {
+                        output.write( (byte)b );
+                    }
+                }
+            }
+            output.write(STRING_CLOSE);
+        }
+        else
+        {
+            output.write(HEX_STRING_OPEN);
+            for(int i=0; i<length; i++ )
+            {
+                output.write( COSHEXTable.TABLE[ (bytes[i]+256)%256 ] );
+            }
+            output.write(HEX_STRING_CLOSE);
+        }
+    }
+
+
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    @Override
+    public Object accept(ICOSVisitor visitor) throws COSVisitorException
+    {
+        return visitor.visitFromString( this );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj instanceof COSString)
+        {
+            COSString strObj = (COSString) obj;
+            return this.getString().equals(strObj.getString()) 
+                && this.forceHexForm == strObj.forceHexForm;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode()
+    {
+        int result = getString().hashCode();
+        return result += forceHexForm ? 17 : 0; 
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/ICOSVisitor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/ICOSVisitor.java
new file mode 100644 (file)
index 0000000..1a09c38
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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.pdfbox.cos;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * An interface for visiting a PDF document at the type (COS) level.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.6 $
+ */
+public interface ICOSVisitor
+{
+    /**
+     * Notification of visit to Array object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromArray( COSArray obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to boolean object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromBoolean( COSBoolean obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to dictionary object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromDictionary( COSDictionary obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to document object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromDocument( COSDocument obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to float object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromFloat( COSFloat obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to integer object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromInt( COSInteger obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to name object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromName( COSName obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to null object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromNull( COSNull obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to stream object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromStream( COSStream obj ) throws COSVisitorException;
+
+    /**
+     * Notification of visit to string object.
+     *
+     * @param obj The Object that is being visited.
+     * @return any Object depending on the visitor implementation, or null
+     * @throws COSVisitorException If there is an error while visiting this object.
+     */
+    public Object visitFromString( COSString obj ) throws COSVisitorException;
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/cos/package.html
new file mode 100644 (file)
index 0000000..5c4fd89
--- /dev/null
@@ -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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These are the low level objects that make up a PDF document.
+<br/><br/>
+
+See the <A href="http://partners.adobe.com/asn/developer/acrosdk/docs/filefmtspecs/PDFReference.pdf">PDF Reference 1.4</A>.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/AFMEncoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/AFMEncoding.java
new file mode 100644 (file)
index 0000000..5370940
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.encoding;
+
+import java.util.Iterator;
+
+import org.apache.fontbox.afm.CharMetric;
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This will handle the encoding from an AFM font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class AFMEncoding extends Encoding
+{
+    private FontMetric metric = null;
+
+    /**
+     * Constructor.
+     *
+     * @param fontInfo The font metric information.
+     */
+    public AFMEncoding( FontMetric fontInfo )
+    {
+        metric = fontInfo;
+        Iterator<CharMetric> characters = metric.getCharMetrics().iterator();
+        while( characters.hasNext() )
+        {
+            CharMetric nextMetric = (CharMetric)characters.next();
+            addCharacterEncoding( nextMetric.getCharacterCode(), nextMetric.getName() );
+        }
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return null;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/DictionaryEncoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/DictionaryEncoding.java
new file mode 100644 (file)
index 0000000..e60f3c2
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.pdfbox.encoding;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+
+/**
+ * This will perform the encoding from a dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ */
+public class DictionaryEncoding extends Encoding
+{
+    private COSDictionary encoding = null;
+
+    /**
+     * Constructor.
+     *
+     * @param fontEncoding The encoding dictionary.
+     *
+     * @throws IOException If there is a problem getting the base font.
+     */
+    public DictionaryEncoding( COSDictionary fontEncoding ) throws IOException
+    {
+        encoding = fontEncoding;
+
+        //first set up the base encoding
+        //The previious value WinAnsiEncoding() has been changed to StandardEnding
+        //see p 389 of the PDF 1.5 ref�rence table 5.11 entries in a dictionary encoding
+        //"If this entry is absent, the Differences entry describes differences from an implicit
+        //base encoding. For a font program that is embedded in the PDF file, the
+        //implicit base encoding is the font program�s built-in encoding, as described
+        //above and further elaborated in the sections on specific font types below. Otherwise,
+        //for a nonsymbolic font, it is StandardEncoding, and for a symbolic font, it
+        //is the font�s built-in encoding."
+
+        // The default base encoding is standardEncoding
+        Encoding baseEncoding = StandardEncoding.INSTANCE;
+        COSName baseEncodingName =
+            (COSName) encoding.getDictionaryObject(COSName.BASE_ENCODING);
+        if (baseEncodingName != null) {
+            baseEncoding =
+                EncodingManager.INSTANCE.getEncoding(baseEncodingName);
+        }
+
+        nameToCode.putAll( baseEncoding.nameToCode );
+        codeToName.putAll( baseEncoding.codeToName );
+
+
+        //now replace with the differences.
+        COSArray differences = (COSArray)encoding.getDictionaryObject( COSName.DIFFERENCES );
+        int currentIndex = -1;
+        for( int i=0; differences != null && i<differences.size(); i++ )
+        {
+            COSBase next = differences.getObject( i );
+            if( next instanceof COSNumber )
+            {
+                currentIndex = ((COSNumber)next).intValue();
+            }
+            else if( next instanceof COSName )
+            {
+                COSName name = (COSName)next;
+                addCharacterEncoding( currentIndex++, name.getName() );
+            }
+        }
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return encoding;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/Encoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/Encoding.java
new file mode 100644 (file)
index 0000000..f9670a7
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * 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.pdfbox.encoding;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.StringTokenizer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ */
+public abstract class Encoding implements COSObjectable
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(Encoding.class);
+
+    /** Identifies a non-mapped character. */
+    public static final String NOTDEF = ".notdef";
+
+    /**
+     * This is a mapping from a character code to a character name.
+     */
+    protected final Map<Integer, String> codeToName =
+        new HashMap<Integer, String>();
+
+    /**
+     * This is a mapping from a character name to a character code.
+     */
+    protected final Map<String, Integer> nameToCode =
+        new HashMap<String, Integer>();
+
+    private static final Map<String, String> NAME_TO_CHARACTER =
+        new HashMap<String, String>();
+
+    private static final Map<String, String> CHARACTER_TO_NAME =
+        new HashMap<String, String>();
+
+    static
+    {
+        //Loads the official Adobe Glyph List
+        loadGlyphList("org/apache/pdfbox/resources/glyphlist.txt");
+        //Loads some additional glyph mappings
+        loadGlyphList("org/apache/pdfbox/resources/additional_glyphlist.txt");
+
+        // Load an external glyph list file that user can give as JVM property
+        String location = System.getProperty("glyphlist_ext");
+        if(location != null)
+        {
+            File external = new File(location);
+            if(external.exists())
+            {
+                loadGlyphList(location);
+            }
+        }
+
+        NAME_TO_CHARACTER.put( NOTDEF, "" );
+        NAME_TO_CHARACTER.put( "fi", "fi" );
+        NAME_TO_CHARACTER.put( "fl", "fl" );
+        NAME_TO_CHARACTER.put( "ffi", "ffi" );
+        NAME_TO_CHARACTER.put( "ff", "ff" );
+        NAME_TO_CHARACTER.put( "pi", "pi" );
+
+        for( Map.Entry<String, String> entry : NAME_TO_CHARACTER.entrySet() )
+        {
+            CHARACTER_TO_NAME.put( entry.getValue(), entry.getKey() );
+        }
+    }
+
+    /**
+     * Loads a glyph list from a given location and populates the NAME_TO_CHARACTER hashmap
+     * for character lookups.
+     * @param location - The string location of the glyphlist file
+     */
+    private static void loadGlyphList(String location)
+    {
+        BufferedReader glyphStream = null;
+        try
+        {
+            InputStream resource = ResourceLoader.loadResource( location );
+            if (resource == null)
+            {
+                throw new MissingResourceException("Glyphlist not found: " + location,
+                        Encoding.class.getName(), location);
+            }
+            glyphStream = new BufferedReader( new InputStreamReader( resource ) );
+            String line = null;
+            while( (line = glyphStream.readLine()) != null )
+            {
+                line = line.trim();
+                //lines starting with # are comments which we can ignore.
+                if( !line.startsWith("#" ) )
+                {
+                    int semicolonIndex = line.indexOf( ';' );
+                    if( semicolonIndex >= 0 )
+                    {
+                        try
+                        {
+                            String characterName = line.substring( 0, semicolonIndex );
+                            String unicodeValue = line.substring( semicolonIndex+1, line.length() );
+                            StringTokenizer tokenizer = new StringTokenizer( unicodeValue, " ", false );
+                            String value = "";
+                            while(tokenizer.hasMoreTokens())
+                            {
+                                int characterCode = Integer.parseInt( tokenizer.nextToken(), 16 );
+                                value += (char)characterCode;
+                            }
+                            if (NAME_TO_CHARACTER.containsKey(characterName))
+                            {
+                                log.warn("duplicate value for characterName="+characterName+","+value);
+                            }
+                            else
+                            {
+                                NAME_TO_CHARACTER.put( characterName, value );
+                            }
+                        }
+                        catch( NumberFormatException nfe )
+                        {
+                            nfe.printStackTrace();
+                        }
+                    }
+                }
+            }
+        }
+        catch( IOException io )
+        {
+            io.printStackTrace();
+        }
+        finally
+        {
+            if( glyphStream != null )
+            {
+                try
+                {
+                    glyphStream.close();
+                }
+                catch( IOException e )
+                {
+                    e.printStackTrace();
+                }
+
+            }
+        }
+    }
+
+    /**
+     * Returns an unmodifiable view of the Code2Name mapping.
+     * @return the Code2Name map
+     */
+    public Map<Integer, String> getCodeToNameMap()
+    {
+        return Collections.unmodifiableMap(codeToName);
+    }
+
+    /**
+     * Returns an unmodifiable view of the Name2Code mapping.
+     * @return the Name2Code map
+     */
+    public Map<String, Integer> getNameToCodeMap()
+    {
+        return Collections.unmodifiableMap(nameToCode);
+    }
+
+    /**
+     * This will add a character encoding.
+     *
+     * @param code The character code that matches the character.
+     * @param name The name of the character.
+     */
+    public void addCharacterEncoding( int code, String name )
+    {
+        codeToName.put( code, name );
+        nameToCode.put( name, code );
+    }
+
+    /**
+     * This will get the character code for the name.
+     *
+     * @param name The name of the character.
+     *
+     * @return The code for the character.
+     *
+     * @throws IOException If there is no character code for the name.
+     */
+    public int getCode( String name ) throws IOException
+    {
+        Integer code = nameToCode.get( name );
+        if( code == null )
+        {
+            throw new IOException( "No character code for character name '" + name + "'" );
+        }
+        return code;
+    }
+
+    /**
+     * This will take a character code and get the name from the code.
+     *
+     * @param code The character code.
+     *
+     * @return The name of the character.
+     *
+     * @throws IOException If there is no name for the code.
+     */
+    public String getName( int code ) throws IOException
+    {
+        return codeToName.get( code );
+    }
+
+    /**
+     * This will take a character code and get the name from the code.
+     *
+     * @param c The character.
+     *
+     * @return The name of the character.
+     *
+     * @throws IOException If there is no name for the character.
+     */
+    public String getNameFromCharacter( char c ) throws IOException
+    {
+        String name = CHARACTER_TO_NAME.get( Character.toString(c) );
+        if( name == null )
+        {
+            throw new IOException( "No name for character '" + c + "'" );
+        }
+        return name;
+    }
+
+    /**
+     * This will get the character from the code.
+     *
+     * @param code The character code.
+     *
+     * @return The printable character for the code.
+     *
+     * @throws IOException If there is not name for the character.
+     */
+    public String getCharacter( int code ) throws IOException
+    {
+        String name = getName( code );
+        if (name != null)
+        {
+            return getCharacter( getName( code ) );
+        }
+        return null;
+    }
+
+    /**
+     * This will get the character from the name.
+     *
+     * @param name The name of the character.
+     *
+     * @return The printable character for the code.
+     */
+    public String getCharacter( String name )
+    {
+        String character = NAME_TO_CHARACTER.get( name );
+        if( character == null )
+        {
+            // test if we have a suffix and if so remove it
+            if ( name.indexOf('.') > 0 )
+            {
+                character = getCharacter(name.substring( 0, name.indexOf('.') ));
+            }
+            // test for Unicode name
+            // (uniXXXX - XXXX must be a multiple of four;
+            // each representing a hexadecimal Unicode code point)
+            else if ( name.startsWith( "uni" ) )
+            {
+                int nameLength = name.length();
+                StringBuilder uniStr = new StringBuilder();
+                try
+                {
+                    for ( int chPos = 3; chPos + 4 <= nameLength; chPos += 4 )
+                    {
+                        int characterCode = Integer.parseInt( name.substring( chPos, chPos + 4), 16 );
+
+                        if ( ( characterCode > 0xD7FF ) && ( characterCode < 0xE000 ) )
+                        {
+                            log.warn( "Unicode character name with not allowed code area: " + name );
+                        }
+                        else
+                        {
+                            uniStr.append( (char) characterCode );
+                        }
+                    }
+                    character = uniStr.toString();
+                    NAME_TO_CHARACTER.put(name, character);
+                }
+                catch (NumberFormatException nfe)
+                {
+                    log.warn( "Not a number in Unicode character name: " + name );
+                    character = name;
+                }
+            }
+            else if (nameToCode.containsKey(name))
+            {
+                int code = nameToCode.get(name);
+                character = Character.toString((char)code);
+            }
+            else
+            {
+                character = name;
+            }
+        }
+        return character;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/EncodingManager.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/EncodingManager.java
new file mode 100644 (file)
index 0000000..5a6c50f
--- /dev/null
@@ -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.pdfbox.encoding;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class will handle getting the appropriate encodings.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class EncodingManager
+{
+
+    /**
+     * Default singleton instance of this class.
+     *
+     * @since Apache PDFBox 1.3.0
+     */
+    public static final EncodingManager INSTANCE = new EncodingManager();
+
+    /**
+     * This will get the standard encoding.
+     *
+     * @return The standard encoding.
+     */
+    public Encoding getStandardEncoding() {
+        return StandardEncoding.INSTANCE;
+    }
+
+    /**
+     * This will get an encoding by name.
+     *
+     * @param name The name of the encoding to get.
+     * @return The encoding that matches the name.
+     * @throws IOException if there is no encoding with that name.
+     */
+    public Encoding getEncoding( COSName name ) throws IOException {
+        if (COSName.STANDARD_ENCODING.equals(name)) {
+            return StandardEncoding.INSTANCE;
+        } else if (COSName.WIN_ANSI_ENCODING.equals(name)) {
+            return WinAnsiEncoding.INSTANCE;
+        } else if (COSName.MAC_ROMAN_ENCODING.equals(name)) {
+            return MacRomanEncoding.INSTANCE;
+        } else if (COSName.PDF_DOC_ENCODING.equals(name)) {
+            return PdfDocEncoding.INSTANCE;
+        } else {
+            throw new IOException(
+                    "Unknown encoding for '" + name.getName() + "'");
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/MacRomanEncoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/MacRomanEncoding.java
new file mode 100644 (file)
index 0000000..f3c7219
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ * 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.pdfbox.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class MacRomanEncoding extends Encoding
+{
+
+    /**
+     * Singleton instance of this class.
+     *
+     * @since Apache PDFBox 1.3.0
+     */
+    public static final MacRomanEncoding INSTANCE = new MacRomanEncoding();
+
+    /**
+     * Constructor.
+     */
+    public MacRomanEncoding()
+    {
+        addCharacterEncoding( 0101, "A" );
+        addCharacterEncoding( 0256, "AE" );
+        addCharacterEncoding( 0347, "Aacute" );
+        addCharacterEncoding( 0345, "Acircumflex" );
+        addCharacterEncoding( 0200, "Adieresis" );
+        addCharacterEncoding( 0313, "Agrave" );
+        addCharacterEncoding( 0201, "Aring" );
+        addCharacterEncoding( 0314, "Atilde" );
+        addCharacterEncoding( 0102, "B" );
+        addCharacterEncoding( 0103, "C" );
+        addCharacterEncoding( 0202, "Ccedilla" );
+        addCharacterEncoding( 0104, "D" );
+        addCharacterEncoding( 0105, "E" );
+        addCharacterEncoding( 0203, "Eacute" );
+        addCharacterEncoding( 0346, "Ecircumflex" );
+        addCharacterEncoding( 0350, "Edieresis" );
+        addCharacterEncoding( 0351, "Egrave" );
+        addCharacterEncoding( 0106, "F" );
+        addCharacterEncoding( 0107, "G" );
+        addCharacterEncoding( 0110, "H" );
+        addCharacterEncoding( 0111, "I" );
+        addCharacterEncoding( 0352, "Iacute" );
+        addCharacterEncoding( 0353, "Icircumflex" );
+        addCharacterEncoding( 0354, "Idieresis" );
+        addCharacterEncoding( 0355, "Igrave" );
+        addCharacterEncoding( 0112, "J" );
+        addCharacterEncoding( 0113, "K" );
+        addCharacterEncoding( 0114, "L" );
+        addCharacterEncoding( 0115, "M" );
+        addCharacterEncoding( 0116, "N" );
+        addCharacterEncoding( 0204, "Ntilde" );
+        addCharacterEncoding( 0117, "O" );
+        addCharacterEncoding( 0316, "OE" );
+        addCharacterEncoding( 0356, "Oacute" );
+        addCharacterEncoding( 0357, "Ocircumflex" );
+        addCharacterEncoding( 0205, "Odieresis" );
+        addCharacterEncoding( 0361, "Ograve" );
+        addCharacterEncoding( 0257, "Oslash" );
+        addCharacterEncoding( 0315, "Otilde" );
+        addCharacterEncoding( 0120, "P" );
+        addCharacterEncoding( 0121, "Q" );
+        addCharacterEncoding( 0122, "R" );
+        addCharacterEncoding( 0123, "S" );
+        addCharacterEncoding( 0124, "T" );
+        addCharacterEncoding( 0125, "U" );
+        addCharacterEncoding( 0362, "Uacute" );
+        addCharacterEncoding( 0363, "Ucircumflex" );
+        addCharacterEncoding( 0206, "Udieresis" );
+        addCharacterEncoding( 0364, "Ugrave" );
+        addCharacterEncoding( 0126, "V" );
+        addCharacterEncoding( 0127, "W" );
+        addCharacterEncoding( 0130, "X" );
+        addCharacterEncoding( 0131, "Y" );
+        addCharacterEncoding( 0331, "Ydieresis" );
+        addCharacterEncoding( 0132, "Z" );
+        addCharacterEncoding( 0141, "a" );
+        addCharacterEncoding( 0207, "aacute" );
+        addCharacterEncoding( 0211, "acircumflex" );
+        addCharacterEncoding( 0253, "acute" );
+        addCharacterEncoding( 0212, "adieresis" );
+        addCharacterEncoding( 0276, "ae" );
+        addCharacterEncoding( 0210, "agrave" );
+        addCharacterEncoding( 046, "ampersand" );
+        addCharacterEncoding( 0214, "aring" );
+        addCharacterEncoding( 0136, "asciicircum" );
+        addCharacterEncoding( 0176, "asciitilde" );
+        addCharacterEncoding( 052, "asterisk" );
+        addCharacterEncoding( 0100, "at" );
+        addCharacterEncoding( 0213, "atilde" );
+        addCharacterEncoding( 0142, "b" );
+        addCharacterEncoding( 0134, "backslash" );
+        addCharacterEncoding( 0174, "bar" );
+        addCharacterEncoding( 0173, "braceleft" );
+        addCharacterEncoding( 0175, "braceright" );
+        addCharacterEncoding( 0133, "bracketleft" );
+        addCharacterEncoding( 0135, "bracketright" );
+        addCharacterEncoding( 0371, "breve" );
+        addCharacterEncoding( 0245, "bullet" );
+        addCharacterEncoding( 0143, "c" );
+        addCharacterEncoding( 0377, "caron" );
+        addCharacterEncoding( 0215, "ccedilla" );
+        addCharacterEncoding( 0374, "cedilla" );
+        addCharacterEncoding( 0242, "cent" );
+        addCharacterEncoding( 0366, "circumflex" );
+        addCharacterEncoding( 072, "colon" );
+        addCharacterEncoding( 054, "comma" );
+        addCharacterEncoding( 0251, "copyright" );
+        addCharacterEncoding( 0333, "currency" );
+        addCharacterEncoding( 0144, "d" );
+        addCharacterEncoding( 0240, "dagger" );
+        addCharacterEncoding( 0340, "daggerdbl" );
+        addCharacterEncoding( 0241, "degree" );
+        addCharacterEncoding( 0254, "dieresis" );
+        addCharacterEncoding( 0326, "divide" );
+        addCharacterEncoding( 044, "dollar" );
+        addCharacterEncoding( 0372, "dotaccent" );
+        addCharacterEncoding( 0365, "dotlessi" );
+        addCharacterEncoding( 0145, "e" );
+        addCharacterEncoding( 0216, "eacute" );
+        addCharacterEncoding( 0220, "ecircumflex" );
+        addCharacterEncoding( 0221, "edieresis" );
+        addCharacterEncoding( 0217, "egrave" );
+        addCharacterEncoding( 070, "eight" );
+        addCharacterEncoding( 0311, "ellipsis" );
+        addCharacterEncoding( 0321, "emdash" );
+        addCharacterEncoding( 0320, "endash" );
+        addCharacterEncoding( 075, "equal" );
+        addCharacterEncoding( 041, "exclam" );
+        addCharacterEncoding( 0301, "exclamdown" );
+        addCharacterEncoding( 0146, "f" );
+        addCharacterEncoding( 0336, "fi" );
+        addCharacterEncoding( 065, "five" );
+        addCharacterEncoding( 0337, "fl" );
+        addCharacterEncoding( 0304, "florin" );
+        addCharacterEncoding( 064, "four" );
+        addCharacterEncoding( 0332, "fraction" );
+        addCharacterEncoding( 0147, "g" );
+        addCharacterEncoding( 0247, "germandbls" );
+        addCharacterEncoding( 0140, "grave" );
+        addCharacterEncoding( 076, "greater" );
+        addCharacterEncoding( 0307, "guillemotleft" );
+        addCharacterEncoding( 0310, "guillemotright" );
+        addCharacterEncoding( 0334, "guilsinglleft" );
+        addCharacterEncoding( 0335, "guilsinglright" );
+        addCharacterEncoding( 0150, "h" );
+        addCharacterEncoding( 0375, "hungarumlaut" );
+        addCharacterEncoding( 055, "hyphen" );
+        addCharacterEncoding( 0151, "i" );
+        addCharacterEncoding( 0222, "iacute" );
+        addCharacterEncoding( 0224, "icircumflex" );
+        addCharacterEncoding( 0225, "idieresis" );
+        addCharacterEncoding( 0223, "igrave" );
+        addCharacterEncoding( 0152, "j" );
+        addCharacterEncoding( 0153, "k" );
+        addCharacterEncoding( 0154, "l" );
+        addCharacterEncoding( 074, "less" );
+        addCharacterEncoding( 0302, "logicalnot" );
+        addCharacterEncoding( 0155, "m" );
+        addCharacterEncoding( 0370, "macron" );
+        addCharacterEncoding( 0265, "mu" );
+        addCharacterEncoding( 0156, "n" );
+        addCharacterEncoding( 071, "nine" );
+        addCharacterEncoding( 0226, "ntilde" );
+        addCharacterEncoding( 043, "numbersign" );
+        addCharacterEncoding( 0157, "o" );
+        addCharacterEncoding( 0227, "oacute" );
+        addCharacterEncoding( 0231, "ocircumflex" );
+        addCharacterEncoding( 0232, "odieresis" );
+        addCharacterEncoding( 0317, "oe" );
+        addCharacterEncoding( 0376, "ogonek" );
+        addCharacterEncoding( 0230, "ograve" );
+        addCharacterEncoding( 061, "one" );
+        addCharacterEncoding( 0273, "ordfeminine" );
+        addCharacterEncoding( 0274, "ordmasculine" );
+        addCharacterEncoding( 0277, "oslash" );
+        addCharacterEncoding( 0233, "otilde" );
+        addCharacterEncoding( 0160, "p" );
+        addCharacterEncoding( 0246, "paragraph" );
+        addCharacterEncoding( 050, "parenleft" );
+        addCharacterEncoding( 051, "parenright" );
+        addCharacterEncoding( 045, "percent" );
+        addCharacterEncoding( 056, "period" );
+        addCharacterEncoding( 0341, "periodcentered" );
+        addCharacterEncoding( 0344, "perthousand" );
+        addCharacterEncoding( 053, "plus" );
+        addCharacterEncoding( 0261, "plusminus" );
+        addCharacterEncoding( 0161, "q" );
+        addCharacterEncoding( 077, "question" );
+        addCharacterEncoding( 0300, "questiondown" );
+        addCharacterEncoding( 042, "quotedbl" );
+        addCharacterEncoding( 0343, "quotedblbase" );
+        addCharacterEncoding( 0322, "quotedblleft" );
+        addCharacterEncoding( 0323, "quotedblright" );
+        addCharacterEncoding( 0324, "quoteleft" );
+        addCharacterEncoding( 0325, "quoteright" );
+        addCharacterEncoding( 0342, "quotesinglbase" );
+        addCharacterEncoding( 047, "quotesingle" );
+        addCharacterEncoding( 0162, "r" );
+        addCharacterEncoding( 0250, "registered" );
+        addCharacterEncoding( 0373, "ring" );
+        addCharacterEncoding( 0163, "s" );
+        addCharacterEncoding( 0244, "section" );
+        addCharacterEncoding( 073, "semicolon" );
+        addCharacterEncoding( 067, "seven" );
+        addCharacterEncoding( 066, "six" );
+        addCharacterEncoding( 057, "slash" );
+        addCharacterEncoding( 040, "space" );
+        addCharacterEncoding( 0243, "sterling" );
+        addCharacterEncoding( 0164, "t" );
+        addCharacterEncoding( 063, "three" );
+        addCharacterEncoding( 0367, "tilde" );
+        addCharacterEncoding( 0252, "trademark" );
+        addCharacterEncoding( 062, "two" );
+        addCharacterEncoding( 0165, "u" );
+        addCharacterEncoding( 0234, "uacute" );
+        addCharacterEncoding( 0236, "ucircumflex" );
+        addCharacterEncoding( 0237, "udieresis" );
+        addCharacterEncoding( 0235, "ugrave" );
+        addCharacterEncoding( 0137, "underscore" );
+        addCharacterEncoding( 0166, "v" );
+        addCharacterEncoding( 0167, "w" );
+        addCharacterEncoding( 0170, "x" );
+        addCharacterEncoding( 0171, "y" );
+        addCharacterEncoding( 0330, "ydieresis" );
+        addCharacterEncoding( 0264, "yen" );
+        addCharacterEncoding( 0172, "z" );
+        addCharacterEncoding( 060, "zero" );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return COSName.MAC_ROMAN_ENCODING;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/PdfDocEncoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/PdfDocEncoding.java
new file mode 100644 (file)
index 0000000..e19cc4d
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * 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.pdfbox.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class PdfDocEncoding extends Encoding
+{
+
+    /**
+     * Singleton instance of this class.
+     *
+     * @since Apache PDFBox 1.3.0
+     */
+    public static final PdfDocEncoding INSTANCE = new PdfDocEncoding();
+
+    /**
+     * Constructor.
+     */
+    public PdfDocEncoding()
+    {
+        addCharacterEncoding( 0101, "A" );
+        addCharacterEncoding( 0306, "AE" );
+        addCharacterEncoding( 0301, "Aacute" );
+        addCharacterEncoding( 0302, "Acircumflex" );
+        addCharacterEncoding( 0304, "Adieresis" );
+        addCharacterEncoding( 0300, "Agrave" );
+        addCharacterEncoding( 0305, "Aring" );
+        addCharacterEncoding( 0303, "Atilde" );
+        addCharacterEncoding( 0102, "B" );
+        addCharacterEncoding( 0103, "C" );
+        addCharacterEncoding( 0307, "Ccedilla" );
+        addCharacterEncoding( 0104, "D" );
+        addCharacterEncoding( 0105, "E" );
+        addCharacterEncoding( 0311, "Eacute" );
+        addCharacterEncoding( 0312, "Ecircumflex" );
+        addCharacterEncoding( 0313, "Edieresis" );
+        addCharacterEncoding( 0310, "Egrave" );
+        addCharacterEncoding( 0320, "Eth" );
+        addCharacterEncoding( 0240, "Euro" );
+        addCharacterEncoding( 0106, "F" );
+        addCharacterEncoding( 0107, "G" );
+        addCharacterEncoding( 0110, "H" );
+        addCharacterEncoding( 0111, "I" );
+        addCharacterEncoding( 0315, "Iacute" );
+        addCharacterEncoding( 0316, "Icircumflex" );
+        addCharacterEncoding( 0317, "Idieresis" );
+        addCharacterEncoding( 0314, "Igrave" );
+        addCharacterEncoding( 0112, "J" );
+        addCharacterEncoding( 0113, "K" );
+        addCharacterEncoding( 0114, "L" );
+        addCharacterEncoding( 0225, "Lslash" );
+        addCharacterEncoding( 0115, "M" );
+        addCharacterEncoding( 0116, "N" );
+        addCharacterEncoding( 0321, "Ntilde" );
+        addCharacterEncoding( 0117, "O" );
+        addCharacterEncoding( 0226, "OE" );
+        addCharacterEncoding( 0323, "Oacute" );
+        addCharacterEncoding( 0324, "Ocircumflex" );
+        addCharacterEncoding( 0326, "Odieresis" );
+        addCharacterEncoding( 0322, "Ograve" );
+        addCharacterEncoding( 0330, "Oslash" );
+        addCharacterEncoding( 0325, "Otilde" );
+        addCharacterEncoding( 0120, "P" );
+        addCharacterEncoding( 0121, "Q" );
+        addCharacterEncoding( 0122, "R" );
+        addCharacterEncoding( 0123, "S" );
+        addCharacterEncoding( 0227, "Scaron" );
+        addCharacterEncoding( 0124, "T" );
+        addCharacterEncoding( 0336, "Thorn" );
+        addCharacterEncoding( 0125, "U" );
+        addCharacterEncoding( 0332, "Uacute" );
+        addCharacterEncoding( 0333, "Ucircumflex" );
+        addCharacterEncoding( 0334, "Udieresis" );
+        addCharacterEncoding( 0331, "Ugrave" );
+        addCharacterEncoding( 0126, "V" );
+        addCharacterEncoding( 0127, "W" );
+        addCharacterEncoding( 0130, "X" );
+        addCharacterEncoding( 0131, "Y" );
+        addCharacterEncoding( 0335, "Yacute" );
+        addCharacterEncoding( 0230, "Ydieresis" );
+        addCharacterEncoding( 0132, "Z" );
+        addCharacterEncoding( 0231, "Zcaron" );
+        addCharacterEncoding( 0141, "a" );
+        addCharacterEncoding( 0341, "aacute" );
+        addCharacterEncoding( 0342, "acircumflex" );
+        addCharacterEncoding( 0264, "acute" );
+        addCharacterEncoding( 0344, "adieresis" );
+        addCharacterEncoding( 0346, "ae" );
+        addCharacterEncoding( 0340, "agrave" );
+        addCharacterEncoding( 046, "ampersand" );
+        addCharacterEncoding( 0345, "aring" );
+        addCharacterEncoding( 0136, "asciicircum" );
+        addCharacterEncoding( 0176, "asciitilde" );
+        addCharacterEncoding( 052, "asterisk" );
+        addCharacterEncoding( 0100, "at" );
+        addCharacterEncoding( 0343, "atilde" );
+        addCharacterEncoding( 0142, "b" );
+        addCharacterEncoding( 0134, "backslash" );
+        addCharacterEncoding( 0174, "bar" );
+        addCharacterEncoding( 0173, "braceleft" );
+        addCharacterEncoding( 0175, "braceright" );
+        addCharacterEncoding( 0133, "bracketleft" );
+        addCharacterEncoding( 0135, "bracketright" );
+        addCharacterEncoding( 030, "breve" );
+        addCharacterEncoding( 0246, "brokenbar" );
+        addCharacterEncoding( 0200, "bullet" );
+        addCharacterEncoding( 0143, "c" );
+        addCharacterEncoding( 031, "caron" );
+        addCharacterEncoding( 0347, "ccedilla" );
+        addCharacterEncoding( 0270, "cedilla" );
+        addCharacterEncoding( 0242, "cent" );
+        addCharacterEncoding( 032, "circumflex" );
+        addCharacterEncoding( 072, "colon" );
+        addCharacterEncoding( 054, "comma" );
+        addCharacterEncoding( 0251, "copyright" );
+        addCharacterEncoding( 0244, "currency" );
+        addCharacterEncoding( 0144, "d" );
+        addCharacterEncoding( 0201, "dagger" );
+        addCharacterEncoding( 0202, "daggerdbl" );
+        addCharacterEncoding( 0260, "degree" );
+        addCharacterEncoding( 0250, "dieresis" );
+        addCharacterEncoding( 0367, "divide" );
+        addCharacterEncoding( 044, "dollar" );
+        addCharacterEncoding( 033, "dotaccent" );
+        addCharacterEncoding( 0232, "dotlessi" );
+        addCharacterEncoding( 0145, "e" );
+        addCharacterEncoding( 0351, "eacute" );
+        addCharacterEncoding( 0352, "ecircumflex" );
+        addCharacterEncoding( 0353, "edieresis" );
+        addCharacterEncoding( 0350, "egrave" );
+        addCharacterEncoding( 070, "eight" );
+        addCharacterEncoding( 0203, "ellipsis" );
+        addCharacterEncoding( 0204, "emdash" );
+        addCharacterEncoding( 0205, "endash" );
+        addCharacterEncoding( 075, "equal" );
+        addCharacterEncoding( 0360, "eth" );
+        addCharacterEncoding( 041, "exclam" );
+        addCharacterEncoding( 0241, "exclamdown" );
+        addCharacterEncoding( 0146, "f" );
+        addCharacterEncoding( 0223, "fi" );
+        addCharacterEncoding( 065, "five" );
+        addCharacterEncoding( 0224, "fl" );
+        addCharacterEncoding( 0206, "florin" );
+        addCharacterEncoding( 064, "four" );
+        addCharacterEncoding( 0207, "fraction" );
+        addCharacterEncoding( 0147, "g" );
+        addCharacterEncoding( 0337, "germandbls" );
+        addCharacterEncoding( 0140, "grave" );
+        addCharacterEncoding( 076, "greater" );
+        addCharacterEncoding( 0253, "guillemotleft" );
+        addCharacterEncoding( 0273, "guillemotright" );
+        addCharacterEncoding( 0210, "guilsinglleft" );
+        addCharacterEncoding( 0211, "guilsinglright" );
+        addCharacterEncoding( 0150, "h" );
+        addCharacterEncoding( 034, "hungarumlaut" );
+        addCharacterEncoding( 055, "hyphen" );
+        addCharacterEncoding( 0151, "i" );
+        addCharacterEncoding( 0355, "iacute" );
+        addCharacterEncoding( 0356, "icircumflex" );
+        addCharacterEncoding( 0357, "idieresis" );
+        addCharacterEncoding( 0354, "igrave" );
+        addCharacterEncoding( 0152, "j" );
+        addCharacterEncoding( 0153, "k" );
+        addCharacterEncoding( 0154, "l" );
+        addCharacterEncoding( 074, "less" );
+        addCharacterEncoding( 0254, "logicalnot" );
+        addCharacterEncoding( 0233, "lslash" );
+        addCharacterEncoding( 0155, "m" );
+        addCharacterEncoding( 0257, "macron" );
+        addCharacterEncoding( 0212, "minus" );
+        addCharacterEncoding( 0265, "mu" );
+        addCharacterEncoding( 0327, "multiply" );
+        addCharacterEncoding( 0156, "n" );
+        addCharacterEncoding( 071, "nine" );
+        addCharacterEncoding( 0361, "ntilde" );
+        addCharacterEncoding( 043, "numbersign" );
+        addCharacterEncoding( 0157, "o" );
+        addCharacterEncoding( 0363, "oacute" );
+        addCharacterEncoding( 0364, "ocircumflex" );
+        addCharacterEncoding( 0366, "odieresis" );
+        addCharacterEncoding( 0234, "oe" );
+        addCharacterEncoding( 035, "ogonek" );
+        addCharacterEncoding( 0362, "ograve" );
+        addCharacterEncoding( 061, "one" );
+        addCharacterEncoding( 0275, "onehalf" );
+        addCharacterEncoding( 0274, "onequarter" );
+        addCharacterEncoding( 0271, "onesuperior" );
+        addCharacterEncoding( 0252, "ordfeminine" );
+        addCharacterEncoding( 0272, "ordmasculine" );
+        addCharacterEncoding( 0370, "oslash" );
+        addCharacterEncoding( 0365, "otilde" );
+        addCharacterEncoding( 0160, "p" );
+        addCharacterEncoding( 0266, "paragraph" );
+        addCharacterEncoding( 050, "parenleft" );
+        addCharacterEncoding( 051, "parenright" );
+        addCharacterEncoding( 045, "percent" );
+        addCharacterEncoding( 056, "period" );
+        addCharacterEncoding( 0267, "periodcentered" );
+        addCharacterEncoding( 0213, "perthousand" );
+        addCharacterEncoding( 053, "plus" );
+        addCharacterEncoding( 0261, "plusminus" );
+        addCharacterEncoding( 0161, "q" );
+        addCharacterEncoding( 077, "question" );
+        addCharacterEncoding( 0277, "questiondown" );
+        addCharacterEncoding( 042, "quotedbl" );
+        addCharacterEncoding( 0214, "quotedblbase" );
+        addCharacterEncoding( 0215, "quotedblleft" );
+        addCharacterEncoding( 0216, "quotedblright" );
+        addCharacterEncoding( 0217, "quoteleft" );
+        addCharacterEncoding( 0220, "quoteright" );
+        addCharacterEncoding( 0221, "quotesinglbase" );
+        addCharacterEncoding( 047, "quotesingle" );
+        addCharacterEncoding( 0162, "r" );
+        addCharacterEncoding( 0256, "registered" );
+        addCharacterEncoding( 036, "ring" );
+        addCharacterEncoding( 0163, "s" );
+        addCharacterEncoding( 0235, "scaron" );
+        addCharacterEncoding( 0247, "section" );
+        addCharacterEncoding( 073, "semicolon" );
+        addCharacterEncoding( 067, "seven" );
+        addCharacterEncoding( 066, "six" );
+        addCharacterEncoding( 057, "slash" );
+        addCharacterEncoding( 040, "space" );
+        addCharacterEncoding( 0243, "sterling" );
+        addCharacterEncoding( 0164, "t" );
+        addCharacterEncoding( 0376, "thorn" );
+        addCharacterEncoding( 063, "three" );
+        addCharacterEncoding( 0276, "threequarters" );
+        addCharacterEncoding( 0263, "threesuperior" );
+        addCharacterEncoding( 037, "tilde" );
+        addCharacterEncoding( 0222, "trademark" );
+        addCharacterEncoding( 062, "two" );
+        addCharacterEncoding( 0262, "twosuperior" );
+        addCharacterEncoding( 0165, "u" );
+        addCharacterEncoding( 0372, "uacute" );
+        addCharacterEncoding( 0373, "ucircumflex" );
+        addCharacterEncoding( 0374, "udieresis" );
+        addCharacterEncoding( 0371, "ugrave" );
+        addCharacterEncoding( 0137, "underscore" );
+        addCharacterEncoding( 0166, "v" );
+        addCharacterEncoding( 0167, "w" );
+        addCharacterEncoding( 0170, "x" );
+        addCharacterEncoding( 0171, "y" );
+        addCharacterEncoding( 0375, "yacute" );
+        addCharacterEncoding( 0377, "ydieresis" );
+        addCharacterEncoding( 0245, "yen" );
+        addCharacterEncoding( 0172, "z" );
+        addCharacterEncoding( 0236, "zcaron" );
+        addCharacterEncoding( 060, "zero" );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return COSName.PDF_DOC_ENCODING;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/StandardEncoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/StandardEncoding.java
new file mode 100644 (file)
index 0000000..a70dc30
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * 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.pdfbox.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class StandardEncoding extends Encoding
+{
+
+    /**
+     * Singleton instance of this class.
+     *
+     * @since Apache PDFBox 1.3.0
+     */
+    public static final StandardEncoding INSTANCE = new StandardEncoding();
+
+    /**
+     * Constructor.
+     */
+    public StandardEncoding()
+    {
+        addCharacterEncoding( 0101, "A" );
+        addCharacterEncoding( 0341, "AE" );
+        addCharacterEncoding( 0102, "B" );
+        addCharacterEncoding( 0103, "C" );
+        addCharacterEncoding( 0104, "D" );
+        addCharacterEncoding( 0105, "E" );
+        addCharacterEncoding( 0106, "F" );
+        addCharacterEncoding( 0107, "G" );
+        addCharacterEncoding( 0110, "H" );
+        addCharacterEncoding( 0111, "I" );
+        addCharacterEncoding( 0112, "J" );
+        addCharacterEncoding( 0113, "K" );
+        addCharacterEncoding( 0114, "L" );
+        addCharacterEncoding( 0350, "Lslash" );
+        addCharacterEncoding( 0115, "M" );
+        addCharacterEncoding( 0116, "N" );
+        addCharacterEncoding( 0117, "O" );
+        addCharacterEncoding( 0352, "OE" );
+        addCharacterEncoding( 0351, "Oslash" );
+        addCharacterEncoding( 0120, "P" );
+        addCharacterEncoding( 0121, "Q" );
+        addCharacterEncoding( 0122, "R" );
+        addCharacterEncoding( 0123, "S" );
+        addCharacterEncoding( 0124, "T" );
+        addCharacterEncoding( 0125, "U" );
+        addCharacterEncoding( 0126, "V" );
+        addCharacterEncoding( 0127, "W" );
+        addCharacterEncoding( 0130, "X" );
+        addCharacterEncoding( 0131, "Y" );
+        addCharacterEncoding( 0132, "Z" );
+        addCharacterEncoding( 0141, "a" );
+        addCharacterEncoding( 0302, "acute" );
+        addCharacterEncoding( 0361, "ae" );
+        addCharacterEncoding( 0046, "ampersand" );
+        addCharacterEncoding( 0136, "asciicircum" );
+        addCharacterEncoding( 0176, "asciitilde" );
+        addCharacterEncoding( 0052, "asterisk" );
+        addCharacterEncoding( 0100, "at" );
+        addCharacterEncoding( 0142, "b" );
+        addCharacterEncoding( 0134, "backslash" );
+        addCharacterEncoding( 0174, "bar" );
+        addCharacterEncoding( 0173, "braceleft" );
+        addCharacterEncoding( 0175, "braceright" );
+        addCharacterEncoding( 0133, "bracketleft" );
+        addCharacterEncoding( 0135, "bracketright" );
+        addCharacterEncoding( 0306, "breve" );
+        addCharacterEncoding( 0267, "bullet" );
+        addCharacterEncoding( 0143, "c" );
+        addCharacterEncoding( 0317, "caron" );
+        addCharacterEncoding( 0313, "cedilla" );
+        addCharacterEncoding( 0242, "cent" );
+        addCharacterEncoding( 0303, "circumflex" );
+        addCharacterEncoding( 0072, "colon" );
+        addCharacterEncoding( 0054, "comma" );
+        addCharacterEncoding( 0250, "currency" );
+        addCharacterEncoding( 0144, "d" );
+        addCharacterEncoding( 0262, "dagger" );
+        addCharacterEncoding( 0263, "daggerdbl" );
+        addCharacterEncoding( 0310, "dieresis" );
+        addCharacterEncoding( 0044, "dollar" );
+        addCharacterEncoding( 0307, "dotaccent" );
+        addCharacterEncoding( 0365, "dotlessi" );
+        addCharacterEncoding( 0145, "e" );
+        addCharacterEncoding( 0070, "eight" );
+        addCharacterEncoding( 0274, "ellipsis" );
+        addCharacterEncoding( 0320, "emdash" );
+        addCharacterEncoding( 0261, "endash" );
+        addCharacterEncoding( 0075, "equal" );
+        addCharacterEncoding( 0041, "exclam" );
+        addCharacterEncoding( 0241, "exclamdown" );
+        addCharacterEncoding( 0146, "f" );
+        addCharacterEncoding( 0256, "fi" );
+        addCharacterEncoding( 0065, "five" );
+        addCharacterEncoding( 0257, "fl" );
+        addCharacterEncoding( 0246, "florin" );
+        addCharacterEncoding( 0064, "four" );
+        addCharacterEncoding( 0244, "fraction" );
+        addCharacterEncoding( 0147, "g" );
+        addCharacterEncoding( 0373, "germandbls" );
+        addCharacterEncoding( 0301, "grave" );
+        addCharacterEncoding( 0076, "greater" );
+        addCharacterEncoding( 0253, "guillemotleft" );
+        addCharacterEncoding( 0273, "guillemotright" );
+        addCharacterEncoding( 0254, "guilsinglleft" );
+        addCharacterEncoding( 0255, "guilsinglright" );
+        addCharacterEncoding( 0150, "h" );
+        addCharacterEncoding( 0315, "hungarumlaut" );
+        addCharacterEncoding( 0055, "hyphen" );
+        addCharacterEncoding( 0151, "i" );
+        addCharacterEncoding( 0152, "j" );
+        addCharacterEncoding( 0153, "k" );
+        addCharacterEncoding( 0154, "l" );
+        addCharacterEncoding( 0074, "less" );
+        addCharacterEncoding( 0370, "lslash" );
+        addCharacterEncoding( 0155, "m" );
+        addCharacterEncoding( 0305, "macron" );
+        addCharacterEncoding( 0156, "n" );
+        addCharacterEncoding( 0071, "nine" );
+        addCharacterEncoding( 0043, "numbersign" );
+        addCharacterEncoding( 0157, "o" );
+        addCharacterEncoding( 0372, "oe" );
+        addCharacterEncoding( 0316, "ogonek" );
+        addCharacterEncoding( 0061, "one" );
+        addCharacterEncoding( 0343, "ordfeminine" );
+        addCharacterEncoding( 0353, "ordmasculine" );
+        addCharacterEncoding( 0371, "oslash" );
+        addCharacterEncoding( 0160, "p" );
+        addCharacterEncoding( 0266, "paragraph" );
+        addCharacterEncoding( 0050, "parenleft" );
+        addCharacterEncoding( 0051, "parenright" );
+        addCharacterEncoding( 0045, "percent" );
+        addCharacterEncoding( 0056, "period" );
+        addCharacterEncoding( 0264, "periodcentered" );
+        addCharacterEncoding( 0275, "perthousand" );
+        addCharacterEncoding( 0053, "plus" );
+        addCharacterEncoding( 0161, "q" );
+        addCharacterEncoding( 0077, "question" );
+        addCharacterEncoding( 0277, "questiondown" );
+        addCharacterEncoding( 0042, "quotedbl" );
+        addCharacterEncoding( 0271, "quotedblbase" );
+        addCharacterEncoding( 0252, "quotedblleft" );
+        addCharacterEncoding( 0272, "quotedblright" );
+        addCharacterEncoding( 0140, "quoteleft" );
+        addCharacterEncoding( 0047, "quoteright" );
+        addCharacterEncoding( 0270, "quotesinglbase" );
+        addCharacterEncoding( 0251, "quotesingle" );
+        addCharacterEncoding( 0162, "r" );
+        addCharacterEncoding( 0312, "ring" );
+        addCharacterEncoding( 0163, "s" );
+        addCharacterEncoding( 0247, "section" );
+        addCharacterEncoding( 0073, "semicolon" );
+        addCharacterEncoding( 0067, "seven" );
+        addCharacterEncoding( 0066, "six" );
+        addCharacterEncoding( 0057, "slash" );
+        addCharacterEncoding( 0040, "space" );
+        addCharacterEncoding( 0243, "sterling" );
+        addCharacterEncoding( 0164, "t" );
+        addCharacterEncoding( 0063, "three" );
+        addCharacterEncoding( 0304, "tilde" );
+        addCharacterEncoding( 0062, "two" );
+        addCharacterEncoding( 0165, "u" );
+        addCharacterEncoding( 0137, "underscore" );
+        addCharacterEncoding( 0166, "v" );
+        addCharacterEncoding( 0167, "w" );
+        addCharacterEncoding( 0170, "x" );
+        addCharacterEncoding( 0171, "y" );
+        addCharacterEncoding( 0245, "yen" );
+        addCharacterEncoding( 0172, "z" );
+        addCharacterEncoding( 0060, "zero" );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return COSName.STANDARD_ENCODING;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/Type1Encoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/Type1Encoding.java
new file mode 100644 (file)
index 0000000..4ee66c9
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This class represents an encoding which was read from a type1 font.
+ * 
+ */
+public class Type1Encoding extends Encoding
+{
+    public Type1Encoding(int size)
+    {
+        for (int i=1;i<size;i++)
+        {
+            addCharacterEncoding(i, NOTDEF);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/WinAnsiEncoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/WinAnsiEncoding.java
new file mode 100644 (file)
index 0000000..9591535
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * 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.pdfbox.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This the win ansi encoding.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class WinAnsiEncoding extends Encoding
+{
+
+    /**
+     * Singleton instance of this class.
+     *
+     * @since Apache PDFBox 1.3.0
+     */
+    public static final WinAnsiEncoding INSTANCE = new WinAnsiEncoding();
+
+    /**
+     * Constructor.
+     */
+    public WinAnsiEncoding()
+    {
+        addCharacterEncoding( 0101, "A" );;
+        addCharacterEncoding( 0306, "AE" );;
+        addCharacterEncoding( 0301, "Aacute" );;
+        addCharacterEncoding( 0302, "Acircumflex" );;
+        addCharacterEncoding( 0304, "Adieresis" );;
+        addCharacterEncoding( 0300, "Agrave" );;
+        addCharacterEncoding( 0305, "Aring" );;
+        addCharacterEncoding( 0303, "Atilde" );;
+        addCharacterEncoding( 0102, "B" );;
+        addCharacterEncoding( 0103, "C" );;
+        addCharacterEncoding( 0307, "Ccedilla" );;
+        addCharacterEncoding( 0104, "D" );;
+        addCharacterEncoding( 0105, "E" );;
+        addCharacterEncoding( 0311, "Eacute" );;
+        addCharacterEncoding( 0312, "Ecircumflex" );;
+        addCharacterEncoding( 0313, "Edieresis" );;
+        addCharacterEncoding( 0310, "Egrave" );;
+        addCharacterEncoding( 0320, "Eth" );;
+        addCharacterEncoding( 0200, "Euro" );;
+        addCharacterEncoding( 0106, "F" );;
+        addCharacterEncoding( 0107, "G" );;
+        addCharacterEncoding( 0110, "H" );;
+        addCharacterEncoding( 0111, "I" );;
+        addCharacterEncoding( 0315, "Iacute" );;
+        addCharacterEncoding( 0316, "Icircumflex" );;
+        addCharacterEncoding( 0317, "Idieresis" );;
+        addCharacterEncoding( 0314, "Igrave" );;
+        addCharacterEncoding( 0112, "J" );;
+        addCharacterEncoding( 0113, "K" );;
+        addCharacterEncoding( 0114, "L" );;
+        addCharacterEncoding( 0115, "M" );;
+        addCharacterEncoding( 0116, "N" );;
+        addCharacterEncoding( 0321, "Ntilde" );;
+        addCharacterEncoding( 0117, "O" );;
+        addCharacterEncoding( 0214, "OE" );;
+        addCharacterEncoding( 0323, "Oacute" );;
+        addCharacterEncoding( 0324, "Ocircumflex" );;
+        addCharacterEncoding( 0326, "Odieresis" );;
+        addCharacterEncoding( 0322, "Ograve" );;
+        addCharacterEncoding( 0330, "Oslash" );;
+        addCharacterEncoding( 0325, "Otilde" );;
+        addCharacterEncoding( 0120, "P" );;
+        addCharacterEncoding( 0121, "Q" );;
+        addCharacterEncoding( 0122, "R" );;
+        addCharacterEncoding( 0123, "S" );;
+        addCharacterEncoding( 0212, "Scaron" );;
+        addCharacterEncoding( 0124, "T" );;
+        addCharacterEncoding( 0336, "Thorn" );;
+        addCharacterEncoding( 0125, "U" );;
+        addCharacterEncoding( 0332, "Uacute" );;
+        addCharacterEncoding( 0333, "Ucircumflex" );;
+        addCharacterEncoding( 0334, "Udieresis" );;
+        addCharacterEncoding( 0331, "Ugrave" );;
+        addCharacterEncoding( 0126, "V" );;
+        addCharacterEncoding( 0127, "W" );;
+        addCharacterEncoding( 0130, "X" );;
+        addCharacterEncoding( 0131, "Y" );;
+        addCharacterEncoding( 0335, "Yacute" );;
+        addCharacterEncoding( 0237, "Ydieresis" );;
+        addCharacterEncoding( 0132, "Z" );;
+        addCharacterEncoding( 0216, "Zcaron" );;
+        addCharacterEncoding( 0141, "a" );;
+        addCharacterEncoding( 0341, "aacute" );;
+        addCharacterEncoding( 0342, "acircumflex" );;
+        addCharacterEncoding( 0264, "acute" );;
+        addCharacterEncoding( 0344, "adieresis" );;
+        addCharacterEncoding( 0346, "ae" );;
+        addCharacterEncoding( 0340, "agrave" );;
+        addCharacterEncoding( 046, "ampersand" );;
+        addCharacterEncoding( 0345, "aring" );;
+        addCharacterEncoding( 0136, "asciicircum" );;
+        addCharacterEncoding( 0176, "asciitilde" );;
+        addCharacterEncoding( 052, "asterisk" );;
+        addCharacterEncoding( 0100, "at" );;
+        addCharacterEncoding( 0343, "atilde" );;
+        addCharacterEncoding( 0142, "b" );;
+        addCharacterEncoding( 0134, "backslash" );;
+        addCharacterEncoding( 0174, "bar" );;
+        addCharacterEncoding( 0173, "braceleft" );;
+        addCharacterEncoding( 0175, "braceright" );;
+        addCharacterEncoding( 0133, "bracketleft" );;
+        addCharacterEncoding( 0135, "bracketright" );;
+        addCharacterEncoding( 0246, "brokenbar" );;
+        addCharacterEncoding( 0225, "bullet" );;
+        addCharacterEncoding( 0143, "c" );;
+        addCharacterEncoding( 0347, "ccedilla" );;
+        addCharacterEncoding( 0270, "cedilla" );;
+        addCharacterEncoding( 0242, "cent" );;
+        addCharacterEncoding( 0210, "circumflex" );;
+        addCharacterEncoding( 072, "colon" );;
+        addCharacterEncoding( 054, "comma" );;
+        addCharacterEncoding( 0251, "copyright" );;
+
+        /**
+         * Added because cweb.pdf uses circlecopyrt
+         */
+        addCharacterEncoding( 0251, "circlecopyrt" );;
+        addCharacterEncoding( 0244, "currency" );;
+        addCharacterEncoding( 0144, "d" );;
+        addCharacterEncoding( 0206, "dagger" );;
+        addCharacterEncoding( 0207, "daggerdbl" );;
+        addCharacterEncoding( 0260, "degree" );;
+        addCharacterEncoding( 0250, "dieresis" );;
+        addCharacterEncoding( 0367, "divide" );;
+        addCharacterEncoding( 044, "dollar" );;
+        addCharacterEncoding( 0145, "e" );;
+        addCharacterEncoding( 0351, "eacute" );;
+        addCharacterEncoding( 0352, "ecircumflex" );;
+        addCharacterEncoding( 0353, "edieresis" );;
+        addCharacterEncoding( 0350, "egrave" );;
+        addCharacterEncoding( 070, "eight" );;
+        addCharacterEncoding( 0205, "ellipsis" );;
+        addCharacterEncoding( 0227, "emdash" );;
+        addCharacterEncoding( 0226, "endash" );;
+        addCharacterEncoding( 075, "equal" );;
+        addCharacterEncoding( 0360, "eth" );;
+        addCharacterEncoding( 041, "exclam" );;
+        addCharacterEncoding( 0241, "exclamdown" );;
+        addCharacterEncoding( 0146, "f" );;
+        addCharacterEncoding( 065, "five" );;
+        addCharacterEncoding( 0203, "florin" );;
+        addCharacterEncoding( 064, "four" );;
+        addCharacterEncoding( 0147, "g" );;
+        addCharacterEncoding( 0337, "germandbls" );;
+        addCharacterEncoding( 0140, "grave" );;
+        addCharacterEncoding( 076, "greater" );;
+        addCharacterEncoding( 0253, "guillemotleft" );;
+        addCharacterEncoding( 0273, "guillemotright" );;
+        addCharacterEncoding( 0213, "guilsinglleft" );;
+        addCharacterEncoding( 0233, "guilsinglright" );;
+        addCharacterEncoding( 0150, "h" );;
+        addCharacterEncoding( 055, "hyphen" );;
+        addCharacterEncoding( 0151, "i" );;
+        addCharacterEncoding( 0355, "iacute" );;
+        addCharacterEncoding( 0356, "icircumflex" );;
+        addCharacterEncoding( 0357, "idieresis" );;
+        addCharacterEncoding( 0354, "igrave" );;
+        addCharacterEncoding( 0152, "j" );;
+        addCharacterEncoding( 0153, "k" );;
+        addCharacterEncoding( 0154, "l" );;
+        addCharacterEncoding( 074, "less" );;
+        addCharacterEncoding( 0254, "logicalnot" );;
+        addCharacterEncoding( 0155, "m" );;
+        addCharacterEncoding( 0257, "macron" );;
+        addCharacterEncoding( 0265, "mu" );;
+        addCharacterEncoding( 0327, "multiply" );;
+        addCharacterEncoding( 0156, "n" );;
+        addCharacterEncoding( 071, "nine" );;
+        addCharacterEncoding( 0361, "ntilde" );;
+        addCharacterEncoding( 043, "numbersign" );;
+        addCharacterEncoding( 0157, "o" );;
+        addCharacterEncoding( 0363, "oacute" );;
+        addCharacterEncoding( 0364, "ocircumflex" );;
+        addCharacterEncoding( 0366, "odieresis" );;
+        addCharacterEncoding( 0234, "oe" );;
+        addCharacterEncoding( 0362, "ograve" );;
+        addCharacterEncoding( 061, "one" );;
+        addCharacterEncoding( 0275, "onehalf" );;
+        addCharacterEncoding( 0274, "onequarter" );;
+        addCharacterEncoding( 0271, "onesuperior" );;
+        addCharacterEncoding( 0252, "ordfeminine" );;
+        addCharacterEncoding( 0272, "ordmasculine" );;
+        addCharacterEncoding( 0370, "oslash" );;
+        addCharacterEncoding( 0365, "otilde" );;
+        addCharacterEncoding( 0160, "p" );;
+        addCharacterEncoding( 0266, "paragraph" );;
+        addCharacterEncoding( 050, "parenleft" );;
+        addCharacterEncoding( 051, "parenright" );;
+        addCharacterEncoding( 045, "percent" );;
+        addCharacterEncoding( 056, "period" );;
+        addCharacterEncoding( 0267, "periodcentered" );;
+        addCharacterEncoding( 0211, "perthousand" );;
+        addCharacterEncoding( 053, "plus" );;
+        addCharacterEncoding( 0261, "plusminus" );;
+        addCharacterEncoding( 0161, "q" );;
+        addCharacterEncoding( 077, "question" );;
+        addCharacterEncoding( 0277, "questiondown" );;
+        addCharacterEncoding( 042, "quotedbl" );;
+        addCharacterEncoding( 0204, "quotedblbase" );;
+        addCharacterEncoding( 0223, "quotedblleft" );;
+        addCharacterEncoding( 0224, "quotedblright" );;
+        addCharacterEncoding( 0221, "quoteleft" );;
+        addCharacterEncoding( 0222, "quoteright" );;
+        addCharacterEncoding( 0202, "quotesinglbase" );;
+        addCharacterEncoding( 047, "quotesingle" );;
+        addCharacterEncoding( 0162, "r" );;
+        addCharacterEncoding( 0256, "registered" );;
+        addCharacterEncoding( 0163, "s" );;
+        addCharacterEncoding( 0232, "scaron" );;
+        addCharacterEncoding( 0247, "section" );;
+        addCharacterEncoding( 073, "semicolon" );;
+        addCharacterEncoding( 067, "seven" );;
+        addCharacterEncoding( 066, "six" );;
+        addCharacterEncoding( 057, "slash" );;
+        addCharacterEncoding( 040, "space" );;
+        addCharacterEncoding( 0243, "sterling" );;
+        addCharacterEncoding( 0164, "t" );;
+        addCharacterEncoding( 0376, "thorn" );;
+        addCharacterEncoding( 063, "three" );;
+        addCharacterEncoding( 0276, "threequarters" );;
+        addCharacterEncoding( 0263, "threesuperior" );;
+        addCharacterEncoding( 0230, "tilde" );;
+        addCharacterEncoding( 0231, "trademark" );;
+        addCharacterEncoding( 062, "two" );;
+        addCharacterEncoding( 0262, "twosuperior" );;
+        addCharacterEncoding( 0165, "u" );;
+        addCharacterEncoding( 0372, "uacute" );;
+        addCharacterEncoding( 0373, "ucircumflex" );;
+        addCharacterEncoding( 0374, "udieresis" );;
+        addCharacterEncoding( 0371, "ugrave" );;
+        addCharacterEncoding( 0137, "underscore" );;
+        addCharacterEncoding( 0166, "v" );;
+        addCharacterEncoding( 0167, "w" );;
+        addCharacterEncoding( 0170, "x" );;
+        addCharacterEncoding( 0171, "y" );;
+        addCharacterEncoding( 0375, "yacute" );;
+        addCharacterEncoding( 0377, "ydieresis" );;
+        addCharacterEncoding( 0245, "yen" );;
+        addCharacterEncoding( 0172, "z" );;
+        addCharacterEncoding( 0236, "zcaron" );;
+        addCharacterEncoding( 060, "zero" );;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return COSName.WIN_ANSI_ENCODING;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CJKConverter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CJKConverter.java
new file mode 100644 (file)
index 0000000..7aa2fbc
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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.pdfbox.encoding.conversion;
+
+import org.apache.fontbox.cmap.CMap;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ *  CJKConverter converts encodings defined in CJKEncodings.
+ *
+ *  @author  Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ *  @version $Revision: 1.0 $
+ */
+public class CJKConverter implements EncodingConverter
+{
+    // The encoding
+    private String encodingName = null;    
+    // The java charset name
+    private String charsetName = null;
+
+
+    /**
+     *  Constructs a CJKConverter from a PDF encoding name.
+     *  
+     *  @param encoding the encoding to be used
+     */
+    public CJKConverter(String encoding)
+    {
+        encodingName = encoding;
+        charsetName = CJKEncodings.getCharset(encoding);
+    }
+
+   /**
+    *  Convert a string. It occurs when a cmap lookup returned
+    *  converted bytes successfully, but we still need to convert its
+    *  encoding. The parameter s is constructs as one byte or a UTF-16BE
+    *  encoded string.
+    *
+    *  Note: pdfbox set string to UTF-16BE charset before calling into
+    *  this.
+    *  
+    *  {@inheritDoc}
+    */
+    public String convertString(String s)
+    {
+        if ( s.length() == 1 )
+        {
+            return s;
+        }
+
+        if ( charsetName.equalsIgnoreCase("UTF-16BE") )
+        {
+            return s;
+        }
+
+        try 
+        {
+            return new String(s.getBytes("UTF-16BE"), charsetName);
+        }
+        catch ( UnsupportedEncodingException uee ) 
+        {
+            return s;   
+        }
+    }
+
+   /**
+    *  Convert bytes to a string. We just convert bytes within
+    *  coderange defined in CMap.
+    *
+    *  {@inheritDoc}
+    */
+    public String convertBytes(byte [] c, int offset, int length, CMap cmap)
+    {
+        if ( cmap != null ) 
+        {
+            try 
+            {
+                if ( cmap.isInCodeSpaceRanges(c, offset, length) )
+                {
+                    return new String(c, offset, length, charsetName);
+                }
+                else
+                {
+                    return null;
+                }
+
+            }
+            catch ( UnsupportedEncodingException uee ) 
+            {
+                return new String(c, offset, length);
+            }
+        }
+        // No cmap?
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CJKEncoding.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CJKEncoding.java
new file mode 100644 (file)
index 0000000..73fadce
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * 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.pdfbox.encoding.conversion;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * This class represents PDF encoding name to Java charset name mapping.
+ *
+ * @author  Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ * @version $Revision: 1.0 $
+ */
+class CJKEncodings
+{
+   // Mapping: PDF encoding name -> Java (IANA) charset name
+   private static HashMap charsetMapping = new HashMap();
+
+   private CJKEncodings()
+   {
+   }
+
+   static
+   {
+       // Chinese (Simplified)
+       // Microsoft Code Page 936 (lfCharSet 0x86), GB 2312-80 character set, EUC-CN encoding
+       charsetMapping.put("GB-EUC-H",        "GB2312");              
+       // Vertical version of GB-EUC-H
+       charsetMapping.put("GB-EUC-V",        "GB2312");              
+       // Mac OS, GB 2312-80 character set, EUC-CN encoding, Script Manager code 19
+       charsetMapping.put("GBpc-EUC-H",      "GB2312");              
+       // Vertical version of GBpc-EUC-H
+       charsetMapping.put("GBpc-EUC-V",      "GB2312");              
+       // Microsoft Code Page 936 (lfCharSet 0x86), GBK character set, GBK encoding
+       charsetMapping.put("GBK-EUC-H",       "GBK");                 
+       // Vertical version of GBK-EUC-H
+       charsetMapping.put("GBK-EUC-V",       "GBK");                 
+       // Same as GBK-EUC-H but replaces half-width Latin characters with proportional 
+       // forms and maps character code 0x24 to a dollar sign ($) instead of a yuan symbol (â\99\80â\88´)
+       charsetMapping.put("GBKp-EUC-H",      "GBK");                 
+       // Vertical version of GBKp-EUC-H
+       charsetMapping.put("GBKp-EUC-V",      "GBK");                 
+       // GB 18030-2000 character set, mixed 1-, 2-, and 4-byte encoding
+       charsetMapping.put("GBK2K-H",         "GB18030");             
+       // Vertical version of GBK2K-H
+       charsetMapping.put("GBK2K-V",         "GB18030");             
+       // Unicode (UCS-2) encoding for the Adobe-GB1 character collection
+       charsetMapping.put("UniGB-UCS2-H",    "ISO-10646-UCS-2");     
+       // Vertical version of UniGB-UCS2-H
+       charsetMapping.put("UniGB-UCS2-V",    "ISO-10646-UCS-2");     
+       // Unicode (UTF-16BE) encoding for the Adobe-GB1 character collection; contains mappings 
+       // for all characters in the GB18030-2000 character set
+       charsetMapping.put("UniGB-UTF16-H",   "UTF-16BE");            
+       // Vertical version of UniGB-UTF16-H
+       charsetMapping.put("UniGB-UTF16-V",   "UTF-16BE");            
+
+       // Chinese (Traditional)
+       // Mac OS, Big Five character set, Big Five encoding, Script Manager code 2
+       charsetMapping.put("B5pc-H",  "BIG5");                    
+       // Vertical version of B5pc-H
+       charsetMapping.put("B5pc-V",  "BIG5");                    
+       // Hong Kong SCS, an extension to the Big Five character set and encoding
+       charsetMapping.put("HKscs-B5-H",      "Big5-HKSCS");          
+       // Vertical version of HKscs-B5-H
+       charsetMapping.put("HKscs-B5-V",      "Big5-HKSCS");          
+       // Microsoft Code Page 950 (lfCharSet 0x88), Big Five character set with ETen extensions
+       charsetMapping.put("ETen-B5-H",       "BIG5");                
+       // Vertical version of ETen-B5-H
+       charsetMapping.put("ETen-B5-V",       "BIG5");                
+       // Same as ETen-B5-H but replaces half-width Latin characters with proportional forms
+       charsetMapping.put("ETenms-B5-H",     "BIG5");                
+       // Vertical version of ETenms-B5-H
+       charsetMapping.put("ETenms-B5-V",     "BIG5");                
+       // CNS 11643-1992 character set, EUC-TW encoding
+       charsetMapping.put("CNS-EUC-H",       "HZ");          
+       // Vertical version of CNS-EUC-H
+       charsetMapping.put("CNS-EUC-V",       "HZ");          
+       // Unicode (UCS-2) encoding for the Adobe-CNS1 character collection
+       charsetMapping.put("UniCNS-UCS2-H",   "ISO-10646-UCS-2");             
+       // Vertical version of UniCNS-UCS2-H
+       charsetMapping.put("UniCNS-UCS2-V",   "ISO-10646-UCS-2");             
+       // Unicode (UTF-16BE) encoding for the Adobe-CNS1 character collection; 
+       // contains mappings for all the characters in the HKSCS-2001 character set and 
+       // contains both 2- and 4- byte character codes
+       charsetMapping.put("UniCNS-UTF16-H",  "UTF-16BE");            
+       // Vertical version of UniCNS-UTF16-H
+       charsetMapping.put("UniCNS-UTF16-V",  "UTF-16BE");            
+
+       //Japanese
+       // Mac OS, JIS X 0208 character set with KanjiTalk6 extensions, Shift-JIS encoding, Script Manager code 1
+       charsetMapping.put("83pv-RKSJ-H",     "JIS");                 
+       // Microsoft Code Page 932 (lfCharSet 0x80), JIS X 0208 character set with NEC and IBM- extensions
+       charsetMapping.put("90ms-RKSJ-H",     "JIS");                 
+       // Vertical version of 90ms-RKSJ-H
+       charsetMapping.put("90ms-RKSJ-V",     "JIS");                 
+       // Same as 90ms-RKSJ-H but replaces half-width Latin characters with proportional forms
+       charsetMapping.put("90msp-RKSJ-H",    "JIS");                 
+       // Vertical version of 90msp-RKSJ-H
+       charsetMapping.put("90msp-RKSJ-V",    "JIS");                 
+       // Mac OS, JIS X 0208 character set with KanjiTalk7 extensions, Shift-JIS encoding, Script Manager code 1
+       charsetMapping.put("90pv-RKSJ-H",     "JIS");                 
+       // JIS X 0208 character set with Fujitsu FMR extensions, Shift-JIS encoding
+       charsetMapping.put("Add-RKSJ-H",      "JIS");                 
+       // Vertical version of Add-RKSJ-H
+       charsetMapping.put("Add-RKSJ-V",      "JIS");                 
+       // JIS X 0208 character set, EUC-JP encoding
+       charsetMapping.put("EUC-H",   "JIS");                    
+       // Vertical version of EUC-H
+       charsetMapping.put("EUC-V",   "JIS");                    
+       // JIS C 6226 (JIS78) character set with NEC extensions, Shift-JIS encoding
+       charsetMapping.put("Ext-RKSJ-H",      "JIS");                 
+       // Vertical version of Ext-RKSJ-H
+       charsetMapping.put("Ext-RKSJ-V",      "JIS");                 
+       // JIS X 0208 character set, ISO-2022-JP encoding
+       charsetMapping.put("H",       "JIS");                    
+       // Vertical version of H
+       charsetMapping.put("V",       "JIS");                   
+       // Unicode (UCS-2) encoding for the Adobe-Japan1 character collection
+       charsetMapping.put("UniJIS-UCS2-H",   "ISO-10646-UCS-2");             
+       // Vertical version of UniJIS-UCS2-H
+       charsetMapping.put("UniJIS-UCS2-V",   "ISO-10646-UCS-2");             
+       // Same as UniJIS-UCS2-H but replaces proportional Latin characters with half-width forms
+       charsetMapping.put("UniJIS-UCS2-HW-H",        "ISO-10646-UCS-2");     
+       // Vertical version of UniJIS-UCS2-HW-H
+       charsetMapping.put("UniJIS-UCS2-HW-V",        "ISO-10646-UCS-2");     
+       // Unicode (UTF-16BE) encoding for the Adobe-Japan1 character collection; 
+       // contains mappings for all characters in the JIS X 0213:1000 character set
+       charsetMapping.put("UniJIS-UTF16-H",  "UTF-16BE");             
+       // Vertical version of UniJIS-UTF16-H
+       charsetMapping.put("UniJIS-UTF16-V",  "UTF-16BE");            
+       // JIS X 0208 character set, ISO-2022-JP encoding
+       charsetMapping.put("Identity-H",       "JIS");                    
+       // Vertical version of H
+       charsetMapping.put("Identity-V",       "JIS");                    
+
+       //Korean
+       // KS X 1001:1992 character set, EUC-KR encoding
+       charsetMapping.put("KSC-EUC-H",       "KSC");                 
+       // Vertical version of KSC-EUC-H
+       charsetMapping.put("KSC-EUC-V",       "KSC");                 
+       // Microsoft Code Page 949 (lfCharSet 0x81), KS X 1001:1992 character set 
+       // plus 8822.putitional hangul, Unified Hangul Code (UHC) encoding
+       charsetMapping.put("KSCms-UHC-H",     "KSC");                
+       // Vertical version of KSCms-UHC-H
+       charsetMapping.put("KSCms-UHC-V",     "KSC");                 
+       // Same as KSCms-UHC-H but replaces proportional Latin characters with half-width forms
+       charsetMapping.put("KSCms-UHC-HW-H",  "KSC");                
+       // Vertical version of KSCms-UHC-HW-H
+       charsetMapping.put("KSCms-UHC-HW-V",  "KSC");                
+       // Mac OS, KS X 1001:1992 character set with Mac OS KH extensions, Script Manager Code 3
+       charsetMapping.put("KSCpc-EUC-H",     "KSC");                 
+       // Unicode (UCS-2) encoding for the Adobe-Korea1 character collection
+       charsetMapping.put("UniKS-UCS2-H",    "ISO-10646-UCS-2");             
+       // Vertical version of UniKS-UCS2-H
+       charsetMapping.put("UniKS-UCS2-V",    "ISO-10646-UCS-2");             
+       // Unicode (UTF-16BE) encoding for the Adobe-Korea1 character collection
+       charsetMapping.put("UniKS-UTF16-H",   "UTF-16BE");            
+       // Vertical version of UniKS-UTF16-H
+       charsetMapping.put("UniKS-UTF16-V",   "UTF-16BE");            
+   }
+
+
+   /**
+    *  Get respective Java charset name from given PDF encoding name.
+    *
+    *  @param encoding   PDF encoding name
+    *  @return Java charset name, or null if not found
+    */
+   public static final String getCharset( String encoding )
+   {
+       if ( encoding.startsWith("COSName"))
+       {
+           encoding = encoding.substring(8, encoding.length()-1);
+       }
+       return (String)(charsetMapping.get(encoding));
+   }
+
+   /**
+    *  Return an iterator to iterate through all encodings.
+    */
+   public static final Iterator getEncodingIterator()
+   {
+          return charsetMapping.keySet().iterator();
+   }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CMapSubstitution.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/CMapSubstitution.java
new file mode 100644 (file)
index 0000000..183672c
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.encoding.conversion;
+
+import java.util.HashMap;
+
+/**
+ * This class provides a mapping from char code to unicode mapping files used for CJK-encoding.
+ * @author Andreas Lehmkühler
+ * @version $Revision: 1.0 $
+ *
+ */
+
+public class CMapSubstitution 
+{
+
+    private static HashMap<String,String> cmapSubstitutions = new HashMap<String,String>();
+
+    private CMapSubstitution()
+    {
+    }
+
+    static 
+    {
+        // I don't know if these mappings are complete. Perhaps there 
+        // has to be added still one or more
+
+        // chinese simplified
+        cmapSubstitutions.put( "Adobe-GB1-4", "Adobe-GB1-UCS2" );
+        cmapSubstitutions.put( "GBK-EUC-H", "GBK-EUC-UCS2" );
+        cmapSubstitutions.put( "GBK-EUC-V", "GBK-EUC-UCS2" );
+        cmapSubstitutions.put( "GBpc-EUC-H", "GBpc-EUC-UCS2C" );
+        cmapSubstitutions.put( "GBpc-EUC-V", "GBpc-EUC-UCS2C" );
+
+        // chinese traditional
+        cmapSubstitutions.put( "Adobe-CNS1-4", "Adobe-CNS1-UCS2" );
+        cmapSubstitutions.put( "B5pc-H", "B5pc-UCS2" );
+        cmapSubstitutions.put( "B5pc-V", "B5pc-UCS2" );
+        cmapSubstitutions.put( "ETen-B5-H", "ETen-B5-UCS2" );
+        cmapSubstitutions.put( "ETen-B5-V", "ETen-B5-UCS2" );
+        cmapSubstitutions.put( "ETenms-B5-H", "ETen-B5-UCS2" );
+        cmapSubstitutions.put( "ETenms-B5-V", "ETen-B5-UCS2" );
+
+        // japanese
+        cmapSubstitutions.put( "90ms-RKSJ-H", "90ms-RKSJ-UCS2" );
+        cmapSubstitutions.put( "90ms-RKSJ-V", "90ms-RKSJ-UCS2" );
+        cmapSubstitutions.put( "90msp-RKSJ-H", "90ms-RKSJ-UCS2" );
+        cmapSubstitutions.put( "90msp-RKSJ-V", "90ms-RKSJ-UCS2" );
+        cmapSubstitutions.put( "90pv-RKSJ-H", "90pv-RKSJ-UCS2");
+        cmapSubstitutions.put( "UniJIS-UCS2-HW-H", "UniJIS-UCS2-H" );
+        cmapSubstitutions.put( "Adobe-Japan1-4", "Adobe-Japan1-UCS2");
+
+        cmapSubstitutions.put( "Adobe-Identity-0", "Identity-H");
+        cmapSubstitutions.put( "Adobe-Identity-1", "Identity-H");
+    }
+
+    /**
+     * 
+     * @param cmapName The name of a cmap for which we have to find a possible substitution
+     * @return the substitution for the given cmap name
+     */
+    public static String substituteCMap(String cmapName) 
+    {
+        if (cmapSubstitutions.containsKey(cmapName))
+        {
+            return (String)cmapSubstitutions.get(cmapName);
+        }
+        return cmapName;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/EncodingConversionManager.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/EncodingConversionManager.java
new file mode 100644 (file)
index 0000000..e3be351
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.pdfbox.encoding.conversion;
+
+import java.util.Iterator;
+import java.util.HashMap;
+
+/**
+ *  EncodingConversionManager maintains relationship between PDF encoding name
+ *  and respective EncodingConverter instance. Those PDF encoding name like
+ *  GBK-EUC-H should be converted to java charset name before constructing a
+ *  java string instance
+ *  
+ *  @author  Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ *  @version $Revision: 1.0 $
+ */
+public class EncodingConversionManager
+{
+       /**
+        *  Mapping from PDF encoding name to EncodingConverter instance.
+        */
+       private static HashMap encodingMap = new HashMap();
+
+       private EncodingConversionManager()
+       {
+       }
+
+       /**
+        *  Initialize the encodingMap before anything calls us.
+        */
+       static {
+
+           // Add CJK encodings to map
+           Iterator it = CJKEncodings.getEncodingIterator();
+
+           while ( it.hasNext() ) 
+           {
+               String encodingName = (String)(it.next());
+               encodingMap.put(encodingName, new CJKConverter(encodingName));
+           }
+           // If there is any other encoding conversions, please add it here.
+
+       }
+
+       /**
+        *  Get converter from given encoding name. If no converted defined,
+        *  a null is returned.
+        *  
+        *  @param encoding search for a converter for the given encoding name
+        *  @return the converter for the given encoding name
+        */
+       public static final EncodingConverter getConverter(String encoding)
+       {
+           return (EncodingConverter)(encodingMap.get(encoding));
+       }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/EncodingConverter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/EncodingConverter.java
new file mode 100644 (file)
index 0000000..e7b073a
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.encoding.conversion;
+
+import org.apache.fontbox.cmap.CMap;
+
+/**
+ *  EncodingConverter converts string or characters in one encoding, which is specified in PDF
+ *  file, to another string with respective java charset. The mapping from
+ *  PDF encoding name to java charset name is maintained by EncodingConversionManager
+
+ *  @author  Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ *  @version $Revision: 1.0 $
+ */
+public interface EncodingConverter
+{
+       /**
+        *  Convert a string.
+        *  
+        *  @param s the string to be converted
+        *  @return the converted string
+        */
+       public String convertString(String s);
+
+       /**
+        *  Convert bytes to a string.
+        *
+        *  @param c the byte array to be converted
+        *  @param offset the starting offset of the array
+        *  @param length the number of bytes
+        *  @param cmap the cmap to be used for conversion   
+        *  @return the converted string
+        */
+       public String convertBytes(byte [] c, int offset, int length, CMap cmap);
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/conversion/package.html
new file mode 100644 (file)
index 0000000..9ef0fd9
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains the implementations for conversions of encodings and CMaps that are used in PDF documents.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encoding/package.html
new file mode 100644 (file)
index 0000000..493554b
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains the implementations for all of the encodings that are used in PDF documents.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/ARCFour.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/ARCFour.java
new file mode 100644 (file)
index 0000000..fca9467
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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.pdfbox.encryption;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class is an implementation of the alleged RC4 algorithm.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class ARCFour
+{
+    private int[] salt;
+    private int b;
+    private int c;
+
+    /**
+     * Constructor.
+     *
+     */
+    public ARCFour()
+    {
+        salt = new int[256];
+    }
+
+    /**
+     * This will reset the key to be used.
+     *
+     * @param key The RC4 key used during encryption.
+    */
+    public void setKey( byte[] key )
+    {
+        b = 0;
+        c = 0;
+
+        if(key.length < 1 || key.length > 32)
+        {
+            throw new IllegalArgumentException("number of bytes must be between 1 and 32");
+        }
+        for(int i = 0; i < salt.length; i++)
+        {
+            salt[i] = i;
+        }
+
+        int keyIndex = 0;
+        int saltIndex = 0;
+        for( int i = 0; i < salt.length; i++)
+        {
+            saltIndex = (fixByte(key[keyIndex]) + salt[i] + saltIndex) % 256;
+            swap( salt, i, saltIndex );
+            keyIndex = (keyIndex + 1) % key.length;
+        }
+
+    }
+
+    /**
+     * Thie will ensure that the value for a byte >=0.
+     *
+     * @param aByte The byte to test against.
+     *
+     * @return A value >=0 and < 256
+     */
+    private static final int fixByte( byte aByte )
+    {
+        return aByte < 0 ? 256 + aByte : aByte;
+    }
+
+    /**
+     * This will swap two values in an array.
+     *
+     * @param data The array to swap from.
+     * @param firstIndex The index of the first element to swap.
+     * @param secondIndex The index of the second element to swap.
+     */
+    private static final void swap( int[] data, int firstIndex, int secondIndex )
+    {
+        int tmp = data[ firstIndex ];
+        data[ firstIndex ] = data[ secondIndex ];
+        data[ secondIndex ] = tmp;
+    }
+
+    /**
+     * This will encrypt and write the next byte.
+     *
+     * @param aByte The byte to encrypt.
+     * @param output The stream to write to.
+     *
+     * @throws IOException If there is an error writing to the output stream.
+     */
+    public void write( byte aByte, OutputStream output ) throws IOException
+    {
+        b = (b + 1) % 256;
+        c = (salt[b] + c) % 256;
+        swap( salt, b, c );
+        int saltIndex = (salt[b] + salt[c]) % 256;
+        output.write(aByte ^ (byte)salt[saltIndex]);
+    }
+
+    /**
+     * This will encrypt and write the data.
+     *
+     * @param data The data to encrypt.
+     * @param output The stream to write to.
+     *
+     * @throws IOException If there is an error writing to the output stream.
+     */
+    public void write( byte[] data, OutputStream output ) throws IOException
+    {
+        for( int i = 0; i < data.length; i++ )
+        {
+            write( data[i], output );
+        }
+    }
+
+    /**
+     * This will encrypt and write the data.
+     *
+     * @param data The data to encrypt.
+     * @param output The stream to write to.
+     *
+     * @throws IOException If there is an error writing to the output stream.
+     */
+    public void write( InputStream data, OutputStream output ) throws IOException
+    {
+        byte[] buffer = new byte[1024];
+        int amountRead = 0;
+        while( (amountRead = data.read( buffer )) != -1 )
+        {
+            write( buffer, 0, amountRead, output );
+        }
+    }
+
+    /**
+     * This will encrypt and write the data.
+     *
+     * @param data The data to encrypt.
+     * @param offset The offset into the array to start reading data from.
+     * @param len The number of bytes to attempt to read.
+     * @param output The stream to write to.
+     *
+     * @throws IOException If there is an error writing to the output stream.
+     */
+    public void write( byte[] data, int offset, int len, OutputStream output) throws IOException
+    {
+        for( int i = offset; i < offset + len; i++ )
+        {
+            write( data[i], output );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/DocumentEncryption.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/DocumentEncryption.java
new file mode 100644 (file)
index 0000000..6479248
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+ * 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.pdfbox.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.PDStandardEncryption;
+
+/**
+ * This class will deal with encrypting/decrypting a document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ *
+ * @deprecated use the new security API instead.
+ *
+ * @see org.apache.pdfbox.pdmodel.encryption.StandardSecurityHandler
+ */
+public class DocumentEncryption
+{
+    private PDDocument pdDocument = null;
+    private COSDocument document = null;
+
+    private byte[] encryptionKey = null;
+    private PDFEncryption encryption = new PDFEncryption();
+
+    private Set objects = new HashSet();
+
+    /**
+     * A set that contains potential signature dictionaries.  This is used
+     * because the Contents entry of the signature is not encrypted.
+     */
+    private Set potentialSignatures = new HashSet();
+
+    /**
+     * Constructor.
+     *
+     * @param doc The document to decrypt.
+     */
+    public DocumentEncryption( PDDocument doc )
+    {
+        pdDocument = doc;
+        document = doc.getDocument();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param doc The document to decrypt.
+     */
+    public DocumentEncryption( COSDocument doc )
+    {
+        pdDocument = new PDDocument( doc );
+        document = doc;
+    }
+
+    /**
+     * This will encrypt the given document, given the owner password and user password.
+     * The encryption method used is the standard filter.
+     *
+     * @throws CryptographyException If an error occurs during encryption.
+     * @throws IOException If there is an error accessing the data.
+     */
+    public void initForEncryption()
+        throws CryptographyException, IOException
+    {
+        String ownerPassword = pdDocument.getOwnerPasswordForEncryption();
+        String userPassword = pdDocument.getUserPasswordForEncryption();
+        if( ownerPassword == null )
+        {
+            ownerPassword = "";
+        }
+        if( userPassword == null )
+        {
+            userPassword = "";
+        }
+        PDStandardEncryption encParameters = (PDStandardEncryption)pdDocument.getEncryptionDictionary();
+        int permissionInt = encParameters.getPermissions();
+        int revision = encParameters.getRevision();
+        int length = encParameters.getLength()/8;
+        COSArray idArray = document.getDocumentID();
+
+        //check if the document has an id yet.  If it does not then
+        //generate one
+        if( idArray == null || idArray.size() < 2 )
+        {
+            idArray = new COSArray();
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance( "MD5" );
+                BigInteger time = BigInteger.valueOf( System.currentTimeMillis() );
+                md.update( time.toByteArray() );
+                md.update( ownerPassword.getBytes("ISO-8859-1") );
+                md.update( userPassword.getBytes("ISO-8859-1") );
+                md.update( document.toString().getBytes() );
+                byte[] id = md.digest( this.toString().getBytes("ISO-8859-1") );
+                COSString idString = new COSString();
+                idString.append( id );
+                idArray.add( idString );
+                idArray.add( idString );
+                document.setDocumentID( idArray );
+            }
+            catch( NoSuchAlgorithmException e )
+            {
+                throw new CryptographyException( e );
+            }
+
+        }
+        COSString id = (COSString)idArray.getObject( 0 );
+        encryption = new PDFEncryption();
+
+        byte[] o = encryption.computeOwnerPassword(
+            ownerPassword.getBytes("ISO-8859-1"),
+            userPassword.getBytes("ISO-8859-1"), revision, length);
+
+        byte[] u = encryption.computeUserPassword(
+            userPassword.getBytes("ISO-8859-1"),
+            o, permissionInt, id.getBytes(), revision, length);
+
+        encryptionKey = encryption.computeEncryptedKey(
+            userPassword.getBytes("ISO-8859-1"), o, permissionInt, id.getBytes(), revision, length);
+
+        encParameters.setOwnerKey( o );
+        encParameters.setUserKey( u );
+
+        document.setEncryptionDictionary( encParameters.getCOSDictionary() );
+    }
+
+
+
+    /**
+     * This will decrypt the document.
+     *
+     * @param password The password for the document.
+     *
+     * @throws CryptographyException If there is an error decrypting the document.
+     * @throws IOException If there is an error getting the stream data.
+     * @throws InvalidPasswordException If the password is not a user or owner password.
+     */
+    public void decryptDocument( String password )
+        throws CryptographyException, IOException, InvalidPasswordException
+    {
+        if( password == null )
+        {
+            password = "";
+        }
+
+        PDStandardEncryption encParameters = (PDStandardEncryption)pdDocument.getEncryptionDictionary();
+
+
+        int permissions = encParameters.getPermissions();
+        int revision = encParameters.getRevision();
+        int length = encParameters.getLength()/8;
+
+        COSString id = (COSString)document.getDocumentID().getObject( 0 );
+        byte[] u = encParameters.getUserKey();
+        byte[] o = encParameters.getOwnerKey();
+
+        boolean isUserPassword =
+            encryption.isUserPassword( password.getBytes("ISO-8859-1"), u,
+                o, permissions, id.getBytes(), revision, length );
+        boolean isOwnerPassword =
+            encryption.isOwnerPassword( password.getBytes("ISO-8859-1"), u,
+                o, permissions, id.getBytes(), revision, length );
+
+        if( isUserPassword )
+        {
+            encryptionKey =
+                encryption.computeEncryptedKey(
+                    password.getBytes("ISO-8859-1"), o,
+                    permissions, id.getBytes(), revision, length );
+        }
+        else if( isOwnerPassword )
+        {
+            byte[] computedUserPassword =
+                encryption.getUserPassword(
+                    password.getBytes("ISO-8859-1"),
+                    o,
+                    revision,
+                    length );
+            encryptionKey =
+                encryption.computeEncryptedKey(
+                    computedUserPassword, o,
+                    permissions, id.getBytes(), revision, length );
+        }
+        else
+        {
+            throw new InvalidPasswordException( "Error: The supplied password does not match " +
+                                                "either the owner or user password in the document." );
+        }
+
+        COSDictionary trailer = document.getTrailer();
+        COSArray fields = (COSArray)trailer.getObjectFromPath( "Root/AcroForm/Fields" );
+
+        //We need to collect all the signature dictionaries, for some
+        //reason the 'Contents' entry of signatures is not really encrypted
+        if( fields != null )
+        {
+            for( int i=0; i<fields.size(); i++ )
+            {
+                COSDictionary field = (COSDictionary)fields.getObject( i );
+                addDictionaryAndSubDictionary( potentialSignatures, field );
+            }
+        }
+
+        List allObjects = document.getObjects();
+        Iterator objectIter = allObjects.iterator();
+        while( objectIter.hasNext() )
+        {
+            decryptObject( (COSObject)objectIter.next() );
+        }
+        document.setEncryptionDictionary( null );
+    }
+
+    private void addDictionaryAndSubDictionary( Set set, COSDictionary dic )
+    {
+        set.add( dic );
+        COSArray kids = (COSArray)dic.getDictionaryObject( COSName.KIDS );
+        for( int i=0; kids != null && i<kids.size(); i++ )
+        {
+            addDictionaryAndSubDictionary( set, (COSDictionary)kids.getObject( i ) );
+        }
+        COSBase value = dic.getDictionaryObject( COSName.V );
+        if( value instanceof COSDictionary )
+        {
+            addDictionaryAndSubDictionary( set, (COSDictionary)value );
+        }
+    }
+
+    /**
+     * This will decrypt an object in the document.
+     *
+     * @param object The object to decrypt.
+     *
+     * @throws CryptographyException If there is an error decrypting the stream.
+     * @throws IOException If there is an error getting the stream data.
+     */
+    private void decryptObject( COSObject object )
+        throws CryptographyException, IOException
+    {
+        long objNum = object.getObjectNumber().intValue();
+        long genNum = object.getGenerationNumber().intValue();
+        COSBase base = object.getObject();
+        decrypt( base, objNum, genNum );
+    }
+
+    /**
+     * This will dispatch to the correct method.
+     *
+     * @param obj The object to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation Number.
+     *
+     * @throws CryptographyException If there is an error decrypting the stream.
+     * @throws IOException If there is an error getting the stream data.
+     */
+    public void decrypt( Object obj, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        if( !objects.contains( obj ) )
+        {
+            objects.add( obj );
+
+            if( obj instanceof COSString )
+            {
+                decryptString( (COSString)obj, objNum, genNum );
+            }
+            else if( obj instanceof COSStream )
+            {
+                decryptStream( (COSStream)obj, objNum, genNum );
+            }
+            else if( obj instanceof COSDictionary )
+            {
+                decryptDictionary( (COSDictionary)obj, objNum, genNum );
+            }
+            else if( obj instanceof COSArray )
+            {
+                decryptArray( (COSArray)obj, objNum, genNum );
+            }
+        }
+    }
+
+    /**
+     * This will decrypt a stream.
+     *
+     * @param stream The stream to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If there is an error getting the stream.
+     * @throws IOException If there is an error getting the stream data.
+     */
+    private void decryptStream( COSStream stream, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        decryptDictionary( stream, objNum, genNum );
+        InputStream encryptedStream = stream.getFilteredStream();
+        encryption.encryptData( objNum,
+                                genNum,
+                                encryptionKey,
+                                encryptedStream,
+                                stream.createFilteredStream() );
+    }
+
+    /**
+     * This will decrypt a dictionary.
+     *
+     * @param dictionary The dictionary to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If there is an error decrypting the document.
+     * @throws IOException If there is an error creating a new string.
+     */
+    private void decryptDictionary( COSDictionary dictionary, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        for( Map.Entry<COSName, COSBase> entry : dictionary.entrySet() )
+        {
+            //if we are a signature dictionary and contain a Contents entry then
+            //we don't decrypt it.
+            if( !(entry.getKey().getName().equals( "Contents" ) &&
+                  entry.getValue() instanceof COSString &&
+                  potentialSignatures.contains( dictionary )))
+            {
+                decrypt( entry.getValue(), objNum, genNum );
+            }
+        }
+    }
+
+    /**
+     * This will decrypt a string.
+     *
+     * @param string the string to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If an error occurs during decryption.
+     * @throws IOException If an error occurs writing the new string.
+     */
+    private void decryptString( COSString string, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        ByteArrayInputStream data = new ByteArrayInputStream( string.getBytes() );
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        encryption.encryptData( objNum,
+                                genNum,
+                                encryptionKey,
+                                data,
+                                buffer );
+        string.reset();
+        string.append( buffer.toByteArray() );
+    }
+
+    /**
+     * This will decrypt an array.
+     *
+     * @param array The array to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If an error occurs during decryption.
+     * @throws IOException If there is an error accessing the data.
+     */
+    private void decryptArray( COSArray array, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        for( int i=0; i<array.size(); i++ )
+        {
+            decrypt( array.get( i ), objNum, genNum );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/PDFEncryption.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/PDFEncryption.java
new file mode 100644 (file)
index 0000000..5963705
--- /dev/null
@@ -0,0 +1,589 @@
+/*
+ * 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.pdfbox.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.pdfbox.exceptions.CryptographyException;
+
+/**
+ * This class will deal with PDF encryption algorithms.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ *
+ * @deprecated use the new security layer instead
+ *
+ * @see org.apache.pdfbox.pdmodel.encryption.StandardSecurityHandler
+ */
+public final class PDFEncryption
+{
+    private ARCFour rc4 = new ARCFour();
+    /**
+     * The encryption padding defined in the PDF 1.4 Spec algorithm 3.2.
+     */
+    public static final byte[] ENCRYPT_PADDING =
+    {
+        (byte)0x28, (byte)0xBF, (byte)0x4E, (byte)0x5E, (byte)0x4E,
+        (byte)0x75, (byte)0x8A, (byte)0x41, (byte)0x64, (byte)0x00,
+        (byte)0x4E, (byte)0x56, (byte)0xFF, (byte)0xFA, (byte)0x01,
+        (byte)0x08, (byte)0x2E, (byte)0x2E, (byte)0x00, (byte)0xB6,
+        (byte)0xD0, (byte)0x68, (byte)0x3E, (byte)0x80, (byte)0x2F,
+        (byte)0x0C, (byte)0xA9, (byte)0xFE, (byte)0x64, (byte)0x53,
+        (byte)0x69, (byte)0x7A
+    };
+
+    /**
+     * This will encrypt a piece of data.
+     *
+     * @param objectNumber The id for the object.
+     * @param genNumber The generation id for the object.
+     * @param key The key used to encrypt the data.
+     * @param data The data to encrypt/decrypt.
+     * @param output The stream to write to.
+     *
+     * @throws CryptographyException If there is an error encrypting the data.
+     * @throws IOException If there is an io error.
+     */
+    public final void encryptData(
+        long objectNumber,
+        long genNumber,
+        byte[] key,
+        InputStream data,
+        OutputStream output )
+        throws CryptographyException, IOException
+    {
+        byte[] newKey = new byte[ key.length + 5 ];
+        System.arraycopy( key, 0, newKey, 0, key.length );
+        //PDF 1.4 reference pg 73
+        //step 1
+        //we have the reference
+
+        //step 2
+        newKey[newKey.length -5] = (byte)(objectNumber & 0xff);
+        newKey[newKey.length -4] = (byte)((objectNumber >> 8) & 0xff);
+        newKey[newKey.length -3] = (byte)((objectNumber >> 16) & 0xff);
+        newKey[newKey.length -2] = (byte)(genNumber & 0xff);
+        newKey[newKey.length -1] = (byte)((genNumber >> 8) & 0xff);
+
+
+        //step 3
+        byte[] digestedKey = null;
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance( "MD5" );
+            digestedKey = md.digest( newKey );
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            throw new CryptographyException( e );
+        }
+
+        //step 4
+        int length = Math.min( newKey.length, 16 );
+        byte[] finalKey = new byte[ length ];
+        System.arraycopy( digestedKey, 0, finalKey, 0, length );
+
+        rc4.setKey( finalKey );
+        rc4.write( data, output );
+        output.flush();
+    }
+
+    /**
+     * This will get the user password from the owner password and the documents o value.
+     *
+     * @param ownerPassword The plaintext owner password.
+     * @param o The document's o entry.
+     * @param revision The document revision number.
+     * @param length The length of the encryption.
+     *
+     * @return The plaintext padded user password.
+     *
+     * @throws CryptographyException If there is an error getting the user password.
+     * @throws IOException If there is an error reading data.
+     */
+    public final byte[] getUserPassword(
+        byte[] ownerPassword,
+        byte[] o,
+        int revision,
+        long length )
+        throws CryptographyException, IOException
+    {
+        try
+        {
+            ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            //3.3 STEP 1
+            byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+            //3.3 STEP 2
+            MessageDigest md = MessageDigest.getInstance( "MD5" );
+            md.update( ownerPadded );
+            byte[] digest = md.digest();
+
+            //3.3 STEP 3
+            if( revision == 3 || revision == 4 )
+            {
+                for( int i=0; i<50; i++ )
+                {
+                    md.reset();
+                    md.update( digest );
+                    digest = md.digest();
+                }
+            }
+            if( revision == 2 && length != 5 )
+            {
+                throw new CryptographyException(
+                    "Error: Expected length=5 actual=" + length );
+            }
+
+            //3.3 STEP 4
+            byte[] rc4Key = new byte[ (int)length ];
+            System.arraycopy( digest, 0, rc4Key, 0, (int)length );
+
+            //3.7 step 2
+            if( revision == 2 )
+            {
+                rc4.setKey( rc4Key );
+                rc4.write( o, result );
+            }
+            else if( revision == 3 || revision == 4)
+            {
+                /**
+                byte[] iterationKey = new byte[ rc4Key.length ];
+                byte[] dataToEncrypt = o;
+                for( int i=19; i>=0; i-- )
+                {
+                    System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+                    for( int j=0; j< iterationKey.length; j++ )
+                    {
+                        iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+                    }
+                    rc4.setKey( iterationKey );
+                    rc4.write( dataToEncrypt, result );
+                    dataToEncrypt = result.toByteArray();
+                    result.reset();
+                }
+                result.write( dataToEncrypt, 0, dataToEncrypt.length );
+                */
+                byte[] iterationKey = new byte[ rc4Key.length ];
+
+
+                byte[] otemp = new byte[ o.length ]; //sm
+                System.arraycopy( o, 0, otemp, 0, o.length ); //sm
+                rc4.write( o, result);//sm
+
+                for( int i=19; i>=0; i-- )
+                {
+                    System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+                    for( int j=0; j< iterationKey.length; j++ )
+                    {
+                        iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+                    }
+                    rc4.setKey( iterationKey );
+                    result.reset();  //sm
+                    rc4.write( otemp, result ); //sm
+                    otemp = result.toByteArray(); //sm
+                }
+            }
+
+
+            return result.toByteArray();
+
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            throw new CryptographyException( e );
+        }
+    }
+
+    /**
+     * This will tell if this is the owner password or not.
+     *
+     * @param ownerPassword The plaintext owner password.
+     * @param u The U value from the PDF Document.
+     * @param o The owner password hash.
+     * @param permissions The document permissions.
+     * @param id The document id.
+     * @param revision The revision of the encryption.
+     * @param length The length of the encryption key.
+     *
+     * @return true if the owner password matches the one from the document.
+     *
+     * @throws CryptographyException If there is an error while executing crypt functions.
+     * @throws IOException If there is an error while checking owner password.
+     */
+    public final boolean isOwnerPassword(
+        byte[] ownerPassword,
+        byte[] u,
+        byte[] o,
+        int permissions,
+        byte[] id,
+        int revision,
+        int length)
+        throws CryptographyException, IOException
+    {
+        byte[] userPassword = getUserPassword( ownerPassword, o, revision, length );
+        return isUserPassword( userPassword, u, o, permissions, id, revision, length );
+    }
+
+    /**
+     * This will tell if this is a valid user password.
+     *
+     * Algorithm 3.6 pg 80
+     *
+     * @param password The password to test.
+     * @param u The U value from the PDF Document.
+     * @param o The owner password hash.
+     * @param permissions The document permissions.
+     * @param id The document id.
+     * @param revision The revision of the encryption.
+     * @param length The length of the encryption key.
+     *
+     * @return true If this is the correct user password.
+     *
+     * @throws CryptographyException If there is an error computing the value.
+     * @throws IOException If there is an IO error while computing the owners password.
+     */
+    public final boolean isUserPassword(
+        byte[] password,
+        byte[] u,
+        byte[] o,
+        int permissions,
+        byte[] id,
+        int revision,
+        int length)
+        throws CryptographyException, IOException
+    {
+        boolean matches = false;
+        //STEP 1
+        byte[] computedValue = computeUserPassword( password, o, permissions, id, revision, length );
+        if( revision == 2 )
+        {
+            //STEP 2
+            matches = arraysEqual( u, computedValue );
+        }
+        else if( revision == 3 || revision == 4 )
+        {
+            //STEP 2
+            matches = arraysEqual( u, computedValue, 16 );
+        }
+        return matches;
+    }
+
+    /**
+     * This will compare two byte[] for equality for count number of bytes.
+     *
+     * @param first The first byte array.
+     * @param second The second byte array.
+     * @param count The number of bytes to compare.
+     *
+     * @return true If the arrays contain the exact same data.
+     */
+    private final boolean arraysEqual( byte[] first, byte[] second, int count )
+    {
+        boolean equal = first.length >= count && second.length >= count;
+        for( int i=0; i<count && equal; i++ )
+        {
+            equal = first[i] == second[i];
+        }
+        return equal;
+    }
+
+    /**
+     * This will compare two byte[] for equality.
+     *
+     * @param first The first byte array.
+     * @param second The second byte array.
+     *
+     * @return true If the arrays contain the exact same data.
+     */
+    private final boolean arraysEqual( byte[] first, byte[] second )
+    {
+        boolean equal = first.length == second.length;
+        for( int i=0; i<first.length && equal; i++ )
+        {
+            equal = first[i] == second[i];
+        }
+        return equal;
+    }
+
+    /**
+     * This will compute the user password hash.
+     *
+     * @param password The plain text password.
+     * @param o The owner password hash.
+     * @param permissions The document permissions.
+     * @param id The document id.
+     * @param revision The revision of the encryption.
+     * @param length The length of the encryption key.
+     *
+     * @return The user password.
+     *
+     * @throws CryptographyException If there is an error computing the user password.
+     * @throws IOException If there is an IO error.
+     */
+    public final byte[] computeUserPassword(
+        byte[] password,
+        byte[] o,
+        int permissions,
+        byte[] id,
+        int revision,
+        int length )
+        throws CryptographyException, IOException
+    {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        //STEP 1
+        byte[] encryptionKey = computeEncryptedKey( password, o, permissions, id, revision, length );
+
+        if( revision == 2 )
+        {
+            //STEP 2
+            rc4.setKey( encryptionKey );
+            rc4.write( ENCRYPT_PADDING, result );
+        }
+        else if( revision == 3 || revision == 4 )
+        {
+            try
+            {
+                //STEP 2
+                MessageDigest md = MessageDigest.getInstance("MD5");
+                //md.update( truncateOrPad( password ) );
+                md.update( ENCRYPT_PADDING );
+
+                //STEP 3
+                md.update( id );
+                result.write( md.digest() );
+
+                //STEP 4 and 5
+                byte[] iterationKey = new byte[ encryptionKey.length ];
+                for( int i=0; i<20; i++ )
+                {
+                    System.arraycopy( encryptionKey, 0, iterationKey, 0, iterationKey.length );
+                    for( int j=0; j< iterationKey.length; j++ )
+                    {
+                        iterationKey[j] = (byte)(iterationKey[j] ^ i);
+                    }
+                    rc4.setKey( iterationKey );
+                    ByteArrayInputStream input = new ByteArrayInputStream( result.toByteArray() );
+                    result.reset();
+                    rc4.write( input, result );
+                }
+
+                //step 6
+                byte[] finalResult = new byte[32];
+                System.arraycopy( result.toByteArray(), 0, finalResult, 0, 16 );
+                System.arraycopy( ENCRYPT_PADDING, 0, finalResult, 16, 16 );
+                result.reset();
+                result.write( finalResult );
+            }
+            catch( NoSuchAlgorithmException e )
+            {
+                throw new CryptographyException( e );
+            }
+        }
+        return result.toByteArray();
+    }
+
+    /**
+     * This will compute the encrypted key.
+     *
+     * @param password The password used to compute the encrypted key.
+     * @param o The owner password hash.
+     * @param permissions The permissions for the document.
+     * @param id The document id.
+     * @param revision The security revision.
+     * @param length The length of the encryption key.
+     *
+     * @return The encryption key.
+     *
+     * @throws CryptographyException If there is an error computing the key.
+     */
+    public final byte[] computeEncryptedKey(
+        byte[] password,
+        byte[] o,
+        int permissions,
+        byte[] id,
+        int revision,
+        int length )
+        throws CryptographyException
+    {
+        byte[] result = new byte[ length ];
+        try
+        {
+            //PDFReference 1.4 pg 78
+            //step1
+            byte[] padded = truncateOrPad( password );
+
+            //step 2
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            md.update( padded );
+
+            //step 3
+            md.update( o );
+
+            //step 4
+            byte zero = (byte)(permissions >>> 0);
+            byte one = (byte)(permissions >>> 8);
+            byte two = (byte)(permissions >>> 16);
+            byte three = (byte)(permissions >>> 24);
+
+            md.update( zero );
+            md.update( one );
+            md.update( two );
+            md.update( three );
+
+            //step 5
+            md.update( id );
+            byte[] digest = md.digest();
+
+            //step 6
+            if( revision == 3 || revision == 4)
+            {
+                for( int i=0; i<50; i++ )
+                {
+                    md.reset();
+                    md.update( digest, 0, length );
+                    digest = md.digest();
+                }
+            }
+
+            //step 7
+            if( revision == 2 && length != 5 )
+            {
+                throw new CryptographyException(
+                    "Error: length should be 5 when revision is two actual=" + length );
+            }
+            System.arraycopy( digest, 0, result, 0, length );
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            throw new CryptographyException( e );
+        }
+        return result;
+    }
+
+    /**
+     * This algorithm is taked from PDF Reference 1.4 Algorithm 3.3 Page 79.
+     *
+     * @param ownerPassword The plain owner password.
+     * @param userPassword The plain user password.
+     * @param revision The version of the security.
+     * @param length The length of the document.
+     *
+     * @return The computed owner password.
+     *
+     * @throws CryptographyException If there is an error computing O.
+     * @throws IOException If there is an error computing O.
+     */
+    public final byte[] computeOwnerPassword(
+        byte[] ownerPassword,
+        byte[] userPassword,
+        int revision,
+        int length )
+        throws CryptographyException, IOException
+    {
+        try
+        {
+            //STEP 1
+            byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+            //STEP 2
+            MessageDigest md = MessageDigest.getInstance( "MD5" );
+            md.update( ownerPadded );
+            byte[] digest = md.digest();
+
+            //STEP 3
+            if( revision == 3 || revision == 4)
+            {
+                for( int i=0; i<50; i++ )
+                {
+                    md.reset();
+                    md.update( digest, 0, length );
+                    digest = md.digest();
+                }
+            }
+            if( revision == 2 && length != 5 )
+            {
+                throw new CryptographyException(
+                    "Error: Expected length=5 actual=" + length );
+            }
+
+            //STEP 4
+            byte[] rc4Key = new byte[ length ];
+            System.arraycopy( digest, 0, rc4Key, 0, length );
+
+            //STEP 5
+            byte[] paddedUser = truncateOrPad( userPassword );
+
+
+            //STEP 6
+            rc4.setKey( rc4Key );
+            ByteArrayOutputStream crypted = new ByteArrayOutputStream();
+            rc4.write( new ByteArrayInputStream( paddedUser ), crypted );
+
+
+            //STEP 7
+            if( revision == 3 || revision == 4 )
+            {
+                byte[] iterationKey = new byte[ rc4Key.length ];
+                for( int i=1; i<20; i++ )
+                {
+                    System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+                    for( int j=0; j< iterationKey.length; j++ )
+                    {
+                        iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+                    }
+                    rc4.setKey( iterationKey );
+                    ByteArrayInputStream input = new ByteArrayInputStream( crypted.toByteArray() );
+                    crypted.reset();
+                    rc4.write( input, crypted );
+                }
+            }
+
+            //STEP 8
+            return crypted.toByteArray();
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            throw new CryptographyException( e.getMessage() );
+        }
+    }
+
+    /**
+     * This will take the password and truncate or pad it as necessary.
+     *
+     * @param password The password to pad or truncate.
+     *
+     * @return The padded or truncated password.
+     */
+    private final byte[] truncateOrPad( byte[] password )
+    {
+        byte[] padded = new byte[ ENCRYPT_PADDING.length ];
+        int bytesBeforePad = Math.min( password.length, padded.length );
+        System.arraycopy( password, 0, padded, 0, bytesBeforePad );
+        System.arraycopy( ENCRYPT_PADDING, 0, padded, bytesBeforePad, ENCRYPT_PADDING.length-bytesBeforePad );
+        return padded;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/encryption/package.html
new file mode 100644 (file)
index 0000000..7647c1c
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These classes deal with encryption algorithms that are used in the PDF Document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/AbstractExample.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/AbstractExample.java
new file mode 100644 (file)
index 0000000..cd32069
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.pdfbox.examples;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.cos.COSDocument;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+/**
+ * A simple class which has some methods used by all examples.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public abstract class AbstractExample
+{
+    /**
+     * Close the stream.
+     *
+     * @param stream The stream to close.
+     *
+     * @throws IOException If there is an error closing the stream.
+     */
+    public void close( InputStream stream ) throws IOException
+    {
+        if( stream != null )
+        {
+            stream.close();
+        }
+    }
+
+    /**
+     * Close the stream.
+     *
+     * @param stream The stream to close.
+     *
+     * @throws IOException If there is an error closing the stream.
+     */
+    public void close( OutputStream stream ) throws IOException
+    {
+        if( stream != null )
+        {
+            stream.close();
+        }
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( COSDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+
+    /**
+     * Close the document.
+     *
+     * @param doc The doc to close.
+     *
+     * @throws IOException If there is an error closing the document.
+     */
+    public void close( PDDocument doc ) throws IOException
+    {
+        if( doc != null )
+        {
+            doc.close();
+        }
+    }
+
+    /**
+     * Close the writer.
+     *
+     * @param writer The writer to close.
+     *
+     * @throws IOException If there is an error closing the writer.
+     */
+    public static void close( COSWriter writer ) throws IOException
+    {
+        if( writer != null )
+        {
+            writer.close();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/PrintFields.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/PrintFields.java
new file mode 100644 (file)
index 0000000..8a45624
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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.pdfbox.examples.fdf;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+/**
+ * This example will take a PDF document and print all the fields from the file.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.16 $
+ */
+public class PrintFields
+{
+
+    /**
+     * This will print all the fields from the document.
+     *
+     * @param pdfDocument The PDF to get the fields from.
+     *
+     * @throws IOException If there is an error getting the fields.
+     */
+    public void printFields( PDDocument pdfDocument ) throws IOException
+    {
+        PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+        PDAcroForm acroForm = docCatalog.getAcroForm();
+        List fields = acroForm.getFields();
+        Iterator fieldsIter = fields.iterator();
+
+        System.out.println(new Integer(fields.size()).toString() + " top-level fields were found on the form");
+
+        while( fieldsIter.hasNext())
+        {
+            PDField field = (PDField)fieldsIter.next();
+               processField(field, "|--", field.getPartialName());
+        }
+    }
+
+    private void processField(PDField field, String sLevel, String sParent) throws IOException
+    {
+        List kids = field.getKids();
+        if(kids != null)
+        {
+            Iterator kidsIter = kids.iterator();
+            if(!sParent.equals(field.getPartialName()))
+            {
+               sParent = sParent + "." + field.getPartialName();
+            }
+            System.out.println(sLevel + sParent);
+            //System.out.println(sParent + " is of type " + field.getClass().getName());
+            while(kidsIter.hasNext())
+            {
+               Object pdfObj = kidsIter.next();
+               if(pdfObj instanceof PDField)
+               {
+                   PDField kid = (PDField)pdfObj;
+                   processField(kid, "|  " + sLevel, sParent);
+               }
+            }
+         }
+         else
+         {
+             String outputString = sLevel + sParent + "." + field.getPartialName() + " = " + field.getValue() +
+                 ",  type=" + field.getClass().getName();
+
+             System.out.println(outputString);
+         }
+    }
+
+    /**
+     * This will read a PDF file and print out the form elements.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     *
+     * @throws IOException If there is an error importing the FDF document.
+     * @throws CryptographyException If there is an error decrypting the document.
+     */
+    public static void main(String[] args) throws IOException, CryptographyException
+    {
+        PDDocument pdf = null;
+        try
+        {
+            if( args.length != 1 )
+            {
+                usage();
+            }
+            else
+            {
+                pdf = PDDocument.load( args[0] );
+                PrintFields exporter = new PrintFields();
+                if( pdf.isEncrypted() )
+                {
+                    try
+                    {
+                        pdf.decrypt( "" );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        System.err.println( "Error: The document is encrypted." );
+                        usage();
+                    }
+                }
+                exporter.printFields( pdf );
+            }
+        }
+        finally
+        {
+            if( pdf != null )
+            {
+                pdf.close();
+            }
+        }
+    }
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: org.apache.pdfbox.examples.fdf.PrintFields <pdf-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/SetField.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/SetField.java
new file mode 100644 (file)
index 0000000..f76e1f9
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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.pdfbox.examples.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.examples.AbstractExample;
+
+/**
+ * This example will take a PDF document and set a FDF field in it.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class SetField extends AbstractExample
+{
+
+    /**
+     * This will set a single field in the document.
+     *
+     * @param pdfDocument The PDF to set the field in.
+     * @param name The name of the field to set.
+     * @param value The new value of the field.
+     *
+     * @throws IOException If there is an error setting the field.
+     */
+    public void setField( PDDocument pdfDocument, String name, String value ) throws IOException
+    {
+        PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+        PDAcroForm acroForm = docCatalog.getAcroForm();
+        PDField field = acroForm.getField( name );
+        if( field != null )
+        {
+            field.setValue( value );
+        }
+        else
+        {
+            System.err.println( "No field found with name:" + name );
+        }
+
+    }
+
+    /**
+     * This will read a PDF file and set a field and then write it the pdf out again.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     *
+     * @throws IOException If there is an error importing the FDF document.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public static void main(String[] args) throws IOException, COSVisitorException
+    {
+        SetField setter = new SetField();
+        setter.setField( args );
+    }
+
+    private void setField( String[] args ) throws IOException, COSVisitorException
+    {
+        PDDocument pdf = null;
+        try
+        {
+            if( args.length != 3 )
+            {
+                usage();
+            }
+            else
+            {
+                SetField example = new SetField();
+
+                pdf = PDDocument.load( args[0] );
+                example.setField( pdf, args[1], args[2] );
+                pdf.save( args[0] );
+            }
+        }
+        finally
+        {
+            if( pdf != null )
+            {
+                pdf.close();
+            }
+        }
+    }
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: org.apache.pdfbox.examples.fdf.SetField <pdf-file> <field-name> <field-value>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/fdf/package.html
new file mode 100644 (file)
index 0000000..8650683
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These are examples that use the FDF features of a PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/package.html
new file mode 100644 (file)
index 0000000..d9de258
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The packages in this package will show how to use the PDFBox API.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddImageToPDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddImageToPDF.java
new file mode 100644 (file)
index 0000000..e4ce909
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccessFile;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDCcitt;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+
+
+/**
+ * This is an example that creates a reads a document and adds an image to it..
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class AddImageToPDF
+{
+
+    /**
+     * Add an image to an existing PDF document.
+     *
+     * @param inputFile The input PDF to add the image to.
+     * @param image The filename of the image to put in the PDF.
+     * @param outputFile The file to write to the pdf to.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void createPDFFromImage( String inputFile, String image, String outputFile ) 
+        throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = PDDocument.load( inputFile );
+
+            //we will add the image to the first page.
+            PDPage page = (PDPage)doc.getDocumentCatalog().getAllPages().get( 0 );
+
+            PDXObjectImage ximage = null;
+            if( image.toLowerCase().endsWith( ".jpg" ) )
+            {
+                ximage = new PDJpeg(doc, new FileInputStream( image ) );
+            }
+            else if (image.toLowerCase().endsWith(".tif") || image.toLowerCase().endsWith(".tiff"))
+            {
+                ximage = new PDCcitt(doc, new RandomAccessFile(new File(image),"r"));
+            }
+            else
+            {
+                //BufferedImage awtImage = ImageIO.read( new File( image ) );
+                //ximage = new PDPixelMap(doc, awtImage);
+                throw new IOException( "Image type not supported:" + image );
+            }
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true);
+
+            contentStream.drawImage( ximage, 20, 20 );
+
+            contentStream.close();
+            doc.save( outputFile );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will load a PDF document and add a single image on it.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        AddImageToPDF app = new AddImageToPDF();
+        try
+        {
+            if( args.length != 3 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.createPDFFromImage( args[0], args[1], args[2] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <input-pdf> <image> <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddJavascript.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddJavascript.java
new file mode 100644 (file)
index 0000000..ad974ba
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionJavaScript;
+
+import java.io.IOException;
+
+/**
+ * This is an example of how to set some javascript in the document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class AddJavascript
+{
+    private AddJavascript()
+    {
+        //static class, should not be instantiated.
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                PDActionJavaScript javascript = new PDActionJavaScript(
+                    "app.alert( {cMsg: 'PDFBox rocks!', nIcon: 3, nType: 0, cTitle: 'PDFBox Javascript example' } );");
+                document.getDocumentCatalog().setOpenAction( javascript );
+                if( document.isEncrypted() )
+                {
+                    throw new IOException( "Encrypted documents are not supported for this example" );
+                }
+                document.save( args[1] );
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.AddJavascript <input-pdf> <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddMessageToEachPage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddMessageToEachPage.java
new file mode 100644 (file)
index 0000000..10a0a66
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This is an example of how to add a message to every page
+ * in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class AddMessageToEachPage
+{
+    /**
+     * Constructor.
+     */
+    public AddMessageToEachPage()
+    {
+        super();
+    }
+
+    /**
+     * create the second sample document from the PDF file format specification.
+     *
+     * @param file The file to write the PDF to.
+     * @param message The message to write in the file.
+     * @param outfile The resulting PDF.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt( String file, String message, String  outfile ) throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = PDDocument.load( file );
+
+            List allPages = doc.getDocumentCatalog().getAllPages();
+            PDFont font = PDType1Font.HELVETICA_BOLD;
+            float fontSize = 36.0f;
+
+            for( int i=0; i<allPages.size(); i++ )
+            {
+                PDPage page = (PDPage)allPages.get( i );
+                PDRectangle pageSize = page.findMediaBox();
+                float stringWidth = font.getStringWidth( message )*fontSize/1000f;
+                // calculate to center of the page
+                int rotation = page.findRotation(); 
+                boolean rotate = rotation == 90 || rotation == 270;
+                float pageWidth = rotate ? pageSize.getHeight() : pageSize.getWidth();
+                float pageHeight = rotate ? pageSize.getWidth() : pageSize.getHeight();
+                double centeredXPosition = rotate ? pageHeight/2f : (pageWidth - stringWidth)/2f;
+                double centeredYPosition = rotate ? (pageWidth - stringWidth)/2f : pageHeight/2f;
+                // append the content to the existing stream
+                PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true,true);
+                contentStream.beginText();
+                // set font and font size
+                contentStream.setFont( font, fontSize );
+                // set text color to red
+                contentStream.setNonStrokingColor(255, 0, 0);
+                if (rotate)
+                {
+                    // rotate the text according to the page rotation
+                    contentStream.setTextRotation(Math.PI/2, centeredXPosition, centeredYPosition);
+                }
+                else
+                {
+                    contentStream.setTextTranslation(centeredXPosition, centeredYPosition);
+                }
+                contentStream.drawString( message );
+                contentStream.endText();
+                contentStream.close();
+            }
+
+            doc.save( outfile );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a hello world PDF document.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        AddMessageToEachPage app = new AddMessageToEachPage();
+        try
+        {
+            if( args.length != 3 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0], args[1], args[2] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <input-file> <Message> <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddMetadataFromDocInfo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/AddMetadataFromDocInfo.java
new file mode 100644 (file)
index 0000000..0bee0c7
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.jempbox.xmp.XMPMetadata;
+import org.apache.jempbox.xmp.XMPSchemaBasic;
+import org.apache.jempbox.xmp.XMPSchemaDublinCore;
+import org.apache.jempbox.xmp.XMPSchemaPDF;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+
+import java.util.GregorianCalendar;
+
+/**
+ * This is an example on how to add metadata to a document.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.AddMetadataToDocument &lt;input-pdf&gt; &lt;output-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class AddMetadataFromDocInfo
+{
+    private AddMetadataFromDocInfo()
+    {
+        //utility class
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    System.err.println( "Error: Cannot add metadata to encrypted document." );
+                    System.exit( 1 );
+                }
+                PDDocumentCatalog catalog = document.getDocumentCatalog();
+                PDDocumentInformation info = document.getDocumentInformation();
+
+                XMPMetadata metadata = new XMPMetadata();
+
+                XMPSchemaPDF pdfSchema = metadata.addPDFSchema();
+                pdfSchema.setKeywords( info.getKeywords() );
+                pdfSchema.setProducer( info.getProducer() );
+
+                XMPSchemaBasic basicSchema = metadata.addBasicSchema();
+                basicSchema.setModifyDate( info.getModificationDate() );
+                basicSchema.setCreateDate( info.getCreationDate() );
+                basicSchema.setCreatorTool( info.getCreator() );
+                basicSchema.setMetadataDate( new GregorianCalendar() );
+
+                XMPSchemaDublinCore dcSchema = metadata.addDublinCoreSchema();
+                dcSchema.setTitle( info.getTitle() );
+                dcSchema.addCreator( "PDFBox" );
+                dcSchema.setDescription( info.getSubject() );
+
+                PDMetadata metadataStream = new PDMetadata(document);
+                metadataStream.importXMPMetadata( metadata );
+                catalog.setMetadata( metadataStream );
+
+                document.save( args[1] );
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.AddMetadataFromDocInfo " +
+            "<input-pdf> <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/Annotation.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/Annotation.java
new file mode 100644 (file)
index 0000000..f4a18ed
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationSquareCircle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
+
+
+import java.util.List;
+
+/**
+ * This is an example on how to add annotations to pages of a PDF document.
+ *
+ * @author Paul King
+ * @version $Revision: 1.2 $
+ */
+public class Annotation
+{
+    private Annotation()
+    {
+        //utility class, should not be instantiated.
+    }
+
+    /**
+     * This will create a doucument showing various annotations.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 1 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = new PDDocument();
+
+            try
+            {
+                PDPage page = new PDPage();
+                document.addPage(page);
+                List annotations = page.getAnnotations();
+
+                // Setup some basic reusable objects/constants
+                // Annotations themselves can only be used once!
+
+                float inch = 72;
+                PDGamma colourRed = new PDGamma();
+                colourRed.setR(1);
+                PDGamma colourBlue = new PDGamma();
+                colourBlue.setB(1);
+                PDGamma colourBlack = new PDGamma();
+
+                PDBorderStyleDictionary borderThick = new PDBorderStyleDictionary();
+                borderThick.setWidth(inch/12);  // 12th inch
+                PDBorderStyleDictionary borderThin = new PDBorderStyleDictionary();
+                borderThin.setWidth(inch/72); // 1 point
+                PDBorderStyleDictionary borderULine = new PDBorderStyleDictionary();
+                borderULine.setStyle(PDBorderStyleDictionary.STYLE_UNDERLINE);
+                borderULine.setWidth(inch/72); // 1 point
+
+
+                float pw = page.getMediaBox().getUpperRightX();
+                float ph = page.getMediaBox().getUpperRightY();
+
+
+                // First add some text, two lines we'll add some annotations to this later
+
+
+                PDFont font = PDType1Font.HELVETICA_BOLD;
+
+                PDPageContentStream contentStream = new PDPageContentStream(document, page);
+                contentStream.beginText();
+                contentStream.setFont( font, 18 );
+                contentStream.moveTextPositionByAmount( inch, ph-inch-18);
+                contentStream.drawString( "PDFBox" );
+                contentStream.moveTextPositionByAmount( 0,-(inch/2));
+                contentStream.drawString( "Click Here" );
+                contentStream.endText();
+
+                contentStream.close();
+
+                // Now add the markup annotation, a highlight to PDFBox text
+                PDAnnotationTextMarkup txtMark = new PDAnnotationTextMarkup(PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT);
+                txtMark.setColour(colourBlue);
+                txtMark.setConstantOpacity((float)0.2);   // Make the highlight 20% transparent
+
+                // Set the rectangle containing the markup
+
+                float textWidth = (font.getStringWidth( "PDFBox" )/1000) * 18;
+                PDRectangle position = new PDRectangle();
+                position.setLowerLeftX(inch);
+                position.setLowerLeftY( ph-inch-18 );
+                position.setUpperRightX(72 + textWidth);
+                position.setUpperRightY(ph-inch);
+                txtMark.setRectangle(position);
+
+                // work out the points forming the four corners of the annotations
+                // set out in anti clockwise form (Completely wraps the text)
+                // OK, the below doesn't match that description.
+                // It's what acrobat 7 does and displays properly!
+                float[] quads = new float[8];
+
+                quads[0] = position.getLowerLeftX();  // x1
+                quads[1] = position.getUpperRightY()-2; // y1
+                quads[2] = position.getUpperRightX(); // x2
+                quads[3] = quads[1]; // y2
+                quads[4] = quads[0];  // x3
+                quads[5] = position.getLowerLeftY()-2; // y3
+                quads[6] = quads[2]; // x4
+                quads[7] = quads[5]; // y5
+
+                txtMark.setQuadPoints(quads);
+                txtMark.setContents("Highlighted since it's important");
+
+                annotations.add(txtMark);
+
+                // Now add the link annotation, so the clickme works
+                PDAnnotationLink txtLink = new PDAnnotationLink();
+                txtLink.setBorderStyle(borderULine);
+
+                // Set the rectangle containing the link
+
+                textWidth = (font.getStringWidth( "Click Here" )/1000) * 18;
+                position = new PDRectangle();
+                position.setLowerLeftX(inch);
+                position.setLowerLeftY( ph-(float)(1.5*inch)-20);  // down a couple of points
+                position.setUpperRightX(72 + textWidth);
+                position.setUpperRightY(ph-(float)(1.5*inch));
+                txtLink.setRectangle(position);
+
+                // add an action
+                PDActionURI action = new PDActionURI();
+                action.setURI("http://www.pdfbox.org");
+                txtLink.setAction(action);
+
+                annotations.add(txtLink);
+
+
+                // Now draw a few more annotations
+
+                PDAnnotationSquareCircle aCircle =
+                    new PDAnnotationSquareCircle( PDAnnotationSquareCircle.SUB_TYPE_CIRCLE);
+                aCircle.setContents("Circle Annotation");
+                aCircle.setInteriorColour(colourRed);  // Fill in circle in red
+                aCircle.setColour(colourBlue); // The border itself will be blue
+                aCircle.setBorderStyle(borderThin);
+
+                // Place the annotation on the page, we'll make this 1" round
+                // 3" down, 1" in on the page
+
+                position = new PDRectangle();
+                position.setLowerLeftX(inch);
+                position.setLowerLeftY(ph-(3*inch)-inch); // 1" height, 3" down
+                position.setUpperRightX(2*inch); // 1" in, 1" width
+                position.setUpperRightY(ph-(3*inch)); // 3" down
+                aCircle.setRectangle(position);
+
+                //  add to the annotations on the page
+                annotations.add(aCircle);
+
+                // Now a square annotation
+
+                PDAnnotationSquareCircle aSquare =
+                    new PDAnnotationSquareCircle( PDAnnotationSquareCircle.SUB_TYPE_SQUARE);
+                aSquare.setContents("Square Annotation");
+                aSquare.setColour(colourRed);  // Outline in red, not setting a fill
+                aSquare.setBorderStyle(borderThick);
+
+                // Place the annotation on the page, we'll make this 1" (72points) square
+                // 3.5" down, 1" in from the right on the page
+
+                position = new PDRectangle(); // Reuse the variable, but note it's a new object!
+                position.setLowerLeftX(pw-(2*inch));  // 1" in from right, 1" wide
+                position.setLowerLeftY(ph-(float)(3.5*inch) - inch); // 1" height, 3.5" down
+                position.setUpperRightX(pw-inch); // 1" in from right
+                position.setUpperRightY(ph-(float)(3.5*inch)); // 3.5" down
+                aSquare.setRectangle(position);
+
+                //  add to the annotations on the page
+                annotations.add(aSquare);
+
+                // Now we want to draw a line between the two, one end with an open arrow
+
+                PDAnnotationLine aLine = new PDAnnotationLine();
+
+                aLine.setEndPointEndingStyle( PDAnnotationLine.LE_OPEN_ARROW );
+                aLine.setContents("Circle->Square");
+                aLine.setCaption(true);  // Make the contents a caption on the line
+
+                // Set the rectangle containing the line
+
+                position = new PDRectangle(); // Reuse the variable, but note it's a new object!
+                position.setLowerLeftX(2*inch);  // 1" in + width of circle
+                position.setLowerLeftY(ph-(float)(3.5*inch)-inch); // 1" height, 3.5" down
+                position.setUpperRightX(pw-inch-inch); // 1" in from right, and width of square
+                position.setUpperRightY(ph-(3*inch)); // 3" down (top of circle)
+                aLine.setRectangle(position);
+
+                // Now set the line position itself
+                float[] linepos = new float[4];
+                linepos[0] = 2*inch;  // x1 = rhs of circle
+                linepos[1] = ph-(float)(3.5*inch); // y1 halfway down circle
+                linepos[2] = pw-(2*inch);  // x2 = lhs of square
+                linepos[3] = ph-(4*inch); // y2 halfway down square
+                aLine.setLine(linepos);
+
+                aLine.setBorderStyle(borderThick);
+                aLine.setColour(colourBlack);
+
+                // add to the annotations on the page
+                annotations.add(aLine);
+
+
+                // Finally all done
+
+
+                document.save( args[0] );
+            }
+            finally
+            {
+                document.close();
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.Annotation <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateBlankPDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateBlankPDF.java
new file mode 100644 (file)
index 0000000..f919975
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+/**
+ * This will create a blank PDF and write the contents to a file.
+ *
+ * usage: java org.apache.pdfbox.examples.pdmodel.CreateBlankPDF &lt;outputfile.pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class CreateBlankPDF
+{
+
+    /**
+     * This will create a blank PDF and write the contents to a file.
+     *
+     * @param file The name of the file to write to.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error while generating the document.
+     */
+    public void create( String file ) throws IOException, COSVisitorException
+    {
+        PDDocument document = null;
+        try
+        {
+            document = new PDDocument();
+            //Every document requires at least one page, so we will add one
+            //blank page.
+            PDPage blankPage = new PDPage();
+            document.addPage( blankPage );
+            document.save( file );
+        }
+        finally
+        {
+            if( document != null )
+            {
+                document.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a blank document.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws IOException If there is an error writing the document data.
+     * @throws COSVisitorException If there is an error generating the data.
+     */
+    public static void main( String[] args ) throws IOException, COSVisitorException
+    {
+        if( args.length != 1 )
+        {
+            usage();
+        }
+        else
+        {
+            CreateBlankPDF creator = new CreateBlankPDF();
+            creator.create( args[0] );
+        }
+    }
+
+    /**
+     * This will print the usage of this class.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: java org.apache.pdfbox.examples.pdmodel.CreateBlankPDF <outputfile.pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateBookmarks.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateBookmarks.java
new file mode 100644 (file)
index 0000000..fdfb2dc
--- /dev/null
@@ -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.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageFitWidthDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+
+import java.util.List;
+
+/**
+ * This is an example on how to add bookmarks to a PDF document.  It simply
+ * adds 1 bookmark for every page.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.CreateBookmarks &lt;input-pdf&gt; &lt;output-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CreateBookmarks
+{
+    private CreateBookmarks()
+    {
+        //utility class
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    System.err.println( "Error: Cannot add bookmarks to encrypted document." );
+                    System.exit( 1 );
+                }
+                PDDocumentOutline outline =  new PDDocumentOutline();
+                document.getDocumentCatalog().setDocumentOutline( outline );
+                PDOutlineItem pagesOutline = new PDOutlineItem();
+                pagesOutline.setTitle( "All Pages" );
+                outline.appendChild( pagesOutline );
+                List pages = document.getDocumentCatalog().getAllPages();
+                for( int i=0; i<pages.size(); i++ )
+                {
+                    PDPage page = (PDPage)pages.get( i );
+                    PDPageFitWidthDestination dest = new PDPageFitWidthDestination();
+                    dest.setPage( page );
+                    PDOutlineItem bookmark = new PDOutlineItem();
+                    bookmark.setDestination( dest );
+                    bookmark.setTitle( "Page " + (i+1) );
+                    pagesOutline.appendChild( bookmark );
+                }
+                pagesOutline.openNode();
+                outline.openNode();
+
+                document.save( args[1] );
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.CreateBookmarks <input-pdf> <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateLandscapePDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/CreateLandscapePDF.java
new file mode 100644 (file)
index 0000000..8beb2ce
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This is an example of how to create a page with a landscape orientation.
+ * @version $Revision: 1.0 $
+ */
+public class CreateLandscapePDF
+{
+    /**
+     * Constructor.
+     */
+    public CreateLandscapePDF()
+    {
+        super();
+    }
+
+    /**
+     * creates a sample document with a landscape orientation and some text surrounded by a box.
+     *
+     * @param message The message to write in the file.
+     * @param outfile The resulting PDF.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt( String message, String  outfile ) throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            PDFont font = PDType1Font.HELVETICA;
+            PDPage page = new PDPage();
+            page.setMediaBox(PDPage.PAGE_SIZE_A4);
+            page.setRotation(90);
+            doc.addPage(page);
+            PDRectangle pageSize = page.findMediaBox();
+            float pageWidth = pageSize.getWidth();
+            float fontSize = 12;
+            float stringWidth = font.getStringWidth( message )*fontSize/1000f;
+            float startX = 100;
+            float startY = 100;
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
+            // add the rotation using the current transformation matrix
+            // including a translation of pageWidth to use the lower left corner as 0,0 reference
+            contentStream.concatenate2CTM(0, 1, -1, 0, pageWidth, 0);
+            contentStream.setFont( font, fontSize );
+            contentStream.beginText();
+            contentStream.moveTextPositionByAmount(startX, startY);
+            contentStream.drawString( message);
+            contentStream.moveTextPositionByAmount(0, 100);
+            contentStream.drawString( message);
+            contentStream.moveTextPositionByAmount(100, 100);
+            contentStream.drawString( message);
+            contentStream.endText();
+            
+            contentStream.drawLine(startX-2, startY-2, startX-2, startY+200+fontSize);
+            contentStream.drawLine(startX-2, startY+200+fontSize, startX+100+stringWidth+2, startY+200+fontSize);
+            contentStream.drawLine(startX+100+stringWidth+2, startY+200+fontSize, startX+100+stringWidth+2, startY-2);
+            contentStream.drawLine(startX+100+stringWidth+2, startY-2, startX-2, startY-2);
+            contentStream.close();
+
+            doc.save( outfile );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a PDF document with a landscape orientation and some text surrounded by a box.
+     * 
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        CreateLandscapePDF app = new CreateLandscapePDF();
+        try
+        {
+            if( args.length != 2 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0], args[1] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <Message> <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/EmbeddedFiles.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/EmbeddedFiles.java
new file mode 100644 (file)
index 0000000..1c1c673
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
+import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This is an example that creates a simple document and embeds a file into it..
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class EmbeddedFiles
+{
+    /**
+     * Constructor.
+     */
+    public EmbeddedFiles()
+    {
+        super();
+    }
+
+    /**
+     * create the second sample document from the PDF file format specification.
+     *
+     * @param file The file to write the PDF to.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt( String file) throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            PDPage page = new PDPage();
+            doc.addPage( page );
+            PDFont font = PDType1Font.HELVETICA_BOLD;
+
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+            contentStream.beginText();
+            contentStream.setFont( font, 12 );
+            contentStream.moveTextPositionByAmount( 100, 700 );
+            contentStream.drawString( "Go to Document->File Attachments to View Embedded Files" );
+            contentStream.endText();
+            contentStream.close();
+
+            //embedded files are stored in a named tree
+            PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode();
+
+
+            //first create the file specification, which holds the embedded file
+            PDComplexFileSpecification fs = new PDComplexFileSpecification();
+            fs.setFile( "Test.txt" );
+            //create a dummy file stream, this would probably normally be a FileInputStream
+            byte[] data = "This is the contents of the embedded file".getBytes("ISO-8859-1");
+            ByteArrayInputStream fakeFile =
+                new ByteArrayInputStream( data );
+            PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile );
+            //now lets some of the optional parameters
+            ef.setSubtype( "test/plain" );
+            ef.setSize( data.length );
+            ef.setCreationDate( new GregorianCalendar() );
+            fs.setEmbeddedFile( ef );
+
+            //now add the entry to the embedded file tree and set in the document.
+            efTree.setNames( Collections.singletonMap( "My first attachment",  fs ) );
+            PDDocumentNameDictionary names = new PDDocumentNameDictionary( doc.getDocumentCatalog() );
+            names.setEmbeddedFiles( efTree );
+            doc.getDocumentCatalog().setNames( names );
+
+
+            doc.save( file );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a hello world PDF document with an embedded file.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        EmbeddedFiles app = new EmbeddedFiles();
+        try
+        {
+            if( args.length != 1 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ExtractMetadata.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ExtractMetadata.java
new file mode 100644 (file)
index 0000000..64d4aea
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.jempbox.xmp.XMPMetadata;
+import org.apache.jempbox.xmp.XMPSchemaBasic;
+import org.apache.jempbox.xmp.XMPSchemaDublinCore;
+import org.apache.jempbox.xmp.XMPSchemaPDF;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This is an example on how to extract metadata from a PDF document.
+ * <p>
+ * Usage: java org.apache.pdfbox.examples.pdmodel.ExtractDocument &lt;input-pdf&gt;
+ *
+ * @version $Revision$
+ */
+public class ExtractMetadata
+{
+    private ExtractMetadata()
+    {
+        //utility class
+    }
+
+    /**
+     * This is the main method.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 1 )
+        {
+            usage();
+            System.exit(1);
+        }
+        else
+        {
+            PDDocument document = null;
+
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if (document.isEncrypted()) 
+                {
+                    try
+                    {
+                        document.decrypt("");
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        System.err.println( "Error: The document is encrypted." );
+                    }
+                    catch( org.apache.pdfbox.exceptions.CryptographyException e )
+                    {
+                        e.printStackTrace();
+                    }
+                }
+                PDDocumentCatalog catalog = document.getDocumentCatalog();
+                PDMetadata meta = catalog.getMetadata();
+                if ( meta != null)
+                {
+                    XMPMetadata metadata = meta.exportXMPMetadata();
+    
+                    XMPSchemaDublinCore dc = metadata.getDublinCoreSchema();
+                    if (dc != null)
+                    {
+                        display("Title:", dc.getTitle());
+                        display("Description:", dc.getDescription());
+                        list("Creators: ", dc.getCreators());
+                        list("Dates:", dc.getDates());
+                    }
+    
+                    XMPSchemaPDF pdf = metadata.getPDFSchema();
+                    if (pdf != null)
+                    {
+                        display("Keywords:", pdf.getKeywords());
+                        display("PDF Version:", pdf.getPDFVersion());
+                        display("PDF Producer:", pdf.getProducer());
+                    }
+    
+                    XMPSchemaBasic basic = metadata.getBasicSchema();
+                    if (basic != null)
+                    {
+                        display("Create Date:", basic.getCreateDate());
+                        display("Modify Date:", basic.getModifyDate());
+                        display("Creator Tool:", basic.getCreatorTool());
+                    }
+                }
+                else
+                {
+                    // The pdf doesn't contain any metadata, try to use the document information instead
+                    PDDocumentInformation information = document.getDocumentInformation();
+                    if ( information != null)
+                    {
+                        showDocumentInformation(information);
+                    }
+                }
+                
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    private static void showDocumentInformation(PDDocumentInformation information)
+    {
+        display("Title:", information.getTitle());
+        display("Subject:", information.getSubject());
+        display("Author:", information.getAuthor());
+        display("Creator:", information.getCreator());
+        display("Producer:", information.getProducer());
+    }
+    
+    private static void list(String title, List list)
+    {
+        if (list == null)
+        {
+            return;
+        }
+        System.out.println(title);
+        Iterator iter = list.iterator();
+        while (iter.hasNext())
+        {
+            Object o = iter.next();
+            System.out.println("  " + format(o));
+        }
+    }
+
+    private static String format(Object o)
+    {
+        if (o instanceof Calendar)
+        {
+            Calendar cal = (Calendar)o;
+            return DateFormat.getDateInstance().format(cal.getTime());
+        }
+        else
+        {
+            return o.toString();
+        }
+    }
+
+    private static void display(String title, Object value)
+    {
+        if (value != null)
+        {
+            System.out.println(title + " " + format(value));
+        }
+    }
+
+    /**
+     * This will print the usage for this program.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java " + ExtractMetadata.class.getName() + " <input-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/GoToSecondBookmarkOnOpen.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/GoToSecondBookmarkOnOpen.java
new file mode 100644 (file)
index 0000000..679bdc4
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionGoTo;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This is an example on how to an action to go to the second page when the PDF is opened.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.GoToSecondPageOnOpen &lt;input-pdf&gt; &lt;output-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class GoToSecondBookmarkOnOpen
+{
+    private GoToSecondBookmarkOnOpen()
+    {
+        //utility class
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    System.err.println( "Error: Cannot add bookmark destination to encrypted documents." );
+                    System.exit( 1 );
+                }
+
+                List pages = document.getDocumentCatalog().getAllPages();
+                if( pages.size() < 2 )
+                {
+                    throw new IOException( "Error: The PDF must have at least 2 pages.");
+                }
+                PDDocumentOutline bookmarks = document.getDocumentCatalog().getDocumentOutline();
+                if( bookmarks == null )
+                {
+                    throw new IOException( "Error: The PDF does not contain any bookmarks" );
+                }
+                PDOutlineItem item = bookmarks.getFirstChild().getNextSibling();
+                PDDestination dest = item.getDestination();
+                PDActionGoTo action = new PDActionGoTo();
+                action.setDestination(dest);
+                document.getDocumentCatalog().setOpenAction(action);
+
+                document.save( args[1] );
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.GoToSecondBookmarkOnOpen" +
+            "<input-pdf> <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorld.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorld.java
new file mode 100644 (file)
index 0000000..781d78b
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This is an example that creates a simple document.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class HelloWorld
+{
+    /**
+     * Constructor.
+     */
+    public HelloWorld()
+    {
+        super();
+    }
+
+    /**
+     * create the second sample document from the PDF file format specification.
+     *
+     * @param file The file to write the PDF to.
+     * @param message The message to write in the file.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt( String file, String message) throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            PDPage page = new PDPage();
+            doc.addPage( page );
+            PDFont font = PDType1Font.HELVETICA_BOLD;
+
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+            contentStream.beginText();
+            contentStream.setFont( font, 12 );
+            contentStream.moveTextPositionByAmount( 100, 700 );
+            contentStream.drawString( message );
+            contentStream.endText();
+            contentStream.close();
+            doc.save( file );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a hello world PDF document.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        HelloWorld app = new HelloWorld();
+        try
+        {
+            if( args.length != 2 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0], args[1] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <output-file> <Message>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java
new file mode 100644 (file)
index 0000000..c81ef83
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
+
+/**
+ * This is an example that creates a simple document
+ * with a ttf-font.
+ *
+ * @author <a href="mailto:m.g.n@gmx.de">Michael Niedermair</a>
+ * @version $Revision: 1.2 $
+ */
+public class HelloWorldTTF
+{
+
+    /**
+     * create the second sample document from the PDF file format specification.
+     *
+     * @param file      The file to write the PDF to.
+     * @param message   The message to write in the file.
+     * @param fontfile  The ttf-font file.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt(final String file, final String message,
+            final String fontfile) throws IOException, COSVisitorException
+    {
+
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            PDPage page = new PDPage();
+            doc.addPage(page);
+            PDFont font = PDTrueTypeFont.loadTTF(doc, fontfile);
+
+            PDPageContentStream contentStream = new PDPageContentStream(doc,
+                    page);
+            contentStream.beginText();
+            contentStream.setFont(font, 12);
+            contentStream.moveTextPositionByAmount(100, 700);
+            contentStream.drawString(message);
+            contentStream.endText();
+            contentStream.close();
+            doc.save(file);
+            System.out.println(file + " created!");
+        }
+        finally
+        {
+            if (doc != null)
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a hello world PDF document
+     * with a ttf-font.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+
+        HelloWorldTTF app = new HelloWorldTTF();
+        try
+        {
+            if (args.length != 3)
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt(args[0], args[1], args[2]);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println("usage: " + this.getClass().getName()
+                + " <output-file> <Message> <ttf-file>");
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorldType1AfmPfb.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/HelloWorldType1AfmPfb.java
new file mode 100644 (file)
index 0000000..43453bb
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1AfmPfbFont;
+
+/**
+ * This is an example that creates a simple document
+ * with a Type 1 font (afm + pfb).
+ *
+ * @author <a href="mailto:m.g.n@gmx.de">Michael Niedermair</a>
+ * @version $Revision: 1.2 $
+ */
+public class HelloWorldType1AfmPfb
+{
+
+    /**
+     * create the second sample document from the PDF file format specification.
+     *
+     * @param file      The file to write the PDF to.
+     * @param message   The message to write in the file.
+     * @param fontfile  The ttf-font file.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt(final String file, final String message,
+            final String fontfile) throws IOException, COSVisitorException
+    {
+
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            PDPage page = new PDPage();
+            doc.addPage(page);
+            PDFont font = new PDType1AfmPfbFont(doc,fontfile);
+
+            PDPageContentStream contentStream = new PDPageContentStream(doc,
+                    page);
+            contentStream.beginText();
+            contentStream.setFont(font, 12);
+            contentStream.moveTextPositionByAmount(100, 700);
+            contentStream.drawString(message);
+            contentStream.endText();
+            contentStream.close();
+            doc.save(file);
+            System.out.println(file + " created!");
+        }
+        finally
+        {
+            if (doc != null)
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a hello world PDF document
+     * with a ttf-font.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+
+        HelloWorldType1AfmPfb app = new HelloWorldType1AfmPfb();
+        try
+        {
+            if (args.length != 3)
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt(args[0], args[1], args[2]);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println("usage: " + this.getClass().getName()
+                + " <output-file> <Message> <afm-file>");
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ImageToPDF.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ImageToPDF.java
new file mode 100644 (file)
index 0000000..d371459
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccessFile;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDCcitt;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+
+
+/**
+ * This is an example that creates a simple document.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class ImageToPDF
+{
+
+    /**
+     * create the second sample document from the PDF file format specification.
+     *
+     * @param file The file to write the PDF to.
+     * @param image The filename of the image to put in the PDF.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void createPDFFromImage( String file, String image) throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            PDPage page = new PDPage();
+            doc.addPage( page );
+
+            PDXObjectImage ximage = null;
+            if( image.toLowerCase().endsWith( ".jpg" ) )
+            {
+                ximage = new PDJpeg(doc, new FileInputStream( image ) );
+            }
+            else if (image.toLowerCase().endsWith(".tif") || image.toLowerCase().endsWith(".tiff"))
+            {
+                ximage = new PDCcitt(doc, new RandomAccessFile(new File(image),"r"));
+            }
+            else
+            {
+                //BufferedImage awtImage = ImageIO.read( new File( image ) );
+                //ximage = new PDPixelMap(doc, awtImage);
+                throw new IOException( "Image type not supported:" + image );
+            }
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+
+            contentStream.drawImage( ximage, 20, 20 );
+
+            contentStream.close();
+            doc.save( file );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a PDF document with a single image on it.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        ImageToPDF app = new ImageToPDF();
+        try
+        {
+            if( args.length != 2 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.createPDFFromImage( args[0], args[1] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <output-file> <image>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintBookmarks.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintBookmarks.java
new file mode 100644 (file)
index 0000000..05b0710
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * This is an example on how to access the bookmarks that are part of a pdf document.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.PrintBookmarks &lt;input-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PrintBookmarks
+{
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 1 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            FileInputStream file = null;
+            try
+            {
+                file = new FileInputStream( args[0] );
+                PDFParser parser = new PDFParser( file );
+                parser.parse();
+                document = parser.getPDDocument();
+                if( document.isEncrypted() )
+                {
+                    try
+                    {
+                        document.decrypt( "" );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        System.err.println( "Error: Document is encrypted with a password." );
+                        System.exit( 1 );
+                    }
+                }
+                PrintBookmarks meta = new PrintBookmarks();
+                PDDocumentOutline outline =  document.getDocumentCatalog().getDocumentOutline();
+                if( outline != null )
+                {
+                    meta.printBookmark( outline, "" );
+                }
+                else
+                {
+                    System.out.println( "This document does not contain any bookmarks" );
+                }
+            }
+            finally
+            {
+                if( file != null )
+                {
+                    file.close();
+                }
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintBookmarks <input-pdf>" );
+    }
+
+    /**
+     * This will print the documents bookmarks to System.out.
+     *
+     * @param bookmark The bookmark to print out.
+     * @param indentation A pretty printing parameter
+     *
+     * @throws IOException If there is an error getting the page count.
+     */
+    public void printBookmark( PDOutlineNode bookmark, String indentation ) throws IOException
+    {
+        PDOutlineItem current = bookmark.getFirstChild();
+        while( current != null )
+        {
+            System.out.println( indentation + current.getTitle() );
+            printBookmark( current, indentation + "    " );
+            current = current.getNextSibling();
+        }
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintDocumentMetaData.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintDocumentMetaData.java
new file mode 100644 (file)
index 0000000..379f089
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import java.text.SimpleDateFormat;
+
+import java.util.Calendar;
+
+/**
+ * This is an example on how to get a documents metadata information.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.PrintDocumentMetaData &lt;input-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public class PrintDocumentMetaData
+{
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 1 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            FileInputStream file = null;
+            try
+            {
+                file = new FileInputStream( args[0] );
+                PDFParser parser = new PDFParser( file );
+                parser.parse();
+                document = parser.getPDDocument();
+                if( document.isEncrypted() )
+                {
+                    try
+                    {
+                        document.decrypt( "" );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        System.err.println( "Error: Document is encrypted with a password." );
+                        System.exit( 1 );
+                    }
+                }
+                PrintDocumentMetaData meta = new PrintDocumentMetaData();
+                meta.printMetadata( document );
+            }
+            finally
+            {
+                if( file != null )
+                {
+                    file.close();
+                }
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintDocumentMetaData <input-pdf>" );
+    }
+
+    /**
+     * This will print the documents data to System.out.
+     *
+     * @param document The document to get the metadata from.
+     *
+     * @throws IOException If there is an error getting the page count.
+     */
+    public void printMetadata( PDDocument document ) throws IOException
+    {
+        PDDocumentInformation info = document.getDocumentInformation();
+        PDDocumentCatalog cat = document.getDocumentCatalog();
+        PDMetadata metadata = cat.getMetadata();
+        System.out.println( "Page Count=" + document.getNumberOfPages() );
+        System.out.println( "Title=" + info.getTitle() );
+        System.out.println( "Author=" + info.getAuthor() );
+        System.out.println( "Subject=" + info.getSubject() );
+        System.out.println( "Keywords=" + info.getKeywords() );
+        System.out.println( "Creator=" + info.getCreator() );
+        System.out.println( "Producer=" + info.getProducer() );
+        System.out.println( "Creation Date=" + formatDate( info.getCreationDate() ) );
+        System.out.println( "Modification Date=" + formatDate( info.getModificationDate() ) );
+        System.out.println( "Trapped=" + info.getTrapped() );
+        if( metadata != null )
+        {
+            System.out.println( "Metadata=" + metadata.getInputStreamAsString() );
+        }
+    }
+
+    /**
+     * This will format a date object.
+     *
+     * @param date The date to format.
+     *
+     * @return A string representation of the date.
+     */
+    private String formatDate( Calendar date )
+    {
+        String retval = null;
+        if( date != null )
+        {
+            SimpleDateFormat formatter = new SimpleDateFormat();
+            retval = formatter.format( date.getTime() );
+        }
+
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintURLs.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/PrintURLs.java
new file mode 100644 (file)
index 0000000..50c7948
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.awt.geom.Rectangle2D;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
+import org.apache.pdfbox.util.PDFTextStripperByArea;
+
+
+/**
+ * This is an example of how to access a URL in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PrintURLs
+{
+    /**
+     * Constructor.
+     */
+    private PrintURLs()
+    {
+        //utility class
+    }
+
+    /**
+     * This will create a hello world PDF document.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     *
+     * @throws Exception If there is an error extracting the URLs.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        PDDocument doc = null;
+        try
+        {
+            if( args.length != 1 )
+            {
+                usage();
+            }
+            else
+            {
+                doc = PDDocument.load( args[0] );
+                List allPages = doc.getDocumentCatalog().getAllPages();
+                for( int i=0; i<allPages.size(); i++ )
+                {
+                    PDFTextStripperByArea stripper = new PDFTextStripperByArea();
+                    PDPage page = (PDPage)allPages.get( i );
+                    List annotations = page.getAnnotations();
+                    //first setup text extraction regions
+                    for( int j=0; j<annotations.size(); j++ )
+                    {
+                        PDAnnotation annot = (PDAnnotation)annotations.get( j );
+                        if( annot instanceof PDAnnotationLink )
+                        {
+                            PDAnnotationLink link = (PDAnnotationLink)annot;
+                            PDRectangle rect = link.getRectangle();
+                            //need to reposition link rectangle to match text space
+                            float x = rect.getLowerLeftX();
+                            float y = rect.getUpperRightY();
+                            float width = rect.getWidth();
+                            float height = rect.getHeight();
+                            int rotation = page.findRotation();
+                            if( rotation == 0 )
+                            {
+                                PDRectangle pageSize = page.findMediaBox();
+                                y = pageSize.getHeight() - y;
+                            }
+                            else if( rotation == 90 )
+                            {
+                                //do nothing
+                            }
+
+                            Rectangle2D.Float awtRect = new Rectangle2D.Float( x,y,width,height );
+                            stripper.addRegion( "" + j, awtRect );
+                        }
+                    }
+
+                    stripper.extractRegions( page );
+
+                    for( int j=0; j<annotations.size(); j++ )
+                    {
+                        PDAnnotation annot = (PDAnnotation)annotations.get( j );
+                        if( annot instanceof PDAnnotationLink )
+                        {
+                            PDAnnotationLink link = (PDAnnotationLink)annot;
+                            PDAction action = link.getAction();
+                            String urlText = stripper.getTextForRegion( "" + j );
+                            if( action instanceof PDActionURI )
+                            {
+                                PDActionURI uri = (PDActionURI)action;
+                                System.out.println( "Page " + (i+1) +":'" + urlText + "'=" + uri.getURI() );
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: " + PrintURLs.class.getName() + " <input-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RemoveFirstPage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RemoveFirstPage.java
new file mode 100644 (file)
index 0000000..b335507
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import java.io.IOException;
+
+/**
+ * This is an example on how to remove pages from a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class RemoveFirstPage
+{
+    private RemoveFirstPage()
+    {
+        //utility class, should not be instantiated.
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    throw new IOException( "Encrypted documents are not supported for this example" );
+                }
+                if( document.getNumberOfPages() <= 1 )
+                {
+                    throw new IOException( "Error: A PDF document must have at least one page, " +
+                                           "cannot remove the last page!");
+                }
+                document.removePage( 0 );
+                document.save( args[1] );
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.RemoveFirstPage <input-pdf> <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ReplaceString.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ReplaceString.java
new file mode 100644 (file)
index 0000000..8f06939
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+import org.apache.pdfbox.util.PDFOperator;
+
+
+/**
+ * This is an example that will replace a string in a PDF with a new one.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ReplaceString
+{
+    /**
+     * Constructor.
+     */
+    public ReplaceString()
+    {
+        super();
+    }
+
+    /**
+     * Locate a string in a PDF and replace it with a new string.
+     *
+     * @param inputFile The PDF to open.
+     * @param outputFile The PDF to write to.
+     * @param strToFind The string to find in the PDF document.
+     * @param message The message to write in the file.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt( String inputFile, String outputFile, String strToFind, String message)
+        throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = PDDocument.load( inputFile );
+            List pages = doc.getDocumentCatalog().getAllPages();
+            for( int i=0; i<pages.size(); i++ )
+            {
+                PDPage page = (PDPage)pages.get( i );
+                PDStream contents = page.getContents();
+                PDFStreamParser parser = new PDFStreamParser(contents.getStream() );
+                parser.parse();
+                List tokens = parser.getTokens();
+                for( int j=0; j<tokens.size(); j++ )
+                {
+                    Object next = tokens.get( j );
+                    if( next instanceof PDFOperator )
+                    {
+                        PDFOperator op = (PDFOperator)next;
+                        //Tj and TJ are the two operators that display
+                        //strings in a PDF
+                        if( op.getOperation().equals( "Tj" ) )
+                        {
+                            //Tj takes one operator and that is the string
+                            //to display so lets update that operator
+                            COSString previous = (COSString)tokens.get( j-1 );
+                            String string = previous.getString();
+                            string = string.replaceFirst( strToFind, message );
+                            previous.reset();
+                            previous.append( string.getBytes("ISO-8859-1") );
+                        }
+                        else if( op.getOperation().equals( "TJ" ) )
+                        {
+                            COSArray previous = (COSArray)tokens.get( j-1 );
+                            for( int k=0; k<previous.size(); k++ )
+                            {
+                                Object arrElement = previous.getObject( k );
+                                if( arrElement instanceof COSString )
+                                {
+                                    COSString cosString = (COSString)arrElement;
+                                    String string = cosString.getString();
+                                    string = string.replaceFirst( strToFind, message );
+                                    cosString.reset();
+                                    cosString.append( string.getBytes("ISO-8859-1") );
+                                }
+                            }
+                        }
+                    }
+                }
+                //now that the tokens are updated we will replace the
+                //page content stream.
+                PDStream updatedStream = new PDStream(doc);
+                OutputStream out = updatedStream.createOutputStream();
+                ContentStreamWriter tokenWriter = new ContentStreamWriter(out);
+                tokenWriter.writeTokens( tokens );
+                page.setContents( updatedStream );
+            }
+            doc.save( outputFile );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will open a PDF and replace a string if it finds it.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        ReplaceString app = new ReplaceString();
+        try
+        {
+            if( args.length != 4 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0], args[1], args[2], args[3] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() +
+            " <input-file> <output-file> <search-string> <Message>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ReplaceURLs.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ReplaceURLs.java
new file mode 100644 (file)
index 0000000..ffda5ff
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
+
+
+/**
+ * This is an example of how to replace a URL in a PDF document.  This
+ * will only replace the URL that the text refers to and not the text
+ * itself.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ReplaceURLs
+{
+    /**
+     * Constructor.
+     */
+    private ReplaceURLs()
+    {
+        //utility class
+    }
+
+    /**
+     * This will read in a document and replace all of the urls with
+     * http://www.pdfbox.org.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     *
+     * @throws Exception If there is an error during the process.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        PDDocument doc = null;
+        try
+        {
+            if( args.length != 2 )
+            {
+                usage();
+            }
+            else
+            {
+                doc = PDDocument.load( args[0] );
+                List allPages = doc.getDocumentCatalog().getAllPages();
+                for( int i=0; i<allPages.size(); i++ )
+                {
+                    PDPage page = (PDPage)allPages.get( i );
+                    List annotations = page.getAnnotations();
+
+                    for( int j=0; j<annotations.size(); j++ )
+                    {
+                        PDAnnotation annot = (PDAnnotation)annotations.get( j );
+                        if( annot instanceof PDAnnotationLink )
+                        {
+                            PDAnnotationLink link = (PDAnnotationLink)annot;
+                            PDAction action = link.getAction();
+                            if( action instanceof PDActionURI )
+                            {
+                                PDActionURI uri = (PDActionURI)action;
+                                String oldURI = uri.getURI();
+                                String newURI = "http://www.pdfbox.org";
+                                System.out.println( "Page " + (i+1) +": Replacing " + oldURI + " with " + newURI );
+                                uri.setURI( newURI );
+                            }
+                        }
+                    }
+                }
+                doc.save( args[1] );
+            }
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: " + ReplaceURLs.class.getName() + " <input-file> <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RubberStamp.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RubberStamp.java
new file mode 100644 (file)
index 0000000..411c90c
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationRubberStamp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an example on how to add annotations to pages of a PDF document.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class RubberStamp
+{
+    private RubberStamp()
+    {
+        //utility class, should not be instantiated.
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    throw new IOException( "Encrypted documents are not supported for this example" );
+                }
+                List allpages = new ArrayList();
+                document.getDocumentCatalog().getPages().getAllKids(allpages);
+
+                for (int i=0; i < allpages.size(); i++)
+                {
+                    PDPage apage = (PDPage) allpages.get(i);
+                    List annotations = apage.getAnnotations();
+
+                    PDAnnotationRubberStamp rs = new PDAnnotationRubberStamp();
+                    rs.setName(PDAnnotationRubberStamp.NAME_TOP_SECRET);
+                    rs.setRectangle(new PDRectangle(100,100));
+                    rs.setContents("A top secret note");
+
+                    annotations.add(rs);
+                }
+
+                document.save( args[1] );
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.RubberStamp <input-pdf> <output-pdf>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RubberStampWithImage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/RubberStampWithImage.java
new file mode 100644 (file)
index 0000000..be250a9
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationRubberStamp;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.util.MapUtil;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This is an example on how to add a rubber stamp with an image to pages of a PDF document.
+ *
+ * @version $Revision: 1.0 $
+ */
+public class RubberStampWithImage
+{
+
+    private static final String SAVE_GRAPHICS_STATE = "q\n";
+    private static final String RESTORE_GRAPHICS_STATE = "Q\n";
+    private static final String CONCATENATE_MATRIX = "cm\n";
+    private static final String XOBJECT_DO = "Do\n";
+    private static final String SPACE = " ";
+
+    private static NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US );
+
+    /**
+     * Add a rubber stamp with an jpg image to every page of the given document.
+     * @param args the command line arguments
+     * @throws IOException an exception is thrown if something went wrong
+     */
+    public void doIt( String[] args ) throws IOException 
+    {
+        if( args.length != 3 )
+        {
+            usage();
+        }
+        else 
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    throw new IOException( "Encrypted documents are not supported for this example" );
+                }
+                List allpages = new ArrayList();
+                document.getDocumentCatalog().getPages().getAllKids(allpages);
+                int numberOfPages = allpages.size();
+    
+                for (int i=0; i < numberOfPages; i++)
+                {
+                    PDPage apage = (PDPage) allpages.get(i);
+                    List annotations = apage.getAnnotations();
+                    PDAnnotationRubberStamp rubberStamp = new PDAnnotationRubberStamp();
+                    rubberStamp.setName(PDAnnotationRubberStamp.NAME_TOP_SECRET);
+                    rubberStamp.setRectangle(new PDRectangle(100,100));
+                    rubberStamp.setContents("A top secret note");
+
+                    // Create a PDXObjectImage with the given jpg
+                    FileInputStream fin = new FileInputStream( args[2] );
+                    PDJpeg mypic = new PDJpeg(document,fin);
+                    
+                    //Define and set the target rectangle
+                    PDRectangle myrect = new PDRectangle();
+                    myrect.setUpperRightX(275);
+                    myrect.setUpperRightY(575);
+                    myrect.setLowerLeftX(250);
+                    myrect.setLowerLeftY(550);
+
+                    // Create a PDXObjectForm
+                    PDStream formstream = new PDStream(document);
+                    OutputStream os = formstream.createOutputStream();
+                    PDXObjectForm form = new PDXObjectForm(formstream);
+                    form.setResources(new PDResources());
+                    form.setBBox(myrect);
+                    form.setFormType(1);
+                    // adjust the image to the target rectangle and add it to the stream
+                    drawXObject(mypic, form.getResources(), os, 250, 550, 25, 25);
+                    os.close();
+
+                    PDAppearanceStream myDic = new PDAppearanceStream(form.getCOSStream());
+                    PDAppearanceDictionary appearance = new PDAppearanceDictionary(new COSDictionary());
+                    appearance.setNormalAppearance(myDic);
+                    rubberStamp.setAppearance(appearance);
+                    rubberStamp.setRectangle(myrect);
+
+                    //Add the new RubberStamp to the document
+                    annotations.add(rubberStamp);
+                
+                }
+                document.save( args[1] );
+            }
+            catch(COSVisitorException exception) 
+            {
+                System.err.println("An error occured during saving the document.");
+                System.err.println("Exception:"+exception);
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }        
+    }
+    
+    private void drawXObject( PDXObjectImage xobject, PDResources resources, OutputStream os, 
+            float x, float y, float width, float height ) throws IOException
+    {
+        // This is similar to PDPageContentStream.drawXObject()
+        String xObjectPrefix = "Im";
+        String objMapping = MapUtil.getNextUniqueKey( resources.getImages(), xObjectPrefix );
+        resources.getXObjects().put( objMapping, xobject );
+
+        appendRawCommands( os, SAVE_GRAPHICS_STATE );
+        appendRawCommands( os, formatDecimal.format( width ) );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, formatDecimal.format( 0 ) );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, formatDecimal.format( 0 ) );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, formatDecimal.format( height ) );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, formatDecimal.format( x ) );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, formatDecimal.format( y ) );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, CONCATENATE_MATRIX );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, "/" );
+        appendRawCommands( os, objMapping );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, XOBJECT_DO );
+        appendRawCommands( os, SPACE );
+        appendRawCommands( os, RESTORE_GRAPHICS_STATE );
+    }
+
+    private void appendRawCommands(OutputStream os, String commands) throws IOException
+    {
+        os.write( commands.getBytes("ISO-8859-1"));
+    }
+
+    /**
+     * This creates an instance of RubberStampWithImage.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        RubberStampWithImage rubberStamp = new RubberStampWithImage();
+        rubberStamp.doIt(args);
+    }
+
+    /**
+     * This will print the usage for this example.
+     */
+    private void usage()
+    {
+        System.err.println( "Usage: java "+getClass().getName()+" <input-pdf> <output-pdf> <jpeg-filename>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ShowColorBoxes.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/ShowColorBoxes.java
new file mode 100644 (file)
index 0000000..f7a92a2
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.examples.pdmodel;
+
+import java.awt.Color;
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+/**
+ * This is an example that creates a simple document.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ShowColorBoxes
+{
+    /**
+     * Constructor.
+     */
+    public ShowColorBoxes()
+    {
+        super();
+    }
+
+    /**
+     * create the second sample document from the PDF file format specification.
+     *
+     * @param file The file to write the PDF to.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt( String file) throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            PDPage page = new PDPage();
+            doc.addPage( page );
+
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+            //first fill the entire background with cyan
+            contentStream.setNonStrokingColor( Color.CYAN );
+            contentStream.fillRect( 0,0, page.findMediaBox().getWidth(), page.findMediaBox().getHeight() );
+
+            //then draw a red box in the lower left hand corner
+            contentStream.setNonStrokingColor( Color.RED );
+            contentStream.fillRect( 10, 10, 100, 100 );
+
+            contentStream.close();
+            doc.save( file );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a hello world PDF document.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        ShowColorBoxes app = new ShowColorBoxes();
+        try
+        {
+            if( args.length != 1 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/UsingTextMatrix.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/UsingTextMatrix.java
new file mode 100644 (file)
index 0000000..0f1ee63
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * 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.pdfbox.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This is an example of how to use a text matrix.
+ * @version $Revision: 1.0 $
+ */
+public class UsingTextMatrix
+{
+    /**
+     * Constructor.
+     */
+    public UsingTextMatrix()
+    {
+        super();
+    }
+
+    /**
+     * creates a sample document with some text using a text matrix.
+     *
+     * @param message The message to write in the file.
+     * @param outfile The resulting PDF.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error writing the PDF.
+     */
+    public void doIt( String message, String  outfile ) throws IOException, COSVisitorException
+    {
+        // the document
+        PDDocument doc = null;
+        try
+        {
+            doc = new PDDocument();
+
+            // Page 1
+            PDFont font = PDType1Font.HELVETICA;
+            PDPage page = new PDPage();
+            page.setMediaBox(PDPage.PAGE_SIZE_A4);
+            doc.addPage(page);
+            float fontSize = 12.0f;
+
+            PDRectangle pageSize = page.findMediaBox();
+            float centeredXPosition = (pageSize.getWidth() - fontSize/1000f)/2f;
+            float stringWidth = font.getStringWidth( message );
+            float centeredYPosition = (pageSize.getHeight() - (stringWidth*fontSize)/1000f)/3f;
+
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
+            contentStream.setFont( font, fontSize );
+            contentStream.beginText();
+            // counterclockwise rotation
+            for (int i=0;i<8;i++) 
+            {
+                contentStream.setTextRotation(i*Math.PI*0.25, centeredXPosition, 
+                        pageSize.getHeight()-centeredYPosition);
+                contentStream.drawString( message + " " + i);
+            }
+            // clockwise rotation
+            for (int i=0;i<8;i++) 
+            {
+                contentStream.setTextRotation(-i*Math.PI*0.25, centeredXPosition, centeredYPosition);
+                contentStream.drawString( message + " " + i);
+            }
+
+            contentStream.endText();
+            contentStream.close();
+
+            // Page 2
+            page = new PDPage();
+            page.setMediaBox(PDPage.PAGE_SIZE_A4);
+            doc.addPage(page);
+            fontSize = 1.0f;
+
+            contentStream = new PDPageContentStream(doc, page, false, false);
+            contentStream.setFont( font, fontSize );
+            contentStream.beginText();
+
+            // text scaling
+            for (int i=0;i<10;i++)
+            {
+                contentStream.setTextScaling(12+(i*6), 12+(i*6), 100, 100+i*50);
+                contentStream.drawString( message + " " +i);
+            }
+            contentStream.endText();
+            contentStream.close();
+
+            // Page 3
+            page = new PDPage();
+            page.setMediaBox(PDPage.PAGE_SIZE_A4);
+            doc.addPage(page);
+            fontSize = 1.0f;
+
+            contentStream = new PDPageContentStream(doc, page, false, false);
+            contentStream.setFont( font, fontSize );
+            contentStream.beginText();
+
+            int i = 0;
+            // text scaling combined with rotation 
+            contentStream.setTextMatrix(12, 0, 0, 12, centeredXPosition, centeredYPosition*1.5);
+            contentStream.drawString( message + " " +i++);
+
+            contentStream.setTextMatrix(0, 18, -18, 0, centeredXPosition, centeredYPosition*1.5);
+            contentStream.drawString( message + " " +i++);
+
+            contentStream.setTextMatrix(-24, 0, 0, -24, centeredXPosition, centeredYPosition*1.5);
+            contentStream.drawString( message + " " +i++);
+
+            contentStream.setTextMatrix(0, -30, 30, 0, centeredXPosition, centeredYPosition*1.5);
+            contentStream.drawString( message + " " +i++);
+
+            contentStream.endText();
+            contentStream.close();
+
+            doc.save( outfile );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    /**
+     * This will create a PDF document with some examples how to use a text matrix.
+     * 
+     * @param args Command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        UsingTextMatrix app = new UsingTextMatrix();
+        try
+        {
+            if( args.length != 2 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0], args[1] );
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <Message> <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/pdmodel/package.html
new file mode 100644 (file)
index 0000000..674bb7c
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These examples show how to use the classes in the PDModel package.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/persistence/CopyDoc.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/persistence/CopyDoc.java
new file mode 100644 (file)
index 0000000..2c4501d
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.examples.persistence;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDocument;
+
+
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This is an example used to copy a documents contents from a source doc to destination doc
+ * via an in-memory document representation.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.7 $
+ */
+public class CopyDoc
+{
+    /**
+     * Constructor.
+     */
+    public CopyDoc()
+    {
+        super();
+    }
+
+    /**
+     * This will perform the document copy.
+     *
+     * @param in The filename used for input.
+     * @param out The filename used for output.
+     *
+     * @throws IOException If there is an error parsing the document.
+     * @throws COSVisitorException If there is an error while copying the document.
+     */
+    public void doIt(String in, String out) throws IOException, COSVisitorException
+    {
+        java.io.InputStream is = null;
+        java.io.OutputStream os = null;
+        COSWriter writer = null;
+        try
+        {
+            is = new java.io.FileInputStream(in);
+            PDFParser parser = new PDFParser(is);
+            parser.parse();
+
+            COSDocument doc = parser.getDocument();
+
+            os = new java.io.FileOutputStream(out);
+            writer = new COSWriter(os);
+
+            writer.write(doc);
+
+        }
+        finally
+        {
+            if( is != null )
+            {
+                is.close();
+            }
+            if( os != null )
+            {
+                os.close();
+            }
+            if( writer != null )
+            {
+                writer.close();
+            }
+        }
+    }
+
+    /**
+     * This will copy a PDF document.
+     * <br />
+     * see usage() for commandline
+     *
+     * @param args command line arguments
+     */
+    public static void main(String[] args)
+    {
+        CopyDoc app = new CopyDoc();
+        try
+        {
+            if( args.length != 2 )
+            {
+                app.usage();
+            }
+            else
+            {
+                app.doIt( args[0], args[1]);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This will print out a message telling how to use this example.
+     */
+    private void usage()
+    {
+        System.err.println( "usage: " + this.getClass().getName() + " <input-file> <output-file>" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/persistence/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/persistence/package.html
new file mode 100644 (file)
index 0000000..fcc381e
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These examples will show how to use the persistence features of the PDFBox.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/signature/ShowSignature.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/signature/ShowSignature.java
new file mode 100644 (file)
index 0000000..4fdbe89
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * 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.pdfbox.examples.signature;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import java.security.cert.CertificateFactory;
+
+import java.util.Collection;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * This will read a document from the filesystem, decrypt it and do something with the signature.
+ *
+ * usage: java org.apache.pdfbox.examples.signature.ShowSignature &lt;password&gt; &lt;inputfile&gt;
+ *
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class ShowSignature
+{
+
+    private ShowSignature()
+    {
+    }
+    /**
+     * This is the entry point for the application.
+     *
+     * @param args The command-line arguments.
+     *
+     * @throws Exception If there is an error reading the file.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        ShowSignature show = new ShowSignature();
+        show.showSignature( args );
+    }
+
+    private void showSignature( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            String password = args[0];
+            String infile = args[1];
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( infile );
+
+                if( document.isEncrypted() )
+                {
+                    document.decrypt( password );
+                }
+                else
+                {
+                    System.err.println( "Warning: Document is not encrypted." );
+                }
+
+                COSDictionary trailer = document.getDocument().getTrailer();
+                COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT );
+                COSDictionary acroForm = (COSDictionary)root.getDictionaryObject( COSName.ACRO_FORM );
+                COSArray fields = (COSArray)acroForm.getDictionaryObject( COSName.FIELDS );
+                for( int i=0; i<fields.size(); i++ )
+                {
+                    COSDictionary field = (COSDictionary)fields.getObject( i );
+                    String type = field.getNameAsString( "FT" );
+                    if( "Sig".equals( type ) )
+                    {
+                        COSDictionary cert = (COSDictionary)field.getDictionaryObject( COSName.V );
+                        if( cert != null )
+                        {
+                            System.out.println( "Certificate found" );
+                            System.out.println( "Name=" + cert.getDictionaryObject( COSName.NAME ) );
+                            System.out.println( "Modified=" + cert.getDictionaryObject( COSName.getPDFName( "M" ) ) );
+                            COSName subFilter = (COSName)cert.getDictionaryObject( COSName.getPDFName( "SubFilter" ) );
+                            if( subFilter != null )
+                            {
+                                if( subFilter.getName().equals( "adbe.x509.rsa_sha1" ) )
+                                {
+                                    COSString certString = (COSString)cert.getDictionaryObject(
+                                        COSName.getPDFName( "Cert" ) );
+                                    byte[] certData = certString.getBytes();
+                                    CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
+                                    ByteArrayInputStream certStream = new ByteArrayInputStream( certData );
+                                    Collection certs = factory.generateCertificates( certStream );
+                                    System.out.println( "certs=" + certs );
+                                }
+                                else if( subFilter.getName().equals( "adbe.pkcs7.sha1" ) )
+                                {
+                                    COSString certString = (COSString)cert.getDictionaryObject(
+                                        COSName.CONTENTS );
+                                    byte[] certData = certString.getBytes();
+                                    CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
+                                    ByteArrayInputStream certStream = new ByteArrayInputStream( certData );
+                                    Collection certs = factory.generateCertificates( certStream );
+                                    System.out.println( "certs=" + certs );
+                                }
+                                else
+                                {
+                                    System.err.println( "Unknown certificate type:" + subFilter );
+                                }
+                            }
+                            else
+                            {
+                                throw new IOException( "Missing subfilter for cert dictionary" );
+                            }
+                        }
+                        else
+                        {
+                            System.out.println( "Signature found, but no certificate" );
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print a usage message.
+     */
+    private static void usage()
+    {
+        System.err.println( "usage: java org.apache.pdfbox.examples.signature.ShowSignature " +
+                            "<password> <inputfile>" );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/signature/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/signature/package.html
new file mode 100644 (file)
index 0000000..cc31cd0
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These examples will show how to gain access to the PDF signature.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/ExtractTextByArea.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/ExtractTextByArea.java
new file mode 100644 (file)
index 0000000..ac02748
--- /dev/null
@@ -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.pdfbox.examples.util;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.util.PDFTextStripperByArea;
+
+import java.awt.Rectangle;
+
+import java.util.List;
+
+/**
+ * This is an example on how to extract text from a specific area on the PDF document.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.ExtractTextByArea &lt;input-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ExtractTextByArea
+{
+    private ExtractTextByArea()
+    {
+        //utility class and should not be constructed.
+    }
+
+
+    /**
+     * This will print the documents text in a certain area.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 1 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    try
+                    {
+                        document.decrypt( "" );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        System.err.println( "Error: Document is encrypted with a password." );
+                        System.exit( 1 );
+                    }
+                }
+                PDFTextStripperByArea stripper = new PDFTextStripperByArea();
+                stripper.setSortByPosition( true );
+                Rectangle rect = new Rectangle( 10, 280, 275, 60 );
+                stripper.addRegion( "class1", rect );
+                List allPages = document.getDocumentCatalog().getAllPages();
+                PDPage firstPage = (PDPage)allPages.get( 0 );
+                stripper.extractRegions( firstPage );
+                System.out.println( "Text in the area:" + rect );
+                System.out.println( stripper.getTextForRegion( "class1" ) );
+
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.util.ExtractTextByArea <input-pdf>" );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/PrintImageLocations.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/PrintImageLocations.java
new file mode 100644 (file)
index 0000000..d50a513
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * 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.pdfbox.examples.util;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.PDFStreamEngine;
+import org.apache.pdfbox.util.ResourceLoader;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is an example on how to get the x/y coordinates of image locations.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.PrintImageLocations &lt;input-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PrintImageLocations extends PDFStreamEngine
+{
+    /**
+     * Default constructor.
+     *
+     * @throws IOException If there is an error loading text stripper properties.
+     */
+    public PrintImageLocations() throws IOException
+    {
+        super( ResourceLoader.loadProperties(
+                "org/apache/pdfbox/resources/PDFTextStripper.properties", true ) );
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 1 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    try
+                    {
+                        document.decrypt( "" );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        System.err.println( "Error: Document is encrypted with a password." );
+                        System.exit( 1 );
+                    }
+                }
+                PrintImageLocations printer = new PrintImageLocations();
+                List allPages = document.getDocumentCatalog().getAllPages();
+                for( int i=0; i<allPages.size(); i++ )
+                {
+                    PDPage page = (PDPage)allPages.get( i );
+                    System.out.println( "Processing page: " + i );
+                    printer.processStream( page, page.findResources(), page.getContents().getStream() );
+                }
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This is used to handle an operation.
+     *
+     * @param operator The operation to perform.
+     * @param arguments The list of arguments.
+     *
+     * @throws IOException If there is an error processing the operation.
+     */
+    protected void processOperator( PDFOperator operator, List arguments ) throws IOException
+    {
+        String operation = operator.getOperation();
+        if( operation.equals( "Do" ) )
+        {
+            COSName objectName = (COSName)arguments.get( 0 );
+            Map xobjects = getResources().getXObjects();
+            PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
+            if( xobject instanceof PDXObjectImage )
+            {
+                try
+                {
+                    PDXObjectImage image = (PDXObjectImage)xobject;
+                    PDPage page = getCurrentPage();
+                    Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
+                    double rotationInRadians =(page.findRotation() * Math.PI)/180;
+
+
+                    AffineTransform rotation = new AffineTransform();
+                    rotation.setToRotation( rotationInRadians );
+                    AffineTransform rotationInverse = rotation.createInverse();
+                    Matrix rotationInverseMatrix = new Matrix();
+                    rotationInverseMatrix.setFromAffineTransform( rotationInverse );
+                    Matrix rotationMatrix = new Matrix();
+                    rotationMatrix.setFromAffineTransform( rotation );
+
+                    Matrix unrotatedCTM = ctm.multiply( rotationInverseMatrix );
+                    float xScale = unrotatedCTM.getXScale();
+                    float yScale = unrotatedCTM.getYScale();
+
+                    System.out.println( "Found image[" + objectName.getName() + "] " +
+                            "at " + unrotatedCTM.getXPosition() + "," + unrotatedCTM.getYPosition() +
+                            " size=" + (xScale/100f*image.getWidth()) + "," + (yScale/100f*image.getHeight() ));
+                }
+                catch( NoninvertibleTransformException e )
+                {
+                    throw new WrappedIOException( e );
+                }
+            }
+        }
+        else
+        {
+            super.processOperator( operator, arguments );
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintImageLocations <input-pdf>" );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/PrintTextLocations.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/PrintTextLocations.java
new file mode 100644 (file)
index 0000000..cb84a2b
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * 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.pdfbox.examples.util;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.PDFTextStripper;
+import org.apache.pdfbox.util.TextPosition;
+
+import java.io.IOException;
+
+import java.util.List;
+
+/**
+ * This is an example on how to get some x/y coordinates of text.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.PrintTextLocations &lt;input-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PrintTextLocations extends PDFTextStripper
+{
+    /**
+     * Default constructor.
+     *
+     * @throws IOException If there is an error loading text stripper properties.
+     */
+    public PrintTextLocations() throws IOException
+    {
+        super.setSortByPosition( true );
+    }
+
+    /**
+     * This will print the documents data.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 1 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    try
+                    {
+                        document.decrypt( "" );
+                    }
+                    catch( InvalidPasswordException e )
+                    {
+                        System.err.println( "Error: Document is encrypted with a password." );
+                        System.exit( 1 );
+                    }
+                }
+                PrintTextLocations printer = new PrintTextLocations();
+                List allPages = document.getDocumentCatalog().getAllPages();
+                for( int i=0; i<allPages.size(); i++ )
+                {
+                    PDPage page = (PDPage)allPages.get( i );
+                    System.out.println( "Processing page: " + i );
+                    PDStream contents = page.getContents();
+                    if( contents != null )
+                    {
+                        printer.processStream( page, page.findResources(), page.getContents().getStream() );
+                    }
+                }
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * A method provided as an event interface to allow a subclass to perform
+     * some specific functionality when text needs to be processed.
+     *
+     * @param text The text to be processed
+     */
+    protected void processTextPosition( TextPosition text )
+    {
+        System.out.println( "String[" + text.getXDirAdj() + "," +
+                text.getYDirAdj() + " fs=" + text.getFontSize() + " xscale=" +
+                text.getXScale() + " height=" + text.getHeightDir() + " space=" +
+                text.getWidthOfSpace() + " width=" +
+                text.getWidthDirAdj() + "]" + text.getCharacter() );
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintTextLocations <input-pdf>" );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/RemoveAllText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/RemoveAllText.java
new file mode 100644 (file)
index 0000000..f8566b2
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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.pdfbox.examples.util;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an example on how to remove all text from PDF document.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.RemoveAllText &lt;input-pdf&gt; &lt;output-pdf&gt;
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class RemoveAllText
+{
+    /**
+     * Default constructor.
+     */
+    private RemoveAllText()
+    {
+        //example class should not be instantiated
+    }
+
+    /**
+     * This will remove all text from a PDF document.
+     *
+     * @param args The command line arguments.
+     *
+     * @throws Exception If there is an error parsing the document.
+     */
+    public static void main( String[] args ) throws Exception
+    {
+        if( args.length != 2 )
+        {
+            usage();
+        }
+        else
+        {
+            PDDocument document = null;
+            try
+            {
+                document = PDDocument.load( args[0] );
+                if( document.isEncrypted() )
+                {
+                    System.err.println( "Error: Encrypted documents are not supported for this example." );
+                    System.exit( 1 );
+                }
+                List allPages = document.getDocumentCatalog().getAllPages();
+                for( int i=0; i<allPages.size(); i++ )
+                {
+                    PDPage page = (PDPage)allPages.get( i );
+                    PDFStreamParser parser = new PDFStreamParser(page.getContents());
+                    parser.parse();
+                    List tokens = parser.getTokens();
+                    List newTokens = new ArrayList();
+                    for( int j=0; j<tokens.size(); j++)
+                    {
+                        Object token = tokens.get( j );
+                        if( token instanceof PDFOperator )
+                        {
+                            PDFOperator op = (PDFOperator)token;
+                            if( op.getOperation().equals( "TJ") || op.getOperation().equals( "Tj" ))
+                            {
+                                //remove the one argument to this operator
+                                newTokens.remove( newTokens.size() -1 );
+                                continue;
+                            }
+                        }
+                        newTokens.add( token );
+
+                    }
+                    PDStream newContents = new PDStream( document );
+                    ContentStreamWriter writer = new ContentStreamWriter( newContents.createOutputStream() );
+                    writer.writeTokens( newTokens );
+                    newContents.addCompression();
+                    page.setContents( newContents );
+                }
+                document.save( args[1] );
+            }
+            finally
+            {
+                if( document != null )
+                {
+                    document.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * This will print the usage for this document.
+     */
+    private static void usage()
+    {
+        System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.RemoveAllText <input-pdf> <output-pdf>" );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/examples/util/package.html
new file mode 100644 (file)
index 0000000..e4abc7a
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The packages in this package will show how to use the PDFBox util API.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/COSVisitorException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/COSVisitorException.java
new file mode 100644 (file)
index 0000000..6b2fa48
--- /dev/null
@@ -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.pdfbox.exceptions;
+
+/**
+ * An exception that represents something gone wrong when visiting a PDF object.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.6 $
+ */
+public class COSVisitorException extends WrappedException
+{
+
+    /**
+     * COSVisitorException constructor comment.
+     *
+     * @param e The root exception that caused this exception.
+     */
+    public COSVisitorException( Exception e )
+    {
+        super( e );
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/CryptographyException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/CryptographyException.java
new file mode 100644 (file)
index 0000000..e9a9aab
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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.pdfbox.exceptions;
+
+/**
+ * An exception that indicates that something has gone wrong during a
+ * cryptography operation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class CryptographyException extends Exception
+{
+    private Exception embedded;
+
+    /**
+     * Constructor.
+     *
+     * @param msg A msg to go with this exception.
+     */
+    public CryptographyException( String msg )
+    {
+        super( msg );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param e The root exception that caused this exception.
+     */
+    public CryptographyException( Exception e )
+    {
+        super( e.getMessage() );
+        setEmbedded( e );
+    }
+    /**
+     * This will get the exception that caused this exception.
+     *
+     * @return The embedded exception if one exists.
+     */
+    public Exception getEmbedded()
+    {
+        return embedded;
+    }
+    /**
+     * This will set the exception that caused this exception.
+     *
+     * @param e The sub exception.
+     */
+    private void setEmbedded( Exception e )
+    {
+        embedded = e;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/InvalidPasswordException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/InvalidPasswordException.java
new file mode 100644 (file)
index 0000000..4532fea
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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.pdfbox.exceptions;
+
+/**
+ * An exception that indicates an invalid password was supplied.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class InvalidPasswordException extends Exception
+{
+
+    /**
+     * Constructor.
+     *
+     * @param msg A msg to go with this exception.
+     */
+    public InvalidPasswordException( String msg )
+    {
+        super( msg );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/OutlineNotLocalException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/OutlineNotLocalException.java
new file mode 100644 (file)
index 0000000..6ee5768
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.pdfbox.exceptions;
+
+import java.io.IOException;
+
+/**
+ * This exception will be thrown when a local destination(page within the same PDF) is required
+ * but the bookmark(PDOutlineItem) refers to an external destination or an action that does not
+ * point to a page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class OutlineNotLocalException extends IOException
+{
+
+    /**
+     * Constructor.
+     *
+     * @param msg An error message.
+     */
+    public OutlineNotLocalException( String msg )
+    {
+        super( msg );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/SignatureException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/SignatureException.java
new file mode 100644 (file)
index 0000000..17c8f2d
--- /dev/null
@@ -0,0 +1,95 @@
+package org.apache.pdfbox.exceptions;
+
+/*
+ * 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.
+ */
+
+/**
+ * An exception that indicates a problem during the signing process.
+ *
+ * @author Thomas Chojecki
+ * @version $Revision: $
+ */
+public class SignatureException extends Exception
+{
+
+    public final static int WRONG_PASSWORD = 1;
+
+    public final static int UNSUPPORTED_OPERATION = 2;
+    
+    public final static int CERT_PATH_CHECK_INVALID = 3;
+    
+    public final static int NO_SUCH_ALGORITHM = 4;
+    
+    public final static int INVALID_PAGE_FOR_SIGNATURE = 5;
+
+    public final static int VISUAL_SIGNATURE_INVALID = 6;
+
+    private int no;
+  
+    /**
+     * Constructor.
+     *
+     * @param msg A msg to go with this exception.
+     */
+    public SignatureException( String msg )
+    {
+        super( msg );
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param errno A error number to fulfill this exception
+     * @param msg A msg to go with this exception.
+     */
+    public SignatureException( int errno , String msg ) 
+    {
+      super( msg );
+      no = errno;
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param e The exception that should be encapsulate.
+     */
+    public SignatureException(Throwable e) 
+    {
+      super(e);
+    }
+    
+    /**
+     * Constructor.
+     * 
+     * @param errno A error number to fulfill this exception
+     * @param e The exception that should be encapsulate.
+     */
+    public SignatureException( int errno, Throwable e) 
+    {
+      super(e);
+    }
+
+    /**
+     * A error number to fulfill this exception
+     * 
+     * @return the error number if available, otherwise 0
+     */
+    public int getErrNo() 
+    {
+      return no;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/WrappedException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/WrappedException.java
new file mode 100644 (file)
index 0000000..db11060
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.exceptions;
+
+import java.io.PrintStream;
+
+/**
+ * An exception that that holds a sub exception.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class WrappedException extends Exception
+{
+    private Exception wrapped = null;
+
+    /**
+     * constructor comment.
+     *
+     * @param e The root exception that caused this exception.
+     */
+    public WrappedException( Exception e )
+    {
+        wrapped = e;
+    }
+
+    /**
+     * Gets the wrapped exception message.
+     *
+     * @return A message indicating the exception.
+     */
+    public String getMessage()
+    {
+        return wrapped.getMessage();
+    }
+
+    /**
+     * Prints this throwable and its backtrace to the specified print stream.
+     *
+     * @param s <code>PrintStream</code> to use for output
+     */
+    public void printStackTrace(PrintStream s)
+    {
+        super.printStackTrace( s );
+        wrapped.printStackTrace( s );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/WrappedIOException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/WrappedIOException.java
new file mode 100644 (file)
index 0000000..3a9ebeb
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.pdfbox.exceptions;
+
+import java.io.IOException;
+
+/**
+ * An simple class that allows a sub exception to be stored.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class WrappedIOException extends IOException
+{
+    /**
+     * constructor comment.
+     *
+     * @param e The root exception that caused this exception.
+     */
+    public WrappedIOException( Throwable e )
+    {
+        initCause( e );
+    }
+
+    /**
+     * constructor comment.
+     *
+     * @param message Descriptive text for the exception.
+     * @param e The root exception that caused this exception.
+     */
+    public WrappedIOException( String message, Throwable e )
+    {
+        super( message );
+        initCause( e );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/exceptions/package.html
new file mode 100644 (file)
index 0000000..e09972e
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package is a place holder for exceptions that are used in the PDFBox project.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/ASCII85Filter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/ASCII85Filter.java
new file mode 100644 (file)
index 0000000..ee67724
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.io.ASCII85InputStream;
+import org.apache.pdfbox.io.ASCII85OutputStream;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the used for the ASCIIHexDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class ASCII85Filter implements Filter
+{
+    /**
+     * {@inheritDoc}
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        ASCII85InputStream is = null;
+        try
+        {
+            is = new ASCII85InputStream(compressedData);
+            byte[] buffer = new byte[1024];
+            int amountRead = 0;
+            while( (amountRead = is.read( buffer, 0, 1024) ) != -1 )
+            {
+                result.write(buffer, 0, amountRead);
+            }
+            result.flush();
+        }
+        finally
+        {
+            if( is != null )
+            {
+                is.close();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        ASCII85OutputStream os = new ASCII85OutputStream(result);
+        byte[] buffer = new byte[1024];
+        int amountRead = 0;
+        while( (amountRead = rawData.read( buffer, 0, 1024 )) != -1 )
+        {
+            os.write( buffer, 0, amountRead );
+        }
+        os.close();
+        result.flush();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/ASCIIHexFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/ASCIIHexFilter.java
new file mode 100644 (file)
index 0000000..d381363
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.persistence.util.COSHEXTable;
+
+/**
+ * This is the used for the ASCIIHexDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class ASCIIHexFilter implements Filter
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(ASCIIHexFilter.class);
+    /**
+     * Whitespace.
+     *   0  0x00  Null (NUL)
+     *   9  0x09  Tab (HT)
+     *  10  0x0A  Line feed (LF)
+     *  12  0x0C  Form feed (FF)
+     *  13  0x0D  Carriage return (CR)
+     *  32  0x20  Space (SP)  
+     */
+
+    private boolean isWhitespace(int c) 
+    {
+        return c == 0 || c == 9 || c == 10 || c == 12 || c == 13 || c == 32;
+    }
+    
+    private boolean isEOD(int c) 
+    {
+        return (c == 62); // '>' - EOD
+    }
+    
+    /**
+      * {@inheritDoc}
+      */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException 
+    {
+        int value = 0;
+        int firstByte = 0;
+        int secondByte = 0;
+        while ((firstByte = compressedData.read()) != -1) 
+        {
+            // always after first char
+            while(isWhitespace(firstByte))
+            {
+                firstByte = compressedData.read();
+            }
+            if(isEOD(firstByte))
+            {
+                break;
+            }
+       
+            if(REVERSE_HEX[firstByte] == -1)
+            {
+                log.error("Invalid Hex Code; int: " + firstByte + " char: " + (char) firstByte);
+            }
+            value = REVERSE_HEX[firstByte] * 16;
+            secondByte = compressedData.read();
+       
+            if(isEOD(secondByte)) 
+            {
+                // second value behaves like 0 in case of EOD
+                result.write( value );
+                break;
+            }
+            if(secondByte >= 0) 
+            {
+                if(REVERSE_HEX[secondByte] == -1)
+                {
+                    log.error("Invalid Hex Code; int: " + secondByte + " char: " + (char) secondByte);
+                }
+                value += REVERSE_HEX[secondByte];
+            }
+            result.write( value );
+        }
+        result.flush();
+    }
+
+    private static final int[] REVERSE_HEX =
+    {
+        -1, //0
+        -1, //1
+        -1, //2
+        -1, //3
+        -1, //4
+        -1, //5
+        -1, //6
+        -1, //7
+        -1, //8
+        -1, //9
+        -1, //10
+        -1, //11
+        -1, //12
+        -1, //13
+        -1, //14
+        -1, //15
+        -1, //16
+        -1, //17
+        -1, //18
+        -1, //19
+        -1, //20
+        -1, //21
+        -1, //22
+        -1, //23
+        -1, //24
+        -1, //25
+        -1, //26
+        -1, //27
+        -1, //28
+        -1, //29
+        -1, //30
+        -1, //31
+        -1, //32
+        -1, //33
+        -1, //34
+        -1, //35
+        -1, //36
+        -1, //37
+        -1, //38
+        -1, //39
+        -1, //40
+        -1, //41
+        -1, //42
+        -1, //43
+        -1, //44
+        -1, //45
+        -1, //46
+        -1, //47
+         0, //48
+         1, //49
+         2, //50
+         3, //51
+         4, //52
+         5, //53
+         6, //54
+         7, //55
+         8, //56
+         9, //57
+        -1, //58
+        -1, //59
+        -1, //60
+        -1, //61
+        -1, //62
+        -1, //63
+        -1, //64
+        10, //65
+        11, //66
+        12, //67
+        13, //68
+        14, //69
+        15, //70
+        -1, //71
+        -1, //72
+        -1, //73
+        -1, //74
+        -1, //75
+        -1, //76
+        -1, //77
+        -1, //78
+        -1, //79
+        -1, //80
+        -1, //81
+        -1, //82
+        -1, //83
+        -1, //84
+        -1, //85
+        -1, //86
+        -1, //87
+        -1, //88
+        -1, //89
+        -1, //90
+        -1, //91
+        -1, //92
+        -1, //93
+        -1, //94
+        -1, //95
+        -1, //96
+        10, //97
+        11, //98
+        12, //99
+        13, //100
+        14, //101
+        15, //102
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        int byteRead = 0;
+        while( (byteRead = rawData.read()) != -1 )
+        {
+            int value = (byteRead+256)%256;
+            result.write( COSHEXTable.TABLE[value] );
+        }
+        result.flush();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/CCITTFaxDecodeFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/CCITTFaxDecodeFilter.java
new file mode 100644 (file)
index 0000000..b90230a
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is a filter for the CCITTFax Decoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Marcel Kammer
+ * @author Paul King
+ * @version $Revision: 1.13 $
+ */
+public class CCITTFaxDecodeFilter implements Filter
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(CCITTFaxDecodeFilter.class);
+
+    /**
+     * Constructor.
+     */
+    public CCITTFaxDecodeFilter()
+    {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decode(InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex) 
+    throws IOException
+    {
+        
+        COSBase decodeP = options.getDictionaryObject(COSName.DECODE_PARMS);
+        COSDictionary decodeParms = null;
+        if (decodeP instanceof COSDictionary)
+        {
+            decodeParms = (COSDictionary)decodeP;
+        }
+        else if (decodeP instanceof COSArray)
+        {
+            decodeParms =  (COSDictionary)((COSArray)decodeP).get(0);
+        }
+        int length = options.getInt(COSName.LENGTH);
+        byte[] compressed = new byte[length];
+        compressedData.read(compressed, 0, length);
+        int cols = decodeParms.getInt(COSName.COLUMNS, 1728);
+        int rows = decodeParms.getInt(COSName.ROWS, 0);
+        int height = options.getInt(COSName.HEIGHT, 0); 
+        if (rows > 0 && height > 0)
+        {
+            // ensure that rows doesn't contain implausible data, see PDFBOX-771
+            rows = Math.min(rows, height);
+        }
+        else 
+        {
+            // at least one of the values has to have a valid value
+            rows = Math.max(rows, height);
+        }
+        int k = decodeParms.getInt(COSName.K);
+        int arraySize = (cols + 7) / 8 * rows;
+        byte[] decompressed = new byte[arraySize];
+        TIFFFaxDecoder faxDecoder = new TIFFFaxDecoder(1, cols, rows);
+        // TODO possible options??
+        long tiffOptions = 0;
+        if (k == 0)
+        {
+           faxDecoder.decode1D(decompressed, compressed, 0, rows);
+        }
+        else if (k > 0)
+        {
+           faxDecoder.decode2D(decompressed, compressed, 0, rows, tiffOptions);
+        }
+        else if (k < 0)
+        {
+           faxDecoder.decodeT6(decompressed, compressed, 0, rows, tiffOptions);
+        }
+        result.write(decompressed);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encode(InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        log.warn("CCITTFaxDecode.encode is not implemented yet, skipping this stream.");
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/CryptFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/CryptFilter.java
new file mode 100644 (file)
index 0000000..ea263e1
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ *
+ * @author adam.nichols
+ */
+public class CryptFilter implements Filter
+{
+    /**
+     * {@inheritDoc}
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        COSName encryptionName = (COSName)options.getDictionaryObject(COSName.NAME);
+        if(encryptionName == null || encryptionName.equals(COSName.IDENTITY)) 
+        {
+            // currently the only supported implementation is the Identity crypt filter
+            Filter identityFilter = new IdentityFilter();
+            identityFilter.decode(compressedData, result, options, filterIndex);
+        }
+        else 
+        {
+            throw new IOException("Unsupported crypt filter "+encryptionName.getName());
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        COSName encryptionName = (COSName)options.getDictionaryObject(COSName.NAME);
+        if(encryptionName == null || encryptionName.equals(COSName.IDENTITY))
+        {
+            // currently the only supported implementation is the Identity crypt filter
+            Filter identityFilter = new IdentityFilter();
+            identityFilter.encode(rawData, result, options, filterIndex);
+        }
+        else
+        {
+            throw new IOException("Unsupported crypt filter "+encryptionName.getName());
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/DCTFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/DCTFilter.java
new file mode 100644 (file)
index 0000000..bd00b4e
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the used for the DCTDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class DCTFilter implements Filter
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(DCTFilter.class);
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        log.warn( "DCTFilter.decode is not implemented yet, skipping this stream." );
+    }
+
+     /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        log.warn( "DCTFilter.encode is not implemented yet, skipping this stream." );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/Filter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/Filter.java
new file mode 100644 (file)
index 0000000..65fd0eb
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the interface that will be used to apply filters to a byte stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public interface Filter
+{
+    /**
+     * This will decode some compressed data.
+     *
+     * @param compressedData The compressed byte stream.
+     * @param result The place to write the uncompressed byte stream.
+     * @param options The options to use to encode the data.
+     * @param filterIndex The index to the filter being decoded.
+     *
+     * @throws IOException If there is an error decompressing the stream.
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException;
+
+    /**
+     * This will encode some data.
+     *
+     * @param rawData The raw data to encode.
+     * @param result The place to write to encoded results to.
+     * @param options The options to use to encode the data.
+     * @param filterIndex The index to the filter being encoded.
+     *
+     * @throws IOException If there is an error compressing the stream.
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException;
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/FilterManager.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/FilterManager.java
new file mode 100644 (file)
index 0000000..37d9e4b
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.IOException;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This will contain manage all the different types of filters that are available.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ */
+public class FilterManager
+{
+    private Map<COSName, Filter> filters = new HashMap<COSName, Filter>();
+
+    /**
+     * Constructor.
+     */
+    public FilterManager()
+    {
+        Filter flateFilter = new FlateFilter();
+        Filter dctFilter = new DCTFilter();
+        Filter ccittFaxFilter = new CCITTFaxDecodeFilter();
+        Filter lzwFilter = new LZWFilter();
+        Filter asciiHexFilter = new ASCIIHexFilter();
+        Filter ascii85Filter = new ASCII85Filter();
+        Filter runLengthFilter = new RunLengthDecodeFilter();
+        Filter cryptFilter = new CryptFilter();
+        Filter jpxFilter = new JPXFilter();
+        Filter jbig2Filter = new JBIG2Filter();
+        
+        addFilter( COSName.FLATE_DECODE, flateFilter );
+        addFilter( COSName.FLATE_DECODE_ABBREVIATION, flateFilter );
+        addFilter( COSName.DCT_DECODE, dctFilter );
+        addFilter( COSName.DCT_DECODE_ABBREVIATION, dctFilter );
+        addFilter( COSName.CCITTFAX_DECODE, ccittFaxFilter );
+        addFilter( COSName.CCITTFAX_DECODE_ABBREVIATION, ccittFaxFilter );
+        addFilter( COSName.LZW_DECODE, lzwFilter );
+        addFilter( COSName.LZW_DECODE_ABBREVIATION, lzwFilter );
+        addFilter( COSName.ASCII_HEX_DECODE, asciiHexFilter );
+        addFilter( COSName.ASCII_HEX_DECODE_ABBREVIATION, asciiHexFilter );
+        addFilter( COSName.ASCII85_DECODE, ascii85Filter );
+        addFilter( COSName.ASCII85_DECODE_ABBREVIATION, ascii85Filter );
+        addFilter( COSName.RUN_LENGTH_DECODE, runLengthFilter );
+        addFilter( COSName.RUN_LENGTH_DECODE_ABBREVIATION, runLengthFilter );
+        addFilter( COSName.CRYPT, cryptFilter );
+        addFilter( COSName.JPX_DECODE, jpxFilter );
+        addFilter( COSName.JBIG2_DECODE, jbig2Filter );
+        
+    }
+
+    /**
+     * This will get all of the filters that are available in the system.
+     *
+     * @return All available filters in the system.
+     */
+    public Collection<Filter> getFilters()
+    {
+        return filters.values();
+    }
+
+    /**
+     * This will add an available filter.
+     *
+     * @param filterName The name of the filter.
+     * @param filter The filter to use.
+     */
+    public void addFilter( COSName filterName, Filter filter )
+    {
+        filters.put( filterName, filter );
+    }
+
+    /**
+     * This will get a filter by name.
+     *
+     * @param filterName The name of the filter to retrieve.
+     *
+     * @return The filter that matches the name.
+     *
+     * @throws IOException If the filter could not be found.
+     */
+    public Filter getFilter( COSName filterName ) throws IOException
+    {
+        Filter filter = (Filter)filters.get( filterName );
+        if( filter == null )
+        {
+            throw new IOException( "Unknown stream filter:" + filterName );
+        }
+
+        return filter;
+    }
+
+    /**
+     * This will get a filter by name.
+     *
+     * @param filterName The name of the filter to retrieve.
+     *
+     * @return The filter that matches the name.
+     *
+     * @throws IOException If the filter could not be found.
+     */
+    public Filter getFilter( String filterName ) throws IOException
+    {
+        return getFilter( COSName.getPDFName( filterName ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/FlateFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/FlateFilter.java
new file mode 100644 (file)
index 0000000..e42d3af
--- /dev/null
@@ -0,0 +1,352 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.EOFException;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the used for the FlateDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Marcel Kammer
+ * @version $Revision: 1.12 $
+ */
+public class FlateFilter implements Filter 
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(FlateFilter.class);
+
+    private static final int    BUFFER_SIZE    = 2048;
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decode(InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+    throws IOException
+    {
+        COSBase baseObj = options.getDictionaryObject(COSName.DECODE_PARMS, COSName.DP);
+        COSDictionary dict = null;
+        if( baseObj instanceof COSDictionary )
+        {
+            dict = (COSDictionary)baseObj;
+        }
+        else if( baseObj instanceof COSArray )
+        {
+            COSArray paramArray = (COSArray)baseObj;
+            if( filterIndex < paramArray.size() )
+            {
+                dict = (COSDictionary)paramArray.getObject( filterIndex );
+            }
+        }
+        else if( baseObj == null )
+        {
+            //do nothing
+        }
+        else
+        {
+            throw new IOException( "Error: Expected COSArray or COSDictionary and not " 
+                    + baseObj.getClass().getName() );
+        }
+
+
+        int predictor = -1;
+        int colors = -1;
+        int bitsPerPixel = -1;
+        int columns = -1;
+        InflaterInputStream decompressor = null;
+        ByteArrayInputStream bais = null;
+        ByteArrayOutputStream baos = null;
+        if (dict!=null)
+        {
+            predictor = dict.getInt(COSName.PREDICTOR);
+            if(predictor > 1)
+            {
+                colors = dict.getInt(COSName.COLORS);
+                bitsPerPixel = options.getInt(COSName.BITS_PER_COMPONENT);
+                columns = dict.getInt(COSName.COLUMNS);
+            }
+        }
+
+        try
+        {
+            // Decompress data to temporary ByteArrayOutputStream
+            decompressor = new InflaterInputStream(compressedData);
+            int amountRead;
+            int mayRead = compressedData.available();
+
+            if (mayRead > 0) 
+            {
+                byte[] buffer = new byte[Math.min(mayRead,BUFFER_SIZE)];
+
+                // Decode data using given predictor
+                if (predictor==-1 || predictor == 1 )
+                {
+                    try 
+                    {
+                        // decoding not needed
+                        while ((amountRead = decompressor.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1)
+                        {
+                            result.write(buffer, 0, amountRead);
+                        }
+                    }
+                    catch (OutOfMemoryError exception) 
+                    {
+                        // if the stream is corrupt an OutOfMemoryError may occur
+                        log.error("Stop reading corrupt stream");
+                    }
+                    catch (ZipException exception) 
+                    {
+                        // if the stream is corrupt an OutOfMemoryError may occur
+                        log.error("Stop reading corrupt stream");
+                    }
+                    catch (EOFException exception) 
+                    {
+                        // if the stream is corrupt an OutOfMemoryError may occur
+                        log.error("Stop reading corrupt stream");
+                    }
+                }
+                else
+                {
+                    /*
+                     * Reverting back to default values
+                     */
+                    if( colors == -1 )
+                    {
+                        colors = 1;
+                    }
+                    if( bitsPerPixel == -1 )
+                    {
+                        bitsPerPixel = 8;
+                    }
+                    if( columns == -1 )
+                    {
+                        columns = 1;
+                    }
+
+                    baos = new ByteArrayOutputStream();
+                    while ((amountRead = decompressor.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1)
+                    {
+                        baos.write(buffer, 0, amountRead);
+                    }
+                    baos.flush();
+
+                    // Copy data to ByteArrayInputStream for reading
+                    bais = new ByteArrayInputStream(baos.toByteArray());
+                    baos.close();
+                    baos = null;
+
+                    byte[] decodedData = decodePredictor(predictor, colors, bitsPerPixel, columns, bais);
+                    bais.close();
+                    bais = null;
+
+                    result.write(decodedData);
+                }
+            }
+
+            result.flush();
+        }
+        finally
+        {
+            if (decompressor != null)
+            {
+                decompressor.close();
+            }
+            if (bais != null)
+            {
+                bais.close();
+            }
+            if (baos != null)
+            {
+                baos.close();
+            }
+        }
+    }
+
+    private byte[] decodePredictor(int predictor, int colors, int bitsPerComponent, int columns, InputStream data)
+        throws IOException
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[2048];
+        if (predictor == 1 )
+        {
+            // No prediction
+            int i = 0;
+            while ((i = data.read(buffer)) != -1)
+            {
+                baos.write(buffer, 0, i);
+            }
+        }
+        else
+        {
+            // calculate sizes
+            int bpp = colors * (bitsPerComponent % 8 +1);
+            int rowlength = columns * bpp;
+            byte[] actline = new byte[rowlength];
+            // Initialize lastline with Zeros according to PNG-specification
+            byte[] lastline = new byte[rowlength];
+
+            boolean done = false;
+            int linepredictor = predictor;
+
+            while (!done && data.available() > 0)
+            {
+                 // test for PNG predictor; each value >= 10 (not only 15) indicates usage of PNG predictor
+               if (predictor >= 10)
+                {
+                           // PNG predictor; each row starts with predictor type (0, 1, 2, 3, 4)
+                    linepredictor = data.read();// read per line predictor
+                    if (linepredictor == -1)
+                    {
+                        done = true;// reached EOF
+                        break;
+                    }
+                    else
+                    {
+                        linepredictor += 10; // add 10 to tread value 0 as 10, 1 as 11, ...
+                    }
+                }
+
+                // read line
+                int i = 0;
+                int offset = 0;
+                while (offset < rowlength && ((i = data.read(actline, offset, rowlength - offset)) != -1))
+                {
+                    offset += i;
+                }
+
+                // Do prediction as specified in PNG-Specification 1.2
+                switch (linepredictor)
+                {
+                    case 2:// PRED TIFF SUB
+                        /**
+                         * @TODO decode tiff with bitsPerComponent != 8; e.g. for 4 bpc each nibble must be subtracted separately
+                         */
+                        if ( bitsPerComponent != 8 )
+                        {
+                               throw new IOException("TIFF-Predictor with " + bitsPerComponent + " bits per component not supported");
+                        }
+                        // for 8 bits per component it is the same algorithm as PRED SUB of PNG format
+                       for (int p = 0; p < rowlength; p++)
+                             {
+                                 int sub = actline[p] & 0xff;
+                                 int left = p - bpp >= 0 ? actline[p - bpp] & 0xff : 0;
+                                 actline[p] = (byte) (sub + left);
+                             }
+                             break;
+                    case 10:// PRED NONE
+                       // do nothing
+                      break;
+                    case 11:// PRED SUB
+                        for (int p = 0; p < rowlength; p++)
+                        {
+                            int sub = actline[p];
+                            int left = p - bpp >= 0 ? actline[p - bpp]: 0;
+                            actline[p] = (byte) (sub + left);
+                        }
+                        break;
+                    case 12:// PRED UP
+                        for (int p = 0; p < rowlength; p++)
+                        {
+                            int up = actline[p] & 0xff;
+                            int prior = lastline[p] & 0xff;
+                            actline[p] = (byte) ((up + prior) & 0xff);
+                        }
+                        break;
+                    case 13:// PRED AVG
+                        for (int p = 0; p < rowlength; p++)
+                        {
+                            int avg = actline[p] & 0xff;
+                            int left = p - bpp >= 0 ? actline[p - bpp] & 0xff: 0;
+                            int up = lastline[p] & 0xff;
+                            actline[p] = (byte) ((avg +  (int)Math.floor( (left + up)/2 ) ) & 0xff);
+                        }
+                        break;
+                    case 14:// PRED PAETH
+                        for (int p = 0; p < rowlength; p++)
+                        {
+                            int paeth = actline[p] & 0xff;
+                            int a = p - bpp >= 0 ? actline[p - bpp] & 0xff : 0;// left
+                            int b = lastline[p] & 0xff;// upper
+                            int c = p - bpp >= 0 ? lastline[p - bpp] & 0xff : 0;// upperleft
+                            int value = a + b - c;
+                            int absa = Math.abs(value - a);
+                            int absb = Math.abs(value - b);
+                            int absc = Math.abs(value - c);
+
+                            if (absa <= absb && absa <= absc)
+                            {
+                                actline[p] = (byte) ((paeth + a) & 0xff);
+                            }
+                            else if (absb <= absc)
+                            {
+                                actline[p] = (byte) ((paeth + b) & 0xff);
+                            }
+                            else
+                            {
+                                actline[p] = (byte) ((paeth + c) & 0xff);
+                            }
+                        }
+                        break;
+                    default:
+                        break;
+                }
+                lastline = (byte[])actline.clone();
+                baos.write(actline, 0, actline.length);
+            }
+        }
+        return baos.toByteArray();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encode(InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+    throws IOException
+    {
+        DeflaterOutputStream out = new DeflaterOutputStream(result);
+        int amountRead = 0;
+        int mayRead = rawData.available();
+        if (mayRead > 0) 
+        {
+            byte[] buffer = new byte[Math.min(mayRead,BUFFER_SIZE)];
+            while ((amountRead = rawData.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1)
+            {
+                out.write(buffer, 0, amountRead);
+            }
+        }
+        out.close();
+        result.flush();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/IdentityFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/IdentityFilter.java
new file mode 100644 (file)
index 0000000..4d5ef14
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * The IdentityFilter filter just passes the data through without any modifications.
+ * This is defined in section 7.6.5 of the PDF 1.7 spec and also stated in table
+ * 26.
+ * 
+ * @author adam.nichols
+ */
+public class IdentityFilter implements Filter
+{
+    private static final int BUFFER_SIZE = 1024;
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int amountRead = 0;
+        while( (amountRead = compressedData.read( buffer, 0, BUFFER_SIZE )) != -1 )
+        {
+            result.write( buffer, 0, amountRead );
+        }
+        result.flush();
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int amountRead = 0;
+        while( (amountRead = rawData.read( buffer, 0, BUFFER_SIZE )) != -1 )
+        {
+            result.write( buffer, 0, amountRead );
+        }
+        result.flush();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/JBIG2Filter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/JBIG2Filter.java
new file mode 100644 (file)
index 0000000..5132e37
--- /dev/null
@@ -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.pdfbox.filter;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * Modeled on the JBIG2Decode filter.
+ *
+ * thanks to Timo Böhme <timo.boehme@ontochem.com>
+ */
+
+public class JBIG2Filter implements Filter
+{
+
+    /** Log instance */
+    private static final Log log = LogFactory.getLog(JBIG2Filter.class);
+  
+    /**
+     * Decode JBIG2 data using Java ImageIO library.
+     * 
+     * {@inheritDoc}
+     * 
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        BufferedImage bi = ImageIO.read( compressedData );
+        if ( bi != null )
+        {
+            DataBuffer dBuf = bi.getData().getDataBuffer();
+            if ( dBuf.getDataType() == DataBuffer.TYPE_BYTE )
+            {
+                result.write( ( ( DataBufferByte ) dBuf ).getData() );
+            }
+            else
+            {
+                log.error( "Image data buffer not of type byte but type " + dBuf.getDataType() );
+            }
+        }
+    }
+
+     /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        System.err.println( "Warning: JBIG2.encode is not implemented yet, skipping this stream." );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/JPXFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/JPXFilter.java
new file mode 100644 (file)
index 0000000..5b7a1b7
--- /dev/null
@@ -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.pdfbox.filter;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is used for the JPXDecode filter.
+ *
+ * @author <a href="mailto:timo.boehme@ontochem.com">Timo Böhme</a>
+ * 
+ */
+public class JPXFilter implements Filter
+{
+
+       /** Log instance */
+    private static final Log log = LogFactory.getLog(JPXFilter.class);
+  
+    /**
+     * Decode JPEG2000 data using Java ImageIO library.
+     * 
+     * {@inheritDoc}
+     * 
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        BufferedImage bi = ImageIO.read( compressedData );
+        if ( bi != null )
+        {
+            DataBuffer dBuf = bi.getData().getDataBuffer();
+            if ( dBuf.getDataType() == DataBuffer.TYPE_BYTE )
+            {
+                result.write( ( ( DataBufferByte ) dBuf ).getData() );
+               }
+            else
+            {
+                       log.error( "Image data buffer not of type byte but type " + dBuf.getDataType() );
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        System.err.println( "Warning: JPXFilter.encode is not implemented yet, skipping this stream." );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWDictionary.java
new file mode 100644 (file)
index 0000000..1d72f44
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This is the used for the LZWDecode filter.  This represents the dictionary mappings
+ * between codes and their values.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+class LZWDictionary
+{
+    private Map codeToData = new HashMap();
+    private LZWNode root = new LZWNode();
+
+    private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    private long nextCode = 258;
+    private int codeSize = 9;
+
+    /**
+     * constructor.
+     */
+    public LZWDictionary()
+    {
+        for( long i=0; i<256; i++ )
+        {
+            LZWNode node = new LZWNode();
+            node.setCode( i );
+            root.setNode( (byte)i, node );
+            codeToData.put( new Long( i ), new byte[]{ (byte)i } );
+        }
+    }
+
+    /**
+     * This will get the value for the code.  It will return null if the code is not
+     * defined.
+     *
+     * @param code The key to the data.
+     *
+     * @return The data that is mapped to the code.
+     */
+    public byte[] getData( long code )
+    {
+        return (byte[])codeToData.get( new Long( code ) );
+    }
+
+    /**
+     * This will take a visit from a byte[].  This will create new code entries as
+     * necessary.
+     *
+     * @param data The byte to get a visit from.
+     *
+     * @throws IOException If there is an error visiting this data.
+     */
+    public void visit( byte[] data ) throws IOException
+    {
+        for( int i=0; i<data.length; i++ )
+        {
+            visit( data[i] );
+        }
+    }
+
+    /**
+     * This will take a visit from a byte.  This will create new code entries as
+     * necessary.
+     *
+     * @param data The byte to get a visit from.
+     *
+     * @throws IOException If there is an error visiting this data.
+     */
+    public void visit( byte data ) throws IOException
+    {
+        buffer.write( data );
+        byte[] curBuffer = buffer.toByteArray();
+        LZWNode previous = null;
+        LZWNode current = root;
+        boolean createNewCode = false;
+        for( int i=0; i<curBuffer.length && current != null; i++ )
+        {
+            previous = current;
+            current = current.getNode( curBuffer[i] );
+            if( current == null )
+            {
+                createNewCode = true;
+                current = new LZWNode();
+                previous.setNode( curBuffer[i], current );
+            }
+        }
+        if( createNewCode )
+        {
+            long code = nextCode++;
+            current.setCode( code );
+            codeToData.put( new Long( code ), curBuffer );
+
+            /**
+            System.out.print( "Adding " + code + "='" );
+            for( int i=0; i<curBuffer.length; i++ )
+            {
+                String hex = Integer.toHexString( ((curBuffer[i]+256)%256) );
+                if( hex.length() <=1 )
+                {
+                    hex = "0" + hex;
+                }
+                if( i != curBuffer.length -1 )
+                {
+                    hex += " ";
+                }
+                System.out.print( hex.toUpperCase() );
+            }
+            System.out.println( "'" );
+            **/
+            buffer.reset();
+            buffer.write( data );
+            resetCodeSize();
+        }
+    }
+
+    /**
+     * This will get the next code that will be created.
+     *
+     * @return The next code to be created.
+     */
+    public long getNextCode()
+    {
+        return nextCode;
+    }
+
+    /**
+     * This will get the size of the code in bits, 9, 10, or 11.
+     *
+     * @return The size of the code in bits.
+     */
+    public int getCodeSize()
+    {
+        return codeSize;
+    }
+
+    /**
+     * This will determine the code size.
+     */
+    private void resetCodeSize()
+    {
+        if( nextCode >= 2048 )
+        {
+            codeSize = 12;
+        }
+        else if( nextCode >= 1024 )
+        {
+            codeSize = 11;
+        }
+        else if( nextCode >= 512 )
+        {
+            codeSize = 10;
+        }
+        else
+        {
+            codeSize = 9;
+        }
+    }
+
+    /**
+     * This will crear the internal buffer that the dictionary uses.
+     */
+    public void clear()
+    {
+        buffer.reset();
+    }
+
+    /**
+     * This will folow the path to the data node.
+     *
+     * @param data The path to the node.
+     *
+     * @return The node that resides at that path.
+     */
+    public LZWNode getNode( byte[] data )
+    {
+        return root.getNode( data );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWFilter.java
new file mode 100644 (file)
index 0000000..7db59be
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.io.StreamCorruptedException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.io.NBitInputStream;
+import org.apache.pdfbox.io.NBitOutputStream;
+
+/**
+ * This is the used for the LZWDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ */
+public class LZWFilter implements Filter
+{
+
+    /**
+     * The LZW clear table code.
+     */
+    public static final long CLEAR_TABLE = 256;
+    /**
+     * The LZW end of data code.
+     */
+    public static final long EOD = 257;
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        //log.debug("decode( )");
+        NBitInputStream in = null;
+        in = new NBitInputStream( compressedData );
+        in.setBitsInChunk( 9 );
+        LZWDictionary dic = new LZWDictionary();
+        byte firstByte = 0;
+        long nextCommand = 0;
+        while( (nextCommand = in.read() ) != EOD )
+        {
+            // log.debug( "decode - nextCommand=" + nextCommand + ", bitsInChunk: " + in.getBitsInChunk());
+
+            if( nextCommand == CLEAR_TABLE )
+            {
+                in.setBitsInChunk( 9 );
+                dic = new LZWDictionary();
+            }
+            else
+            {
+                byte[] data = dic.getData( nextCommand );
+                if( data == null )
+                {
+                    dic.visit( firstByte );
+                    data = dic.getData( nextCommand );
+                    dic.clear();
+                }
+                if( data == null )
+                {
+                    throw new StreamCorruptedException( "Error: data is null" );
+                }
+                dic.visit(data);
+
+                //log.debug( "decode - dic.getNextCode(): " + dic.getNextCode());
+
+                if( dic.getNextCode() >= 2047 )
+                {
+                    in.setBitsInChunk( 12 );
+                }
+                else if( dic.getNextCode() >= 1023 )
+                {
+                    in.setBitsInChunk( 11 );
+                }
+                else if( dic.getNextCode() >= 511 )
+                {
+                    in.setBitsInChunk( 10 );
+                }
+                else
+                {
+                    in.setBitsInChunk( 9 );
+                }
+                /**
+                if( in.getBitsInChunk() != dic.getCodeSize() )
+                {
+                    in.unread( nextCommand );
+                    in.setBitsInChunk( dic.getCodeSize() );
+                    System.out.print( "Switching " + nextCommand + " to " );
+                    nextCommand = in.read();
+                    System.out.println( "" +  nextCommand );
+                    data = dic.getData( nextCommand );
+                }**/
+                firstByte = data[0];
+                result.write( data );
+            }
+        }
+        result.flush();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        //log.debug("encode( )");
+        PushbackInputStream input = new PushbackInputStream( rawData, 4096 );
+        LZWDictionary dic = new LZWDictionary();
+        NBitOutputStream out = new NBitOutputStream( result );
+        out.setBitsInChunk( 9 ); //initially nine
+        out.write( CLEAR_TABLE );
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        int byteRead = 0;
+        for( int i=0; (byteRead = input.read()) != -1; i++ )
+        {
+            //log.debug( "byteRead = '" + (char)byteRead + "' (0x" + Integer.toHexString(byteRead) + "), i=" + i);
+            buffer.write( byteRead );
+            dic.visit( (byte)byteRead );
+            out.setBitsInChunk( dic.getCodeSize() );
+
+            //log.debug( "Getting node '" + new String( buffer.toByteArray() ) + "', buffer.size = " + buffer.size() );
+            LZWNode node = dic.getNode( buffer.toByteArray() );
+            int nextByte = input.read();
+            if( nextByte != -1 )
+            {
+                //log.debug( "nextByte = '" + (char)nextByte + "' (0x" + Integer.toHexString(nextByte) + ")");
+                LZWNode next = node.getNode( (byte)nextByte );
+                if( next == null )
+                {
+                    //log.debug("encode - No next node, writing node and resetting buffer (" +
+                    //          " node.getCode: " + node.getCode() + ")" +
+                    //          " bitsInChunk: " + out.getBitsInChunk() +
+                    //          ")");
+                    out.write( node.getCode() );
+                    buffer.reset();
+                }
+
+                input.unread( nextByte );
+            }
+            else
+            {
+                //log.debug("encode - EOF on lookahead: writing node, resetting buffer, and terminating read loop (" +
+                //          " node.getCode: " + node.getCode() + ")" +
+                //          " bitsInChunk: " + out.getBitsInChunk() +
+                //          ")");
+                out.write( node.getCode() );
+                buffer.reset();
+                break;
+            }
+
+            if( dic.getNextCode() == 4096 )
+            {
+                //log.debug("encode - Clearing dictionary and unreading pending buffer data (" +
+                //          " bitsInChunk: " + out.getBitsInChunk() +
+                //          ")");
+                out.write( CLEAR_TABLE );
+                dic = new LZWDictionary();
+                input.unread( buffer.toByteArray() );
+                buffer.reset();
+            }
+        }
+
+        // Fix the code size based on the fact that we are writing the EOD
+        //
+        if( dic.getNextCode() >= 2047 )
+        {
+            out.setBitsInChunk( 12 );
+        }
+        else if( dic.getNextCode() >= 1023 )
+        {
+            out.setBitsInChunk( 11 );
+        }
+        else if( dic.getNextCode() >= 511 )
+        {
+            out.setBitsInChunk( 10 );
+        }
+        else
+        {
+            out.setBitsInChunk( 9 );
+        }
+
+        //log.debug("encode - Writing EOD (" +
+        //          " bitsInChunk: " + out.getBitsInChunk() +
+        //          ")");
+        out.write( EOD );
+        out.close();
+        result.flush();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/LZWNode.java
new file mode 100644 (file)
index 0000000..defad95
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.filter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This is the used for the LZWDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+class LZWNode
+{
+    private long code;
+    private Map subNodes = new HashMap();
+
+    /**
+     * This will get the number of children.
+     *
+     * @return The number of children.
+     */
+    public int childCount()
+    {
+        return subNodes.size();
+    }
+
+    /**
+     * This will set the node for a particular byte.
+     *
+     * @param b The byte for that node.
+     * @param node The node to add.
+     */
+    public void setNode( byte b, LZWNode node )
+    {
+        subNodes.put( new Byte( b ), node );
+    }
+
+    /**
+     * This will get the node that is a direct sub node of this node.
+     *
+     * @param data The byte code to the node.
+     *
+     * @return The node at that value if it exists.
+     */
+    public LZWNode getNode( byte data )
+    {
+        return (LZWNode)subNodes.get( new Byte( data ) );
+    }
+
+
+    /**
+     * This will traverse the tree until it gets to the sub node.
+     * This will return null if the node does not exist.
+     *
+     * @param data The path to the node.
+     *
+     * @return The node that resides at the data path.
+     */
+    public LZWNode getNode( byte[] data )
+    {
+        LZWNode current = this;
+        for( int i=0; i<data.length && current != null; i++ )
+        {
+            current = current.getNode( data[i] );
+        }
+        return current;
+    }
+
+    /** Getter for property code.
+     * @return Value of property code.
+     */
+    public long getCode()
+    {
+        return code;
+    }
+
+    /** Setter for property code.
+     * @param codeValue New value of property code.
+     */
+    public void setCode(long codeValue)
+    {
+        code = codeValue;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/RunLengthDecodeFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/RunLengthDecodeFilter.java
new file mode 100644 (file)
index 0000000..3b7d80c
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is a filter for the RunLength Decoder.
+ *
+ * From the PDF Reference
+ * <pre>
+ * The RunLengthDecode filter decodes data that has been encoded in a simple
+ * byte-oriented format based on run length. The encoded data is a sequence of
+ * runs, where each run consists of a length byte followed by 1 to 128 bytes of data. If
+ * the length byte is in the range 0 to 127, the following length + 1 (1 to 128) bytes
+ * are copied literally during decompression. If length is in the range 129 to 255, the
+ * following single byte is to be copied 257 ? length (2 to 128) times during decompression.
+ * A length value of 128 denotes EOD.
+ *
+ * The compression achieved by run-length encoding depends on the input data. In
+ * the best case (all zeros), a compression of approximately 64:1 is achieved for long
+ * files. The worst case (the hexadecimal sequence 00 alternating with FF) results in
+ * an expansion of 127:128.
+ * </pre>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class RunLengthDecodeFilter implements Filter
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(RunLengthDecodeFilter.class);
+
+    private static final int RUN_LENGTH_EOD = 128;
+
+    /**
+     * Constructor.
+     */
+    public RunLengthDecodeFilter()
+    {
+        //default constructor
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        int dupAmount = -1;
+        byte[] buffer = new byte[128];
+        while( (dupAmount = compressedData.read()) != -1 && dupAmount != RUN_LENGTH_EOD )
+        {
+            if( dupAmount <= 127 )
+            {
+                int amountToCopy = dupAmount+1;
+                int compressedRead = 0;
+                while( amountToCopy > 0 )
+                {
+                    compressedRead = compressedData.read( buffer, 0, amountToCopy );
+                    result.write( buffer, 0, compressedRead );
+                    amountToCopy -= compressedRead;
+                }
+            }
+            else
+            {
+                int dupByte = compressedData.read();
+                for( int i=0; i<257-dupAmount; i++ )
+                {
+                    result.write( dupByte );
+                }
+            }
+        }
+    }
+
+     /**
+     * {@inheritDoc}
+     */
+    public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 
+        throws IOException
+    {
+        log.warn( "RunLengthDecodeFilter.encode is not implemented yet, skipping this stream." );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/TIFFFaxDecoder.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/TIFFFaxDecoder.java
new file mode 100644 (file)
index 0000000..dd7090d
--- /dev/null
@@ -0,0 +1,1446 @@
+/*
+ * 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.pdfbox.filter;
+
+class TIFFFaxDecoder {
+
+    private int bitPointer, bytePointer;
+    private byte[] data;
+    private int w, h;
+    private int fillOrder;
+
+    // Data structures needed to store changing elements for the previous
+    // and the current scanline
+    private int changingElemSize = 0;
+    private int[] prevChangingElems;
+    private int[] currChangingElems;
+
+    // Element at which to start search in getNextChangingElement
+    private int lastChangingElement = 0;
+
+    private int compression = 2;
+
+    // Variables set by T4Options
+    private int uncompressedMode = 0;
+    private int fillBits = 0;
+    private int oneD;
+
+    static int[] table1 = {
+        0x00, // 0 bits are left in first byte - SHOULD NOT HAPPEN
+        0x01, // 1 bits are left in first byte
+        0x03, // 2 bits are left in first byte
+        0x07, // 3 bits are left in first byte
+        0x0f, // 4 bits are left in first byte
+        0x1f, // 5 bits are left in first byte
+        0x3f, // 6 bits are left in first byte
+        0x7f, // 7 bits are left in first byte
+        0xff  // 8 bits are left in first byte
+    };
+
+    static int[] table2 = {
+        0x00, // 0
+        0x80, // 1
+        0xc0, // 2
+        0xe0, // 3
+        0xf0, // 4
+        0xf8, // 5
+        0xfc, // 6
+        0xfe, // 7
+        0xff  // 8
+    };
+
+    // Table to be used when fillOrder = 2, for flipping bytes.
+    static byte[] flipTable = {
+         0,  -128,    64,   -64,    32,   -96,    96,   -32,
+        16,  -112,    80,   -48,    48,   -80,   112,   -16,
+         8,  -120,    72,   -56,    40,   -88,   104,   -24,
+        24,  -104,    88,   -40,    56,   -72,   120,    -8,
+         4,  -124,    68,   -60,    36,   -92,   100,   -28,
+        20,  -108,    84,   -44,    52,   -76,   116,   -12,
+        12,  -116,    76,   -52,    44,   -84,   108,   -20,
+        28,  -100,    92,   -36,    60,   -68,   124,    -4,
+         2,  -126,    66,   -62,    34,   -94,    98,   -30,
+        18,  -110,    82,   -46,    50,   -78,   114,   -14,
+        10,  -118,    74,   -54,    42,   -86,   106,   -22,
+        26,  -102,    90,   -38,    58,   -70,   122,    -6,
+         6,  -122,    70,   -58,    38,   -90,   102,   -26,
+        22,  -106,    86,   -42,    54,   -74,   118,   -10,
+        14,  -114,    78,   -50,    46,   -82,   110,   -18,
+        30,   -98,    94,   -34,    62,   -66,   126,    -2,
+         1,  -127,    65,   -63,    33,   -95,    97,   -31,
+        17,  -111,    81,   -47,    49,   -79,   113,   -15,
+         9,  -119,    73,   -55,    41,   -87,   105,   -23,
+        25,  -103,    89,   -39,    57,   -71,   121,    -7,
+         5,  -123,    69,   -59,    37,   -91,   101,   -27,
+        21,  -107,    85,   -43,    53,   -75,   117,   -11,
+        13,  -115,    77,   -51,    45,   -83,   109,   -19,
+        29,   -99,    93,   -35,    61,   -67,   125,    -3,
+         3,  -125,    67,   -61,    35,   -93,    99,   -29,
+        19,  -109,    83,   -45,    51,   -77,   115,   -13,
+        11,  -117,    75,   -53,    43,   -85,   107,   -21,
+        27,  -101,    91,   -37,    59,   -69,   123,    -5,
+         7,  -121,    71,   -57,    39,   -89,   103,   -25,
+        23,  -105,    87,   -41,    55,   -73,   119,    -9,
+        15,  -113,    79,   -49,    47,   -81,   111,   -17,
+        31,   -97,    95,   -33,    63,   -65,   127,    -1,
+    };
+
+    // The main 10 bit white runs lookup table
+    static short[] white = {
+        // 0 - 7
+        6430,   6400,   6400,   6400,   3225,   3225,   3225,   3225,
+        // 8 - 15
+        944,    944,    944,    944,    976,    976,    976,    976,
+        // 16 - 23
+        1456,   1456,   1456,   1456,   1488,   1488,   1488,   1488,
+        // 24 - 31
+        718,    718,    718,    718,    718,    718,    718,    718,
+        // 32 - 39
+        750,    750,    750,    750,    750,    750,    750,    750,
+        // 40 - 47
+        1520,   1520,   1520,   1520,   1552,   1552,   1552,   1552,
+        // 48 - 55
+        428,    428,    428,    428,    428,    428,    428,    428,
+        // 56 - 63
+        428,    428,    428,    428,    428,    428,    428,    428,
+        // 64 - 71
+        654,    654,    654,    654,    654,    654,    654,    654,
+        // 72 - 79
+        1072,   1072,   1072,   1072,   1104,   1104,   1104,   1104,
+        // 80 - 87
+        1136,   1136,   1136,   1136,   1168,   1168,   1168,   1168,
+        // 88 - 95
+        1200,   1200,   1200,   1200,   1232,   1232,   1232,   1232,
+        // 96 - 103
+        622,    622,    622,    622,    622,    622,    622,    622,
+        // 104 - 111
+        1008,   1008,   1008,   1008,   1040,   1040,   1040,   1040,
+        // 112 - 119
+        44,     44,     44,     44,     44,     44,     44,     44,
+        // 120 - 127
+        44,     44,     44,     44,     44,     44,     44,     44,
+        // 128 - 135
+        396,    396,    396,    396,    396,    396,    396,    396,
+        // 136 - 143
+        396,    396,    396,    396,    396,    396,    396,    396,
+        // 144 - 151
+        1712,   1712,   1712,   1712,   1744,   1744,   1744,   1744,
+        // 152 - 159
+        846,    846,    846,    846,    846,    846,    846,    846,
+        // 160 - 167
+        1264,   1264,   1264,   1264,   1296,   1296,   1296,   1296,
+        // 168 - 175
+        1328,   1328,   1328,   1328,   1360,   1360,   1360,   1360,
+        // 176 - 183
+        1392,   1392,   1392,   1392,   1424,   1424,   1424,   1424,
+        // 184 - 191
+        686,    686,    686,    686,    686,    686,    686,    686,
+        // 192 - 199
+        910,    910,    910,    910,    910,    910,    910,    910,
+        // 200 - 207
+        1968,   1968,   1968,   1968,   2000,   2000,   2000,   2000,
+        // 208 - 215
+        2032,   2032,   2032,   2032,     16,     16,     16,     16,
+        // 216 - 223
+        10257,  10257,  10257,  10257,  12305,  12305,  12305,  12305,
+        // 224 - 231
+        330,    330,    330,    330,    330,    330,    330,    330,
+        // 232 - 239
+        330,    330,    330,    330,    330,    330,    330,    330,
+        // 240 - 247
+        330,    330,    330,    330,    330,    330,    330,    330,
+        // 248 - 255
+        330,    330,    330,    330,    330,    330,    330,    330,
+        // 256 - 263
+        362,    362,    362,    362,    362,    362,    362,    362,
+        // 264 - 271
+        362,    362,    362,    362,    362,    362,    362,    362,
+        // 272 - 279
+        362,    362,    362,    362,    362,    362,    362,    362,
+        // 280 - 287
+        362,    362,    362,    362,    362,    362,    362,    362,
+        // 288 - 295
+        878,    878,    878,    878,    878,    878,    878,    878,
+        // 296 - 303
+        1904,   1904,   1904,   1904,   1936,   1936,   1936,   1936,
+        // 304 - 311
+        -18413, -18413, -16365, -16365, -14317, -14317, -10221, -10221,
+        // 312 - 319
+        590,    590,    590,    590,    590,    590,    590,    590,
+        // 320 - 327
+        782,    782,    782,    782,    782,    782,    782,    782,
+        // 328 - 335
+        1584,   1584,   1584,   1584,   1616,   1616,   1616,   1616,
+        // 336 - 343
+        1648,   1648,   1648,   1648,   1680,   1680,   1680,   1680,
+        // 344 - 351
+        814,    814,    814,    814,    814,    814,    814,    814,
+        // 352 - 359
+        1776,   1776,   1776,   1776,   1808,   1808,   1808,   1808,
+        // 360 - 367
+        1840,   1840,   1840,   1840,   1872,   1872,   1872,   1872,
+        // 368 - 375
+        6157,   6157,   6157,   6157,   6157,   6157,   6157,   6157,
+        // 376 - 383
+        6157,   6157,   6157,   6157,   6157,   6157,   6157,   6157,
+        // 384 - 391
+        -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275,
+        // 392 - 399
+        -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275,
+        // 400 - 407
+        14353,  14353,  14353,  14353,  16401,  16401,  16401,  16401,
+        // 408 - 415
+        22547,  22547,  24595,  24595,  20497,  20497,  20497,  20497,
+        // 416 - 423
+        18449,  18449,  18449,  18449,  26643,  26643,  28691,  28691,
+        // 424 - 431
+        30739,  30739, -32749, -32749, -30701, -30701, -28653, -28653,
+        // 432 - 439
+        -26605, -26605, -24557, -24557, -22509, -22509, -20461, -20461,
+        // 440 - 447
+        8207,   8207,   8207,   8207,   8207,   8207,   8207,   8207,
+        // 448 - 455
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 456 - 463
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 464 - 471
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 472 - 479
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 480 - 487
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 488 - 495
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 496 - 503
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 504 - 511
+        72,     72,     72,     72,     72,     72,     72,     72,
+        // 512 - 519
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 520 - 527
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 528 - 535
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 536 - 543
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 544 - 551
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 552 - 559
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 560 - 567
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 568 - 575
+        104,    104,    104,    104,    104,    104,    104,    104,
+        // 576 - 583
+        4107,   4107,   4107,   4107,   4107,   4107,   4107,   4107,
+        // 584 - 591
+        4107,   4107,   4107,   4107,   4107,   4107,   4107,   4107,
+        // 592 - 599
+        4107,   4107,   4107,   4107,   4107,   4107,   4107,   4107,
+        // 600 - 607
+        4107,   4107,   4107,   4107,   4107,   4107,   4107,   4107,
+        // 608 - 615
+        266,    266,    266,    266,    266,    266,    266,    266,
+        // 616 - 623
+        266,    266,    266,    266,    266,    266,    266,    266,
+        // 624 - 631
+        266,    266,    266,    266,    266,    266,    266,    266,
+        // 632 - 639
+        266,    266,    266,    266,    266,    266,    266,    266,
+        // 640 - 647
+        298,    298,    298,    298,    298,    298,    298,    298,
+        // 648 - 655
+        298,    298,    298,    298,    298,    298,    298,    298,
+        // 656 - 663
+        298,    298,    298,    298,    298,    298,    298,    298,
+        // 664 - 671
+        298,    298,    298,    298,    298,    298,    298,    298,
+        // 672 - 679
+        524,    524,    524,    524,    524,    524,    524,    524,
+        // 680 - 687
+        524,    524,    524,    524,    524,    524,    524,    524,
+        // 688 - 695
+        556,    556,    556,    556,    556,    556,    556,    556,
+        // 696 - 703
+        556,    556,    556,    556,    556,    556,    556,    556,
+        // 704 - 711
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 712 - 719
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 720 - 727
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 728 - 735
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 736 - 743
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 744 - 751
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 752 - 759
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 760 - 767
+        136,    136,    136,    136,    136,    136,    136,    136,
+        // 768 - 775
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 776 - 783
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 784 - 791
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 792 - 799
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 800 - 807
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 808 - 815
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 816 - 823
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 824 - 831
+        168,    168,    168,    168,    168,    168,    168,    168,
+        // 832 - 839
+        460,    460,    460,    460,    460,    460,    460,    460,
+        // 840 - 847
+        460,    460,    460,    460,    460,    460,    460,    460,
+        // 848 - 855
+        492,    492,    492,    492,    492,    492,    492,    492,
+        // 856 - 863
+        492,    492,    492,    492,    492,    492,    492,    492,
+        // 864 - 871
+        2059,   2059,   2059,   2059,   2059,   2059,   2059,   2059,
+        // 872 - 879
+        2059,   2059,   2059,   2059,   2059,   2059,   2059,   2059,
+        // 880 - 887
+        2059,   2059,   2059,   2059,   2059,   2059,   2059,   2059,
+        // 888 - 895
+        2059,   2059,   2059,   2059,   2059,   2059,   2059,   2059,
+        // 896 - 903
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 904 - 911
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 912 - 919
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 920 - 927
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 928 - 935
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 936 - 943
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 944 - 951
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 952 - 959
+        200,    200,    200,    200,    200,    200,    200,    200,
+        // 960 - 967
+        232,    232,    232,    232,    232,    232,    232,    232,
+        // 968 - 975
+        232,    232,    232,    232,    232,    232,    232,    232,
+        // 976 - 983
+        232,    232,    232,    232,    232,    232,    232,    232,
+        // 984 - 991
+        232,    232,    232,    232,    232,    232,    232,    232,
+        // 992 - 999
+        232,    232,    232,    232,    232,    232,    232,    232,
+        // 1000 - 1007
+        232,    232,    232,    232,    232,    232,    232,    232,
+        // 1008 - 1015
+        232,    232,    232,    232,    232,    232,    232,    232,
+        // 1016 - 1023
+        232,    232,    232,    232,    232,    232,    232,    232,
+    };
+
+    // Additional make up codes for both White and Black runs
+    static short[] additionalMakeup = {
+        28679,  28679,  31752,  (short)32777,
+        (short)33801,  (short)34825,  (short)35849,  (short)36873,
+        (short)29703,  (short)29703,  (short)30727,  (short)30727,
+        (short)37897,  (short)38921,  (short)39945,  (short)40969
+    };
+
+    // Initial black run look up table, uses the first 4 bits of a code
+    static short[] initBlack = {
+        // 0 - 7
+        3226,  6412,    200,    168,    38,     38,    134,    134,
+        // 8 - 15
+        100,    100,    100,    100,    68,     68,     68,     68
+    };
+
+    //
+    static short[] twoBitBlack = {292, 260, 226, 226};   // 0 - 3
+
+    // Main black run table, using the last 9 bits of possible 13 bit code
+    static short[] black = {
+        // 0 - 7
+        62,     62,     30,     30,     0,      0,      0,      0,
+        // 8 - 15
+        0,      0,      0,      0,      0,      0,      0,      0,
+        // 16 - 23
+        0,      0,      0,      0,      0,      0,      0,      0,
+        // 24 - 31
+        0,      0,      0,      0,      0,      0,      0,      0,
+        // 32 - 39
+        3225,   3225,   3225,   3225,   3225,   3225,   3225,   3225,
+        // 40 - 47
+        3225,   3225,   3225,   3225,   3225,   3225,   3225,   3225,
+        // 48 - 55
+        3225,   3225,   3225,   3225,   3225,   3225,   3225,   3225,
+        // 56 - 63
+        3225,   3225,   3225,   3225,   3225,   3225,   3225,   3225,
+        // 64 - 71
+        588,    588,    588,    588,    588,    588,    588,    588,
+        // 72 - 79
+        1680,   1680,  20499,  22547,  24595,  26643,   1776,   1776,
+        // 80 - 87
+        1808,   1808, -24557, -22509, -20461, -18413,   1904,   1904,
+        // 88 - 95
+        1936,   1936, -16365, -14317,    782,    782,    782,    782,
+        // 96 - 103
+        814,    814,    814,    814, -12269, -10221,  10257,  10257,
+        // 104 - 111
+        12305,  12305,  14353,  14353,  16403,  18451,   1712,   1712,
+        // 112 - 119
+        1744,   1744,  28691,  30739, -32749, -30701, -28653, -26605,
+        // 120 - 127
+        2061,   2061,   2061,   2061,   2061,   2061,   2061,   2061,
+        // 128 - 135
+        424,    424,    424,    424,    424,    424,    424,    424,
+        // 136 - 143
+        424,    424,    424,    424,    424,    424,    424,    424,
+        // 144 - 151
+        424,    424,    424,    424,    424,    424,    424,    424,
+        // 152 - 159
+        424,    424,    424,    424,    424,    424,    424,    424,
+        // 160 - 167
+        750,    750,    750,    750,   1616,   1616,   1648,   1648,
+        // 168 - 175
+        1424,   1424,   1456,   1456,   1488,   1488,   1520,   1520,
+        // 176 - 183
+        1840,   1840,   1872,   1872,   1968,   1968,   8209,   8209,
+        // 184 - 191
+        524,    524,    524,    524,    524,    524,    524,    524,
+        // 192 - 199
+        556,    556,    556,    556,    556,    556,    556,    556,
+        // 200 - 207
+        1552,   1552,   1584,   1584,   2000,   2000,   2032,   2032,
+        // 208 - 215
+        976,    976,   1008,   1008,   1040,   1040,   1072,   1072,
+        // 216 - 223
+        1296,   1296,   1328,   1328,    718,    718,    718,    718,
+        // 224 - 231
+        456,    456,    456,    456,    456,    456,    456,    456,
+        // 232 - 239
+        456,    456,    456,    456,    456,    456,    456,    456,
+        // 240 - 247
+        456,    456,    456,    456,    456,    456,    456,    456,
+        // 248 - 255
+        456,    456,    456,    456,    456,    456,    456,    456,
+        // 256 - 263
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 264 - 271
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 272 - 279
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 280 - 287
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 288 - 295
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 296 - 303
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 304 - 311
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 312 - 319
+        326,    326,    326,    326,    326,    326,    326,    326,
+        // 320 - 327
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 328 - 335
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 336 - 343
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 344 - 351
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 352 - 359
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 360 - 367
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 368 - 375
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 376 - 383
+        358,    358,    358,    358,    358,    358,    358,    358,
+        // 384 - 391
+        490,    490,    490,    490,    490,    490,    490,    490,
+        // 392 - 399
+        490,    490,    490,    490,    490,    490,    490,    490,
+        // 400 - 407
+        4113,   4113,   6161,   6161,    848,    848,    880,    880,
+        // 408 - 415
+        912,    912,    944,    944,    622,    622,    622,    622,
+        // 416 - 423
+        654,    654,    654,    654,   1104,   1104,   1136,   1136,
+        // 424 - 431
+        1168,   1168,   1200,   1200,   1232,   1232,   1264,   1264,
+        // 432 - 439
+        686,    686,    686,    686,   1360,   1360,   1392,   1392,
+        // 440 - 447
+        12,     12,     12,     12,     12,     12,     12,     12,
+        // 448 - 455
+        390,    390,    390,    390,    390,    390,    390,    390,
+        // 456 - 463
+        390,    390,    390,    390,    390,    390,    390,    390,
+        // 464 - 471
+        390,    390,    390,    390,    390,    390,    390,    390,
+        // 472 - 479
+        390,    390,    390,    390,    390,    390,    390,    390,
+        // 480 - 487
+        390,    390,    390,    390,    390,    390,    390,    390,
+        // 488 - 495
+        390,    390,    390,    390,    390,    390,    390,    390,
+        // 496 - 503
+        390,    390,    390,    390,    390,    390,    390,    390,
+        // 504 - 511
+        390,    390,    390,    390,    390,    390,    390,    390,
+    };
+
+    static byte[] twoDCodes = {
+        // 0 - 7
+        80,     88,     23,     71,     30,     30,     62,     62,
+        // 8 - 15
+        4,      4,      4,      4,      4,      4,      4,      4,
+        // 16 - 23
+        11,     11,     11,     11,     11,     11,     11,     11,
+        // 24 - 31
+        11,     11,     11,     11,     11,     11,     11,     11,
+        // 32 - 39
+        35,     35,     35,     35,     35,     35,     35,     35,
+        // 40 - 47
+        35,     35,     35,     35,     35,     35,     35,     35,
+        // 48 - 55
+        51,     51,     51,     51,     51,     51,     51,     51,
+        // 56 - 63
+        51,     51,     51,     51,     51,     51,     51,     51,
+        // 64 - 71
+        41,     41,     41,     41,     41,     41,     41,     41,
+        // 72 - 79
+        41,     41,     41,     41,     41,     41,     41,     41,
+        // 80 - 87
+        41,     41,     41,     41,     41,     41,     41,     41,
+        // 88 - 95
+        41,     41,     41,     41,     41,     41,     41,     41,
+        // 96 - 103
+        41,     41,     41,     41,     41,     41,     41,     41,
+        // 104 - 111
+        41,     41,     41,     41,     41,     41,     41,     41,
+        // 112 - 119
+        41,     41,     41,     41,     41,     41,     41,     41,
+        // 120 - 127
+        41,     41,     41,     41,     41,     41,     41,     41,
+    };
+
+    /**
+     * @param fillOrder   The fill order of the compressed data bytes.
+     * @param w The width of the image in pixels
+     * @param h The height of the image in pixels
+     */
+    public TIFFFaxDecoder(int fillOrder, int w, int h) {
+        this.fillOrder = fillOrder;
+        this.w = w;
+        this.h = h;
+
+        this.bitPointer = 0;
+        this.bytePointer = 0;
+        this.prevChangingElems = new int[w];
+        this.currChangingElems = new int[w];
+    }
+
+    // One-dimensional decoding methods
+
+    public void decode1D(byte[] buffer, byte[] compData,
+                         int startX, int height) {
+        this.data = compData;
+
+        int lineOffset = 0;
+        int scanlineStride = (w + 7)/8;
+
+        bitPointer = 0;
+        bytePointer = 0;
+
+        for (int i = 0; i < height; i++) {
+            decodeNextScanline(buffer, lineOffset, startX);
+            lineOffset += scanlineStride;
+        }
+    }
+
+    public void decodeNextScanline(byte[] buffer,
+                                   int lineOffset, int bitOffset) {
+        int bits = 0, code = 0, isT = 0;
+        int current, entry, twoBits;
+        boolean isWhite = true;
+
+        // Initialize starting of the changing elements array
+        changingElemSize = 0;
+
+        // While scanline not complete
+        while (bitOffset < w) {
+            while (isWhite) {
+                // White run
+                current = nextNBits(10);
+                entry = white[current];
+
+                // Get the 3 fields from the entry
+                isT = entry & 0x0001;
+                bits = (entry >>> 1) & 0x0f;
+
+                if (bits == 12) {          // Additional Make up code
+                    // Get the next 2 bits
+                    twoBits = nextLesserThan8Bits(2);
+                    // Consolidate the 2 new bits and last 2 bits into 4 bits
+                    current = ((current << 2) & 0x000c) | twoBits;
+                    entry = additionalMakeup[current];
+                    bits = (entry >>> 1) & 0x07;     // 3 bits 0000 0111
+                    code  = (entry >>> 4) & 0x0fff;  // 12 bits
+                    bitOffset += code; // Skip white run
+
+                    updatePointer(4 - bits);
+                } else if (bits == 0) {     // ERROR
+                    throw new Error("TIFFFaxDecoder0");
+                } else if (bits == 15) {    // EOL
+                    throw new Error("TIFFFaxDecoder1");
+                } else {
+                    // 11 bits - 0000 0111 1111 1111 = 0x07ff
+                    code = (entry >>> 5) & 0x07ff;
+                    bitOffset += code;
+
+                    updatePointer(10 - bits);
+                    if (isT == 0) {
+                        isWhite = false;
+                        currChangingElems[changingElemSize++] = bitOffset;
+                    }
+                }
+            }
+
+            // Check whether this run completed one width, if so
+            // advance to next byte boundary for compression = 2.
+            if (bitOffset == w) {
+                if (compression == 2) {
+                    advancePointer();
+                }
+                break;
+            }
+
+            while (!isWhite) {
+                // Black run
+                current = nextLesserThan8Bits(4);
+                entry = initBlack[current];
+
+                // Get the 3 fields from the entry
+                isT = entry & 0x0001;
+                bits = (entry >>> 1) & 0x000f;
+                code = (entry >>> 5) & 0x07ff;
+
+                if (code == 100) {
+                    current = nextNBits(9);
+                    entry = black[current];
+
+                    // Get the 3 fields from the entry
+                    isT = entry & 0x0001;
+                    bits = (entry >>> 1) & 0x000f;
+                    code = (entry >>> 5) & 0x07ff;
+
+                    if (bits == 12) {
+                        // Additional makeup codes
+                        updatePointer(5);
+                        current = nextLesserThan8Bits(4);
+                        entry = additionalMakeup[current];
+                        bits = (entry >>> 1) & 0x07;     // 3 bits 0000 0111
+                        code  = (entry >>> 4) & 0x0fff;  // 12 bits
+
+                        setToBlack(buffer, lineOffset, bitOffset, code);
+                        bitOffset += code;
+
+                        updatePointer(4 - bits);
+                    } else if (bits == 15) {
+                        // EOL code
+                        throw new Error("TIFFFaxDecoder2");
+                    } else {
+                        setToBlack(buffer, lineOffset, bitOffset, code);
+                        bitOffset += code;
+
+                        updatePointer(9 - bits);
+                        if (isT == 0) {
+                            isWhite = true;
+                            currChangingElems[changingElemSize++] = bitOffset;
+                        }
+                    }
+                } else if (code == 200) {
+                    // Is a Terminating code
+                    current = nextLesserThan8Bits(2);
+                    entry = twoBitBlack[current];
+                    code = (entry >>> 5) & 0x07ff;
+                    bits = (entry >>> 1) & 0x0f;
+
+                    setToBlack(buffer, lineOffset, bitOffset, code);
+                    bitOffset += code;
+
+                    updatePointer(2 - bits);
+                    isWhite = true;
+                    currChangingElems[changingElemSize++] = bitOffset;
+                } else {
+                    // Is a Terminating code
+                    setToBlack(buffer, lineOffset, bitOffset, code);
+                    bitOffset += code;
+
+                    updatePointer(4 - bits);
+                    isWhite = true;
+                    currChangingElems[changingElemSize++] = bitOffset;
+                }
+            }
+
+            // Check whether this run completed one width
+            if (bitOffset == w) {
+                if (compression == 2) {
+                    advancePointer();
+                }
+                break;
+            }
+        }
+
+        currChangingElems[changingElemSize++] = bitOffset;
+    }
+
+    // Two-dimensional decoding methods
+
+    public void decode2D(byte[] buffer,
+                         byte[] compData,
+                         int startX,
+                         int height,
+                         long tiffT4Options) {
+        this.data = compData;
+        compression = 3;
+
+        bitPointer = 0;
+        bytePointer = 0;
+
+        int scanlineStride = (w + 7)/8;
+
+        int a0, a1, b1, b2;
+        int[] b = new int[2];
+        int entry, code, bits;
+        boolean isWhite;
+        int currIndex = 0;
+        int[] temp;
+
+        // fillBits - dealt with this in readEOL
+        // 1D/2D encoding - dealt with this in readEOL
+
+        // uncompressedMode - haven't dealt with this yet.
+
+
+        oneD = (int)(tiffT4Options & 0x01);
+        uncompressedMode = (int)((tiffT4Options & 0x02) >> 1);
+        fillBits = (int)((tiffT4Options & 0x04) >> 2);
+
+        // The data must start with an EOL code
+        if (readEOL() != 1) {
+            throw new Error("TIFFFaxDecoder3");
+        }
+
+        int lineOffset = 0;
+        int bitOffset;
+
+        // Then the 1D encoded scanline data will occur, changing elements
+        // array gets set.
+        decodeNextScanline(buffer, lineOffset, startX);
+        lineOffset += scanlineStride;
+
+        for (int lines = 1; lines < height; lines++) {
+
+            // Every line must begin with an EOL followed by a bit which
+            // indicates whether the following scanline is 1D or 2D encoded.
+            if (readEOL() == 0) {
+                // 2D encoded scanline follows
+
+                // Initialize previous scanlines changing elements, and
+                // initialize current scanline's changing elements array
+                temp = prevChangingElems;
+                prevChangingElems = currChangingElems;
+                currChangingElems = temp;
+                currIndex = 0;
+
+                // a0 has to be set just before the start of this scanline.
+                a0 = -1;
+                isWhite = true;
+                bitOffset = startX;
+
+                lastChangingElement = 0;
+
+                while (bitOffset < w) {
+                    // Get the next changing element
+                    getNextChangingElement(a0, isWhite, b);
+
+                    b1 = b[0];
+                    b2 = b[1];
+
+                    // Get the next seven bits
+                    entry = nextLesserThan8Bits(7);
+
+                    // Run these through the 2DCodes table
+                    entry = (int)(twoDCodes[entry] & 0xff);
+
+                    // Get the code and the number of bits used up
+                    code = (entry & 0x78) >>> 3;
+                    bits = entry & 0x07;
+
+                    if (code == 0) {
+                        if (!isWhite) {
+                            setToBlack(buffer, lineOffset, bitOffset,
+                                       b2 - bitOffset);
+                        }
+                        bitOffset = a0 = b2;
+
+                        // Set pointer to consume the correct number of bits.
+                        updatePointer(7 - bits);
+                    } else if (code == 1) {
+                        // Horizontal
+                        updatePointer(7 - bits);
+
+                        // identify the next 2 codes.
+                        int number;
+                        if (isWhite) {
+                            number = decodeWhiteCodeWord();
+                            bitOffset += number;
+                            currChangingElems[currIndex++] = bitOffset;
+
+                            number = decodeBlackCodeWord();
+                            setToBlack(buffer, lineOffset, bitOffset, number);
+                            bitOffset += number;
+                            currChangingElems[currIndex++] = bitOffset;
+                        } else {
+                            number = decodeBlackCodeWord();
+                            setToBlack(buffer, lineOffset, bitOffset, number);
+                            bitOffset += number;
+                            currChangingElems[currIndex++] = bitOffset;
+
+                            number = decodeWhiteCodeWord();
+                            bitOffset += number;
+                            currChangingElems[currIndex++] = bitOffset;
+                        }
+
+                        a0 = bitOffset;
+                    } else if (code <= 8) {
+                        // Vertical
+                        a1 = b1 + (code - 5);
+
+                        currChangingElems[currIndex++] = a1;
+
+                        // We write the current color till a1 - 1 pos,
+                        // since a1 is where the next color starts
+                        if (!isWhite) {
+                            setToBlack(buffer, lineOffset, bitOffset,
+                                       a1 - bitOffset);
+                        }
+                        bitOffset = a0 = a1;
+                        isWhite = !isWhite;
+
+                        updatePointer(7 - bits);
+                    } else {
+                        throw new Error("TIFFFaxDecoder4");
+                    }
+                }
+
+                // Add the changing element beyond the current scanline for the
+                // other color too
+                currChangingElems[currIndex++] = bitOffset;
+                changingElemSize = currIndex;
+            } else {
+                // 1D encoded scanline follows
+                decodeNextScanline(buffer, lineOffset, startX);
+            }
+
+            lineOffset += scanlineStride;
+        }
+    }
+
+    public synchronized void decodeT6(byte[] buffer,
+                                      byte[] compData,
+                                      int startX,
+                                      int height,
+                                      long tiffT6Options) {
+        this.data = compData;
+        compression = 4;
+
+        bitPointer = 0;
+        bytePointer = 0;
+
+        int scanlineStride = (w + 7)/8;
+
+        int a0, a1, b1, b2;
+        int entry, code, bits;
+        boolean isWhite;
+        int currIndex;
+        int[] temp;
+
+        // Return values from getNextChangingElement
+        int[] b = new int[2];
+
+        // uncompressedMode - have written some code for this, but this
+        // has not been tested due to lack of test images using this optional
+
+        uncompressedMode = (int)((tiffT6Options & 0x02) >> 1);
+
+        // Local cached reference
+        int[] cce = currChangingElems;
+
+        // Assume invisible preceding row of all white pixels and insert
+        // both black and white changing elements beyond the end of this
+        // imaginary scanline.
+        changingElemSize = 0;
+        cce[changingElemSize++] = w;
+        cce[changingElemSize++] = w;
+
+        int lineOffset = 0;
+        int bitOffset;
+
+        for (int lines = 0; lines < height; lines++) {
+            // a0 has to be set just before the start of the scanline.
+            a0 = -1;
+            isWhite = true;
+
+            // Assign the changing elements of the previous scanline to
+            // prevChangingElems and start putting this new scanline's
+            // changing elements into the currChangingElems.
+            temp = prevChangingElems;
+            prevChangingElems = currChangingElems;
+            cce = currChangingElems = temp;
+            currIndex = 0;
+
+            // Start decoding the scanline at startX in the raster
+            bitOffset = startX;
+
+            // Reset search start position for getNextChangingElement
+            lastChangingElement = 0;
+
+            // Till one whole scanline is decoded
+            while (bitOffset < w) {
+                // Get the next changing element
+                getNextChangingElement(a0, isWhite, b);
+                b1 = b[0];
+                b2 = b[1];
+
+                // Get the next seven bits
+                entry = nextLesserThan8Bits(7);
+                // Run these through the 2DCodes table
+                entry = (int)(twoDCodes[entry] & 0xff);
+
+                // Get the code and the number of bits used up
+                code = (entry & 0x78) >>> 3;
+                bits = entry & 0x07;
+
+                if (code == 0) { // Pass
+                    // We always assume WhiteIsZero format for fax.
+                    if (!isWhite) {
+                        setToBlack(buffer, lineOffset, bitOffset,
+                                   b2 - bitOffset);
+                    }
+                    bitOffset = a0 = b2;
+
+                    // Set pointer to only consume the correct number of bits.
+                    updatePointer(7 - bits);
+                } else if (code == 1) { // Horizontal
+                    // Set pointer to only consume the correct number of bits.
+                    updatePointer(7 - bits);
+
+                    // identify the next 2 alternating color codes.
+                    int number;
+                    if (isWhite) {
+                        // Following are white and black runs
+                        number = decodeWhiteCodeWord();
+                        bitOffset += number;
+                        cce[currIndex++] = bitOffset;
+
+                        number = decodeBlackCodeWord();
+                        setToBlack(buffer, lineOffset, bitOffset, number);
+                        bitOffset += number;
+                        cce[currIndex++] = bitOffset;
+                    } else {
+                        // First a black run and then a white run follows
+                        number = decodeBlackCodeWord();
+                        setToBlack(buffer, lineOffset, bitOffset, number);
+                        bitOffset += number;
+                        cce[currIndex++] = bitOffset;
+
+                        number = decodeWhiteCodeWord();
+                        bitOffset += number;
+                        cce[currIndex++] = bitOffset;
+                    }
+
+                    a0 = bitOffset;
+                } else if (code <= 8) { // Vertical
+                    a1 = b1 + (code - 5);
+                    cce[currIndex++] = a1;
+
+                    // We write the current color till a1 - 1 pos,
+                    // since a1 is where the next color starts
+                    if (!isWhite) {
+                        setToBlack(buffer, lineOffset, bitOffset,
+                                   a1 - bitOffset);
+                    }
+                    bitOffset = a0 = a1;
+                    isWhite = !isWhite;
+
+                    updatePointer(7 - bits);
+                } else if (code == 11) {
+                    if (nextLesserThan8Bits(3) != 7) {
+                        throw new Error("TIFFFaxDecoder5");
+                    }
+
+                    int zeros = 0;
+                    boolean exit = false;
+
+                    while (!exit) {
+                        while (nextLesserThan8Bits(1) != 1) {
+                            zeros++;
+                        }
+
+                        if (zeros > 5) {
+                            // Exit code
+
+                            // Zeros before exit code
+                            zeros = zeros - 6;
+
+                            if (!isWhite && (zeros > 0)) {
+                                cce[currIndex++] = bitOffset;
+                            }
+
+                            // Zeros before the exit code
+                            bitOffset += zeros;
+                            if (zeros > 0) {
+                                // Some zeros have been written
+                                isWhite = true;
+                            }
+
+                            // Read in the bit which specifies the color of
+                            // the following run
+                            if (nextLesserThan8Bits(1) == 0) {
+                                if (!isWhite) {
+                                    cce[currIndex++] = bitOffset;
+                                }
+                                isWhite = true;
+                            } else {
+                                if (isWhite) {
+                                    cce[currIndex++] = bitOffset;
+                                }
+                                isWhite = false;
+                            }
+
+                            exit = true;
+                        }
+
+                        if (zeros == 5) {
+                            if (!isWhite) {
+                                cce[currIndex++] = bitOffset;
+                            }
+                            bitOffset += zeros;
+
+                            // Last thing written was white
+                            isWhite = true;
+                        } else {
+                            bitOffset += zeros;
+
+                            cce[currIndex++] = bitOffset;
+                            setToBlack(buffer, lineOffset, bitOffset, 1);
+                            ++bitOffset;
+
+                            // Last thing written was black
+                            isWhite = false;
+                        }
+
+                    }
+                } else {
+                    throw new Error("TIFFFaxDecoder5");
+                }
+            }
+
+            // Add the changing element beyond the current scanline for the
+            // other color too
+            cce[currIndex++] = bitOffset;
+
+            // Number of changing elements in this scanline.
+            changingElemSize = currIndex;
+
+            lineOffset += scanlineStride;
+        }
+    }
+
+    private void setToBlack(byte[] buffer,
+                            int lineOffset, int bitOffset,
+                            int numBits) {
+        int bitNum = 8*lineOffset + bitOffset;
+        int lastBit = bitNum + numBits;
+
+        int byteNum = bitNum >> 3;
+
+        // Handle bits in first byte
+        int shift = bitNum & 0x7;
+        if (shift > 0) {
+            int maskVal = 1 << (7 - shift);
+            byte val = buffer[byteNum];
+            while (maskVal > 0 && bitNum < lastBit) {
+                val |= maskVal;
+                maskVal >>= 1;
+                ++bitNum;
+            }
+            buffer[byteNum] = val;
+        }
+
+        // Fill in 8 bits at a time
+        byteNum = bitNum >> 3;
+        while (bitNum < lastBit - 7) {
+            buffer[byteNum++] = (byte)255;
+            bitNum += 8;
+        }
+
+        // Fill in remaining bits
+        while (bitNum < lastBit) {
+            byteNum = bitNum >> 3;
+            buffer[byteNum] |= 1 << (7 - (bitNum & 0x7));
+            ++bitNum;
+        }
+    }
+
+    // Returns run length
+    private int decodeWhiteCodeWord() {
+        int current, entry, bits, isT, twoBits, code = -1;
+        int runLength = 0;
+        boolean isWhite = true;
+
+        while (isWhite) {
+            current = nextNBits(10);
+            entry = white[current];
+
+            // Get the 3 fields from the entry
+            isT = entry & 0x0001;
+            bits = (entry >>> 1) & 0x0f;
+
+            if (bits == 12) {           // Additional Make up code
+                // Get the next 2 bits
+                twoBits = nextLesserThan8Bits(2);
+                // Consolidate the 2 new bits and last 2 bits into 4 bits
+                current = ((current << 2) & 0x000c) | twoBits;
+                entry = additionalMakeup[current];
+                bits = (entry >>> 1) & 0x07;     // 3 bits 0000 0111
+                code = (entry >>> 4) & 0x0fff;   // 12 bits
+                runLength += code;
+                updatePointer(4 - bits);
+            } else if (bits == 0) {     // ERROR
+                throw new Error("TIFFFaxDecoder0");
+            } else if (bits == 15) {    // EOL
+                throw new Error("TIFFFaxDecoder1");
+            } else {
+                // 11 bits - 0000 0111 1111 1111 = 0x07ff
+                code = (entry >>> 5) & 0x07ff;
+                runLength += code;
+                updatePointer(10 - bits);
+                if (isT == 0) {
+                    isWhite = false;
+                }
+            }
+        }
+
+        return runLength;
+    }
+
+    // Returns run length
+    private int decodeBlackCodeWord() {
+        int current, entry, bits, isT, code = -1;
+        int runLength = 0;
+        boolean isWhite = false;
+
+        while (!isWhite) {
+            current = nextLesserThan8Bits(4);
+            entry = initBlack[current];
+
+            // Get the 3 fields from the entry
+            isT = entry & 0x0001;
+            bits = (entry >>> 1) & 0x000f;
+            code = (entry >>> 5) & 0x07ff;
+
+            if (code == 100) {
+                current = nextNBits(9);
+                entry = black[current];
+
+                // Get the 3 fields from the entry
+                isT = entry & 0x0001;
+                bits = (entry >>> 1) & 0x000f;
+                code = (entry >>> 5) & 0x07ff;
+
+                if (bits == 12) {
+                    // Additional makeup codes
+                    updatePointer(5);
+                    current = nextLesserThan8Bits(4);
+                    entry = additionalMakeup[current];
+                    bits = (entry >>> 1) & 0x07;     // 3 bits 0000 0111
+                    code  = (entry >>> 4) & 0x0fff;  // 12 bits
+                    runLength += code;
+
+                    updatePointer(4 - bits);
+                } else if (bits == 15) {
+                    // EOL code
+                    throw new Error("TIFFFaxDecoder2");
+                } else {
+                    runLength += code;
+                    updatePointer(9 - bits);
+                    if (isT == 0) {
+                        isWhite = true;
+                    }
+                }
+            } else if (code == 200) {
+                // Is a Terminating code
+                current = nextLesserThan8Bits(2);
+                entry = twoBitBlack[current];
+                code = (entry >>> 5) & 0x07ff;
+                runLength += code;
+                bits = (entry >>> 1) & 0x0f;
+                updatePointer(2 - bits);
+                isWhite = true;
+            } else {
+                // Is a Terminating code
+                runLength += code;
+                updatePointer(4 - bits);
+                isWhite = true;
+            }
+        }
+
+        return runLength;
+    }
+
+    private int readEOL() {
+        if (fillBits == 0) {
+            if (nextNBits(12) != 1) {
+                throw new Error("TIFFFaxDecoder6");
+            }
+        } else if (fillBits == 1) {
+
+            // First EOL code word xxxx 0000 0000 0001 will occur
+            // As many fill bits will be present as required to make
+            // the EOL code of 12 bits end on a byte boundary.
+
+            int bitsLeft = 8 - bitPointer;
+
+            if (nextNBits(bitsLeft) != 0) {
+                    throw new Error("TIFFFaxDecoder8");
+            }
+
+            // If the number of bitsLeft is less than 8, then to have a 12
+            // bit EOL sequence, two more bytes are certainly going to be
+            // required. The first of them has to be all zeros, so ensure
+            // that.
+            if (bitsLeft < 4) {
+                if (nextNBits(8) != 0) {
+                    throw new Error("TIFFFaxDecoder8");
+                }
+            }
+
+            // There might be a random number of fill bytes with 0s, so
+            // loop till the EOL of 0000 0001 is found, as long as all
+            // the bytes preceding it are 0's.
+            int n;
+            while ((n = nextNBits(8)) != 1) {
+
+                // If not all zeros
+                if (n != 0) {
+                    throw new Error("TIFFFaxDecoder8");
+                }
+            }
+        }
+
+        // If one dimensional encoding mode, then always return 1
+        if (oneD == 0) {
+            return 1;
+        } else {
+            // Otherwise for 2D encoding mode,
+            // The next one bit signifies 1D/2D encoding of next line.
+            return nextLesserThan8Bits(1);
+        }
+    }
+
+    private void getNextChangingElement(int a0, boolean isWhite, int[] ret) {
+        // Local copies of instance variables
+        int[] pce = this.prevChangingElems;
+        int ces = this.changingElemSize;
+
+        // If the previous match was at an odd element, we still
+        // have to search the preceeding element.
+        // int start = lastChangingElement & ~0x1;
+        int start = lastChangingElement > 0 ? lastChangingElement - 1 : 0;
+        if (isWhite) {
+            start &= ~0x1; // Search even numbered elements
+        } else {
+            start |= 0x1; // Search odd numbered elements
+        }
+
+        int i = start;
+        for (; i < ces; i += 2) {
+            int temp = pce[i];
+            if (temp > a0) {
+                lastChangingElement = i;
+                ret[0] = temp;
+                break;
+            }
+        }
+
+        if (i + 1 < ces) {
+            ret[1] = pce[i + 1];
+        }
+    }
+
+    private int nextNBits(int bitsToGet) {
+        byte b, next, next2next;
+        int l = data.length - 1;
+        int bp = this.bytePointer;
+
+        if (fillOrder == 1) {
+            b = data[bp];
+
+            if (bp == l) {
+                next = 0x00;
+                next2next = 0x00;
+            } else if ((bp + 1) == l) {
+                next = data[bp + 1];
+                next2next = 0x00;
+            } else {
+                next = data[bp + 1];
+                next2next = data[bp + 2];
+            }
+        } else if (fillOrder == 2) {
+            b = flipTable[data[bp] & 0xff];
+
+            if (bp == l) {
+                next = 0x00;
+                next2next = 0x00;
+            } else if ((bp + 1) == l) {
+                next = flipTable[data[bp + 1] & 0xff];
+                next2next = 0x00;
+            } else {
+                next = flipTable[data[bp + 1] & 0xff];
+                next2next = flipTable[data[bp + 2] & 0xff];
+            }
+        } else {
+            throw new Error("TIFFFaxDecoder7");
+        }
+
+        int bitsLeft = 8 - bitPointer;
+        int bitsFromNextByte = bitsToGet - bitsLeft;
+        int bitsFromNext2NextByte = 0;
+        if (bitsFromNextByte > 8) {
+            bitsFromNext2NextByte = bitsFromNextByte - 8;
+            bitsFromNextByte = 8;
+        }
+
+        bytePointer++;
+
+        int i1 = (b & table1[bitsLeft]) << (bitsToGet - bitsLeft);
+        int i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte);
+
+        int i3 = 0;
+        if (bitsFromNext2NextByte != 0) {
+            i2 <<= bitsFromNext2NextByte;
+            i3 = (next2next & table2[bitsFromNext2NextByte]) >>>
+                (8 - bitsFromNext2NextByte);
+            i2 |= i3;
+            bytePointer++;
+            bitPointer = bitsFromNext2NextByte;
+        } else {
+            if (bitsFromNextByte == 8) {
+                bitPointer = 0;
+                bytePointer++;
+            } else {
+                bitPointer = bitsFromNextByte;
+            }
+        }
+
+        int i = i1 | i2;
+        return i;
+    }
+
+    private int nextLesserThan8Bits(int bitsToGet) {
+        byte b, next;
+        int l = data.length - 1;
+        int bp = this.bytePointer;
+
+        if (fillOrder == 1) {
+            b = data[bp];
+            if (bp == l) {
+                next = 0x00;
+            } else {
+                next = data[bp + 1];
+            }
+        } else if (fillOrder == 2) {
+            b = flipTable[data[bp] & 0xff];
+            if (bp == l) {
+                next = 0x00;
+            } else {
+                next = flipTable[data[bp + 1] & 0xff];
+            }
+        } else {
+            throw new Error("TIFFFaxDecoder7");
+        }
+
+        int bitsLeft = 8 - bitPointer;
+        int bitsFromNextByte = bitsToGet - bitsLeft;
+
+        int shift = bitsLeft - bitsToGet;
+        int i1, i2;
+        if (shift >= 0) {
+            i1 = (b & table1[bitsLeft]) >>> shift;
+            bitPointer += bitsToGet;
+            if (bitPointer == 8) {
+                bitPointer = 0;
+                bytePointer++;
+            }
+        } else {
+            i1 = (b & table1[bitsLeft]) << (-shift);
+            i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte);
+
+            i1 |= i2;
+            bytePointer++;
+            bitPointer = bitsFromNextByte;
+        }
+
+        return i1;
+    }
+
+    // Move pointer backwards by given amount of bits
+    private void updatePointer(int bitsToMoveBack) {
+        int i = bitPointer - bitsToMoveBack;
+
+        if (i < 0) {
+            bytePointer--;
+            bitPointer = 8 + i;
+        } else {
+            bitPointer = i;
+        }
+    }
+
+    // Move to the next byte boundary
+    private boolean advancePointer() {
+        if (bitPointer != 0) {
+            bytePointer++;
+            bitPointer = 0;
+        }
+
+        return true;
+    }
+}
+
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/filter/package.html
new file mode 100644 (file)
index 0000000..5576f92
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package will hold the PDFBox implementations of the filters that are used in PDF documents.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ASCII85InputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ASCII85InputStream.java
new file mode 100644 (file)
index 0000000..aee64d9
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.FilterInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This class represents an ASCII85 stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class ASCII85InputStream extends FilterInputStream
+{
+    private int index;
+    private int n;
+    private boolean eof;
+
+    private byte[] ascii;
+    private byte[] b;
+
+    /**
+     * Constructor.
+     *
+     * @param is The input stream to actually read from.
+     */
+    public ASCII85InputStream( InputStream is )
+    {
+        super(is);
+        index = 0;
+        n = 0;
+        eof = false;
+        ascii = new byte[5];
+        b = new byte[4];
+    }
+
+    /**
+     * This will read the next byte from the stream.
+     *
+     * @return The next byte read from the stream.
+     *
+     * @throws IOException If there is an error reading from the wrapped stream.
+     */
+    public final int read() throws IOException
+    {
+        if( index >= n )
+        {
+            if(eof)
+            {
+                return -1;
+            }
+            index = 0;
+            int k;
+            byte z;
+            do
+            {
+                int zz=(byte)in.read();
+                if(zz==-1)
+                {
+                    eof=true;
+                    return -1;
+                }
+                z = (byte)zz;
+            }  while( z=='\n' || z=='\r' || z==' ');
+
+            if (z == '~' || z=='x')
+            {
+                eof=true;
+                ascii=b=null;
+                n = 0;
+                return -1;
+            }
+            else if (z == 'z')
+            {
+                b[0]=b[1]=b[2]=b[3]=0;
+                n = 4;
+            }
+            else
+            {
+                ascii[0]=z; // may be EOF here....
+                for (k=1;k<5;++k)
+                {
+                    do
+                    {
+                        int zz=(byte)in.read();
+                        if(zz==-1)
+                        {
+                            eof=true;
+                            return -1;
+                        }
+                        z=(byte)zz;
+                    } while ( z=='\n' || z=='\r' || z==' ' );
+                    ascii[k]=z;
+                    if (z == '~' || z=='x')
+                    {
+                        break;
+                    }
+                }
+                n = k - 1;
+                if ( n==0 )
+                {
+                    eof = true;
+                    ascii = null;
+                    b = null;
+                    return -1;
+                }
+                if ( k < 5 )
+                {
+                    for (++k; k < 5; ++k )
+                    {
+                        ascii[k] = 0x21;
+                    }
+                    eof=true;
+                }
+                // decode stream
+                long t=0;
+                for ( k=0; k<5; ++k)
+                {
+                    z=(byte)(ascii[k] - 0x21);
+                    if (z < 0 || z > 93)
+                    {
+                        n = 0;
+                        eof = true;
+                        ascii = null;
+                        b = null;
+                        throw new IOException("Invalid data in Ascii85 stream");
+                    }
+                    t = (t * 85L) + z;
+                }
+                for ( k = 3; k>=0; --k)
+                {
+                    b[k] = (byte)(t & 0xFFL);
+                    t>>>=8;
+                }
+            }
+        }
+        return b[index++] & 0xFF;
+    }
+
+    /**
+     * This will read a chunk of data.
+     *
+     * @param data The buffer to write data to.
+     * @param offset The offset into the data stream.
+     * @param len The number of byte to attempt to read.
+     *
+     * @return The number of bytes actually read.
+     *
+     * @throws IOException If there is an error reading data from the underlying stream.
+     */
+    public final int read(byte[] data, int offset, int len) throws IOException
+    {
+        if(eof && index>=n)
+        {
+            return -1;
+        }
+        for(int i=0;i<len;i++)
+        {
+            if(index<n)
+            {
+                data[i+offset]=b[index++];
+            }
+            else
+            {
+                int t = read();
+                if ( t == -1 )
+                {
+                    return i;
+                }
+                data[i+offset]=(byte)t;
+            }
+        }
+        return len;
+    }
+
+    /**
+     * This will close the underlying stream and release any resources.
+     *
+     * @throws IOException If there is an error closing the underlying stream.
+     */
+    public void close() throws IOException
+    {
+        ascii = null;
+        eof = true;
+        b = null;
+        super.close();
+    }
+
+    /**
+     * non supported interface methods.
+     *
+     * @return False always.
+     */
+    public boolean markSupported()
+    {
+        return false;
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @param nValue ignored.
+     *
+     * @return Always zero.
+     */
+    public long skip(long nValue)
+    {
+        return 0;
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @return Always zero.
+     */
+    public int available()
+    {
+        return 0;
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @param readlimit ignored.
+     */
+    public void mark(int readlimit)
+    {
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @throws IOException telling that this is an unsupported action.
+     */
+    public void reset() throws IOException
+    {
+        throw new IOException("Reset is not supported");
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ASCII85OutputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ASCII85OutputStream.java
new file mode 100644 (file)
index 0000000..d0b74eb
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class represents an ASCII85 output stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class ASCII85OutputStream extends FilterOutputStream
+{
+
+    private int lineBreak;
+    private int count;
+
+    private byte[] indata;
+    private byte[] outdata;
+
+    /**
+     * Function produces five ASCII printing characters from
+     * four bytes of binary data.
+     */
+    private int maxline;
+    private boolean flushed;
+    private char terminator;
+
+    /**
+     * Constructor.
+     *
+     * @param out The output stream to write to.
+     */
+    public ASCII85OutputStream( OutputStream out )
+    {
+        super( out );
+        lineBreak = 36*2;
+        maxline = 36*2;
+        count=0;
+        indata=new byte[4];
+        outdata=new byte[5];
+        flushed=true;
+        terminator='~';
+    }
+
+    /**
+     * This will set the terminating character.
+     *
+     * @param term The terminating character.
+     */
+    public void setTerminator(char term)
+    {
+        if(term<118 || term>126 || term=='z')
+        {
+            throw new IllegalArgumentException("Terminator must be 118-126 excluding z");
+        }
+        terminator=term;
+    }
+
+    /**
+     * This will get the terminating character.
+     *
+     * @return The terminating character.
+     */
+    public char getTerminator()
+    {
+        return terminator;
+    }
+
+    /**
+     * This will set the line length that will be used.
+     *
+     * @param l The length of the line to use.
+     */
+    public void setLineLength(int l)
+    {
+        if( lineBreak > l )
+        {
+            lineBreak = l;
+        }
+        maxline=l;
+    }
+
+    /**
+     * This will get the length of the line.
+     *
+     * @return The line length attribute.
+     */
+    public int getLineLength()
+    {
+        return maxline;
+    }
+
+    /**
+     * This will transform the next four ascii bytes.
+     */
+    private final void  transformASCII85()
+    {
+        long word;
+        word=( (((indata[0] << 8) | (indata[1] &0xFF)) << 16) |
+        ( (indata[2] & 0xFF) << 8) | (indata[3] & 0xFF)
+        )    & 0xFFFFFFFFL;
+        // System.out.println("word=0x"+Long.toString(word,16)+" "+word);
+
+        if (word == 0 )
+        {
+            outdata[0]=(byte)'z';
+            outdata[1]=0;
+            return;
+        }
+        long x;
+        x=word/(85L*85L*85L*85L);
+        // System.out.println("x0="+x);
+        outdata[0]=(byte)(x+'!');
+        word-=x*85L*85L*85L*85L;
+
+        x=word/(85L*85L*85L);
+        // System.out.println("x1="+x);
+        outdata[1]=(byte)(x+'!');
+        word-=x*85L*85L*85L;
+
+        x=word/(85L*85L);
+        // System.out.println("x2="+x);
+        outdata[2]=(byte)(x+'!');
+        word-=x*85L*85L;
+
+        x=word/85L;
+        // System.out.println("x3="+x);
+        outdata[3]=(byte)(x+'!');
+
+        // word-=x*85L;
+
+        // System.out.println("x4="+(word % 85L));
+        outdata[4]=(byte)((word%85L)+'!');
+    }
+
+    /**
+     * This will write a single byte.
+     *
+     * @param b The byte to write.
+     *
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public final void write(int b) throws IOException
+    {
+        flushed=false;
+        indata[count++]=(byte)b;
+        if(count < 4 )
+        {
+            return;
+        }
+        transformASCII85();
+        for(int i=0;i<5;i++)
+        {
+            if(outdata[i]==0)
+            {
+                break;
+            }
+            out.write(outdata[i]);
+            if(--lineBreak==0)
+            {
+                out.write('\n');
+                lineBreak=maxline;
+            }
+        }
+        count = 0;
+    }
+
+    /**
+     * This will write a chunk of data to the stream.
+     *
+     * @param b The byte buffer to read from.
+     * @param off The offset into the buffer.
+     * @param sz The number of bytes to read from the buffer.
+     *
+     * @throws IOException If there is an error writing to the underlying stream.
+     */
+    public final void write(byte[] b,int off, int sz) throws IOException
+    {
+        for(int i=0;i<sz;i++)
+        {
+            if(count < 3)
+            {
+                indata[count++]=b[off+i];
+            }
+            else
+            {
+                write(b[off+i]);
+            }
+        }
+    }
+
+    /**
+     * This will flush the data to the stream.
+     *
+     * @throws IOException If there is an error writing the data to the stream.
+     */
+    public final void flush() throws IOException
+    {
+        if(flushed)
+        {
+            return;
+        }
+        if(count > 0 )
+        {
+            for( int i=count; i<4; i++ )
+            {
+                indata[i]=0;
+            }
+            transformASCII85();
+            if(outdata[0]=='z')
+            {
+                for(int i=0;i<5;i++) // expand 'z',
+                {
+                    outdata[i]=(byte)'!';
+                }
+            }
+            for(int i=0;i<count+1;i++)
+            {
+                out.write(outdata[i]);
+                if(--lineBreak==0)
+                {
+                    out.write('\n');
+                    lineBreak=maxline;
+                }
+            }
+        }
+        if(--lineBreak==0)
+        {
+            out.write('\n');
+        }
+        out.write(terminator);
+        out.write('\n');
+        count = 0;
+        lineBreak=maxline;
+        flushed=true;
+        super.flush();
+    }
+
+    /**
+     * This will close the stream.
+     *
+     * @throws IOException If there is an error closing the wrapped stream.
+     */
+    public void close() throws IOException
+    {
+        try
+        {
+            super.close();
+        }
+        finally
+        {
+            indata=outdata=null;
+        }
+    }
+
+    /**
+     * This will flush the stream.
+     *
+     * @throws Throwable If there is an error.
+     */
+    protected void finalize() throws Throwable
+    {
+        try
+        {
+            flush();
+        }
+        finally
+        {
+            super.finalize();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ByteArrayPushBackInputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/ByteArrayPushBackInputStream.java
new file mode 100644 (file)
index 0000000..bf82b2d
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * PushBackInputStream for byte arrays.
+ *
+ * The inheritance from PushBackInputStream is only to avoid the
+ * introduction of an interface with all PushBackInputStream
+ * methods. The parent PushBackInputStream is not used in any way and
+ * all methods are overridden. (Thus when adding new methods to PushBackInputStream
+ * override them in this class as well!)
+ * unread() is limited to the number of bytes already read from this stream (i.e.
+ * the current position in the array). This limitation usually poses no problem
+ * to a parser, but allows for some optimization since only one array has to
+ * be dealt with.
+ *
+ * Note: This class is not thread safe. Clients must provide synchronization
+ * if needed.
+ *
+ * Note: Calling unread() after mark() will cause (part of) the unread data to be
+ * read again after reset(). Thus do not call unread() between mark() and reset().
+ *
+ * @author Andreas Weiss (andreas.weiss@switzerland.org)
+ * @version $Revision: 1.2 $
+ */
+public class ByteArrayPushBackInputStream extends PushBackInputStream
+{
+    private byte[] data;
+    private int datapos;
+    private int datalen;
+    private int save;
+
+    // dummy for base class constructor
+    private static final InputStream DUMMY = new ByteArrayInputStream("".getBytes());
+
+    /**
+     * Constructor.
+     * @param input Data to read from. Note that calls to unread() will
+     * modify this array! If this is not desired, pass a copy.
+     *
+     * @throws IOException If there is an IO error.
+     */
+    public ByteArrayPushBackInputStream(byte[] input) throws IOException
+    {
+        super(DUMMY, 1);
+        data = input;
+        datapos = 0;
+        save = datapos;
+        datalen = input != null ? input.length : 0;
+    }
+
+    /**
+     * This will peek at the next byte.
+     *
+     * @return The next byte on the stream, leaving it as available to read.
+     */
+    public int peek()
+    {
+        try
+        {
+            // convert negative values to 128..255
+            return (data[datapos] + 0x100) & 0xff;
+        }
+        catch (ArrayIndexOutOfBoundsException ex)
+        {
+            // could check this before, but this is a rare case
+            // and this method is called sufficiently often to justify this
+            // optimization
+            return -1;
+        }
+    }
+
+    /**
+     * A simple test to see if we are at the end of the stream.
+     *
+     * @return true if we are at the end of the stream.
+     */
+    public boolean isEOF()
+    {
+        return datapos >= datalen;
+    }
+
+    /**
+     * Save the state of this stream.
+     * @param readlimit Has no effect.
+     * @see InputStream#mark(int)
+     */
+    public void mark(int readlimit)
+    {
+        if (false)
+        {
+            ++readlimit; // avoid unused param warning
+        }
+        save = datapos;
+    }
+
+    /**
+     * Check if mark is supported.
+     * @return Always true.
+     * @see InputStream#markSupported()
+     */
+    public boolean markSupported()
+    {
+        return true;
+    }
+
+    /**
+     * Restore the state of this stream to the last saveState call.
+     * @see InputStream#reset()
+     */
+    public void reset()
+    {
+        datapos = save;
+    }
+
+    /** Available bytes.
+     * @see InputStream#available()
+     * @return Available bytes.
+     */
+    public int available()
+    {
+        int av = datalen - datapos;
+        return av > 0 ? av : 0;
+    }
+
+    /** Totally available bytes in the underlying array.
+     * @return Available bytes.
+     */
+    public int size()
+    {
+        return datalen;
+    }
+
+    /**
+     * Pushes back a byte.
+     * After this method returns, the next byte to be read will have the value (byte)by.
+     * @param by the int value whose low-order byte is to be pushed back.
+     * @throws IOException - If there is not enough room in the buffer for the byte.
+     * @see java.io.PushbackInputStream#unread(int)
+     */
+    public void unread(int by) throws IOException
+    {
+        if (datapos == 0)
+        {
+            throw new IOException("ByteArrayParserInputStream.unread(int): " +
+                                  "cannot unread 1 byte at buffer position " + datapos);
+        }
+        --datapos;
+        data[datapos] = (byte)by;
+    }
+
+    /**
+     * Pushes back a portion of an array of bytes by copying it to the
+     * front of the pushback buffer. After this method returns, the next byte
+     * to be read will have the value b[off], the byte after that will have
+     * the value b[off+1], and so forth.
+     * @param buffer the byte array to push back.
+     * @param off the start offset of the data.
+     * @param len the number of bytes to push back.
+     * @throws IOException If there is not enough room in the pushback buffer
+     * for the specified number of bytes.
+     * @see java.io.PushbackInputStream#unread(byte[], int, int)
+     */
+    public void unread(byte[] buffer, int off, int len) throws IOException
+    {
+        if (len <= 0 || off >= buffer.length)
+        {
+            return;
+        }
+        if (off < 0)
+        {
+            off = 0;
+        }
+        if (len > buffer.length)
+        {
+            len = buffer.length;
+        }
+        localUnread(buffer, off, len);
+    }
+
+    /**
+     * Pushes back a portion of an array of bytes by copying it to the
+     * front of the pushback buffer. After this method returns, the next byte
+     * to be read will have the value buffer[0], the byte after that will have
+     * the value buffer[1], and so forth.
+     * @param buffer the byte array to push back.
+     * @throws IOException If there is not enough room in the pushback buffer
+     * for the specified number of bytes.
+     * @see java.io.PushbackInputStream#unread(byte[])
+     */
+    public void unread(byte[] buffer) throws IOException
+    {
+        localUnread(buffer, 0, buffer.length);
+    }
+
+    /**
+     * Pushes back a portion of an array of bytes by copying it to the
+     * front of the pushback buffer. After this method returns, the next byte
+     * to be read will have the value buffer[off], the byte after that will have
+     * the value buffer[off+1], and so forth.
+     * Internal method that assumes off and len to be valid.
+     * @param buffer the byte array to push back.
+     * @param off the start offset of the data.
+     * @param len the number of bytes to push back.
+     * @throws IOException If there is not enough room in the pushback buffer
+     * for the specified number of bytes.
+     * @see java.io.PushbackInputStream#unread(byte[], int, int)
+     */
+    private void localUnread(byte[] buffer, int off, int len) throws IOException
+    {
+        if (datapos < len)
+        {
+            throw new IOException("ByteArrayParserInputStream.unread(int): " +
+                                  "cannot unread " + len +
+                                  " bytes at buffer position " + datapos);
+        }
+        datapos -= len;
+        System.arraycopy(buffer, off, data, datapos, len);
+    }
+
+    /**
+     * Read a byte.
+     * @see InputStream#read()
+     * @return Byte read or -1 if no more bytes are available.
+     */
+    public int read()
+    {
+        try
+        {
+            // convert negative values to 128..255
+            return (data[datapos++] + 0x100) & 0xff;
+        }
+        catch (ArrayIndexOutOfBoundsException ex)
+        {
+            // could check this before, but this is a rare case
+            // and this method is called sufficiently often to justify this
+            // optimization
+            datapos = datalen;
+            return -1;
+        }
+    }
+
+    /**
+     * Read a number of bytes.
+     * @see InputStream#read(byte[])
+     * @param buffer the buffer into which the data is read.
+     * @return the total number of bytes read into the buffer, or -1 if there
+     * is no more data because the end of the stream has been reached.
+     */
+    public int read(byte[] buffer)
+    {
+        return localRead(buffer, 0, buffer.length);
+    }
+
+    /**
+     * Read a number of bytes.
+     * @see InputStream#read(byte[], int, int)
+     * @param buffer the buffer into which the data is read.
+     * @param off the start offset in array buffer at which the data is written.
+     * @param len the maximum number of bytes to read.
+     * @return the total number of bytes read into the buffer, or -1 if there
+     * is no more data because the end of the stream has been reached.
+     */
+    public int read(byte[] buffer, int off, int len)
+    {
+        if (len <= 0 || off >= buffer.length)
+        {
+            return 0;
+        }
+        if (off < 0)
+        {
+            off = 0;
+        }
+        if (len > buffer.length)
+        {
+            len = buffer.length;
+        }
+        return localRead(buffer, off, len);
+    }
+
+
+    /**
+     * Read a number of bytes. Internal method that assumes off and len to be
+     * valid.
+     * @see InputStream#read(byte[], int, int)
+     * @param buffer the buffer into which the data is read.
+     * @param off the start offset in array buffer at which the data is written.
+     * @param len the maximum number of bytes to read.
+     * @return the total number of bytes read into the buffer, or -1 if there
+     * is no more data because the end of the stream has been reached.
+     */
+    public int localRead(byte[] buffer, int off, int len)
+    {
+        if (len == 0)
+        {
+            return 0; // must return 0 even if at end!
+        }
+        else if (datapos >= datalen)
+        {
+            return -1;
+        }
+        else
+        {
+            int newpos = datapos + len;
+            if (newpos > datalen)
+            {
+                newpos = datalen;
+                len = newpos - datapos;
+            }
+            System.arraycopy(data, datapos, buffer, off, len);
+            datapos = newpos;
+            return len;
+        }
+    }
+
+    /**
+     * Skips over and discards n bytes of data from this input stream.
+     * The skip method may, for a variety of reasons, end up skipping over some
+     * smaller number of bytes, possibly 0. This may result from any of a number
+     * of conditions; reaching end of file before n bytes have been skipped is
+     * only one possibility. The actual number of bytes skipped is returned.
+     * If n is negative, no bytes are skipped.
+     * @param num the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
+     * @see InputStream#skip(long)
+     */
+    public long skip(long num)
+    {
+        if (num <= 0)
+        {
+            return 0;
+        }
+        else
+        {
+            long newpos = datapos + num;
+            if (newpos >= datalen)
+            {
+                num = datalen - datapos;
+                datapos = datalen;
+            }
+            else
+            {
+                datapos = (int)newpos;
+            }
+            return num;
+        }
+    }
+
+    /** Position the stream at a given index. Positioning the stream
+     * at position size() will cause the next call to read() to return -1.
+     *
+     * @param newpos Position in the underlying array. A negative value will be
+     * interpreted as 0, a value greater than size() as size().
+     * @return old position.
+     */
+    public int seek(int newpos)
+    {
+        if (newpos < 0)
+        {
+            newpos = 0;
+        }
+        else if (newpos > datalen)
+        {
+            newpos = datalen;
+        }
+        int oldpos = pos;
+        pos = newpos;
+        return oldpos;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/FastByteArrayOutputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/FastByteArrayOutputStream.java
new file mode 100644 (file)
index 0000000..f201b0c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * An byte array output stream that allows direct access to the byte array.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class FastByteArrayOutputStream extends ByteArrayOutputStream
+{
+    /**
+     * Constructor.
+     *
+     * @param size An initial size of the stream.
+     */
+    public FastByteArrayOutputStream( int size )
+    {
+        super( size );
+    }
+
+    /**
+     * This will get the underlying byte array.
+     *
+     * @return The underlying byte array at this moment in time.
+     */
+    public byte[] getByteArray()
+    {
+        return buf;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/NBitInputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/NBitInputStream.java
new file mode 100644 (file)
index 0000000..cc6d158
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This is an n-bit input stream.  This means that you can read chunks of data
+ * as any number of bits, not just 8 bits like the regular input stream.  Just set the
+ * number of bits that you would like to read on each call.  The default is 8.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class NBitInputStream
+{
+    private int bitsInChunk;
+    private InputStream in;
+
+    private int currentByte;
+    private int bitsLeftInCurrentByte;
+
+    /**
+     * Constructor.
+     *
+     * @param is The input stream to read from.
+     */
+    public NBitInputStream( InputStream is )
+    {
+        in = is;
+        bitsLeftInCurrentByte = 0;
+        bitsInChunk = 8;
+    }
+
+    /**
+     * This will unread some data.
+     *
+     * @param data The data to put back into the stream.
+     */
+    public void unread( long data )
+    {
+        data <<= bitsLeftInCurrentByte;
+        currentByte |= data;
+        bitsLeftInCurrentByte += bitsInChunk;
+    }
+
+    /**
+     * This will read the next n bits from the stream and return the unsigned
+     * value of  those bits.
+     *
+     * @return The next n bits from the stream.
+     *
+     * @throws IOException If there is an error reading from the underlying stream.
+     */
+    public long read() throws IOException
+    {
+        long retval = 0;
+        for( int i=0; i<bitsInChunk && retval != -1; i++ )
+        {
+            if( bitsLeftInCurrentByte == 0 )
+            {
+                currentByte = in.read();
+                bitsLeftInCurrentByte = 8;
+            }
+            if( currentByte == -1 )
+            {
+                retval = -1;
+            }
+            else
+            {
+                retval <<= 1;
+                retval |= ((currentByte >> (bitsLeftInCurrentByte-1))&0x1);
+                bitsLeftInCurrentByte--;
+            }
+        }
+        return retval;
+    }
+
+    /** Getter for property bitsToRead.
+     * @return Value of property bitsToRead.
+     */
+    public int getBitsInChunk()
+    {
+        return bitsInChunk;
+    }
+
+    /** Setter for property bitsToRead.
+     * @param bitsInChunkValue New value of property bitsToRead.
+     */
+    public void setBitsInChunk(int bitsInChunkValue)
+    {
+        bitsInChunk = bitsInChunkValue;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/NBitOutputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/NBitOutputStream.java
new file mode 100644 (file)
index 0000000..4127c1f
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * This is an n-bit output stream.  This means that you write data in n-bit chunks.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class NBitOutputStream
+{
+    private int bitsInChunk;
+    private OutputStream out;
+
+    private int currentByte;
+    private int positionInCurrentByte;
+
+    /**
+     * Constructor.
+     *
+     * @param os The output stream to write to.
+     */
+    public NBitOutputStream( OutputStream os )
+    {
+        out = os;
+        currentByte = 0;
+        positionInCurrentByte = 7;
+    }
+
+    /**
+     * This will write the next n-bits to the stream.
+     *
+     * @param chunk The next chunk of data to write.
+     *
+     * @throws IOException If there is an error writing the chunk.
+     */
+    public void write( long chunk ) throws IOException
+    {
+        long bitToWrite;
+        for( int i=(bitsInChunk-1); i>=0; i-- )
+        {
+            bitToWrite = (chunk >> i) & 0x1;
+            bitToWrite <<= positionInCurrentByte;
+            currentByte |= bitToWrite;
+            positionInCurrentByte--;
+            if( positionInCurrentByte < 0 )
+            {
+                out.write( currentByte );
+                currentByte = 0;
+                positionInCurrentByte = 7;
+            }
+        }
+    }
+
+    /**
+     * This will close the stream.
+     *
+     * @throws IOException if there is an error closing the stream.
+     */
+    public void close() throws IOException
+    {
+        if( positionInCurrentByte < 7 )
+        {
+            out.write( currentByte );
+        }
+    }
+
+    /** Getter for property bitsToRead.
+     * @return Value of property bitsToRead.
+     */
+    public int getBitsInChunk()
+    {
+        return bitsInChunk;
+    }
+
+    /** Setter for property bitsToRead.
+     * @param bitsInChunkValue New value of property bitsToRead.
+     */
+    public void setBitsInChunk(int bitsInChunkValue)
+    {
+        bitsInChunk = bitsInChunkValue;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/PushBackInputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/PushBackInputStream.java
new file mode 100644 (file)
index 0000000..17902c3
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * A simple subclass that adds a few convience methods.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PushBackInputStream extends java.io.PushbackInputStream
+{
+    /*
+     * The current position in the file. 
+     */
+    private int offset = 0;
+    
+    /**
+     * Constructor.
+     *
+     * @param input The input stream.
+     * @param size The size of the push back buffer.
+     *
+     * @throws IOException If there is an error with the stream.
+     */
+    public PushBackInputStream( InputStream input, int size ) throws IOException
+    {
+        super( input, size );
+        if( input == null )
+        {
+            throw new IOException( "Error: input was null" );
+        }
+    }
+
+    /**
+     * This will peek at the next byte.
+     *
+     * @return The next byte on the stream, leaving it as available to read.
+     *
+     * @throws IOException If there is an error reading the next byte.
+     */
+    public int peek() throws IOException
+    {
+        int result = read();
+        if( result != -1 )
+        {
+            unread( result );
+        }
+        return result;
+    }
+    
+    /**
+     * Returns the current byte offset in the file.
+     * @return the int byte offset
+     */
+    public int getOffset()
+    {
+        return offset;
+    }
+    
+    /**
+     * {@inheritDoc} 
+     */
+    public int read() throws IOException
+    {
+        int retval = super.read();
+        if (retval != -1)
+        {
+            offset++;
+        }
+        return retval;
+    }
+    
+    /**
+     * {@inheritDoc} 
+     */
+    public int read(byte[] b) throws IOException
+    {
+        return this.read(b, 0, b.length);
+    }
+    /**
+     * {@inheritDoc} 
+     */
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        int retval = super.read(b, off, len);
+        if (retval != -1)
+        {
+            offset += retval;
+        }
+        return retval;
+    }
+    
+    /**
+     * {@inheritDoc} 
+     */
+    public void unread(int b) throws IOException
+    {
+        offset--;
+        super.unread(b);
+    }
+    
+    /**
+     * {@inheritDoc} 
+     */
+    public void unread(byte[] b) throws IOException
+    {
+        this.unread(b, 0, b.length);
+    }
+    
+    /**
+     * {@inheritDoc} 
+     */
+    public void unread(byte[] b, int off, int len) throws IOException
+    {
+        if (len > 0)
+        {
+            offset -= len;
+            super.unread(b, off, len);
+        }
+    }
+    
+    /**
+     * A simple test to see if we are at the end of the stream.
+     *
+     * @return true if we are at the end of the stream.
+     *
+     * @throws IOException If there is an error reading the next byte.
+     */
+    public boolean isEOF() throws IOException
+    {
+        int peek = peek();
+        return peek == -1;
+    }
+
+    /**
+     * This is a method used to fix PDFBox issue 974661, the PDF parsing code needs
+     * to know if there is at least x amount of data left in the stream, but the available()
+     * method returns how much data will be available without blocking.  PDFBox is willing to
+     * block to read the data, so we will first fill the internal buffer.
+     *
+     * @throws IOException If there is an error filling the buffer.
+     */
+    public void fillBuffer() throws IOException
+    {
+        int bufferLength = buf.length;
+        byte[] tmpBuffer = new byte[bufferLength];
+        int amountRead = 0;
+        int totalAmountRead = 0;
+        while( amountRead != -1 && totalAmountRead < bufferLength )
+        {
+            amountRead = this.read( tmpBuffer, totalAmountRead, bufferLength - totalAmountRead );
+            if( amountRead != -1 )
+            {
+                totalAmountRead += amountRead;
+            }
+        }
+        this.unread( tmpBuffer, 0, totalAmountRead );
+    }
+    
+    /**
+     * Reads a given number of bytes from the underlying stream.
+     * @param length the number of bytes to be read
+     * @return a byte array containing the bytes just read
+     * @throws IOException if an I/O error occurs while reading data
+     */
+    public byte[] readFully(int length) throws IOException
+    {
+        byte[] data = new byte[length];
+        int pos = 0;
+        while (pos < length)
+        {
+            int amountRead = read( data, pos, length - pos );
+            if (amountRead < 0) 
+            {
+                throw new EOFException("Premature end of file");
+            }
+            pos += amountRead;
+        }
+        return data;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccess.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccess.java
new file mode 100644 (file)
index 0000000..738623e
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.IOException;
+
+/**
+ * An interface to allow PDF files to be stored completely in memory or
+ * to use a scratch file on the disk.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public interface RandomAccess
+{
+
+    /**
+     * Release resources that are being held.
+     *
+     * @throws IOException If there is an error closing this resource.
+     */
+    public void close() throws IOException;
+
+    /**
+     * Seek to a position in the data.
+     *
+     * @param position The position to seek to.
+     * @throws IOException If there is an error while seeking.
+     */
+    public void seek(long position) throws IOException;
+
+    /**
+     * Read a single byte of data.
+     *
+     * @return The byte of data that is being read.
+     *
+     * @throws IOException If there is an error while reading the data.
+     */
+    public int read() throws IOException;
+
+    /**
+     * Read a buffer of data.
+     *
+     * @param b The buffer to write the data to.
+     * @param offset Offset into the buffer to start writing.
+     * @param length The amount of data to attempt to read.
+     * @return The number of bytes that were actually read.
+     * @throws IOException If there was an error while reading the data.
+     */
+    public int read(byte[] b, int offset, int length) throws IOException;
+
+    /**
+     * The total number of bytes that are available.
+     *
+     * @return The number of bytes available.
+     *
+     * @throws IOException If there is an IO error while determining the
+     * length of the data stream.
+     */
+    public long length() throws IOException;
+
+    /**
+     * Write a byte to the stream.
+     *
+     * @param b The byte to write.
+     * @throws IOException If there is an IO error while writing.
+     */
+    public void write(int b) throws IOException;
+
+    /**
+     * Write a buffer of data to the stream.
+     *
+     * @param b The buffer to get the data from.
+     * @param offset An offset into the buffer to get the data from.
+     * @param length The length of data to write.
+     * @throws IOException If there is an error while writing the data.
+     */
+    public void write(byte[] b, int offset, int length) throws IOException;
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessBuffer.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessBuffer.java
new file mode 100644 (file)
index 0000000..d91f757
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.IOException;
+
+/**
+ * An interface to allow PDF files to be stored completely in memory.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class RandomAccessBuffer implements RandomAccess
+{
+
+    private byte[] buffer;
+    private long pointer;
+    private long size;
+
+    /**
+     * Default constructor.
+     */
+    public RandomAccessBuffer()
+    {
+        // starting with a 16kb buffer
+        buffer = new byte[16384];
+        pointer = 0;
+        size = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws IOException
+    {
+        buffer = null;
+        pointer = 0;
+        size = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void seek(long position) throws IOException
+    {
+        this.pointer = position;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int read() throws IOException
+    {
+        if (pointer >= this.size)
+        {
+            return -1;
+        }
+        return buffer[(int)pointer++] & 0xff;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int read(byte[] b, int offset, int length) throws IOException
+    {
+        if (pointer >= this.size)
+        {
+            return 0;
+        }
+        int maxLength = (int) Math.min(length, this.size-pointer);
+        System.arraycopy(buffer, (int) pointer, b, offset, maxLength);
+        pointer += maxLength;
+        return maxLength;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long length() throws IOException
+    {
+        return size;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void write(int b) throws IOException
+    {
+        if (pointer >= buffer.length)
+        {
+            if (pointer >= Integer.MAX_VALUE) 
+            {
+                throw new IOException("RandomAccessBuffer overflow");
+            }
+            buffer = expandBuffer(buffer, (int)Math.min(2L * buffer.length, Integer.MAX_VALUE));
+        }
+        buffer[(int)pointer++] = (byte)b;
+        if (pointer > this.size)
+        {
+            this.size = pointer;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void write(byte[] b, int offset, int length) throws IOException
+    {
+        long newSize = pointer+length;
+        if (newSize >= buffer.length)
+        {
+            if (newSize > Integer.MAX_VALUE) 
+            {
+                throw new IOException("RandomAccessBuffer overflow");
+            }
+            newSize = Math.min(Math.max(2L * buffer.length, newSize), Integer.MAX_VALUE);
+            buffer = expandBuffer(buffer, (int)newSize);
+        }
+        System.arraycopy(b, offset, buffer, (int)pointer, length);
+        pointer += length;
+        if (pointer > this.size)
+        {
+            this.size = pointer;
+        }
+    }
+
+    /**
+     * expand the given buffer to the new size.
+     * 
+     * @param buffer the given buffer
+     * @param newSize the new size
+     * @return the expanded buffer
+     * 
+     */
+    private byte[] expandBuffer(byte[] buffer, int newSize) 
+    {
+        byte[] expandedBuffer = new byte[newSize];
+        System.arraycopy(buffer, 0, expandedBuffer, 0, buffer.length);
+        return expandedBuffer;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFile.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFile.java
new file mode 100644 (file)
index 0000000..1b0105f
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * An interface to allow temp PDF data to be stored in a scratch
+ * file on the disk to reduce memory consumption.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class RandomAccessFile implements RandomAccess
+{
+    private java.io.RandomAccessFile ras;
+
+    /**
+     * Constructor.
+     *
+     * @param file The file to write the data to.
+     * @param mode The writing mode.
+     * @throws FileNotFoundException If the file cannot be created.
+     */
+    public RandomAccessFile(File file, String mode) throws FileNotFoundException
+    {
+        ras = new java.io.RandomAccessFile(file, mode);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws IOException
+    {
+        ras.close();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void seek(long position) throws IOException
+    {
+        ras.seek(position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int read() throws IOException
+    {
+        return ras.read();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int read(byte[] b, int offset, int length) throws IOException
+    {
+        return ras.read(b, offset, length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long length() throws IOException
+    {
+        return ras.length();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void write(byte[] b, int offset, int length) throws IOException
+    {
+        ras.write(b, offset, length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void write(int b) throws IOException
+    {
+        ras.write(b);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFileInputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFileInputStream.java
new file mode 100644 (file)
index 0000000..4b85526
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This class allows a section of a RandomAccessFile to be accessed as an
+ * input stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class RandomAccessFileInputStream extends InputStream
+{
+    private RandomAccess file;
+    private long currentPosition;
+    private long endPosition;
+
+    /**
+     * Constructor.
+     *
+     * @param raFile The file to read the data from.
+     * @param startPosition The position in the file that this stream starts.
+     * @param length The length of the input stream.
+     */
+    public RandomAccessFileInputStream( RandomAccess raFile, long startPosition, long length )
+    {
+        file = raFile;
+        currentPosition = startPosition;
+        endPosition = currentPosition+length;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public int available()
+    {
+        return (int)(endPosition - currentPosition);
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public void close()
+    {
+        //do nothing because we want to leave the random access file open.
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public int read() throws IOException
+    {
+        synchronized(file)
+        {
+            int retval = -1;
+            if( currentPosition < endPosition )
+            {
+                file.seek( currentPosition );
+                currentPosition++;
+                retval = file.read();
+            }
+            return retval;
+        }
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public int read( byte[] b, int offset, int length ) throws IOException
+    {
+        //only allow a read of the amount available.
+        if( length > available() )
+        {
+            length = available();
+        }
+        int amountRead = -1;
+        //only read if there are bytes actually available, otherwise
+        //return -1 if the EOF has been reached.
+        if( available() > 0 )
+        {
+            synchronized(file)
+            {
+                file.seek( currentPosition );
+                amountRead = file.read( b, offset, length );
+            }
+        }
+        //update the current cursor position.
+        if( amountRead > 0 )
+        {
+            currentPosition += amountRead;
+        }
+        return amountRead;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long skip( long amountToSkip )
+    {
+        long amountSkipped = Math.min( amountToSkip, available() );
+        currentPosition+= amountSkipped;
+        return amountSkipped;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFileOutputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/RandomAccessFileOutputStream.java
new file mode 100644 (file)
index 0000000..d0ddbc2
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSNumber;
+
+/**
+ * This will write to a RandomAccessFile in the filesystem and keep track
+ * of the position it is writing to and the length of the stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class RandomAccessFileOutputStream extends OutputStream
+{
+    private RandomAccess file;
+    private long position;
+    private long lengthWritten = 0;
+    private COSBase expectedLength = null;
+
+    /**
+     * Constructor to create an output stream that will write to the end of a
+     * random access file.
+     *
+     * @param raf The file to write to.
+     *
+     * @throws IOException If there is a problem accessing the raf.
+     */
+    public RandomAccessFileOutputStream( RandomAccess raf ) throws IOException
+    {
+        file = raf;
+        //first get the position that we will be writing to
+        position = raf.length();
+    }
+
+    /**
+     * This will get the position in the RAF that the stream was written
+     * to.
+     *
+     * @return The position in the raf where the file can be obtained.
+     */
+    public long getPosition()
+    {
+        return position;
+    }
+
+    /**
+     * Get the amount of data that was actually written to the stream, in theory this
+     * should be the same as the length specified but in some cases it doesn't match.
+     *
+     * @return The number of bytes actually written to this stream.
+     */
+    public long getLengthWritten()
+    {
+        return lengthWritten;
+    }
+
+    /**
+     * The number of bytes written to the stream.
+     *
+     * @return The number of bytes read to the stream.
+     */
+    public long getLength()
+    {
+        long length = -1;
+        if( expectedLength instanceof COSNumber )
+        {
+            length = ((COSNumber)expectedLength).intValue();
+        }
+        else if( expectedLength instanceof COSObject &&
+                 ((COSObject)expectedLength).getObject() instanceof COSNumber )
+        {
+            length = ((COSNumber)((COSObject)expectedLength).getObject()).intValue();
+        }
+        if( length == -1 )
+        {
+            length = lengthWritten;
+        }
+        return length;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void write( byte[] b, int offset, int length ) throws IOException
+    {
+        file.seek( position+lengthWritten );
+        lengthWritten += length;
+        file.write( b, offset, length );
+
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public void write( int b ) throws IOException
+    {
+        file.seek( position+lengthWritten );
+        lengthWritten++;
+        file.write( b );
+    }
+
+    /**
+     * This will get the length that the PDF document specified this stream
+     * should be.  This may not match the number of bytes read.
+     *
+     * @return The expected length.
+     */
+    public COSBase getExpectedLength()
+    {
+        return expectedLength;
+    }
+
+    /**
+     * This will set the expected length of this stream.
+     *
+     * @param value The expected value.
+     */
+    public void setExpectedLength(COSBase value)
+    {
+        expectedLength = value;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/io/package.html
new file mode 100644 (file)
index 0000000..5ac24bd
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains IO streams.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/package.html
new file mode 100644 (file)
index 0000000..987dc03
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package holds executable classes that interact with the PDFBox application.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/BaseParser.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/BaseParser.java
new file mode 100644 (file)
index 0000000..10c76c4
--- /dev/null
@@ -0,0 +1,1388 @@
+/*
+ * 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.pdfbox.pdfparser;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.io.PushBackInputStream;
+import org.apache.pdfbox.io.RandomAccess;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This class is used to contain parsing logic that will be used by both the
+ * PDFParser and the COSStreamParser.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.61 $
+ */
+public abstract class BaseParser
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(BaseParser.class);
+
+    private static final int E = 'e';
+    private static final int N = 'n';
+    private static final int D = 'd';
+
+    private static final int S = 's';
+    private static final int T = 't';
+    private static final int R = 'r';
+    //private static final int E = 'e';
+    private static final int A = 'a';
+    private static final int M = 'm';
+
+    private static final int O = 'o';
+    private static final int B = 'b';
+    private static final int J = 'j';
+
+    /**
+     * This is a byte array that will be used for comparisons.
+     */
+    public static final byte[] ENDSTREAM =
+        new byte[] { E, N, D, S, T, R, E, A, M };
+
+    /**
+     * This is a byte array that will be used for comparisons.
+     */
+    public static final byte[] ENDOBJ =
+        new byte[] { E, N, D, O, B, J };
+
+    /**
+     * This is a byte array that will be used for comparisons.
+     */
+    public static final String DEF = "def";
+
+    /**
+     * Default value of the {@link #forceParsing} flag.
+     */
+    protected static final boolean FORCE_PARSING =
+        Boolean.getBoolean("org.apache.pdfbox.forceParsing");
+
+    /**
+     * This is the stream that will be read from.
+     */
+    protected PushBackInputStream pdfSource;
+
+    /**
+     * This is the document that will be parsed.
+     */
+    protected COSDocument document;
+
+    /**
+     * Flag to skip malformed or otherwise unparseable input where possible.
+     */
+    protected final boolean forceParsing;
+
+    /**
+     * Constructor.
+     *
+     * @since Apache PDFBox 1.3.0
+     * @param input The input stream to read the data from.
+     * @param forceParcing flag to skip malformed or otherwise unparseable
+     *                     input where possible
+     * @throws IOException If there is an error reading the input stream.
+     */
+    public BaseParser(InputStream input, boolean forceParsing)
+            throws IOException {
+        this.pdfSource = new PushBackInputStream(
+                new BufferedInputStream(input, 16384),  4096);
+        this.forceParsing = forceParsing;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param input The input stream to read the data from.
+     * @throws IOException If there is an error reading the input stream.
+     */
+    public BaseParser(InputStream input) throws IOException {
+        this(input, FORCE_PARSING);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param input The array to read the data from.
+     * @throws IOException If there is an error reading the byte data.
+     */
+    protected BaseParser(byte[] input) throws IOException {
+        this(new ByteArrayInputStream(input));
+    }
+
+    /**
+     * Set the document for this stream.
+     *
+     * @param doc The current document.
+     */
+    public void setDocument( COSDocument doc )
+    {
+        document = doc;
+    }
+
+    private static boolean isHexDigit(char ch)
+    {
+        return (ch >= '0' && ch <= '9') ||
+        (ch >= 'a' && ch <= 'f') ||
+        (ch >= 'A' && ch <= 'F');
+        // the line below can lead to problems with certain versions of the IBM JIT compiler
+        // (and is slower anyway)
+        //return (HEXDIGITS.indexOf(ch) != -1);
+    }
+
+    /**
+     * This will parse a PDF dictionary value.
+     *
+     * @return The parsed Dictionary object.
+     *
+     * @throws IOException If there is an error parsing the dictionary object.
+     */
+    private COSBase parseCOSDictionaryValue() throws IOException
+    {
+        COSBase retval = null;
+        COSBase number = parseDirObject();
+        skipSpaces();
+        char next = (char)pdfSource.peek();
+        if( next >= '0' && next <= '9' )
+        {
+            COSBase generationNumber = parseDirObject();
+            skipSpaces();
+            char r = (char)pdfSource.read();
+            if( r != 'R' )
+            {
+                throw new IOException( "expected='R' actual='" + r + "' " + pdfSource );
+            }
+            COSObjectKey key = new COSObjectKey(((COSInteger) number).intValue(),
+                    ((COSInteger) generationNumber).intValue());
+            retval = document.getObjectFromPool(key);
+        }
+        else
+        {
+            retval = number;
+        }
+        return retval;
+    }
+
+    /**
+     * This will parse a PDF dictionary.
+     *
+     * @return The parsed dictionary.
+     *
+     * @throws IOException IF there is an error reading the stream.
+     */
+    protected COSDictionary parseCOSDictionary() throws IOException
+    {
+        char c = (char)pdfSource.read();
+        if( c != '<')
+        {
+            throw new IOException( "expected='<' actual='" + c + "'" );
+        }
+        c = (char)pdfSource.read();
+        if( c != '<')
+        {
+            throw new IOException( "expected='<' actual='" + c + "' " + pdfSource );
+        }
+        skipSpaces();
+        COSDictionary obj = new COSDictionary();
+        boolean done = false;
+        while( !done )
+        {
+            skipSpaces();
+            c = (char)pdfSource.peek();
+            if( c == '>')
+            {
+                done = true;
+            } 
+            else 
+                if(c != '/') 
+                {
+                    //an invalid dictionary, we are expecting
+                    //the key, read until we can recover
+                    log.warn("Invalid dictionary, found: '" + (char)c + "' but expected: '/'");
+                    int read = pdfSource.read();
+                    while(read != -1 && read != '/' && read != '>')
+                    {
+                        // in addition to stopping when we find / or >, we also want 
+                        // to stop when we find endstream or endobj.
+                        if(read==E) {
+                            read = pdfSource.read();
+                            if(read==N) {
+                                read = pdfSource.read();
+                                if(read==D) {
+                                    read = pdfSource.read();
+                                    if(read==S) {
+                                        read = pdfSource.read();
+                                        if(read==T) {
+                                            read = pdfSource.read();
+                                            if(read==R) {
+                                                read = pdfSource.read();
+                                                if(read==E) {
+                                                    read = pdfSource.read();
+                                                    if(read==A) {
+                                                        read = pdfSource.read();
+                                                        if(read==M) {
+                                                            return obj; // we're done reading this object!
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    } else if(read==O) {
+                                        read = pdfSource.read();
+                                        if(read==B) {
+                                            read = pdfSource.read();
+                                            if(read==J) {
+                                                return obj; // we're done reading this object!
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        read = pdfSource.read();
+                    }
+                    if(read != -1) 
+                    {
+                        pdfSource.unread(read);
+                    }
+                    else
+                    {
+                        return obj;
+                    }
+                }
+            else
+            {
+                COSName key = parseCOSName();
+                COSBase value = parseCOSDictionaryValue();
+                skipSpaces();
+                if( ((char)pdfSource.peek()) == 'd' )
+                {
+                    //if the next string is 'def' then we are parsing a cmap stream
+                    //and want to ignore it, otherwise throw an exception.
+                    String potentialDEF = readString();
+                    if( !potentialDEF.equals( DEF ) )
+                    {
+                        pdfSource.unread( potentialDEF.getBytes("ISO-8859-1") );
+                    }
+                    else
+                    {
+                        skipSpaces();
+                    }
+                }
+
+                if( value == null )
+                {
+                    log.warn("Bad Dictionary Declaration " + pdfSource );
+                }
+                else
+                {
+                    obj.setItem( key, value );
+                }
+            }
+        }
+        char ch = (char)pdfSource.read();
+        if( ch != '>' )
+        {
+            throw new IOException( "expected='>' actual='" + ch + "'" );
+        }
+        ch = (char)pdfSource.read();
+        if( ch != '>' )
+        {
+            throw new IOException( "expected='>' actual='" + ch + "'" );
+        }
+        return obj;
+    }
+
+    /**
+     * This will read a COSStream from the input stream.
+     *
+     * @param file The file to write the stream to when reading.
+     * @param dic The dictionary that goes with this stream.
+     *
+     * @return The parsed pdf stream.
+     *
+     * @throws IOException If there is an error reading the stream.
+     */
+    protected COSStream parseCOSStream( COSDictionary dic, RandomAccess file ) throws IOException
+    {
+        COSStream stream = new COSStream( dic, file );
+        OutputStream out = null;
+        try
+        {
+            String streamString = readString();
+            //long streamLength;
+
+            if (!streamString.equals("stream"))
+            {
+                throw new IOException("expected='stream' actual='" + streamString + "'");
+            }
+
+            //PDF Ref 3.2.7 A stream must be followed by either
+            //a CRLF or LF but nothing else.
+
+            int whitespace = pdfSource.read();
+
+            //see brother_scan_cover.pdf, it adds whitespaces
+            //after the stream but before the start of the
+            //data, so just read those first
+            while (whitespace == 0x20)
+            {
+                whitespace = pdfSource.read();
+            }
+
+            if( whitespace == 0x0D )
+            {
+                whitespace = pdfSource.read();
+                if( whitespace != 0x0A )
+                {
+                    pdfSource.unread( whitespace );
+                    //The spec says this is invalid but it happens in the real
+                    //world so we must support it.
+                }
+            }
+            else if (whitespace == 0x0A)
+            {
+                //that is fine
+            }
+            else
+            {
+                //we are in an error.
+                //but again we will do a lenient parsing and just assume that everything
+                //is fine
+                pdfSource.unread( whitespace );
+            }
+
+            /*This needs to be dic.getItem because when we are parsing, the underlying object
+             * might still be null.
+             */
+            COSBase streamLength = dic.getItem(COSName.LENGTH);
+
+            //Need to keep track of the
+            out = stream.createFilteredStream( streamLength );
+
+            String endStream = null;
+            readUntilEndStream(out);
+            skipSpaces();
+            endStream = readString(); 
+
+            if (!endStream.equals("endstream"))
+            {
+                /*
+                 * Sometimes stream objects don't have an endstream tag so readUntilEndStream(out)
+                 * also can stop on endobj tags. If that's the case we need to make sure to unread
+                 * the endobj so parseObject() can handle that case normally. 
+                 */
+                if (endStream.startsWith("endobj"))
+                {
+                    byte[] endobjarray = endStream.getBytes("ISO-8859-1");
+                    pdfSource.unread(endobjarray);
+                }
+                /*
+                 * Some PDF files don't contain a new line after endstream so we 
+                 * need to make sure that the next object number is getting read separately
+                 * and not part of the endstream keyword. Ex. Some files would have "endstream8"
+                 * instead of "endstream"
+                 */
+                else if(endStream.startsWith("endstream"))
+                {
+                    String extra = endStream.substring(9, endStream.length());
+                    endStream = endStream.substring(0, 9);
+                    byte[] array = extra.getBytes("ISO-8859-1");
+                    pdfSource.unread(array);
+                }
+                else
+                {
+                    /*
+                     * If for some reason we get something else here, Read until we find the next
+                     * "endstream"
+                     */
+                    readUntilEndStream( out );
+                    endStream = readString();
+                    if( !endStream.equals( "endstream" ) )
+                    {
+                        throw new IOException("expected='endstream' actual='" + endStream + "' " + pdfSource);
+                    }
+                }
+            }
+        }
+        finally
+        {
+            if( out != null )
+            {
+                out.close();
+            }
+        }
+        return stream;
+    }
+
+    /**
+     * This method will read through the current stream object until
+     * we find the keyword "endstream" meaning we're at the end of this
+     * object. Some pdf files, however, forget to write some endstream tags
+     * and just close off objects with an "endobj" tag so we have to handle
+     * this case as well.
+     * @param out The stream we write out to. 
+     * @throws IOException
+     */    
+    private void readUntilEndStream( OutputStream out ) throws IOException{
+        int byteRead;
+        do{ //use a fail fast test for end of stream markers
+            byteRead = pdfSource.read();
+            if(byteRead==E){//only branch if "e"
+                byteRead = pdfSource.read();
+                if(byteRead==N){ //only continue branch if "en"
+                    byteRead = pdfSource.read();
+                    if(byteRead==D){//up to "end" now
+                        byteRead = pdfSource.read();
+                        if(byteRead==S){
+                            byteRead = pdfSource.read();
+                            if(byteRead==T){
+                                byteRead = pdfSource.read();
+                                if(byteRead==R){
+                                    byteRead = pdfSource.read();
+                                    if(byteRead==E){
+                                        byteRead = pdfSource.read();
+                                        if(byteRead==A){
+                                            byteRead = pdfSource.read();
+                                            if(byteRead==M){
+                                                //found the whole marker
+                                                pdfSource.unread( ENDSTREAM );
+                                                return;
+                                            }else{
+                                                out.write(ENDSTREAM, 0, 8);
+                                            }
+                                        }else{
+                                            out.write(ENDSTREAM, 0, 7);
+                                        }
+                                    }else{
+                                        out.write(ENDSTREAM, 0, 6);
+                                    }
+                                }else{
+                                    out.write(ENDSTREAM, 0, 5);
+                                }
+                            }else{
+                                out.write(ENDSTREAM, 0, 4);
+                            }
+                        }else if(byteRead==O){
+                            byteRead = pdfSource.read();
+                            if(byteRead==B){
+                                byteRead = pdfSource.read();
+                                if(byteRead==J){
+                                    //found whole marker
+                                    pdfSource.unread( ENDOBJ );
+                                    return;
+                                }else{
+                                    out.write(ENDOBJ, 0, 5);
+                                }
+                            }else{
+                                out.write(ENDOBJ, 0, 4);
+                            }
+                        }else{
+                            out.write(E);
+                            out.write(N);
+                            out.write(D);
+                        }
+                    }else{
+                        out.write(E);
+                        out.write(N);
+                    }
+                }else{
+                    out.write(E);
+                }
+            }
+            if(byteRead!=-1)out.write(byteRead);
+
+        }while(byteRead!=-1);
+    }
+
+    /**
+     * This is really a bug in the Document creators code, but it caused a crash
+     * in PDFBox, the first bug was in this format:
+     * /Title ( (5)
+     * /Creator which was patched in 1 place.
+     * However it missed the case where the Close Paren was escaped
+     * 
+     * The second bug was in this format 
+     * /Title (c:\)
+     * /Producer 
+     * 
+     * This patch  moves this code out of the parseCOSString method, so it can be used twice.
+     * 
+     * 
+     * @param bracesParameter the number of braces currently open.
+     * 
+     * @return the corrected value of the brace counter
+     * @throws IOException
+     */
+       private int checkForMissingCloseParen(final int bracesParameter) throws IOException {
+               int braces=bracesParameter;
+           byte[] nextThreeBytes = new byte[3];
+           int amountRead = pdfSource.read(nextThreeBytes);
+       
+           //lets handle the special case seen in Bull  River Rules and Regulations.pdf
+           //The dictionary looks like this
+           //    2 0 obj
+           //    <<
+           //        /Type /Info
+           //        /Creator (PaperPort http://www.scansoft.com)
+           //        /Producer (sspdflib 1.0 http://www.scansoft.com)
+           //        /Title ( (5)
+           //        /Author ()
+           //        /Subject ()
+           //
+           // Notice the /Title, the braces are not even but they should
+           // be.  So lets assume that if we encounter an this scenario
+           //   <end_brace><new_line><opening_slash> then that
+           // means that there is an error in the pdf and assume that
+           // was the end of the document.
+           //
+           if( amountRead == 3 )
+           {
+               if(( nextThreeBytes[0] == 0x0d &&  // Look for a carriage return
+                    nextThreeBytes[1] == 0x0a &&  // Look for a new line
+                    nextThreeBytes[2] == 0x2f ) || // Look for a slash / 
+                                                  // Add a second case without a new line
+                   (nextThreeBytes[0] == 0x0d &&  // Look for a carriage return
+                    nextThreeBytes[1] == 0x2f ))  // Look for a slash /
+               {
+                   braces = 0;
+               }
+           }
+           if (amountRead > 0) 
+        {
+            pdfSource.unread( nextThreeBytes, 0, amountRead );
+        }
+           return braces;
+       }
+    /**
+     * This will parse a PDF string.
+     *
+     * @return The parsed PDF string.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected COSString parseCOSString() throws IOException
+    {
+        char nextChar = (char)pdfSource.read();
+        COSString retval = new COSString();
+        char openBrace;
+        char closeBrace;
+        if( nextChar == '(' )
+        {
+            openBrace = '(';
+            closeBrace = ')';
+        }
+        else if( nextChar == '<' )
+        {
+            openBrace = '<';
+            closeBrace = '>';
+        }
+        else
+        {
+            throw new IOException( "parseCOSString string should start with '(' or '<' and not '" +
+                    nextChar + "' " + pdfSource );
+        }
+
+        //This is the number of braces read
+        //
+        int braces = 1;
+        int c = pdfSource.read();
+        while( braces > 0 && c != -1)
+        {
+            char ch = (char)c;
+            int nextc = -2; // not yet read
+
+            if(ch == closeBrace)
+            {
+               
+               braces--;
+               braces=checkForMissingCloseParen(braces);
+                if( braces != 0 )
+                {
+                    retval.append( ch );
+                }
+            }
+            else if( ch == openBrace )
+            {
+                braces++;
+                retval.append( ch );
+            }
+            else if( ch == '\\' )
+            {
+                //patched by ram
+                char next = (char)pdfSource.read();
+                switch(next)
+                {
+                case 'n':
+                    retval.append( '\n' );
+                    break;
+                case 'r':
+                    retval.append( '\r' );
+                    break;
+                case 't':
+                    retval.append( '\t' );
+                    break;
+                case 'b':
+                    retval.append( '\b' );
+                    break;
+                case 'f':
+                    retval.append( '\f' );
+                    break;
+                case ')':
+                       // PDFBox 276 /Title (c:\)
+                       braces=checkForMissingCloseParen(braces);
+                    if( braces != 0 )
+                    {
+                        retval.append( next );
+                    }
+                    else {
+                       retval.append('\\');
+                    }
+                    break;
+                case '(':
+                case '\\':
+                    retval.append( next );
+                    break;
+                case 10:
+                case 13:
+                    //this is a break in the line so ignore it and the newline and continue
+                    c = pdfSource.read();
+                    while( isEOL(c) && c != -1)
+                    {
+                        c = pdfSource.read();
+                    }
+                    nextc = c;
+                    break;
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                {
+                    StringBuffer octal = new StringBuffer();
+                    octal.append( next );
+                    c = pdfSource.read();
+                    char digit = (char)c;
+                    if( digit >= '0' && digit <= '7' )
+                    {
+                        octal.append( digit );
+                        c = pdfSource.read();
+                        digit = (char)c;
+                        if( digit >= '0' && digit <= '7' )
+                        {
+                            octal.append( digit );
+                        }
+                        else
+                        {
+                            nextc = c;
+                        }
+                    }
+                    else
+                    {
+                        nextc = c;
+                    }
+
+                    int character = 0;
+                    try
+                    {
+                        character = Integer.parseInt( octal.toString(), 8 );
+                    }
+                    catch( NumberFormatException e )
+                    {
+                        throw new IOException( "Error: Expected octal character, actual='" + octal + "'" );
+                    }
+                    retval.append( character );
+                    break;
+                }
+                default:
+                {
+                    retval.append( '\\' );
+                    retval.append( next );
+                    //another ficken problem with PDF's, sometimes the \ doesn't really
+                    //mean escape like the PDF spec says it does, sometimes is should be literal
+                    //which is what we will assume here.
+                    //throw new IOException( "Unexpected break sequence '" + next + "' " + pdfSource );
+                }
+                }
+            }
+            else
+            {
+                if( openBrace == '<' )
+                {
+                    if( isHexDigit(ch) )
+                    {
+                        retval.append( ch );
+                    }
+                }
+                else
+                {
+                    retval.append( ch );
+                }
+            }
+            if (nextc != -2)
+            {
+                c = nextc;
+            }
+            else
+            {
+                c = pdfSource.read();
+            }
+        }
+        if (c != -1)
+        {
+            pdfSource.unread(c);
+        }
+        if( openBrace == '<' )
+        {
+            retval = COSString.createFromHexString(
+                    retval.getString(), forceParsing);
+        }
+        return retval;
+    }
+
+    /**
+     * This will parse a PDF array object.
+     *
+     * @return The parsed PDF array.
+     *
+     * @throws IOException If there is an error parsing the stream.
+     */
+    protected COSArray parseCOSArray() throws IOException
+    {
+        char ch = (char)pdfSource.read();
+        if( ch != '[')
+        {
+            throw new IOException( "expected='[' actual='" + ch + "'" );
+        }
+        COSArray po = new COSArray();
+        COSBase pbo = null;
+        skipSpaces();
+        int i = 0;
+        while( ((i = pdfSource.peek()) > 0) && ((char)i != ']') )
+        {
+            pbo = parseDirObject();
+            if( pbo instanceof COSObject )
+            {
+                // We have to check if the expected values are there or not PDFBOX-385 
+                if (po.get(po.size()-1) instanceof COSInteger) 
+                {
+                    COSInteger genNumber = (COSInteger)po.remove( po.size() -1 );
+                    if (po.get(po.size()-1) instanceof COSInteger) 
+                    {
+                        COSInteger number = (COSInteger)po.remove( po.size() -1 );
+                        COSObjectKey key = new COSObjectKey(number.intValue(), genNumber.intValue());
+                        pbo = document.getObjectFromPool(key);
+                    }
+                    else 
+                    {
+                        // the object reference is somehow wrong
+                        pbo = null;
+                    }
+                }
+                else 
+                {
+                    pbo = null;
+                }
+            }
+            if( pbo != null )
+            {
+                po.add( pbo );
+            }
+            else
+            {
+                //it could be a bad object in the array which is just skipped
+                log.warn("Corrupt object reference" );
+                
+                // This could also be an "endobj" or "endstream" which means we can assume that
+                // the array has ended.
+                String isThisTheEnd = readString();
+                pdfSource.unread(isThisTheEnd.getBytes("ISO-8859-1"));
+                if("endobj".equals(isThisTheEnd) || "endstream".equals(isThisTheEnd))
+                    return po;
+            }
+            skipSpaces();
+        }
+        pdfSource.read(); //read ']'
+        skipSpaces();
+        return po;
+    }
+
+    /**
+     * Determine if a character terminates a PDF name.
+     *
+     * @param ch The character
+     * @return <code>true</code> if the character terminates a PDF name, otherwise <code>false</code>.
+     */
+    protected boolean isEndOfName(char ch)
+    {
+        return (ch == ' ' || ch == 13 || ch == 10 || ch == 9 || ch == '>' || ch == '<'
+            || ch == '[' || ch =='/' || ch ==']' || ch ==')' || ch =='(' ||
+            ch == -1 //EOF
+        );
+    }
+
+    /**
+     * This will parse a PDF name from the stream.
+     *
+     * @return The parsed PDF name.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected COSName parseCOSName() throws IOException
+    {
+        COSName retval = null;
+        int c = pdfSource.read();
+        if( (char)c != '/')
+        {
+            throw new IOException("expected='/' actual='" + (char)c + "'-" + c + " " + pdfSource );
+        }
+        // costruisce il nome
+        StringBuffer buffer = new StringBuffer();
+        c = pdfSource.read();
+        while( c != -1 )
+        {
+            char ch = (char)c;
+            if(ch == '#')
+            {
+                char ch1 = (char)pdfSource.read();
+                char ch2 = (char)pdfSource.read();
+
+                // Prior to PDF v1.2, the # was not a special character.  Also,
+                // it has been observed that various PDF tools do not follow the
+                // spec with respect to the # escape, even though they report
+                // PDF versions of 1.2 or later.  The solution here is that we
+                // interpret the # as an escape only when it is followed by two
+                // valid hex digits.
+                //
+                if (isHexDigit(ch1) && isHexDigit(ch2))
+                {
+                    String hex = "" + ch1 + ch2;
+                    try
+                    {
+                        buffer.append( (char) Integer.parseInt(hex, 16));
+                    }
+                    catch (NumberFormatException e)
+                    {
+                        throw new IOException("Error: expected hex number, actual='" + hex + "'");
+                    }
+                    c = pdfSource.read();
+                }
+                else
+                {
+                    pdfSource.unread(ch2);
+                    c = ch1;
+                    buffer.append( ch );
+                }
+            }
+            else if (isEndOfName(ch))
+            {
+                break;
+            }
+            else
+            {
+                buffer.append( ch );
+                c = pdfSource.read();
+            }
+        }
+        if (c != -1)
+        {
+            pdfSource.unread(c);
+        }
+        retval = COSName.getPDFName( buffer.toString() );
+        return retval;
+    }
+
+    /**
+     * This will parse a boolean object from the stream.
+     *
+     * @return The parsed boolean object.
+     *
+     * @throws IOException If an IO error occurs during parsing.
+     */
+    protected COSBoolean parseBoolean() throws IOException
+    {
+        COSBoolean retval = null;
+        char c = (char)pdfSource.peek();
+        if( c == 't' )
+        {
+            String trueString = new String( pdfSource.readFully( 4 ), "ISO-8859-1" );
+            if( !trueString.equals( "true" ) )
+            {
+                throw new IOException( "Error parsing boolean: expected='true' actual='" + trueString + "'" );
+            }
+            else
+            {
+                retval = COSBoolean.TRUE;
+            }
+        }
+        else if( c == 'f' )
+        {
+            String falseString = new String( pdfSource.readFully( 5 ), "ISO-8859-1" );
+            if( !falseString.equals( "false" ) )
+            {
+                throw new IOException( "Error parsing boolean: expected='true' actual='" + falseString + "'" );
+            }
+            else
+            {
+                retval = COSBoolean.FALSE;
+            }
+        }
+        else
+        {
+            throw new IOException( "Error parsing boolean expected='t or f' actual='" + c + "'" );
+        }
+        return retval;
+    }
+
+    /**
+     * This will parse a directory object from the stream.
+     *
+     * @return The parsed object.
+     *
+     * @throws IOException If there is an error during parsing.
+     */
+    protected COSBase parseDirObject() throws IOException
+    {
+        COSBase retval = null;
+
+        skipSpaces();
+        int nextByte = pdfSource.peek();
+        char c = (char)nextByte;
+        switch(c)
+        {
+        case '<':
+        {
+            int leftBracket = pdfSource.read();//pull off first left bracket
+            c = (char)pdfSource.peek(); //check for second left bracket
+            pdfSource.unread( leftBracket );
+            if(c == '<')
+            {
+
+                retval = parseCOSDictionary();
+                skipSpaces();
+            }
+            else
+            {
+                retval = parseCOSString();
+            }
+            break;
+        }
+        case '[': // array
+        {
+            retval = parseCOSArray();
+            break;
+        }
+        case '(':
+            retval = parseCOSString();
+            break;
+        case '/':   // name
+            retval = parseCOSName();
+            break;
+        case 'n':   // null
+        {
+            String nullString = readString();
+            if( !nullString.equals( "null") )
+            {
+                throw new IOException("Expected='null' actual='" + nullString + "'");
+            }
+            retval = COSNull.NULL;
+            break;
+        }
+        case 't':
+        {
+            String trueString = new String( pdfSource.readFully(4), "ISO-8859-1" );
+            if( trueString.equals( "true" ) )
+            {
+                retval = COSBoolean.TRUE;
+            }
+            else
+            {
+                throw new IOException( "expected true actual='" + trueString + "' " + pdfSource );
+            }
+            break;
+        }
+        case 'f':
+        {
+            String falseString = new String( pdfSource.readFully(5), "ISO-8859-1" );
+            if( falseString.equals( "false" ) )
+            {
+                retval = COSBoolean.FALSE;
+            }
+            else
+            {
+                throw new IOException( "expected false actual='" + falseString + "' " + pdfSource );
+            }
+            break;
+        }
+        case 'R':
+            pdfSource.read();
+            retval = new COSObject(null);
+            break;
+        case (char)-1:
+            return null;
+        default:
+        {
+            if( Character.isDigit(c) || c == '-' || c == '+' || c == '.')
+            {
+                StringBuffer buf = new StringBuffer();
+                int ic = pdfSource.read();
+                c = (char)ic;
+                while( Character.isDigit( c )||
+                        c == '-' ||
+                        c == '+' ||
+                        c == '.' ||
+                        c == 'E' ||
+                        c == 'e' )
+                {
+                    buf.append( c );
+                    ic = pdfSource.read();
+                    c = (char)ic;
+                }
+                if( ic != -1 )
+                {
+                    pdfSource.unread( ic );
+                }
+                retval = COSNumber.get( buf.toString() );
+            }
+            else
+            {
+                //This is not suppose to happen, but we will allow for it
+                //so we are more compatible with POS writers that don't
+                //follow the spec
+                String badString = readString();
+                //throw new IOException( "Unknown dir object c='" + c +
+                //"' peek='" + (char)pdfSource.peek() + "' " + pdfSource );
+                if( badString == null || badString.length() == 0 )
+                {
+                    int peek = pdfSource.peek();
+                    // we can end up in an infinite loop otherwise
+                    throw new IOException( "Unknown dir object c='" + c +
+                            "' cInt=" + (int)c + " peek='" + (char)peek + "' peekInt=" + peek + " " + pdfSource );
+                }
+                
+                // if it's an endstream/endobj, we want to put it back so the caller will see it
+                if("endobj".equals(badString) || "endstream".equals(badString))
+                    pdfSource.unread(badString.getBytes("ISO-8859-1"));
+            }
+        }
+        }
+        return retval;
+    }
+
+    /**
+     * This will read the next string from the stream.
+     *
+     * @return The string that was read from the stream.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected String readString() throws IOException
+    {
+        skipSpaces();
+        StringBuffer buffer = new StringBuffer();
+        int c = pdfSource.read();
+        while( !isEndOfName((char)c) && !isClosing(c) && c != -1 )
+        {
+            buffer.append( (char)c );
+            c = pdfSource.read();
+        }
+        if (c != -1)
+        {
+            pdfSource.unread(c);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * This will read bytes until the end of line marker occurs.
+     *
+     * @param theString The next expected string in the stream.
+     *
+     * @return The characters between the current position and the end of the line.
+     *
+     * @throws IOException If there is an error reading from the stream or theString does not match what was read.
+     */
+    protected String readExpectedString( String theString ) throws IOException
+    {
+        int c = pdfSource.read();
+        while( isWhitespace(c) && c != -1)
+        {
+            c = pdfSource.read();
+        }
+        StringBuffer buffer = new StringBuffer( theString.length() );
+        int charsRead = 0;
+        while( !isEOL(c) && c != -1 && charsRead < theString.length() )
+        {
+            char next = (char)c;
+            buffer.append( next );
+            if( theString.charAt( charsRead ) == next )
+            {
+                charsRead++;
+            }
+            else
+            {
+                pdfSource.unread(buffer.toString().getBytes("ISO-8859-1"));
+                throw new IOException( "Error: Expected to read '" + theString +
+                        "' instead started reading '" +buffer.toString() + "'" );
+            }
+            c = pdfSource.read();
+        }
+        while( isEOL(c) && c != -1 )
+        {
+            c = pdfSource.read();
+        }
+        if (c != -1)
+        {
+            pdfSource.unread(c);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * This will read the next string from the stream up to a certain length.
+     *
+     * @param length The length to stop reading at.
+     *
+     * @return The string that was read from the stream of length 0 to length.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected String readString( int length ) throws IOException
+    {
+        skipSpaces();
+
+        int c = pdfSource.read();
+
+        //average string size is around 2 and the normal string buffer size is
+        //about 16 so lets save some space.
+        StringBuffer buffer = new StringBuffer(length);
+        while( !isWhitespace(c) && !isClosing(c) && c != -1 && buffer.length() < length &&
+                c != '[' &&
+                c != '<' &&
+                c != '(' &&
+                c != '/' )
+        {
+            buffer.append( (char)c );
+            c = pdfSource.read();
+        }
+        if (c != -1)
+        {
+            pdfSource.unread(c);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * This will tell if the next character is a closing brace( close of PDF array ).
+     *
+     * @return true if the next byte is ']', false otherwise.
+     *
+     * @throws IOException If an IO error occurs.
+     */
+    protected boolean isClosing() throws IOException
+    {
+        return isClosing(pdfSource.peek());
+    }
+
+    /**
+     * This will tell if the next character is a closing brace( close of PDF array ).
+     *
+     * @param c The character to check against end of line
+     * @return true if the next byte is ']', false otherwise.
+     */
+    protected boolean isClosing(int c)
+    {
+        return c == ']';
+    }
+
+    /**
+     * This will read bytes until the first end of line marker occurs.
+     * Note: if you later unread the results of this function, you'll
+     * need to add a newline character to the end of the string.
+     *
+     * @return The characters between the current position and the end of the line.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected String readLine() throws IOException 
+    {
+        if (pdfSource.isEOF())
+        {
+            throw new IOException( "Error: End-of-File, expected line");
+        }
+
+        StringBuffer buffer = new StringBuffer( 11 );
+       
+        int c;
+        while ((c = pdfSource.read()) != -1) 
+        {
+            if (isEOL(c)) 
+            {
+                break;
+            }
+            buffer.append( (char)c );
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * This will tell if the next byte to be read is an end of line byte.
+     *
+     * @return true if the next byte is 0x0A or 0x0D.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected boolean isEOL() throws IOException
+    {
+        return isEOL(pdfSource.peek());
+    }
+
+    /**
+     * This will tell if the next byte to be read is an end of line byte.
+     *
+     * @param c The character to check against end of line
+     * @return true if the next byte is 0x0A or 0x0D.
+     */
+    protected boolean isEOL(int c)
+    {
+        return c == 10 || c == 13;
+    }
+
+    /**
+     * This will tell if the next byte is whitespace or not.
+     *
+     * @return true if the next byte in the stream is a whitespace character.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected boolean isWhitespace() throws IOException
+    {
+        return isWhitespace( pdfSource.peek() );
+    }
+
+    /**
+     * This will tell if the next byte is whitespace or not.
+     *
+     * @param c The character to check against whitespace
+     *
+     * @return true if the next byte in the stream is a whitespace character.
+     */
+    protected boolean isWhitespace( int c )
+    {
+        return c == 0 || c == 9 || c == 12  || c == 10
+        || c == 13 || c == 32;
+    }
+
+    /**
+     * This will skip all spaces and comments that are present.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected void skipSpaces() throws IOException
+    {
+        //log( "skipSpaces() " + pdfSource );
+        int c = pdfSource.read();
+        // identical to, but faster as: isWhiteSpace(c) || c == 37
+        while(c == 0 || c == 9 || c == 12  || c == 10
+                || c == 13 || c == 32 || c == 37)//37 is the % character, a comment
+        {
+            if ( c == 37 )
+            {
+                // skip past the comment section
+                c = pdfSource.read();
+                while(!isEOL(c) && c != -1)
+                {
+                    c = pdfSource.read();
+                }
+            }
+            else
+            {
+                c = pdfSource.read();
+            }
+        }
+        if (c != -1)
+        {
+            pdfSource.unread(c);
+        }
+        //log( "skipSpaces() done peek='" + (char)pdfSource.peek() + "'" );
+    }
+
+    /**
+     * This will read an integer from the stream.
+     *
+     * @return The integer that was read from the stream.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected int readInt() throws IOException
+    {
+        skipSpaces();
+        int retval = 0;
+
+        int lastByte = 0;
+        StringBuffer intBuffer = new StringBuffer();
+        while( (lastByte = pdfSource.read() ) != 32 &&
+                lastByte != 10 &&
+                lastByte != 13 &&
+                lastByte != 60 && //see sourceforge bug 1714707
+                lastByte != 0 && //See sourceforge bug 853328
+                lastByte != -1 )
+        {
+            intBuffer.append( (char)lastByte );
+        }
+        if( lastByte != -1 )
+        {
+            pdfSource.unread( lastByte );
+        }
+
+        try
+        {
+            retval = Integer.parseInt( intBuffer.toString() );
+        }
+        catch( NumberFormatException e )
+        {
+            pdfSource.unread(intBuffer.toString().getBytes("ISO-8859-1"));
+            throw new IOException( "Error: Expected an integer type, actual='" + intBuffer + "'" );
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFObjectStreamParser.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFObjectStreamParser.java
new file mode 100644 (file)
index 0000000..fc47c5c
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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.pdfbox.pdfparser;
+
+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.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+
+/**
+ * This will parse a PDF 1.5 object stream and extract all of the objects from the stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFObjectStreamParser extends BaseParser
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log =
+        LogFactory.getLog(PDFObjectStreamParser.class);
+
+    private List<COSObject> streamObjects = null;
+    private List<Integer> objectNumbers = null;
+    private COSStream stream;
+
+    /**
+     * Constructor.
+     *
+     * @since Apache PDFBox 1.3.0
+     * @param strm The stream to parse.
+     * @param doc The document for the current parsing.
+     * @param forceParcing flag to skip malformed or otherwise unparseable
+     *                     input where possible
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFObjectStreamParser(
+            COSStream strm, COSDocument doc, boolean forceParsing)
+            throws IOException {
+       super(strm.getUnfilteredStream(), forceParsing);
+       setDocument( doc );
+       stream = strm;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param strm The stream to parse.
+     * @param doc The document for the current parsing.
+     *
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFObjectStreamParser(COSStream strm, COSDocument doc)
+            throws IOException {
+        this(strm, doc, FORCE_PARSING);
+    }
+
+    /**
+     * This will parse the tokens in the stream.  This will close the
+     * stream when it is finished parsing.
+     *
+     * @throws IOException If there is an error while parsing the stream.
+     */
+    public void parse() throws IOException
+    {
+        try
+        {
+            //need to first parse the header.
+            int numberOfObjects = stream.getInt( "N" );
+            objectNumbers = new ArrayList<Integer>( numberOfObjects );
+            streamObjects = new ArrayList<COSObject>( numberOfObjects );
+            for( int i=0; i<numberOfObjects; i++ )
+            {
+                int objectNumber = readInt();
+                int offset = readInt();
+                objectNumbers.add( new Integer( objectNumber ) );
+            }
+            COSObject object = null;
+            COSBase cosObject = null;
+            int objectCounter = 0;
+            while( (cosObject = parseDirObject()) != null )
+            {
+                object = new COSObject(cosObject);
+                object.setGenerationNumber( COSInteger.ZERO );
+                COSInteger objNum =
+                    COSInteger.get( objectNumbers.get( objectCounter).intValue() );
+                object.setObjectNumber( objNum );
+                streamObjects.add( object );
+                if(log.isDebugEnabled())
+                {
+                    log.debug( "parsed=" + object );
+                }
+                objectCounter++;
+            }
+        }
+        finally
+        {
+            pdfSource.close();
+        }
+    }
+
+    /**
+     * This will get the objects that were parsed from the stream.
+     *
+     * @return All of the objects in the stream.
+     */
+    public List<COSObject> getObjects()
+    {
+        return streamObjects;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFParser.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFParser.java
new file mode 100644 (file)
index 0000000..31eea66
--- /dev/null
@@ -0,0 +1,888 @@
+/*
+ * 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.pdfbox.pdfparser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This class will handle the parsing of the PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.53 $
+ */
+public class PDFParser extends BaseParser
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDFParser.class);
+
+    private static final int SPACE_BYTE = 32;
+
+    private static final String PDF_HEADER = "%PDF-";
+    private static final String FDF_HEADER = "%FDF-";
+    /**
+     * A list of duplicate objects found when Parsing the PDF
+     * File. 
+     */
+    private List<ConflictObj> conflictList = new ArrayList<ConflictObj>();
+    
+    /** Collects all Xref/trailer objects and resolves them into single
+     *  object using startxref reference */
+    private XrefTrailerResolver xrefTrailerResolver = new XrefTrailerResolver();
+   
+    /**
+     * Temp file directory.
+     */
+    private File tempDirectory = null;
+
+    private RandomAccess raf = null;
+
+    /**
+     * Constructor.
+     *
+     * @param input The input stream that contains the PDF document.
+     *
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFParser( InputStream input ) throws IOException {
+        this(input, null, FORCE_PARSING);
+    }
+
+    /**
+     * Constructor to allow control over RandomAccessFile.
+     * @param input The input stream that contains the PDF document.
+     * @param rafi The RandomAccessFile to be used in internal COSDocument
+     *
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFParser(InputStream input, RandomAccess rafi)
+        throws IOException {
+        this(input, rafi, FORCE_PARSING);
+    }
+    
+    /**
+     * Constructor to allow control over RandomAccessFile.
+     * Also enables parser to skip corrupt objects to try and force parsing
+     * @param input The input stream that contains the PDF document.
+     * @param rafi The RandomAccessFile to be used in internal COSDocument
+     * @param force When true, the parser will skip corrupt pdf objects and 
+     * will continue parsing at the next object in the file
+     *
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFParser(InputStream input, RandomAccess rafi, boolean force)
+        throws IOException {
+        super(input, force);
+        this.raf = rafi;
+    }
+
+    /**
+     * This is the directory where pdfbox will create a temporary file
+     * for storing pdf document stream in.  By default this directory will
+     * be the value of the system property java.io.tmpdir.
+     *
+     * @param tmpDir The directory to create scratch files needed to store
+     *        pdf document streams.
+     */
+    public void setTempDirectory( File tmpDir )
+    {
+        tempDirectory = tmpDir;
+    }
+
+    /**
+     * Returns true if parsing should be continued. By default, forceParsing is returned. 
+     * This can be overridden to add application specific handling (for example to stop 
+     * parsing when the number of exceptions thrown exceed a certain number).
+     * 
+     * @param e The exception if vailable. Can be null if there is no exception available
+     */
+    protected boolean isContinueOnError(Exception e)
+    {
+        return forceParsing;
+    }
+    
+    /**
+     * This will parse the stream and populate the COSDocument object.  This will close
+     * the stream when it is done parsing.
+     *
+     * @throws IOException If there is an error reading from the stream or corrupt data
+     * is found.
+     */
+    public void parse() throws IOException
+    {
+        try
+        {
+            if ( raf == null )
+            {
+                if( tempDirectory != null )
+                {
+                    document = new COSDocument( tempDirectory );
+                }
+                else
+                {
+                    document = new COSDocument();
+                }
+            }
+            else
+            {
+                document = new COSDocument( raf );
+            }
+            setDocument( document );
+
+            parseHeader();
+            
+            //Some PDF files have garbage between the header and the
+            //first object
+            skipToNextObj();
+
+            boolean wasLastParsedObjectEOF = false;
+            while(true)
+            {
+                if(pdfSource.isEOF())
+                {
+                    break;
+                }
+                try
+                {
+                    wasLastParsedObjectEOF = parseObject();
+                }
+                catch(IOException e)
+                {
+                    /*
+                     * PDF files may have random data after the EOF marker. Ignore errors if
+                     * last object processed is EOF. 
+                     */
+                    if( wasLastParsedObjectEOF ) 
+                    {
+                        break;
+                    }
+                    if(isContinueOnError(e))
+                    {
+                        /*
+                         * Warning is sent to the PDFBox.log and to the Console that
+                         * we skipped over an object
+                         */
+                        log.warn("Parsing Error, Skipping Object", e);
+                        skipToNextObj();
+                    }
+                    else
+                    { 
+                        throw e;
+                    }
+                }
+                skipSpaces();
+            }
+            
+            // set xref to start with 
+            xrefTrailerResolver.setStartxref( document.getStartXref() );
+            
+            // get resolved xref table + trailer
+            document.setTrailer( xrefTrailerResolver.getTrailer() );
+            document.addXRefTable( xrefTrailerResolver.getXrefTable() );
+            
+            if( !document.isEncrypted() )
+            {
+                document.dereferenceObjectStreams();
+            }
+            ConflictObj.resolveConflicts(document, conflictList);     
+        }
+        catch( Throwable t )
+        {
+            //so if the PDF is corrupt then close the document and clear
+            //all resources to it
+            if( document != null )
+            {
+                document.close();
+            }
+            if( t instanceof IOException )
+            {
+                throw (IOException)t;
+            }
+            else
+            {
+                throw new WrappedIOException( t );
+            }
+        }
+        finally
+        {
+            pdfSource.close();
+        }
+    }
+    
+    /**
+     * Skip to the start of the next object.  This is used to recover
+     * from a corrupt object. This should handle all cases that parseObject
+     * supports. This assumes that the next object will
+     * start on its own line.
+     * 
+     * @throws IOException 
+     */
+    private void skipToNextObj() throws IOException 
+    {
+        byte[] b = new byte[16];
+        Pattern p = Pattern.compile("\\d+\\s+\\d+\\s+obj.*", Pattern.DOTALL);
+        /* Read a buffer of data each time to see if it starts with a
+         * known keyword. This is not the most efficient design, but we should
+         * rarely be needing this function. We could update this to use the 
+         * circular buffer, like in readUntilEndStream().
+         */
+        while(!pdfSource.isEOF())
+        {
+             int l = pdfSource.read(b);
+             if(l < 1)
+             {
+                 break;
+             }
+             String s = new String(b, "US-ASCII");  
+             if(s.startsWith("trailer") ||
+                     s.startsWith("xref") || 
+                     s.startsWith("startxref") ||
+                     s.startsWith("stream") ||
+                     p.matcher(s).matches())
+             {
+                 pdfSource.unread(b);
+                 break;
+             }
+             else
+             {
+                 pdfSource.unread(b, 1, l-1);
+             }
+        }   
+    }
+
+    private void parseHeader() throws IOException
+    {
+        // read first line
+        String header = readLine();
+        // some pdf-documents are broken and the pdf-version is in one of the following lines
+        if ((header.indexOf( PDF_HEADER ) == -1) && (header.indexOf( FDF_HEADER ) == -1))
+        {
+            header = readLine();
+            while ((header.indexOf( PDF_HEADER ) == -1) && (header.indexOf( FDF_HEADER ) == -1))
+            {
+                // if a line starts with a digit, it has to be the first one with data in it
+                if ((header.length() > 0) && (Character.isDigit(header.charAt(0))))
+                {
+                    break;
+                }
+                header = readLine();
+            }
+        }
+
+        // nothing found
+        if ((header.indexOf( PDF_HEADER ) == -1) && (header.indexOf( FDF_HEADER ) == -1))
+        {
+            throw new IOException( "Error: Header doesn't contain versioninfo" );
+        }
+        
+        //sometimes there are some garbage bytes in the header before the header
+        //actually starts, so lets try to find the header first.
+        int headerStart = header.indexOf( PDF_HEADER );
+        if (headerStart == -1)
+        {
+            headerStart = header.indexOf(FDF_HEADER);
+        }
+
+        //greater than zero because if it is zero then
+        //there is no point of trimming
+        if ( headerStart > 0 )
+        {
+            //trim off any leading characters
+            header = header.substring( headerStart, header.length() );
+        }
+
+        /*
+         * This is used if there is garbage after the header on the same line
+         */
+        if (header.startsWith(PDF_HEADER)) 
+        {
+            if(!header.matches(PDF_HEADER + "\\d.\\d")) 
+            {
+                String headerGarbage = header.substring(PDF_HEADER.length()+3, header.length()) + "\n";
+                header = header.substring(0, PDF_HEADER.length()+3);
+                pdfSource.unread(headerGarbage.getBytes("ISO-8859-1"));
+            }
+        }
+        else 
+        {
+            if(!header.matches(FDF_HEADER + "\\d.\\d")) 
+            {
+                String headerGarbage = header.substring(FDF_HEADER.length()+3, header.length()) + "\n";
+                header = header.substring(0, FDF_HEADER.length()+3);
+                pdfSource.unread(headerGarbage.getBytes("ISO-8859-1"));
+            }
+        }
+        document.setHeaderString(header);
+        
+        try
+        {
+            if (header.startsWith( PDF_HEADER )) 
+            {
+                float pdfVersion = Float. parseFloat(
+                        header.substring( PDF_HEADER.length(), Math.min( header.length(), PDF_HEADER .length()+3) ) );
+                document.setVersion( pdfVersion );
+            }
+            else 
+            {
+                float pdfVersion = Float. parseFloat(
+                        header.substring( FDF_HEADER.length(), Math.min( header.length(), FDF_HEADER.length()+3) ) );
+                document.setVersion( pdfVersion );
+            }
+        }
+        catch ( NumberFormatException e )
+        {
+            throw new IOException( "Error getting pdf version:" + e );
+        } 
+    } 
+
+    /**
+     * This will get the document that was parsed.  parse() must be called before this is called.
+     * When you are done with this document you must call close() on it to release
+     * resources.
+     *
+     * @return The document that was parsed.
+     *
+     * @throws IOException If there is an error getting the document.
+     */
+    public COSDocument getDocument() throws IOException
+    {
+        if( document == null )
+        {
+            throw new IOException( "You must call parse() before calling getDocument()" );
+        }
+        return document;
+    }
+
+    /**
+     * This will get the PD document that was parsed.  When you are done with
+     * this document you must call close() on it to release resources.
+     *
+     * @return The document at the PD layer.
+     *
+     * @throws IOException If there is an error getting the document.
+     */
+    public PDDocument getPDDocument() throws IOException
+    {
+        return new PDDocument( getDocument() );
+    }
+
+    /**
+     * This will get the FDF document that was parsed.  When you are done with
+     * this document you must call close() on it to release resources.
+     *
+     * @return The document at the PD layer.
+     *
+     * @throws IOException If there is an error getting the document.
+     */
+    public FDFDocument getFDFDocument() throws IOException
+    {
+        return new FDFDocument( getDocument() );
+    }
+
+    /**
+     * This will parse the next object from the stream and add it to 
+     * the local state. 
+     *
+     * @return Returns true if the processed object had an endOfFile marker
+     *
+     * @throws IOException If an IO error occurs.
+     */
+    private boolean parseObject() throws IOException
+    {
+        int currentObjByteOffset = pdfSource.getOffset();
+        boolean isEndOfFile = false; 
+        skipSpaces();
+        //peek at the next character to determine the type of object we are parsing
+        char peekedChar = (char)pdfSource.peek();
+        
+        //ignore endobj and endstream sections.
+        while( peekedChar == 'e' )
+        {
+            //there are times when there are multiple endobj, so lets
+            //just read them and move on.
+            readString();
+            skipSpaces();
+            peekedChar = (char)pdfSource.peek();
+        }
+        if( pdfSource.isEOF())
+        {
+            //"Skipping because of EOF" );
+            //end of file we will return a false and call it a day.
+        }
+        //xref table. Note: The contents of the Xref table are currently ignored
+        else if( peekedChar == 'x') 
+        {
+            parseXrefTable( currentObjByteOffset );
+        }
+        // Note: startxref can occur in either a trailer section or by itself 
+        else if (peekedChar == 't' || peekedChar == 's') 
+        {
+            if(peekedChar == 't')
+            {
+                parseTrailer();
+                peekedChar = (char)pdfSource.peek(); 
+            }
+            if (peekedChar == 's')
+            {  
+                parseStartXref();
+                // readString() calls skipSpaces() will skip comments... that's 
+                // bad for us b/c the %%EOF flag is a comment
+                while(isWhitespace(pdfSource.peek()) && !pdfSource.isEOF())
+                    pdfSource.read(); // read (get rid of) all the whitespace
+                String eof = "";
+                if(!pdfSource.isEOF())
+                    eof = readLine(); // if there's more data to read, get the EOF flag
+                
+                // verify that EOF exists (see PDFBOX-979 for documentation on special cases)
+                if(!"%%EOF".equals(eof)) {
+                    if(eof.startsWith("%%EOF")) {
+                        // content after marker -> unread with first space byte for read newline
+                        pdfSource.unread(SPACE_BYTE); // we read a whole line; add space as newline replacement 
+                        pdfSource.unread(eof.substring(5).getBytes("ISO-8859-1"));
+                    } else {
+                        // PDF does not conform to spec, we should warn someone 
+                        log.warn("expected='%%EOF' actual='" + eof + "'"); 
+                        // if we're not at the end of a file, just put it back and move on 
+                        if(!pdfSource.isEOF()) { 
+                            pdfSource.unread( SPACE_BYTE ); // we read a whole line; add space as newline replacement 
+                            pdfSource.unread(eof.getBytes("ISO-8859-1")); 
+                        }
+                    }
+                }
+                isEndOfFile = true; 
+            }
+        }
+        //we are going to parse an normal object
+        else
+        {
+            int number = -1;
+            int genNum = -1;
+            String objectKey = null;
+            boolean missingObjectNumber = false;
+            try
+            {
+                char peeked = (char)pdfSource.peek();
+                if( peeked == '<' )
+                {
+                    missingObjectNumber = true;
+                }
+                else
+                {
+                    number = readInt();
+                }
+            }
+            catch( IOException e )
+            {
+                //ok for some reason "GNU Ghostscript 5.10" puts two endobj
+                //statements after an object, of course this is nonsense
+                //but because we want to support as many PDFs as possible
+                //we will simply try again
+                number = readInt();
+            }
+            if( !missingObjectNumber )
+            {
+                skipSpaces();
+                genNum = readInt();
+
+                objectKey = readString( 3 );
+                //System.out.println( "parseObject() num=" + number +
+                //" genNumber=" + genNum + " key='" + objectKey + "'" );
+                if( !objectKey.equals( "obj" ) )
+                {
+                    if (!isContinueOnError(null) || !objectKey.equals("o")) {
+                        throw new IOException("expected='obj' actual='" + objectKey + "' " + pdfSource);
+                    }
+                    //assume that "o" was meant to be "obj" (this is a workaround for 
+                    // PDFBOX-773 attached PDF Andersens_Fairy_Tales.pdf). 
+                }
+            }
+            else
+            {
+                number = -1;
+                genNum = -1;
+            }
+
+            skipSpaces();
+            COSBase pb = parseDirObject();
+            String endObjectKey = readString();
+            
+            if( endObjectKey.equals( "stream" ) )
+            {
+                pdfSource.unread( endObjectKey.getBytes("ISO-8859-1") );
+                pdfSource.unread( ' ' );
+                if( pb instanceof COSDictionary )
+                {
+                    pb = parseCOSStream( (COSDictionary)pb, getDocument().getScratchFile() );
+                    
+                    // test for XRef type
+                    final COSStream strmObj = (COSStream) pb;
+                    final COSName objectType = (COSName)strmObj.getItem( COSName.TYPE );
+                    if( objectType != null && objectType.equals( COSName.XREF ) )
+                    {
+                        // XRef stream
+                       parseXrefStream( strmObj, currentObjByteOffset );
+                    }
+                }
+                else
+                {
+                    // this is not legal
+                    // the combination of a dict and the stream/endstream forms a complete stream object
+                    throw new IOException("stream not preceded by dictionary");
+                }
+                skipSpaces();
+                endObjectKey = readLine();
+            }
+            
+            COSObjectKey key = new COSObjectKey( number, genNum );
+            COSObject pdfObject = document.getObjectFromPool( key );
+            if(pdfObject.getObject() == null)
+            {
+                pdfObject.setObject(pb);
+            }
+            /*
+             * If the object we returned already has a baseobject, then we have a conflict
+             * which we will resolve using information after we parse the xref table.
+             */
+            else
+            {
+                addObjectToConflicts(currentObjByteOffset, key, pb); 
+            }
+            
+            if( !endObjectKey.equals( "endobj" ) )
+            {
+                if (endObjectKey.startsWith( "endobj" ) ) 
+                {
+                    /*
+                     * Some PDF files don't contain a new line after endobj so we 
+                     * need to make sure that the next object number is getting read separately
+                     * and not part of the endobj keyword. Ex. Some files would have "endobj28"
+                     * instead of "endobj"
+                     */
+                    pdfSource.unread( SPACE_BYTE ); // add a space first in place of the newline consumed by readline()
+                    pdfSource.unread( endObjectKey.substring( 6 ).getBytes("ISO-8859-1") );
+                } 
+                else if(endObjectKey.trim().endsWith("endobj"))
+                {
+                    /*
+                     * Some PDF files contain junk (like ">> ", in the case of a PDF
+                     * I found which was created by Exstream Dialogue Version 5.0.039)
+                     * in which case we ignore the data before endobj and just move on
+                     */
+                    log.warn("expected='endobj' actual='" + endObjectKey + "' ");
+                }
+                else if( !pdfSource.isEOF() )
+                {
+                    //It is possible that the endobj is missing, there
+                    //are several PDFs out there that do that so. Unread
+                    //and assume that endobj was missing
+                    pdfSource.unread( SPACE_BYTE ); // add a space first in place of the newline consumed by readline()
+                    pdfSource.unread( endObjectKey.getBytes("ISO-8859-1") );
+                }
+            }
+            skipSpaces();
+        }
+        return isEndOfFile;
+    }
+    
+   /**
+    * Adds a new ConflictObj to the conflictList.
+    * @param offset the offset of the ConflictObj
+    * @param key The COSObjectKey of this object
+    * @param pb The COSBase of this conflictObj
+    * @throws IOException
+    */
+    private void addObjectToConflicts(int offset, COSObjectKey key, COSBase pb) throws IOException
+    {
+        COSObject obj = new COSObject(null);
+        obj.setObjectNumber( COSInteger.get( key.getNumber() ) );
+        obj.setGenerationNumber( COSInteger.get( key.getGeneration() ) );
+        obj.setObject(pb);
+        ConflictObj conflictObj = new ConflictObj(offset, key, obj);
+        conflictList.add(conflictObj);   
+    }
+
+    /**
+     * This will parse the startxref section from the stream.
+     * The startxref value is ignored.
+     *            
+     * @return false on parsing error 
+     * @throws IOException If an IO error occurs.
+     */
+    private boolean parseStartXref() throws IOException
+    {
+        if(pdfSource.peek() != 's')
+        {
+            return false; 
+        }
+        String startXRef = readString();
+        if( !startXRef.trim().equals( "startxref" ) )
+        {
+            return false;
+        }
+        skipSpaces();
+        /* This integer is the byte offset of the first object referenced by the xref or xref stream
+         * Needed for the incremental update (PREV)
+         */
+        getDocument().setStartXref(readInt());
+        return true;
+    }
+
+
+    /**
+     * This will parse the xref table from the stream and add it to the state
+     * The XrefTable contents are ignored.
+     * @param startByteOffset the offset to start at           
+     * @return false on parsing error 
+     * @throws IOException If an IO error occurs.
+     */
+    private boolean parseXrefTable( int startByteOffset ) throws IOException
+    {
+        if(pdfSource.peek() != 'x')
+        {
+            return false;
+        }
+        String xref = readString();
+        if( !xref.trim().equals( "xref" ) ) 
+        {
+            return false;
+        }
+        
+        // signal start of new XRef
+        xrefTrailerResolver.nextXrefObj( startByteOffset );
+        
+        /*
+         * Xref tables can have multiple sections. 
+         * Each starts with a starting object id and a count.
+         */
+        while(true)
+        {
+            int currObjID = readInt(); // first obj id
+            int count = readInt(); // the number of objects in the xref table
+            skipSpaces();
+            for(int i = 0; i < count; i++)
+            {
+                if(pdfSource.isEOF() || isEndOfName((char)pdfSource.peek()))
+                {
+                    break;
+                }
+                if(pdfSource.peek() == 't')
+                {
+                    break;
+                }
+                //Ignore table contents
+                String currentLine = readLine();
+                String[] splitString = currentLine.split(" ");
+                if (splitString.length < 3)
+                {
+                    log.warn("invalid xref line: " + currentLine);
+                    break;
+                }
+                /* This supports the corrupt table as reported in 
+                 * PDFBOX-474 (XXXX XXX XX n) */
+                if(splitString[splitString.length-1].equals("n"))
+                {
+                    try
+                    {
+                        int currOffset = Integer.parseInt(splitString[0]);
+                        int currGenID = Integer.parseInt(splitString[1]);
+                        COSObjectKey objKey = new COSObjectKey(currObjID, currGenID);
+                        xrefTrailerResolver.setXRef(objKey, currOffset);
+                    }
+                    catch(NumberFormatException e)
+                    {
+                        throw new IOException(e.getMessage());
+                    }
+                }
+                else if(!splitString[2].equals("f"))
+                {
+                    throw new IOException("Corrupt XRefTable Entry - ObjID:" + currObjID);
+                }
+                currObjID++;
+                skipSpaces();
+            }
+            skipSpaces();
+            char c = (char)pdfSource.peek();
+            if(c < '0' || c > '9')
+            {
+                break;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * This will parse the trailer from the stream and add it to the state.
+     *            
+     * @return false on parsing error
+     * @throws IOException If an IO error occurs.
+     */
+    private boolean parseTrailer() throws IOException
+    {
+        if(pdfSource.peek() != 't')
+        {
+            return false;
+        }
+        //read "trailer"
+        String nextLine = readLine();
+        if( !nextLine.trim().equals( "trailer" ) ) 
+        {
+            // in some cases the EOL is missing and the trailer immediately 
+            // continues with "<<" or with a blank character
+            // even if this does not comply with PDF reference we want to support as many PDFs as possible
+            // Acrobat reader can also deal with this.
+            if (nextLine.startsWith("trailer")) 
+            {
+                byte[] b = nextLine.getBytes("ISO-8859-1");
+                int len = "trailer".length();
+                pdfSource.unread('\n');
+                pdfSource.unread(b, len, b.length-len);
+            }
+            else 
+            {
+                return false;
+            }
+        }
+
+        // in some cases the EOL is missing and the trailer continues with " <<"
+        // even if this does not comply with PDF reference we want to support as many PDFs as possible
+        // Acrobat reader can also deal with this.
+        skipSpaces();
+
+        COSDictionary parsedTrailer = parseCOSDictionary();
+        xrefTrailerResolver.setTrailer( parsedTrailer );
+
+        // The version can also be specified within the document /Catalog
+        readVersionInTrailer(parsedTrailer);
+
+        skipSpaces();
+        return true;
+    }
+    
+    /**
+     * The document catalog can also have a /Version parameter which overrides the version specified
+     * in the header if, and only if it is greater.
+     * 
+     * @param parsedTrailer the parsed catalog in the trailer
+     */
+    private void readVersionInTrailer(COSDictionary parsedTrailer) 
+    {
+        COSObject root = (COSObject) parsedTrailer.getItem(COSName.ROOT);
+        if (root != null)
+        {
+            COSName version =  (COSName) root.getItem(COSName.VERSION);
+            if (version != null) 
+            {
+                float trailerVersion = Float.valueOf(version.getName());
+                if (trailerVersion > document.getVersion()) 
+                {
+                    document.setVersion(trailerVersion);
+                }
+            }
+        }
+    }
+
+    /**
+     * Fills XRefTrailerResolver with data of given stream.
+     * Stream must be of type XRef.
+     * @param stream the stream to be read
+     * @param objByteOffset the offset to start at
+     * @throws IOException if there is an error parsing the stream
+     */
+    public void parseXrefStream( COSStream stream, int objByteOffset ) throws IOException
+    {
+        xrefTrailerResolver.nextXrefObj( objByteOffset );
+       xrefTrailerResolver.setTrailer( stream );
+       PDFXrefStreamParser parser =
+            new PDFXrefStreamParser( stream, document, forceParsing, xrefTrailerResolver );
+        parser.parse();
+    }
+    
+    /**
+     * Used to resolve conflicts when a PDF Document has multiple objects with
+     * the same id number. Ideally, we could use the Xref table when parsing
+     * the document to be able to determine which of the objects with the same ID
+     * is correct, but we do not have access to the Xref Table during parsing.
+     * Instead, we queue up the conflicts and resolve them after the Xref has
+     * been parsed. The Objects listed in the Xref Table are kept and the 
+     * others are ignored. 
+     */
+    private static class ConflictObj
+    {
+
+        private int offset;
+        private COSObjectKey objectKey;
+        private COSObject object;
+        
+        public ConflictObj(int offsetValue, COSObjectKey key, COSObject pdfObject) 
+        {
+            this.offset = offsetValue;
+            this.objectKey = key;
+            this.object = pdfObject;
+        }
+        @Override
+        public String toString()
+        {
+            return "Object(" + offset + ", " + objectKey + ")";
+        }
+        
+        /**
+         * Sometimes pdf files have objects with the same ID number yet are
+         * not referenced by the Xref table and therefore should be excluded.             
+         * This method goes through the conflicts list and replaces the object stored
+         * in the objects array with this one if it is referenced by the xref
+         * table. 
+         * @throws IOException
+         */
+        private static void resolveConflicts(COSDocument document, List<ConflictObj> conflictList) throws IOException
+        {
+            Iterator<ConflictObj> conflicts = conflictList.iterator();
+            while(conflicts.hasNext())
+            {
+                ConflictObj o = conflicts.next();
+                Integer offset = new Integer(o.offset);
+                if(document.getXrefTable().containsValue(offset))
+                {
+                    COSObject pdfObject = document.getObjectFromPool(o.objectKey);
+                    pdfObject.setObject(o.object.getObject());
+                }
+            }
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFStreamParser.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFStreamParser.java
new file mode 100644 (file)
index 0000000..4f849e6
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * 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.pdfbox.pdfparser;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * This will parse a PDF byte stream and extract operands and such.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.32 $
+ */
+public class PDFStreamParser extends BaseParser
+{
+    private List<Object> streamObjects = new ArrayList<Object>( 100 );
+    private RandomAccess file;
+    private PDFOperator lastBIToken = null;
+
+    /**
+     * Constructor that takes a stream to parse.
+     *
+     * @since Apache PDFBox 1.3.0
+     * @param stream The stream to read data from.
+     * @param raf The random access file.
+     * @param forceParcing flag to skip malformed or otherwise unparseable
+     *                     input where possible
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public PDFStreamParser(
+            InputStream stream, RandomAccess raf, boolean forceParsing)
+            throws IOException {
+        super(stream, forceParsing);
+        file = raf;
+    }
+
+    /**
+     * Constructor that takes a stream to parse.
+     *
+     * @param stream The stream to read data from.
+     * @param raf The random access file.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public PDFStreamParser(InputStream stream, RandomAccess raf)
+            throws IOException {
+        this(stream, raf, FORCE_PARSING);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param stream The stream to parse.
+     *
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFStreamParser( PDStream stream ) throws IOException
+    {
+       this( stream.createInputStream(), stream.getStream().getScratchFile() );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @since Apache PDFBox 1.3.0
+     * @param stream The stream to parse.
+     * @param forceParcing flag to skip malformed or otherwise unparseable
+     *                     input where possible
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFStreamParser(COSStream stream, boolean forceParsing)
+            throws IOException {
+       this(stream.getUnfilteredStream(), stream.getScratchFile(), forceParsing);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param stream The stream to parse.
+     *
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFStreamParser( COSStream stream ) throws IOException
+    {
+       this( stream.getUnfilteredStream(), stream.getScratchFile() );
+    }
+
+    /**
+     * This will parse the tokens in the stream.  This will close the
+     * stream when it is finished parsing.
+     *
+     * @throws IOException If there is an error while parsing the stream.
+     */
+    public void parse() throws IOException
+    {
+        try
+        {
+            Object token = null;
+            while( (token = parseNextToken()) != null )
+            {
+                streamObjects.add( token );
+                //logger().fine( "parsed=" + token );
+            }
+        }
+        finally
+        {
+            pdfSource.close();
+        }
+    }
+
+    /**
+     * This will get the tokens that were parsed from the stream.
+     *
+     * @return All of the tokens in the stream.
+     */
+    public List<Object> getTokens()
+    {
+        return streamObjects;
+    }
+    
+    public void close() throws IOException
+    {
+       pdfSource.close();
+    }
+
+    /**
+     * This will get an iterator which can be used to parse the stream
+     * one token after the other.
+     * 
+     * @return an iterator to get one token after the other
+     */
+    public Iterator<Object> getTokenIterator()
+    {
+        return new Iterator<Object>()
+        {
+                       private Object  token;
+                       
+                       private void tryNext()
+                       {
+                               try {
+                                       if(token == null)
+                                       {
+                                               token = parseNextToken();
+                                       }
+                               } catch (IOException e) {
+                                       throw new RuntimeException(e);
+                               }
+                       }
+
+                   /**
+                    * {@inheritDoc}
+                    */
+                       public boolean hasNext() 
+                       {
+                               tryNext();
+                               return token != null;
+                       }
+
+                   /**
+                    * {@inheritDoc}
+                    */
+                       public Object next() {
+                               tryNext();
+                               Object tmp = token;
+                               if(tmp == null)
+                               {
+                                       throw new NoSuchElementException();
+                               }
+                               token = null;
+                               return tmp;
+                       }
+
+                   /**
+                    * {@inheritDoc}
+                    */
+                       public void remove() 
+                       {
+                               throw new UnsupportedOperationException();
+                       }
+        };
+    }
+
+    /**
+     * This will parse the next token in the stream.
+     *
+     * @return The next token in the stream or null if there are no more tokens in the stream.
+     *
+     * @throws IOException If an io error occurs while parsing the stream.
+     */
+    private Object parseNextToken() throws IOException
+    {
+        Object retval = null;
+
+        skipSpaces();
+        int nextByte = pdfSource.peek();
+        if( ((byte)nextByte) == -1 )
+        {
+            return null;
+        }
+        char c = (char)nextByte;
+        switch(c)
+        {
+            case '<':
+            {
+                int leftBracket = pdfSource.read();//pull off first left bracket
+                c = (char)pdfSource.peek(); //check for second left bracket
+                pdfSource.unread( leftBracket ); //put back first bracket
+                if(c == '<')
+                {
+
+                    COSDictionary pod = parseCOSDictionary();
+                    skipSpaces();
+                    if((char)pdfSource.peek() == 's')
+                    {
+                        retval = parseCOSStream( pod, file );
+                    }
+                    else
+                    {
+                        retval = pod;
+                    }
+                }
+                else
+                {
+                    retval = parseCOSString();
+                }
+                break;
+            }
+            case '[': // array
+            {
+                retval = parseCOSArray();
+                break;
+            }
+            case '(': // string
+                retval = parseCOSString();
+                break;
+            case '/':   // name
+                retval = parseCOSName();
+                break;
+            case 'n':   // null
+            {
+                String nullString = readString();
+                if( nullString.equals( "null") )
+                {
+                    retval = COSNull.NULL;
+                }
+                else
+                {
+                    retval = PDFOperator.getOperator( nullString );
+                }
+                break;
+            }
+            case 't':
+            case 'f':
+            {
+                String next = readString();
+                if( next.equals( "true" ) )
+                {
+                    retval = COSBoolean.TRUE;
+                    break;
+                }
+                else if( next.equals( "false" ) )
+                {
+                    retval = COSBoolean.FALSE;
+                }
+                else
+                {
+                    retval = PDFOperator.getOperator( next );
+                }
+                break;
+            }
+            case 'R':
+            {
+                String line = readString();
+                if( line.equals( "R" ) )
+                {
+                    retval = new COSObject( null );
+                }
+                else
+                {
+                    retval = PDFOperator.getOperator( line );
+                }
+                break;
+            }
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            case '-':
+            case '+':
+            case '.':
+            {
+                /* We will be filling buf with the rest of the number.  Only
+                 * allow 1 "." and "-" and "+" at start of number. */
+                StringBuffer buf = new StringBuffer();
+                buf.append( c );
+                pdfSource.read();
+
+                boolean dotNotRead = (c != '.');
+                while( Character.isDigit(( c = (char)pdfSource.peek()) ) || (dotNotRead && (c == '.')) )
+                {
+                    buf.append( c );
+                    pdfSource.read();
+
+                    if (dotNotRead && (c == '.'))
+                    {
+                        dotNotRead = false;
+                    }
+                }
+                retval = COSNumber.get( buf.toString() );
+                break;
+            }
+            case 'B':
+            {
+                String next = readString();
+                retval = PDFOperator.getOperator( next );
+
+                if( next.equals( "BI" ) )
+                {
+                    lastBIToken = (PDFOperator)retval;
+                    COSDictionary imageParams = new COSDictionary();
+                    lastBIToken.setImageParameters( new ImageParameters( imageParams ) );
+                    Object nextToken = null;
+                    while( (nextToken = parseNextToken()) instanceof COSName )
+                    {
+                        Object value = parseNextToken();
+                        imageParams.setItem( (COSName)nextToken, (COSBase)value );
+                    }
+                    //final token will be the image data, maybe??
+                    PDFOperator imageData = (PDFOperator)nextToken;
+                    lastBIToken.setImageData( imageData.getImageData() );
+                }
+                break;
+            }
+            case 'I':
+            {
+                //ImageParameters imageParams = lastBIToken.getImageParameters();
+
+                //int expectedBytes = (int)Math.ceil(imageParams.getHeight() * imageParams.getWidth() *
+                //                    (imageParams.getBitsPerComponent()/8) );
+                //Special case for ID operator
+                String id = "" + (char)pdfSource.read() + (char)pdfSource.read();
+                if( !id.equals( "ID" ) )
+                {
+                    throw new IOException( "Error: Expected operator 'ID' actual='" + id + "'" );
+                }
+                ByteArrayOutputStream imageData = new ByteArrayOutputStream();
+                //boolean foundEnd = false;
+                if( this.isWhitespace() )
+                {
+                    //pull off the whitespace character
+                    pdfSource.read();
+                }
+                int twoBytesAgo = 0;
+                int lastByte = pdfSource.read();
+                int currentByte = pdfSource.read();
+                int count = 0;
+                //PDF spec is kinda unclear about this.  Should a whitespace
+                //always appear before EI? Not sure, I found a PDF
+                //(UnderstandingWebSphereClassLoaders.pdf) which has EI as part
+                //of the image data and will stop parsing prematurely if there is
+                //not a check for <whitespace>EI<whitespace>.
+                while( !(isWhitespace( twoBytesAgo ) &&
+                         lastByte == 'E' &&
+                         currentByte == 'I' &&
+                         isWhitespace() //&&
+                         //amyuni2_05d__pdf1_3_acro4x.pdf has image data that
+                         //is compressed, so expectedBytes is useless here.
+                         //count >= expectedBytes
+                         ) &&
+                       !pdfSource.isEOF() )
+                {
+                    imageData.write( lastByte );
+                    twoBytesAgo = lastByte;
+                    lastByte = currentByte;
+                    currentByte = pdfSource.read();
+                    count++;
+                }
+                pdfSource.unread( 'I' ); //unread the EI operator
+                pdfSource.unread( 'E' );
+                retval = PDFOperator.getOperator( "ID" );
+                ((PDFOperator)retval).setImageData( imageData.toByteArray() );
+                break;
+            }
+            case ']':
+            {
+                // some ']' around without its previous '['
+                // this means a PDF is somewhat corrupt but we will continue to parse.
+                pdfSource.read();
+                retval = COSNull.NULL;  // must be a better solution than null...
+                break;
+            }
+            default:
+            {
+                //we must be an operator
+                String operator = readOperator();
+                if( operator.trim().length() == 0 )
+                {
+                    //we have a corrupt stream, stop reading here
+                    retval = null;
+                }
+                else
+                {
+                    retval = PDFOperator.getOperator( operator );
+                }
+            }
+
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will read an operator from the stream.
+     *
+     * @return The operator that was read from the stream.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    protected String readOperator() throws IOException
+    {
+        skipSpaces();
+
+        //average string size is around 2 and the normal string buffer size is
+        //about 16 so lets save some space.
+        StringBuffer buffer = new StringBuffer(4);
+        int nextChar = (int)pdfSource.peek();
+        while(
+            nextChar != -1 && // EOF
+            !isWhitespace(nextChar) &&
+            !isClosing(nextChar) &&
+            nextChar != (int)'[' &&
+            nextChar != (int)'<' &&
+            nextChar != (int)'(' &&
+            nextChar != (int)'/' &&
+            (nextChar < (int)'0' ||
+             nextChar > (int)'9' ) )
+        {
+            char currentChar = (char)pdfSource.read();
+            nextChar = (int)pdfSource.peek();
+            buffer.append( currentChar );
+            // Type3 Glyph description has operators with a number in the name 
+            if (currentChar == 'd' && (nextChar == '0' || nextChar == '1') ) {
+                buffer.append( (char)pdfSource.read() );
+                nextChar = (int)pdfSource.peek();
+            }
+        }
+        return buffer.toString();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java
new file mode 100644 (file)
index 0000000..e508a8c
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdfparser;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This will parse a PDF 1.5 (or better) Xref stream and 
+ * extract the xref information from the stream.
+ * 
+ *  @author <a href="mailto:justinl@basistech.com">Justin LeFebvre</a>
+ *  @version $Revision: 1.0 $
+ */
+public class PDFXrefStreamParser extends BaseParser 
+{
+    private COSStream stream;
+    private XrefTrailerResolver xrefTrailerResolver;
+
+    /**
+     * Constructor.
+     *
+     * @since 1.3.0
+     * @param strm The stream to parse.
+     * @param doc The document for the current parsing.
+     * @param forceParcing flag to skip malformed or otherwise unparseable
+     *                     input where possible
+        * @param xrefTrailerResolver resolver to read the xref/trailer information
+        * 
+     * @throws IOException If there is an error initializing the stream.
+     */
+    public PDFXrefStreamParser(
+            COSStream strm, COSDocument doc, boolean forceParsing,
+            XrefTrailerResolver xrefTrailerResolver )
+            throws IOException {
+        super(strm.getUnfilteredStream(), forceParsing);
+        setDocument(doc);
+        stream = strm;
+        this.xrefTrailerResolver = xrefTrailerResolver;
+    }
+
+    /**
+     * Parses through the unfiltered stream and populates the xrefTable HashMap.
+     * @throws IOException If there is an error while parsing the stream.
+     */
+    public void parse() throws IOException
+    {
+        try
+        {
+            COSArray xrefFormat = (COSArray)stream.getDictionaryObject(COSName.W);
+            COSArray indexArray = (COSArray)stream.getDictionaryObject(COSName.INDEX);
+            /*
+             * If Index doesn't exist, we will use the default values. 
+             */
+            if(indexArray == null)
+            {
+                indexArray = new COSArray();
+                indexArray.add(COSInteger.ZERO);
+                indexArray.add(stream.getDictionaryObject(COSName.SIZE));
+            }
+            
+            ArrayList<Integer> objNums = new ArrayList<Integer>();
+            
+            /*
+             * Populates objNums with all object numbers available
+             */
+            Iterator<COSBase> indexIter = indexArray.iterator();
+            while(indexIter.hasNext())
+            {
+                int objID = ((COSInteger)indexIter.next()).intValue();
+                int size = ((COSInteger)indexIter.next()).intValue();
+                for(int i = 0; i < size; i++)
+                {
+                    objNums.add(new Integer(objID + i));
+                }
+            }
+            Iterator<Integer> objIter = objNums.iterator();
+            /*
+             * Calculating the size of the line in bytes
+             */
+            int w0 = xrefFormat.getInt(0);
+            int w1 = xrefFormat.getInt(1);
+            int w2 = xrefFormat.getInt(2);
+            int lineSize = w0 + w1 + w2;
+            
+            while(pdfSource.available() > 0 && objIter.hasNext())
+            {
+                byte[] currLine = new byte[lineSize];
+                pdfSource.read(currLine);
+
+                int type = 0;
+                /*
+                 * Grabs the number of bytes specified for the first column in 
+                 * the W array and stores it.
+                 */
+                for(int i = 0; i < w0; i++)
+                {
+                    type += (currLine[i] & 0x00ff) << ((w0 - i - 1)* 8);
+                }
+                //Need to remember the current objID
+                Integer objID = (Integer)objIter.next();
+                /*
+                 * 3 different types of entries. 
+                 */
+                switch(type)
+                {
+                    case 0:
+                        /*
+                         * Skipping free objects
+                         */
+                        break;
+                    case 1:                   
+                        int offset = 0;
+                        for(int i = 0; i < w1; i++)
+                        {
+                            offset += (currLine[i + w0] & 0x00ff) << ((w1 - i - 1) * 8);
+                        }
+                        int genNum = 0;
+                        for(int i = 0; i < w2; i++)
+                        {
+                            genNum += (currLine[i + w0 + w1] & 0x00ff) << ((w2 - i - 1) * 8);
+                        }
+                        COSObjectKey objKey = new COSObjectKey(objID.intValue(), genNum);
+                        xrefTrailerResolver.setXRef(objKey, offset);
+                        break;
+                    case 2:
+                        /*
+                         * These objects are handled by the dereferenceObjects() method
+                         * since they're only pointing to object numbers
+                         */
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        finally
+        {
+            pdfSource.close();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/VisualSignatureParser.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/VisualSignatureParser.java
new file mode 100644 (file)
index 0000000..8bd6cda
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * 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.pdfbox.pdfparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Pattern;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+public class VisualSignatureParser extends BaseParser {
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDFParser.class);
+
+    public VisualSignatureParser(InputStream input) throws IOException {
+        super(input);
+    }
+
+    public void parse() throws IOException {
+        document = new COSDocument();
+        skipToNextObj();
+
+        boolean wasLastParsedObjectEOF = false;
+        try {
+            while(!wasLastParsedObjectEOF) {
+                if(pdfSource.isEOF()) {
+                    break;
+                }
+                try {
+                    wasLastParsedObjectEOF = parseObject();
+                } catch(IOException e) {
+                    /*
+                     * Warning is sent to the PDFBox.log and to the Console that
+                     * we skipped over an object
+                     */
+                    log.warn("Parsing Error, Skipping Object", e);
+                    skipToNextObj();
+                }
+                skipSpaces();
+            }
+        } catch(IOException e) {
+            /*
+             * PDF files may have random data after the EOF marker. Ignore errors if
+             * last object processed is EOF.
+             */
+            if(!wasLastParsedObjectEOF) {
+                throw e;
+            }
+        }
+    }
+
+    private void skipToNextObj() throws IOException {
+        byte[] b = new byte[16];
+        Pattern p = Pattern.compile("\\d+\\s+\\d+\\s+obj.*", Pattern.DOTALL);
+        /* Read a buffer of data each time to see if it starts with a
+         * known keyword. This is not the most efficient design, but we should
+         * rarely be needing this function. We could update this to use the
+         * circular buffer, like in readUntilEndStream().
+         */
+        while(!pdfSource.isEOF()) {
+            int l = pdfSource.read(b);
+            if(l < 1) {
+                break;
+            }
+            String s = new String(b, "US-ASCII");
+            if(s.startsWith("trailer")
+                    || s.startsWith("xref")
+                    || s.startsWith("startxref")
+                    || s.startsWith("stream")
+                    || p.matcher(s).matches()) {
+                pdfSource.unread(b);
+                break;
+            } else {
+                pdfSource.unread(b, 1, l - 1);
+            }
+        }
+    }
+
+    private boolean parseObject() throws IOException {
+        boolean isEndOfFile = false;
+        skipSpaces();
+        //peek at the next character to determine the type of object we are parsing
+        char peekedChar = (char) pdfSource.peek();
+
+        //ignore endobj and endstream sections.
+        while(peekedChar == 'e') {
+            //there are times when there are multiple endobj, so lets
+            //just read them and move on.
+            readString();
+            skipSpaces();
+            peekedChar = (char) pdfSource.peek();
+        }
+        if(pdfSource.isEOF()) {
+            // end of file we will return a false and call it a day.
+        } else if(peekedChar == 'x') {
+            //xref table. Note: The contents of the Xref table are currently ignored
+            return true;
+        } else if(peekedChar == 't' || peekedChar == 's') {
+            // Note: startxref can occur in either a trailer section or by itself
+            if(peekedChar == 't') {
+                return true;
+            }
+            if(peekedChar == 's') {
+                skipToNextObj();
+                //verify that EOF exists
+                String eof = readExpectedString("%%EOF");
+                if(eof.indexOf("%%EOF") == -1 && !pdfSource.isEOF()) {
+                    throw new IOException("expected='%%EOF' actual='" + eof + "' next=" + readString()
+                            + " next=" + readString());
+                }
+                isEndOfFile = true;
+            }
+        } else {
+            //we are going to parse an normal object
+            int number = -1;
+            int genNum = -1;
+            String objectKey = null;
+            boolean missingObjectNumber = false;
+            try {
+                char peeked = (char) pdfSource.peek();
+                if(peeked == '<') {
+                    missingObjectNumber = true;
+                } else {
+                    number = readInt();
+                }
+            } catch(IOException e) {
+                //ok for some reason "GNU Ghostscript 5.10" puts two endobj
+                //statements after an object, of course this is nonsense
+                //but because we want to support as many PDFs as possible
+                //we will simply try again
+                number = readInt();
+            }
+            if(!missingObjectNumber) {
+                skipSpaces();
+                genNum = readInt();
+
+                objectKey = readString(3);
+                //System.out.println( "parseObject() num=" + number +
+                //" genNumber=" + genNum + " key='" + objectKey + "'" );
+                if(!objectKey.equals("obj")) {
+                    throw new IOException("expected='obj' actual='" + objectKey + "' " + pdfSource);
+                }
+            } else {
+                number = -1;
+                genNum = -1;
+            }
+
+            skipSpaces();
+            COSBase pb = parseDirObject();
+            String endObjectKey = readString();
+
+            if(endObjectKey.equals("stream")) {
+                pdfSource.unread(endObjectKey.getBytes());
+                pdfSource.unread(' ');
+                if(pb instanceof COSDictionary) {
+                    pb = parseCOSStream((COSDictionary) pb, getDocument().getScratchFile());
+
+                } else {
+                    // this is not legal
+                    // the combination of a dict and the stream/endstream forms a complete stream object
+                    throw new IOException("stream not preceded by dictionary");
+                }
+                endObjectKey = readString();
+            }
+
+            COSObjectKey key = new COSObjectKey(number, genNum);
+            COSObject pdfObject = document.getObjectFromPool(key);
+            pb.setNeedToBeUpdate(true);
+            pdfObject.setObject(pb);
+
+            if(!endObjectKey.equals("endobj")) {
+                if(endObjectKey.startsWith("endobj")) {
+                    /*
+                     * Some PDF files don't contain a new line after endobj so we
+                     * need to make sure that the next object number is getting read separately
+                     * and not part of the endobj keyword. Ex. Some files would have "endobj28"
+                     * instead of "endobj"
+                     */
+                    pdfSource.unread(endObjectKey.substring(6).getBytes());
+                } else if(!pdfSource.isEOF()) {
+                    try {
+                        //It is possible that the endobj  is missing, there
+                        //are several PDFs out there that do that so skip it and move on.
+                        Float.parseFloat(endObjectKey);
+                        pdfSource.unread(COSWriter.SPACE);
+                        pdfSource.unread(endObjectKey.getBytes());
+                    } catch(NumberFormatException e) {
+                        //we will try again incase there was some garbage which
+                        //some writers will leave behind.
+                        String secondEndObjectKey = readString();
+                        if(!secondEndObjectKey.equals("endobj")) {
+                            if(isClosing()) {
+                                //found a case with 17506.pdf object 41 that was like this
+                                //41 0 obj [/Pattern /DeviceGray] ] endobj
+                                //notice the second array close, here we are reading it
+                                //and ignoring and attempting to continue
+                                pdfSource.read();
+                            }
+                            skipSpaces();
+                            String thirdPossibleEndObj = readString();
+                            if(!thirdPossibleEndObj.equals("endobj")) {
+                                throw new IOException("expected='endobj' firstReadAttempt='" + endObjectKey + "' "
+                                        + "secondReadAttempt='" + secondEndObjectKey + "' " + pdfSource);
+                            }
+                        }
+                    }
+                }
+            }
+            skipSpaces();
+        }
+        return isEndOfFile;
+    }
+
+    public COSDocument getDocument() throws IOException {
+        if(document == null) {
+            throw new IOException("You must call parse() before calling getDocument()");
+        }
+        return document;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java
new file mode 100644 (file)
index 0000000..8ea578a
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * 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.pdfbox.pdfparser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/** 
+ * This class will collect all XRef/trailer objects and creates correct
+ * xref/trailer information after all objects are read using startxref
+ * and 'Prev' information (unused XRef/trailer objects are discarded).
+ *
+ * In case of missing startxref or wrong startxref pointer all
+ * XRef/trailer objects are used to create xref table / trailer dictionary
+ * in order they occur.
+ *  
+ * For each new xref object/XRef stream method {@link #nextXrefObj(int)}
+ * must be called with start byte position. All following calls to
+ * {@link #setXRef(COSObjectKey, int)} or {@link #setTrailer(COSDictionary)}
+ * will add the data for this byte position.
+ *  
+ * After all objects are parsed the startxref position must be provided
+ * using {@link #setStartxref(int)}. This is used to build the chain of
+ * active xref/trailer objects used for creating document trailer and xref table.
+ *  
+ * @author Timo Böhme (timo.boehme at ontochem.com)
+ */
+public class XrefTrailerResolver
+{
+
+    /**
+     * A class which represents a xref/trailer object
+     * 
+     */
+       class XrefTrailerObj
+       {
+           private COSDictionary trailer = null;
+           private final Map<COSObjectKey, Integer> xrefTable = new HashMap<COSObjectKey, Integer>();
+       }
+       
+       private final Map<Integer, XrefTrailerObj> bytePosToXrefMap = new HashMap<Integer, XrefTrailerObj>();
+       private XrefTrailerObj curXrefTrailerObj   = null;
+       private XrefTrailerObj resolvedXrefTrailer = null;
+       
+    /** Log instance. */
+    private static final Log log = LogFactory.getLog( XrefTrailerResolver.class );
+    
+       /** 
+        * Signals that a new XRef object (table or stream) starts. 
+        * @param startBytePos the offset to start at
+        * 
+        */
+       public void nextXrefObj( final int startBytePos )
+       {
+           bytePosToXrefMap.put( startBytePos, curXrefTrailerObj = new XrefTrailerObj() ); 
+       }
+       
+    /**
+     * Populate XRef HashMap of current XRef object.
+     * Will add an Xreftable entry that maps ObjectKeys to byte offsets in the file.
+     * @param objKey The objkey, with id and gen numbers
+     * @param offset The byte offset in this file
+     */
+    public void setXRef( COSObjectKey objKey, int offset )
+    {
+        if ( curXrefTrailerObj == null ) 
+       {
+            // should not happen...
+               log.warn( "Cannot add XRef entry for '" + objKey.getNumber() + "' because XRef start was not signalled." );
+               return;
+       }
+        curXrefTrailerObj.xrefTable.put( objKey, offset );
+    }
+    
+    /**
+     * Adds trailer information for current XRef object.
+     *
+     * @param trailer the current document trailer dictionary
+     */
+    public void setTrailer( COSDictionary trailer )
+    {
+        if ( curXrefTrailerObj == null ) 
+        {
+            // should not happen...
+               log.warn( "Cannot add trailer because XRef start was not signalled." );
+               return;
+        }
+        curXrefTrailerObj.trailer = trailer;
+    }
+    
+    /** 
+     * Sets the byte position of the first XRef
+     * (has to be called after very last startxref was read).
+     * This is used to resolve chain of active XRef/trailer.
+     * 
+     * In case startxref position is not found we output a
+     * warning and use all XRef/trailer objects combined
+     * in byte position order.
+     * Thus for incomplete PDF documents with missing
+     * startxref one could call this method with parameter value -1.
+     */
+    public void setStartxref( int startxrefBytePos )
+    {
+        if ( resolvedXrefTrailer != null ) 
+       {
+            log.warn( "Method must be called only ones with last startxref value." );
+               return;
+       }
+         
+       resolvedXrefTrailer = new XrefTrailerObj();
+       resolvedXrefTrailer.trailer = new COSDictionary();
+         
+       XrefTrailerObj curObj = bytePosToXrefMap.get( startxrefBytePos );
+               List<Integer>  xrefSeqBytePos = new ArrayList<Integer>();
+         
+               if ( curObj == null )
+               {
+                   // no XRef at given position
+               log.warn( "Did not found XRef object at specified startxref position " + startxrefBytePos );
+               
+               // use all objects in byte position order (last entries overwrite previous ones)
+               xrefSeqBytePos.addAll( bytePosToXrefMap.keySet() );
+               Collections.sort( xrefSeqBytePos );
+               }
+               else
+               {
+                   // found starting Xref object
+                   // add this and follow chain defined by 'Prev' keys
+                   xrefSeqBytePos.add( startxrefBytePos );
+                   while ( curObj.trailer != null )
+                   {
+                       int prevBytePos = curObj.trailer.getInt( COSName.PREV, -1 );
+                       if ( prevBytePos == -1 )
+                       {
+                           break;
+                       }
+                       
+                       curObj = bytePosToXrefMap.get( prevBytePos );
+                       if ( curObj == null ) 
+                       {
+                           log.warn( "Did not found XRef object pointed to by 'Prev' key at position " + prevBytePos );
+                           break;
+                       }
+                       xrefSeqBytePos.add( prevBytePos );
+                       
+                       // sanity check to prevent infinite loops
+                       if ( xrefSeqBytePos.size() >= bytePosToXrefMap.size() )
+                       {
+                           break;
+                       }
+                   }  
+                   // have to reverse order so that later XRefs will overwrite previous ones
+                   Collections.reverse( xrefSeqBytePos );
+               }
+         
+         // merge used and sorted XRef/trailer
+               for ( Integer bPos : xrefSeqBytePos ) 
+               {
+                   curObj = bytePosToXrefMap.get( bPos );
+                   if ( curObj.trailer != null )
+                   {
+                       resolvedXrefTrailer.trailer.addAll( curObj.trailer );
+                   }
+                   resolvedXrefTrailer.xrefTable.putAll( curObj.xrefTable );
+               }
+         
+    }
+    
+    /** 
+     * Gets the resolved trailer. Might return <code>null</code> in case
+     * {@link #setStartxref(int)} was not called before. 
+     * 
+     */
+    public COSDictionary getTrailer()
+    {
+        return ( resolvedXrefTrailer == null ) ? null : resolvedXrefTrailer.trailer;
+    }
+    
+    /** 
+     * Gets the resolved xref table. Might return <code>null</code> in case
+     *  {@link #setStartxref(int)} was not called before. 
+     *
+     */
+    public Map<COSObjectKey, Integer> getXrefTable()
+    {
+        return ( resolvedXrefTrailer == null ) ? null : resolvedXrefTrailer.xrefTable;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfparser/package.html
new file mode 100644 (file)
index 0000000..b78ef4d
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The pdfparser package contains classes to parse PDF documents and objects within the document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/ArrayEntry.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/ArrayEntry.java
new file mode 100644 (file)
index 0000000..8fe4629
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdfviewer;
+
+/**
+ * This is a simple class that will contain an index and a value.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ArrayEntry
+{
+    private int index;
+    private Object value;
+
+    /**
+     * This will get the value for this entry.
+     *
+     * @return The value for this entry.
+     */
+    public Object getValue()
+    {
+        return value;
+    }
+
+    /**
+     * This will set the value for this entry.
+     *
+     * @param val the new value for this entry.
+     */
+    public void setValue(Object val)
+    {
+        this.value = val;
+    }
+
+    /**
+     * This will get the index of the array entry.
+     *
+     * @return The 0-based index into the array
+     */
+    public int getIndex()
+    {
+        return index;
+    }
+
+    /**
+     * This will set the index value.
+     *
+     * @param i The new index value.
+     */
+    public void setIndex(int i)
+    {
+        index = i;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/MapEntry.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/MapEntry.java
new file mode 100644 (file)
index 0000000..d584d55
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.pdfbox.pdfviewer;
+
+import org.apache.pdfbox.cos.COSName;
+
+
+/**
+ * This is a simple class that will contain a key and a value.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class MapEntry
+{
+    private Object key;
+    private Object value;
+
+    /**
+     * Get the key for this entry.
+     *
+     * @return The entry's key.
+     */
+    public Object getKey()
+    {
+        return key;
+    }
+
+    /**
+     * This will set the key for this entry.
+     *
+     * @param k the new key for this entry.
+     */
+    public void setKey(Object k)
+    {
+        key = k;
+    }
+
+    /**
+     * This will get the value for this entry.
+     *
+     * @return The value for this entry.
+     */
+    public Object getValue()
+    {
+        return value;
+    }
+
+    /**
+     * This will set the value for this entry.
+     *
+     * @param val the new value for this entry.
+     */
+    public void setValue(Object val)
+    {
+        this.value = val;
+    }
+
+    /**
+     * This will output a string representation of this class.
+     *
+     * @return A string representation of this class.
+     */
+    public String toString()
+    {
+        String retval = null;
+        if( key instanceof COSName )
+        {
+            retval = ((COSName)key).getName();
+        }
+        else
+        {
+            retval = "" +key;
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFPagePanel.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFPagePanel.java
new file mode 100644 (file)
index 0000000..944a63e
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdfviewer;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import java.io.IOException;
+
+import javax.swing.JPanel;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This is a simple JPanel that can be used to display a PDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFPagePanel extends JPanel
+{
+
+    private PDPage page;
+    private PageDrawer drawer = null;
+    private Dimension pageDimension = null;
+    private Dimension drawDimension = null;
+
+    /**
+     * Constructor.
+     *
+     * @throws IOException If there is an error creating the Page drawing objects.
+     */
+    public PDFPagePanel() throws IOException
+    {
+        drawer = new PageDrawer();
+    }
+
+    /**
+     * This will set the page that should be displayed in this panel.
+     *
+     * @param pdfPage The page to draw.
+     */
+    public void setPage( PDPage pdfPage )
+    {
+        page = pdfPage;
+        PDRectangle pageSize = page.findMediaBox();
+        drawDimension = pageSize.createDimension();
+        int rotation = page.findRotation();
+        if (rotation == 90 || rotation == 270)
+        {
+            pageDimension = new Dimension(drawDimension.height, drawDimension.width);
+        }
+        else
+        {
+            pageDimension = drawDimension;
+        }
+        setSize( pageDimension );
+        setBackground( java.awt.Color.white );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void paint(Graphics g )
+    {
+        try
+        {
+            g.setColor( getBackground() );
+            g.fillRect( 0, 0, getWidth(), getHeight() );
+
+            int rotation = page.findRotation();
+            if (rotation == 90 || rotation == 270)
+            {
+                Graphics2D g2D = (Graphics2D)g;
+                g2D.translate(pageDimension.getWidth(), 0.0f);
+                g2D.rotate(Math.toRadians(rotation));
+            }
+
+            drawer.drawPage( g, page, drawDimension );
+        }
+        catch( IOException e )
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFTreeCellRenderer.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFTreeCellRenderer.java
new file mode 100644 (file)
index 0000000..c37f884
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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.pdfbox.pdfviewer;
+
+import java.awt.Component;
+
+import javax.swing.JTree;
+
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * A class to render tree cells for the pdfviewer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFTreeCellRenderer extends DefaultTreeCellRenderer
+{
+    /**
+     * {@inheritDoc}
+     */
+    public Component getTreeCellRendererComponent(
+        JTree tree,
+        Object nodeValue,
+        boolean isSelected,
+        boolean expanded,
+        boolean leaf,
+        int row,
+        boolean componentHasFocus)
+    {
+        nodeValue = convertToTreeObject( nodeValue );
+        return super.getTreeCellRendererComponent( tree, nodeValue, isSelected, expanded, leaf,
+                row, componentHasFocus );
+    }
+
+    private Object convertToTreeObject( Object nodeValue )
+    {
+        if( nodeValue instanceof MapEntry )
+        {
+            MapEntry entry = (MapEntry)nodeValue;
+            COSName key = (COSName)entry.getKey();
+            COSBase value = (COSBase)entry.getValue();
+            nodeValue = key.getName() + ":" + convertToTreeObject( value );
+        }
+        else if( nodeValue instanceof COSFloat )
+        {
+            nodeValue = "" + ((COSFloat)nodeValue).floatValue();
+        }
+        else if( nodeValue instanceof COSInteger )
+        {
+            nodeValue = "" + ((COSInteger)nodeValue).intValue();
+        }
+        else if( nodeValue instanceof COSString )
+        {
+            nodeValue = ((COSString)nodeValue).getString();
+        }
+        else if( nodeValue instanceof COSName )
+        {
+            nodeValue = ((COSName)nodeValue).getName();
+        }
+        else if( nodeValue instanceof ArrayEntry )
+        {
+            ArrayEntry entry = (ArrayEntry)nodeValue;
+            nodeValue = "[" + entry.getIndex() + "]" + convertToTreeObject( entry.getValue() );
+        }
+        else if( nodeValue instanceof COSNull )
+        {
+            nodeValue = "null";
+        }
+        else if( nodeValue instanceof COSDictionary )
+        {
+            COSDictionary dict = (COSDictionary)nodeValue;
+            if( nodeValue instanceof COSStream )
+            {
+                nodeValue = "Stream";
+            }
+            else
+            {
+                nodeValue = "Dictionary";
+            }
+
+            COSName type = (COSName)dict.getDictionaryObject( COSName.TYPE );
+            if( type != null )
+            {
+                nodeValue = nodeValue + "(" + type.getName();
+                COSName subType = (COSName)dict.getDictionaryObject( COSName.SUBTYPE );
+                if( subType != null )
+                {
+                    nodeValue = nodeValue + ":" + subType.getName();
+                }
+
+                nodeValue = nodeValue + ")";
+            }
+        }
+        else if( nodeValue instanceof COSArray )
+        {
+            nodeValue="Array";
+        }
+        else if( nodeValue instanceof COSString )
+        {
+            nodeValue = ((COSString)nodeValue).getString();
+        }
+        return nodeValue;
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFTreeModel.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PDFTreeModel.java
new file mode 100644 (file)
index 0000000..0f5f688
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * 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.pdfbox.pdfviewer;
+
+/**
+ * A tree model that uses a cos document.
+ *
+ *
+ * @author  wurtz
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeModel;
+
+//import java.awt.event.*;
+import javax.swing.event.TreeModelListener;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A class to model a PDF document as a tree structure.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class PDFTreeModel implements TreeModel
+{
+    private PDDocument document;
+
+    /**
+     * constructor.
+     */
+    public PDFTreeModel()
+    {
+        //default constructor
+    }
+
+    /**
+     * Constructor to take a document.
+     *
+     * @param doc The document to display in the tree.
+     */
+    public PDFTreeModel(PDDocument doc)
+    {
+         setDocument(doc);
+    }
+
+    /**
+     * Set the document to display in the tree.
+     *
+     * @param doc The document to display in the tree.
+     */
+    public void setDocument(PDDocument doc)
+    {
+        document = doc;
+    }
+    /**
+     * Adds a listener for the <code>TreeModelEvent</code>
+     * posted after the tree changes.
+     *
+     * @param   l       the listener to add
+     * @see     #removeTreeModelListener
+     *
+     */
+    public void addTreeModelListener(TreeModelListener l)
+    {
+        //required for interface
+    }
+
+    /**
+     * Returns the child of <code>parent</code> at index <code>index</code>
+     * in the parent's
+     * child array.  <code>parent</code> must be a node previously obtained
+     * from this data source. This should not return <code>null</code>
+     * if <code>index</code>
+     * is a valid index for <code>parent</code> (that is <code>index >= 0 &&
+     * index < getChildCount(parent</code>)).
+     *
+     * @param   parent  a node in the tree, obtained from this data source
+     * @param index The index into the parent object to location the child object.
+     * @return  the child of <code>parent</code> at index <code>index</code>
+     *
+     */
+    public Object getChild(Object parent, int index)
+    {
+        Object retval = null;
+        if( parent instanceof COSArray )
+        {
+            ArrayEntry entry = new ArrayEntry();
+            entry.setIndex( index );
+            entry.setValue( ((COSArray)parent).getObject( index ) );
+            retval = entry;
+        }
+        else if( parent instanceof COSDictionary )
+        {
+            COSDictionary dict = ((COSDictionary)parent);
+            List<COSName> keys = new ArrayList<COSName>(dict.keySet());
+            Collections.sort( keys );
+            Object key = keys.get( index );
+            Object value = dict.getDictionaryObject( (COSName)key );
+            MapEntry entry = new MapEntry();
+            entry.setKey( key );
+            entry.setValue( value );
+            retval = entry;
+        }
+        else if( parent instanceof MapEntry )
+        {
+            retval = getChild( ((MapEntry)parent).getValue(), index );
+        }
+        else if( parent instanceof ArrayEntry )
+        {
+            retval = getChild( ((ArrayEntry)parent).getValue(), index );
+        }
+        else if( parent instanceof COSDocument )
+        {
+            retval = ((COSDocument)parent).getObjects().get( index );
+        }
+        else if( parent instanceof COSObject )
+        {
+            retval = ((COSObject)parent).getObject();
+        }
+        else
+        {
+            throw new RuntimeException( "Unknown COS type " + parent.getClass().getName() );
+        }
+        return retval;
+    }
+
+    /** Returns the number of children of <code>parent</code>.
+     * Returns 0 if the node
+     * is a leaf or if it has no children.  <code>parent</code> must be a node
+     * previously obtained from this data source.
+     *
+     * @param   parent  a node in the tree, obtained from this data source
+     * @return  the number of children of the node <code>parent</code>
+     *
+     */
+    public int getChildCount(Object parent)
+    {
+        int retval = 0;
+        if( parent instanceof COSArray )
+        {
+            retval = ((COSArray)parent).size();
+        }
+        else if( parent instanceof COSDictionary )
+        {
+            retval = ((COSDictionary)parent).size();
+        }
+        else if( parent instanceof MapEntry )
+        {
+            retval = getChildCount( ((MapEntry)parent).getValue() );
+        }
+        else if( parent instanceof ArrayEntry )
+        {
+            retval = getChildCount( ((ArrayEntry)parent).getValue() );
+        }
+        else if( parent instanceof COSDocument )
+        {
+            retval = ((COSDocument)parent).getObjects().size();
+        }
+        else if( parent instanceof COSObject )
+        {
+            retval = 1;
+        }
+        return retval;
+    }
+
+    /** Returns the index of child in parent.  If <code>parent</code>
+     * is <code>null</code> or <code>child</code> is <code>null</code>,
+     * returns -1.
+     *
+     * @param parent a note in the tree, obtained from this data source
+     * @param child the node we are interested in
+     * @return the index of the child in the parent, or -1 if either
+     *    <code>child</code> or <code>parent</code> are <code>null</code>
+     *
+     */
+    public int getIndexOfChild(Object parent, Object child)
+    {
+        int retval = -1;
+        if( parent != null && child != null )
+        {
+            if( parent instanceof COSArray )
+            {
+                COSArray array = (COSArray)parent;
+                if( child instanceof ArrayEntry )
+                {
+                    ArrayEntry arrayEntry = (ArrayEntry)child;
+                    retval = arrayEntry.getIndex();
+                }
+                else
+                {
+                    retval = array.indexOf( (COSBase)child );
+                }
+            }
+            else if( parent instanceof COSDictionary )
+            {
+                MapEntry entry = (MapEntry)child;
+                COSDictionary dict = (COSDictionary)parent;
+                List<COSName> keys = new ArrayList<COSName>(dict.keySet());
+                Collections.sort( keys );
+                for( int i=0; retval == -1 && i<keys.size(); i++ )
+                {
+                    if( keys.get( i ).equals( entry.getKey() ) )
+                    {
+                        retval = i;
+                    }
+                }
+            }
+            else if( parent instanceof MapEntry )
+            {
+                retval = getIndexOfChild( ((MapEntry)parent).getValue(), child );
+            }
+            else if( parent instanceof ArrayEntry )
+            {
+                retval = getIndexOfChild( ((ArrayEntry)parent).getValue(), child );
+            }
+            else if( parent instanceof COSDocument )
+            {
+                retval = ((COSDocument)parent).getObjects().indexOf( child );
+            }
+            else if( parent instanceof COSObject )
+            {
+                retval = 0;
+            }
+            else
+            {
+                throw new RuntimeException( "Unknown COS type " + parent.getClass().getName() );
+            }
+        }
+        return retval;
+    }
+
+    /** Returns the root of the tree.  Returns <code>null</code>
+     * only if the tree has no nodes.
+     *
+     * @return  the root of the tree
+     *
+     */
+    public Object getRoot()
+    {
+        return document.getDocument().getTrailer();
+    }
+
+    /** Returns <code>true</code> if <code>node</code> is a leaf.
+     * It is possible for this method to return <code>false</code>
+     * even if <code>node</code> has no children.
+     * A directory in a filesystem, for example,
+     * may contain no files; the node representing
+     * the directory is not a leaf, but it also has no children.
+     *
+     * @param   node  a node in the tree, obtained from this data source
+     * @return  true if <code>node</code> is a leaf
+     *
+     */
+    public boolean isLeaf(Object node)
+    {
+        boolean isLeaf = !(node instanceof COSDictionary ||
+                 node instanceof COSArray ||
+                 node instanceof COSDocument ||
+                 node instanceof COSObject ||
+                 (node instanceof MapEntry && !isLeaf(((MapEntry)node).getValue()) ) ||
+                 (node instanceof ArrayEntry && !isLeaf(((ArrayEntry)node).getValue()) ));
+        return isLeaf;
+    }
+
+    /** Removes a listener previously added with
+     * <code>addTreeModelListener</code>.
+     *
+     * @see     #addTreeModelListener
+     * @param   l       the listener to remove
+     *
+     */
+
+    public void removeTreeModelListener(TreeModelListener l)
+    {
+        //required for interface
+    }
+
+    /** Messaged when the user has altered the value for the item identified
+     * by <code>path</code> to <code>newValue</code>.
+     * If <code>newValue</code> signifies a truly new value
+     * the model should post a <code>treeNodesChanged</code> event.
+     *
+     * @param path path to the node that the user has altered
+     * @param newValue the new value from the TreeCellEditor
+     *
+     */
+    public void valueForPathChanged(TreePath path, Object newValue)
+    {
+        //required for interface
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PageDrawer.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PageDrawer.java
new file mode 100644 (file)
index 0000000..aadc4e2
--- /dev/null
@@ -0,0 +1,547 @@
+/*
+ * 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.pdfbox.pdfviewer;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Area;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.Image;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.PDShading;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.text.PDTextState;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFStreamEngine;
+import org.apache.pdfbox.util.ResourceLoader;
+import org.apache.pdfbox.util.TextPosition;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSDictionary;
+
+
+/**
+ * This will paint a page in a PDF document to a graphics context.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.22 $
+ */
+public class PageDrawer extends PDFStreamEngine
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PageDrawer.class);
+
+    private Graphics2D graphics;
+    protected Dimension pageSize;
+    protected PDPage page;
+
+    private GeneralPath linePath = new GeneralPath();
+
+    /**
+     * Default constructor, loads properties from file.
+     *
+     * @throws IOException If there is an error loading properties from the file.
+     */
+    public PageDrawer() throws IOException
+    {
+        super( ResourceLoader.loadProperties(
+                "org/apache/pdfbox/resources/PageDrawer.properties", true ) );
+    }
+
+    /**
+     * This will draw the page to the requested context.
+     *
+     * @param g The graphics context to draw onto.
+     * @param p The page to draw.
+     * @param pageDimension The size of the page to draw.
+     *
+     * @throws IOException If there is an IO error while drawing the page.
+     */
+    public void drawPage( Graphics g, PDPage p, Dimension pageDimension ) throws IOException
+    {
+        graphics = (Graphics2D)g;
+        page = p;
+        pageSize = pageDimension;
+        graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
+        graphics.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON );
+        // Only if there is some content, we have to process it. 
+        // Otherwise we are done here and we will produce an empty page
+        if ( page.getContents() != null) 
+        {
+            PDResources resources = page.findResources();
+            processStream( page, resources, page.getContents().getStream() );
+        }
+        List annotations = page.getAnnotations();
+        for( int i=0; i<annotations.size(); i++ )
+        {
+            PDAnnotation annot = (PDAnnotation)annotations.get( i );
+            PDRectangle rect = annot.getRectangle();
+            String appearanceName = annot.getAppearanceStream();
+            PDAppearanceDictionary appearDictionary = annot.getAppearance();
+            if( appearDictionary != null )
+            {
+                if( appearanceName == null )
+                {
+                    appearanceName = "default";
+                }
+                Map appearanceMap = appearDictionary.getNormalAppearance();
+                if (appearanceMap != null) { 
+                    PDAppearanceStream appearance = 
+                        (PDAppearanceStream)appearanceMap.get( appearanceName ); 
+                    if( appearance != null ) 
+                    { 
+                        g.translate( (int)rect.getLowerLeftX(), (int)-rect.getLowerLeftY() ); 
+                        processSubStream( page, appearance.getResources(), appearance.getStream() ); 
+                        g.translate( (int)-rect.getLowerLeftX(), (int)+rect.getLowerLeftY() ); 
+                    }
+                }
+            }
+        }
+
+    }
+
+    /**
+     * You should override this method if you want to perform an action when a
+     * text is being processed.
+     *
+     * @param text The text to process
+     */
+    protected void processTextPosition( TextPosition text )
+    {
+        try
+        {
+            switch(this.getGraphicsState().getTextState().getRenderingMode()) {
+                case PDTextState.RENDERING_MODE_FILL_TEXT:
+                    graphics.setComposite( this.getGraphicsState().getNonStrokeJavaComposite() );
+                    graphics.setColor( this.getGraphicsState().getNonStrokingColor().getJavaColor() );
+                    break;
+                case PDTextState.RENDERING_MODE_STROKE_TEXT:
+                    graphics.setComposite( this.getGraphicsState().getStrokeJavaComposite() );
+                    graphics.setColor( this.getGraphicsState().getStrokingColor().getJavaColor() );
+                    break;
+                case PDTextState.RENDERING_MODE_NEITHER_FILL_NOR_STROKE_TEXT:
+                    //basic support for text rendering mode "invisible"
+                    Color nsc = this.getGraphicsState().getStrokingColor().getJavaColor();
+                    float[] components = {Color.black.getRed(),Color.black.getGreen(),Color.black.getBlue()};
+                    Color  c = new Color(nsc.getColorSpace(),components,0f);
+                    graphics.setComposite( this.getGraphicsState().getStrokeJavaComposite() );
+                    graphics.setColor(c);
+                    break;
+                default:
+                    // TODO : need to implement....
+                    log.debug("Unsupported RenderingMode "
+                            + this.getGraphicsState().getTextState().getRenderingMode()
+                            + " in PageDrawer.processTextPosition()."
+                            + " Using RenderingMode "
+                            + PDTextState.RENDERING_MODE_FILL_TEXT
+                            + " instead");
+                    graphics.setComposite( this.getGraphicsState().getNonStrokeJavaComposite() );
+                    graphics.setColor( this.getGraphicsState().getNonStrokingColor().getJavaColor() );
+            }
+
+            PDFont font = text.getFont();
+            Matrix textPos = text.getTextPos().copy();
+            float x = textPos.getXPosition();
+            // the 0,0-reference has to be moved from the lower left (PDF) to the upper left (AWT-graphics)
+            float y = pageSize.height - textPos.getYPosition();
+            // Set translation to 0,0. We only need the scaling and shearing
+            textPos.setValue(2, 0, 0);
+            textPos.setValue(2, 1, 0);
+            // because of the moved 0,0-reference, we have to shear in the opposite direction
+            textPos.setValue(0, 1, (-1)*textPos.getValue(0, 1));
+            textPos.setValue(1, 0, (-1)*textPos.getValue(1, 0));
+            AffineTransform at = textPos.createAffineTransform();
+            PDMatrix fontMatrix = font.getFontMatrix();
+            at.scale(fontMatrix.getValue(0, 0) * 1000f, fontMatrix.getValue(1, 1) * 1000f);
+            graphics.setClip(getGraphicsState().getCurrentClippingPath());
+            // the fontSize is no longer needed as it is already part of the transformation
+            // we should remove it from the parameter list in the long run
+            font.drawString( text.getCharacter(), graphics, 1, at, x, y );
+        }
+        catch( IOException io )
+        {
+            io.printStackTrace();
+        }
+    }
+
+    /**
+     * Get the graphics that we are currently drawing on.
+     *
+     * @return The graphics we are drawing on.
+     */
+    public Graphics2D getGraphics()
+    {
+        return graphics;
+    }
+
+    /**
+     * Get the page that is currently being drawn.
+     *
+     * @return The page that is being drawn.
+     */
+    public PDPage getPage()
+    {
+        return page;
+    }
+
+    /**
+     * Get the size of the page that is currently being drawn.
+     *
+     * @return The size of the page that is being drawn.
+     */
+    public Dimension getPageSize()
+    {
+        return pageSize;
+    }
+
+    /**
+     * Fix the y coordinate.
+     *
+     * @param y The y coordinate.
+     * @return The updated y coordinate.
+     */
+    public double fixY( double y )
+    {
+        return pageSize.getHeight() - y;
+    }
+
+    /**
+     * Get the current line path to be drawn.
+     *
+     * @return The current line path to be drawn.
+     */
+    public GeneralPath getLinePath()
+    {
+        return linePath;
+    }
+
+    /**
+     * Set the line path to draw.
+     *
+     * @param newLinePath Set the line path to draw.
+     */
+    public void setLinePath(GeneralPath newLinePath)
+    {
+        if (linePath == null || linePath.getCurrentPoint() == null)
+        {
+            linePath = newLinePath;
+        }
+        else
+        {
+            linePath.append(newLinePath, false);
+        }
+    }
+
+
+    /**
+     * Fill the path.
+     *
+     * @param windingRule The winding rule this path will use.
+     * 
+     * @throws IOException If there is an IO error while filling the path.
+     */
+    public void fillPath(int windingRule) throws IOException
+    {
+        graphics.setComposite(getGraphicsState().getNonStrokeJavaComposite());
+        graphics.setColor( getGraphicsState().getNonStrokingColor().getJavaColor() );
+        getLinePath().setWindingRule(windingRule);
+        graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
+        graphics.setClip(getGraphicsState().getCurrentClippingPath());
+        graphics.fill( getLinePath() );
+        getLinePath().reset();
+    }
+
+
+    /**
+     * This will set the current stroke.
+     *
+     * @param newStroke The current stroke.
+     * 
+     */
+    public void setStroke(BasicStroke newStroke)
+    {
+        getGraphics().setStroke( newStroke );
+    }
+
+    /**
+     * This will return the current stroke.
+     *
+     * @return The current stroke.
+     * 
+     */
+    public BasicStroke getStroke()
+    {
+        return (BasicStroke)getGraphics().getStroke();
+    }
+    
+    /**
+     * Stroke the path.
+     *
+     * @throws IOException If there is an IO error while stroking the path.
+     */
+    public void strokePath() throws IOException
+    {
+        graphics.setComposite(getGraphicsState().getStrokeJavaComposite());
+        graphics.setColor( getGraphicsState().getStrokingColor().getJavaColor() );
+        graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
+        graphics.setClip(getGraphicsState().getCurrentClippingPath());
+        GeneralPath path = getLinePath();
+        graphics.draw( path );
+        path.reset();
+    }
+
+    /**
+     * Called when the color changed.
+     * @param bStroking true for the stroking color, false for the non-stroking color
+     * @throws IOException if an I/O error occurs
+     */
+    @Deprecated
+    public void colorChanged(boolean bStroking) throws IOException
+    {
+        //logger().info("changing " + (bStroking ? "" : "non") + "stroking color");
+    }
+
+    //This code generalizes the code Jim Lynch wrote for AppendRectangleToPath
+    /**
+     * use the current transformation matrix to transform a single point.
+     * @param x x-coordinate of the point to be transform
+     * @param y y-coordinate of the point to be transform
+     * @return the transformed coordinates as Point2D.Double
+     */
+    public java.awt.geom.Point2D.Double transformedPoint(double x, double y)
+    {
+        double[] position = {x,y}; 
+        getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
+                position, 0, position, 0, 1);
+        position[1] = fixY(position[1]);
+        return new Point2D.Double(position[0],position[1]);
+    }
+
+    /**
+     * Set the clipping Path.
+     *
+     * @param windingRule The winding rule this path will use.
+     * 
+     */
+    public void setClippingPath(int windingRule)
+    {
+        PDGraphicsState graphicsState = getGraphicsState();
+        GeneralPath clippingPath = (GeneralPath)getLinePath().clone();
+        clippingPath.setWindingRule(windingRule);
+        // If there is already set a clipping path, we have to intersect the new with the existing one
+        if (graphicsState.getCurrentClippingPath() != null) 
+        {
+            Area currentArea = new Area(getGraphicsState().getCurrentClippingPath());
+            Area newArea = new Area(clippingPath);
+            currentArea.intersect(newArea);
+            graphicsState.setCurrentClippingPath(currentArea);
+        }
+        else 
+        {
+            graphicsState.setCurrentClippingPath(clippingPath);
+        }
+        getLinePath().reset();
+    }
+    
+    /**
+     * Draw the AWT image. Called by Invoke.
+     * Moved into PageDrawer so that Invoke doesn't have to reach in here for Graphics as that breaks extensibility.
+     *
+     * @param awtImage The image to draw.
+     * @param at The transformation to use when drawing.
+     * 
+     */
+    public void drawImage(Image awtImage, AffineTransform at){
+        graphics.setComposite(getGraphicsState().getStrokeJavaComposite());
+        graphics.setClip(getGraphicsState().getCurrentClippingPath());
+        graphics.drawImage( awtImage, at, null );
+    }
+    
+    /**
+     * Fill with Shading.  Called by SHFill operator.
+     *
+     * @param ShadingName  The name of the Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    public void SHFill(COSName ShadingName) throws IOException
+    {
+        PDShading Shading =FindShadingDictionary(ShadingName);
+        log.info("Shading = " + Shading.toString());
+        
+        switch (Shading.getShadingType()){
+            case 1:
+                SHFill_Function(Shading);
+            break;
+            case 2:
+                SHFill_Axial(Shading);
+                break;
+            case 3:
+                SHFill_Radial(Shading);
+                break;
+            case 4:
+                SHFill_FreeGourad(Shading);
+                break;
+            case 5:
+                SHFill_LatticeGourad(Shading);
+                break;
+            case 6:
+                SHFill_CoonsPatch(Shading);
+                break;
+            case 7:
+                SHFill_TensorPatch(Shading);
+                break;
+            
+            default:
+                throw new IOException("Invalid ShadingType " + Shading.getShadingType() + " for Shading " + ShadingName);
+        }
+    }
+    
+    /**
+     * Find the appropriate Shading Dictionary. This is its own private function as it is really not appropriate to override when deriving from PageDrawer.
+     *
+     * @param ShadingName  The name of the Shading Dictionary to use for this fill instruction.
+     *
+     * @returns The PDShading object
+     * @throws IOException If there is an IO error while attempting to find the appropriate PDShading object.
+     */
+    private PDShading FindShadingDictionary(COSName ShadingName) throws IOException
+    {
+        
+        PDResources resources = (PDResources)page.getResources();
+                
+        COSDictionary AllShadings = (COSDictionary)(resources.getCOSDictionary().getDictionaryObject(COSName.SHADING));
+        
+        PDShading Shading = new PDShading(ShadingName, (COSDictionary)(AllShadings.getDictionaryObject(ShadingName)));
+        
+        return Shading;
+        
+    }
+    
+    /**
+     * Fill with a Function-based gradient / shading.  
+     * If extending the class, override this and its siblings, not the public SHFill method.
+     *
+     * @param Shading  The Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    protected void SHFill_Function(PDShading Shading) throws IOException
+    {
+        throw new IOException("Not Implemented");
+    }
+
+    /**
+     * Fill with an Axial Shading.  
+     * If extending the class, override this and its siblings, not the public SHFill method.
+     *
+     * @param Shading  The Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    protected void SHFill_Axial(PDShading Shading) throws IOException
+    {
+        throw new IOException("Not Implemented");
+        
+    }
+
+    /**
+     * Fill with a Radial gradient / shading.  
+     * If extending the class, override this and its siblings, not the public SHFill method.
+     *
+     * @param Shading  The Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    protected void SHFill_Radial(PDShading Shading) throws IOException
+    {
+        throw new IOException("Not Implemented");
+    }
+    
+    /**
+     * Fill with a Free-form Gourad-shaded triangle mesh.
+     * If extending the class, override this and its siblings, not the public SHFill method.
+     *
+     * @param Shading  The Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    protected void SHFill_FreeGourad(PDShading Shading) throws IOException
+    {
+        throw new IOException("Not Implemented");
+    }
+    
+    /**
+     * Fill with a Lattice-form Gourad-shaded triangle mesh.
+     * If extending the class, override this and its siblings, not the public SHFill method.
+     *
+     * @param Shading  The Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    protected void SHFill_LatticeGourad(PDShading Shading) throws IOException
+    {
+        throw new IOException("Not Implemented");
+    }
+    
+    /**
+     * Fill with a Coons patch mesh
+     * If extending the class, override this and its siblings, not the public SHFill method.
+     *
+     * @param Shading  The Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    protected void SHFill_CoonsPatch(PDShading Shading) throws IOException
+    {
+        throw new IOException("Not Implemented");
+    }
+    
+    /**
+     * Fill with a Tensor-product patch mesh.
+     * If extending the class, override this and its siblings, not the public SHFill method.
+     *
+     * @param Shading  The Shading Dictionary to use for this fill instruction.
+     *
+     * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+     */
+    protected void SHFill_TensorPatch(PDShading Shading) throws IOException
+    {
+        throw new IOException("Not Implemented");
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PageWrapper.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/PageWrapper.java
new file mode 100644 (file)
index 0000000..dd08305
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.pdfbox.pdfviewer;
+
+import java.awt.Dimension;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+
+import javax.swing.JPanel;
+
+import org.apache.pdfbox.PDFReader;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+/**
+ * A class to handle some prettyness around a single PDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PageWrapper implements MouseMotionListener
+{
+    private JPanel pageWrapper = new JPanel();
+    private PDFPagePanel pagePanel = null;
+    private PDFReader reader = null;
+
+    private static final int SPACE_AROUND_DOCUMENT = 20;
+
+    /**
+     * Constructor.
+     *
+     * @param aReader The reader application that holds this page.
+     *
+     * @throws IOException If there is an error creating the page drawing objects.
+     */
+    public PageWrapper( PDFReader aReader ) throws IOException
+    {
+        reader = aReader;
+        pagePanel = new PDFPagePanel();
+        pageWrapper.setLayout( null );
+        pageWrapper.add( pagePanel );
+        pagePanel.setLocation( SPACE_AROUND_DOCUMENT, SPACE_AROUND_DOCUMENT );
+        pageWrapper.setBorder( javax.swing.border.LineBorder.createBlackLineBorder() );
+        pagePanel.addMouseMotionListener( this );
+    }
+
+    /**
+     * This will display the PDF page in this component.
+     *
+     * @param page The PDF page to display.
+     */
+    public void displayPage( PDPage page )
+    {
+        pagePanel.setPage( page );
+        pagePanel.setPreferredSize( pagePanel.getSize() );
+        Dimension d = pagePanel.getSize();
+        d.width+=(SPACE_AROUND_DOCUMENT*2);
+        d.height+=(SPACE_AROUND_DOCUMENT*2);
+
+        pageWrapper.setPreferredSize( d );
+        pageWrapper.validate();
+    }
+
+    /**
+     * This will get the JPanel that can be displayed.
+     *
+     * @return The panel with the displayed PDF page.
+     */
+    public JPanel getPanel()
+    {
+        return pageWrapper;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void mouseDragged(MouseEvent e)
+    {
+        //do nothing when mouse moves.
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void mouseMoved(MouseEvent e)
+    {
+        //reader.getBottomStatusPanel().getStatusLabel().setText( e.getX() + "," + (pagePanel.getHeight() - e.getY()) );
+        reader.getBottomStatusPanel().getStatusLabel().setText( e.getX() + "," + e.getY() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/ReaderBottomPanel.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/ReaderBottomPanel.java
new file mode 100644 (file)
index 0000000..899f860
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.pdfbox.pdfviewer;
+
+import java.awt.Dimension;
+
+import javax.swing.JPanel;
+
+import javax.swing.JLabel;
+import java.awt.FlowLayout;
+/**
+ * A panel to display at the bottom of the window for status and other stuff.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ReaderBottomPanel extends JPanel
+{
+
+    private JLabel statusLabel = null;
+
+    /**
+     * This is the default constructor.
+     */
+    public ReaderBottomPanel()
+    {
+        super();
+        initialize();
+    }
+
+    /**
+     * This method initializes this.
+     */
+    private void initialize()
+    {
+        FlowLayout flowLayout1 = new FlowLayout();
+        this.setLayout(flowLayout1);
+        this.setComponentOrientation(java.awt.ComponentOrientation.LEFT_TO_RIGHT);
+        this.setPreferredSize( new Dimension( 1000, 20 ) );
+        flowLayout1.setAlignment(java.awt.FlowLayout.LEFT);
+        this.add(getStatusLabel(), null);
+    }
+
+    /**
+     * This method initializes status label.
+     *
+     * @return javax.swing.JLabel
+     */
+    public JLabel getStatusLabel()
+    {
+        if (statusLabel == null)
+        {
+            statusLabel = new JLabel();
+            statusLabel.setText("Ready");
+        }
+        return statusLabel;
+    }
+ }
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfviewer/package.html
new file mode 100644 (file)
index 0000000..9d9f398
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The pdfviewer package contains classes to graphically display information about a PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSFilterInputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSFilterInputStream.java
new file mode 100644 (file)
index 0000000..dbed0da
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.pdfbox.pdfwriter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public class COSFilterInputStream extends FilterInputStream
+{
+  int[] byteRange;
+  long position = 0;
+  
+  public COSFilterInputStream(InputStream in, int[] byteRange)
+  {
+    super(in);
+    this.byteRange = byteRange;
+  }
+
+  public COSFilterInputStream(byte[] in, int[] byteRange)
+  {
+    super(new ByteArrayInputStream(in));
+    this.byteRange = byteRange;
+  }
+
+  @Override
+  public int read() throws IOException
+  {
+    nextAvailable();
+    int i = super.read();
+    if (i>-1)
+      ++position;
+    return i;
+  }
+  
+  @Override
+  public int read(byte[] b) throws IOException
+  {
+    return read(b,0,b.length);
+  }
+  
+  @Override
+  public int read(byte[] b, int off, int len) throws IOException
+  {
+    if (b == null) {
+      throw new NullPointerException();
+    } else if (off < 0 || len < 0 || len > b.length - off) {
+        throw new IndexOutOfBoundsException();
+    } else if (len == 0) {
+        return 0;
+    }
+    
+    int c = read();
+    if (c == -1) {
+        return -1;
+    }
+    b[off] = (byte)c;
+  
+    int i = 1;
+    try {
+        for (; i < len ; i++) {
+            c = read();
+            if (c == -1) {
+                break;
+            }
+            b[off + i] = (byte)c;
+        }
+    } catch (IOException ee) {
+    }
+    return i;
+  }
+
+  private boolean inRange() throws IOException {
+    long pos = position;
+    for (int i = 0; i<byteRange.length/2;++i)
+    {
+      if(byteRange[i*2] <= pos &&  byteRange[i*2]+byteRange[i*2+1]>pos)
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void nextAvailable() throws IOException {
+    while (!inRange()) {
+      ++position;
+      if(super.read()<0)
+        break;
+    }
+  }
+  
+  public byte[] toByteArray() throws IOException 
+  {
+    ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
+    byte[] buffer = new byte[1024];
+    int c;
+    while ((c = this.read(buffer)) != -1)
+        byteOS.write(buffer, 0, c);
+    return byteOS.toByteArray();
+  }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSStandardOutputStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSStandardOutputStream.java
new file mode 100644 (file)
index 0000000..8f8ab7b
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * 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.pdfbox.pdfwriter;
+
+
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+
+import org.apache.pdfbox.util.StringUtil;
+
+/**
+ * simple output stream with some minor features for generating "pretty"
+ * pdf files.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.5 $
+ */
+public class COSStandardOutputStream extends FilterOutputStream
+{
+
+    /**
+     * To be used when 2 byte sequence is enforced.
+     */
+    public static final byte[] CRLF = StringUtil.getBytes("\r\n");
+
+    /**
+     * Line feed character.
+     */
+    public static final byte[] LF = StringUtil.getBytes("\n");
+
+    /**
+     * standard line separator.
+     */
+    public static final byte[] EOL = StringUtil.getBytes("\n");
+
+    // current byte pos in the output stream
+    private long pos = 0;
+    // flag to prevent generating two newlines in sequence
+    private boolean onNewLine = false;
+    private FileChannel fileChannel = null;
+    private FileDescriptor fileDescriptor = null;
+    private long mark = -1;
+    
+    /**
+     * COSOutputStream constructor comment.
+     *
+     * @param out The underlying stream to write to.
+     */
+    public COSStandardOutputStream(OutputStream out)
+    {
+        super(out);
+        if(out instanceof FileOutputStream) {
+            try {
+                fileChannel = ((FileOutputStream)out).getChannel();
+                fileDescriptor = ((FileOutputStream)out).getFD();
+                pos = fileChannel.position();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+    
+    /**
+     * This will get the current position in the stream.
+     *
+     * @return The current position in the stream.
+     */
+    public long getPos()
+    {
+        return pos;
+    }
+    
+    /**
+     * This will get the current position in the stream.
+     *
+     * @return The current position in the stream.
+     * @throws IOException 
+     */
+    public void setPos(long pos) throws IOException
+    {
+        if(fileChannel!=null) {
+            checkPos();
+            this.pos=pos;
+            fileChannel.position(pos);
+        }
+    }
+    
+    /**
+     * This will tell if we are on a newline.
+     *
+     * @return true If we are on a newline.
+     */
+    public boolean isOnNewLine()
+    {
+        return onNewLine;
+    }
+    /**
+     * This will set a flag telling if we are on a newline.
+     *
+     * @param newOnNewLine The new value for the onNewLine attribute.
+     */
+    public void setOnNewLine(boolean newOnNewLine)
+    {
+        onNewLine = newOnNewLine;
+    }
+
+    /**
+     * This will write some byte to the stream.
+     *
+     * @param b The source byte array.
+     * @param off The offset into the array to start writing.
+     * @param len The number of bytes to write.
+     *
+     * @throws IOException If the underlying stream throws an exception.
+     */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        checkPos();
+        setOnNewLine(false);
+        out.write(b, off, len);
+        pos += len;
+    }
+
+    /**
+     * This will write a single byte to the stream.
+     *
+     * @param b The byte to write to the stream.
+     *
+     * @throws IOException If there is an error writing to the underlying stream.
+     */
+    @Override
+    public void write(int b) throws IOException
+    {
+        checkPos();
+        setOnNewLine(false);
+        out.write(b);
+        pos++;
+    }
+    
+    /**
+     * This will write a CRLF to the stream.
+     *
+     * @throws IOException If there is an error writing the data to the stream.
+     */
+    public void writeCRLF() throws IOException
+    {
+        write(CRLF);
+    }
+
+    /**
+     * This will write an EOL to the stream.
+     *
+     * @throws IOException If there is an error writing to the stream
+     */
+    public void writeEOL() throws IOException
+    {
+        if (!isOnNewLine())
+        {
+            write(EOL);
+            setOnNewLine(true);
+        }
+    }
+
+    /**
+     * This will write a Linefeed to the stream.
+     *
+     * @throws IOException If there is an error writing to the underlying stream.
+     */
+    public void writeLF() throws IOException
+    {
+        write(LF);
+    }
+    
+    public void mark() throws IOException 
+    {
+        checkPos();
+        mark = getPos();
+    }
+    
+    public void reset() throws IOException 
+    {
+        if(mark<0)
+            return;
+        setPos(mark);
+    }
+    
+    private void checkPos() throws IOException 
+    {
+        if(fileChannel!=null && fileChannel.position() != getPos())
+            throw new IOException("OutputStream has an invalid position");
+    }
+
+    public byte[] getFileInBytes(int[] byteRange) throws IOException 
+    {
+        return null;
+    }
+    
+    public InputStream getFilterInputStream(int[] byteRange) 
+    {
+        return new COSFilterInputStream(new FileInputStream(fileDescriptor), byteRange);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSWriter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSWriter.java
new file mode 100644 (file)
index 0000000..77b7a5f
--- /dev/null
@@ -0,0 +1,1362 @@
+/*
+ * 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.pdfbox.pdfwriter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.cos.ICOSVisitor;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.SignatureException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.SecurityHandler;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+import org.apache.pdfbox.util.StringUtil;
+
+/**
+ * this class acts on a in-memory representation of a pdf document.
+ *
+ * todo no support for incremental updates
+ * todo single xref section only
+ * todo no linearization
+ *
+ * @author Michael Traut
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.36 $
+ */
+public class COSWriter implements ICOSVisitor
+{
+    /**
+     * The dictionary open token.
+     */
+    public static final byte[] DICT_OPEN = StringUtil.getBytes("<<");
+    /**
+     * The dictionary close token.
+     */
+    public static final byte[] DICT_CLOSE = StringUtil.getBytes(">>");
+    /**
+     * space character.
+     */
+    public static final byte[] SPACE = StringUtil.getBytes(" ");
+    /**
+     * The start to a PDF comment.
+     */
+    public static final byte[] COMMENT = StringUtil.getBytes("%");
+
+    /**
+     * The output version of the PDF.
+     */
+    public static final byte[] VERSION = StringUtil.getBytes("PDF-1.4");
+    /**
+     * Garbage bytes used to create the PDF header.
+     */
+    public static final byte[] GARBAGE = new byte[] {(byte)0xf6, (byte)0xe4, (byte)0xfc, (byte)0xdf};
+    /**
+     * The EOF constant.
+     */
+    public static final byte[] EOF = StringUtil.getBytes("%%EOF");
+    // pdf tokens
+
+    /**
+     * The reference token.
+     */
+    public static final byte[] REFERENCE = StringUtil.getBytes("R");
+    /**
+     * The XREF token.
+     */
+    public static final byte[] XREF = StringUtil.getBytes("xref");
+    /**
+     * The xref free token.
+     */
+    public static final byte[] XREF_FREE = StringUtil.getBytes("f");
+    /**
+     * The xref used token.
+     */
+    public static final byte[] XREF_USED = StringUtil.getBytes("n");
+    /**
+     * The trailer token.
+     */
+    public static final byte[] TRAILER = StringUtil.getBytes("trailer");
+    /**
+     * The start xref token.
+     */
+    public static final byte[] STARTXREF = StringUtil.getBytes("startxref");
+    /**
+     * The starting object token.
+     */
+    public static final byte[] OBJ = StringUtil.getBytes("obj");
+    /**
+     * The end object token.
+     */
+    public static final byte[] ENDOBJ = StringUtil.getBytes("endobj");
+    /**
+     * The array open token.
+     */
+    public static final byte[] ARRAY_OPEN = StringUtil.getBytes("[");
+    /**
+     * The array close token.
+     */
+    public static final byte[] ARRAY_CLOSE = StringUtil.getBytes("]");
+    /**
+     * The open stream token.
+     */
+    public static final byte[] STREAM = StringUtil.getBytes("stream");
+    /**
+     * The close stream token.
+     */
+    public static final byte[] ENDSTREAM = StringUtil.getBytes("endstream");
+
+    private NumberFormat formatXrefOffset = new DecimalFormat("0000000000");
+    /**
+     * The decimal format for the xref object generation number data.
+     */
+    private NumberFormat formatXrefGeneration = new DecimalFormat("00000");
+
+    private NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US );
+
+    // the stream where we create the pdf output
+    private OutputStream output;
+
+    // the stream used to write standard cos data
+    private COSStandardOutputStream standardOutput;
+
+    // the start position of the x ref section
+    private long startxref = 0;
+
+    // the current object number
+    private long number = 0;
+
+    // maps the object to the keys generated in the writer
+    // these are used for indirect references in other objects
+    //A hashtable is used on purpose over a hashmap
+    //so that null entries will not get added.
+    private Map<COSBase,COSObjectKey> objectKeys = new Hashtable<COSBase,COSObjectKey>();
+    private Map<COSObjectKey,COSBase> keyObject = new Hashtable<COSObjectKey,COSBase>();
+
+    // the list of x ref entries to be made so far
+    private List<COSWriterXRefEntry> xRefEntries = new ArrayList<COSWriterXRefEntry>();
+    private HashSet<COSBase> objectsToWriteSet = new HashSet<COSBase>();
+
+    //A list of objects to write.
+    private LinkedList<COSBase> objectsToWrite = new LinkedList<COSBase>();
+
+    //a list of objects already written
+    private Set<COSBase> writtenObjects = new HashSet<COSBase>();
+    //An 'actual' is any COSBase that is not a COSObject.
+    //need to keep a list of the actuals that are added
+    //as well as the objects because there is a problem
+    //when adding a COSObject and then later adding
+    //the actual for that object, so we will track
+    //actuals separately.
+    private Set<COSBase> actualsAdded = new HashSet<COSBase>();
+
+    private COSObjectKey currentObjectKey = null;
+
+    private PDDocument document = null;
+
+    private boolean willEncrypt = false;
+    
+    private boolean incrementalUpdate = false;
+    
+    private boolean reachedSignature = false;
+    
+    private int[] signaturePosition = new int[2];
+    
+    private int[] byterangePosition = new int[2];
+    
+    private FileInputStream in;
+
+    /**
+     * COSWriter constructor comment.
+     *
+     * @param os The wrapped output stream.
+     */
+    public COSWriter(OutputStream os)
+    {
+        super();
+        setOutput(os);
+        setStandardOutput(new COSStandardOutputStream(output));
+        formatDecimal.setMaximumFractionDigits( 10 );
+        formatDecimal.setGroupingUsed( false );
+    }
+    
+    /**
+     * COSWriter constructor for incremental updates. 
+     *
+     * @param os The wrapped output stream.
+     */
+    public COSWriter(OutputStream os, FileInputStream is)
+    {
+      this(os);
+      in = is;
+      incrementalUpdate = true;
+    }
+
+    protected void prepareIncrement(PDDocument doc)
+    {
+      try
+      {
+        if (doc != null)
+        {
+          COSDocument cosDoc = doc.getDocument();
+          
+          Map<COSObjectKey, Integer> xrefTable = cosDoc.getXrefTable();
+          Set<COSObjectKey> keySet = xrefTable.keySet();
+          long highestNumber=0;
+          for ( COSObjectKey cosObjectKey : keySet ) {
+            COSBase object = cosDoc.getObjectFromPool(cosObjectKey).getObject();
+            if (object != null && cosObjectKey!= null && !(object instanceof COSNumber))
+            {
+            objectKeys.put(object, cosObjectKey);
+            keyObject.put(cosObjectKey,object);
+            }
+            
+            long num = cosObjectKey.getNumber();
+            if (num > highestNumber)
+              highestNumber=num;
+          }
+          setNumber(highestNumber);
+          // xrefTable.clear();
+
+        }
+      }
+      catch (IOException e)
+      {
+        e.printStackTrace();
+      }
+    }
+    
+    /**
+     * add an entry in the x ref table for later dump.
+     *
+     * @param entry The new entry to add.
+     */
+    protected void addXRefEntry(COSWriterXRefEntry entry)
+    {
+        getXRefEntries().add(entry);
+    }
+
+    /**
+     * This will close the stream.
+     *
+     * @throws IOException If the underlying stream throws an exception.
+     */
+    public void close() throws IOException
+    {
+        if (getStandardOutput() != null)
+        {
+            getStandardOutput().close();
+        }
+        if (getOutput() != null)
+        {
+            getOutput().close();
+        }
+    }
+
+    /**
+     * This will get the current object number.
+     *
+     * @return The current object number.
+     */
+    protected long getNumber()
+    {
+        return number;
+    }
+
+    /**
+     * This will get all available object keys.
+     *
+     * @return A map of all object keys.
+     */
+    public Map<COSBase,COSObjectKey> getObjectKeys()
+    {
+        return objectKeys;
+    }
+
+    /**
+     * This will get the output stream.
+     *
+     * @return The output stream.
+     */
+    protected java.io.OutputStream getOutput()
+    {
+        return output;
+    }
+
+    /**
+     * This will get the standard output stream.
+     *
+     * @return The standard output stream.
+     */
+    protected COSStandardOutputStream getStandardOutput()
+    {
+        return standardOutput;
+    }
+
+    /**
+     * This will get the current start xref.
+     *
+     * @return The current start xref.
+     */
+    protected long getStartxref()
+    {
+        return startxref;
+    }
+    /**
+     * This will get the xref entries.
+     *
+     * @return All available xref entries.
+     */
+    protected List<COSWriterXRefEntry> getXRefEntries()
+    {
+        return xRefEntries;
+    }
+
+    /**
+     * This will set the current object number.
+     *
+     * @param newNumber The new object number.
+     */
+    protected void setNumber(long newNumber)
+    {
+        number = newNumber;
+    }
+
+    /**
+     * This will set the output stream.
+     *
+     * @param newOutput The new output stream.
+     */
+    private void setOutput( OutputStream newOutput )
+    {
+        output = newOutput;
+    }
+
+    /**
+     * This will set the standard output stream.
+     *
+     * @param newStandardOutput The new standard output stream.
+     */
+    private void setStandardOutput(COSStandardOutputStream newStandardOutput)
+    {
+        standardOutput = newStandardOutput;
+    }
+
+    /**
+     * This will set the start xref.
+     *
+     * @param newStartxref The new start xref attribute.
+     */
+    protected void setStartxref(long newStartxref)
+    {
+        startxref = newStartxref;
+    }
+
+    /**
+     * This will write the body of the document.
+     *
+     * @param doc The document to write the body for.
+     *
+     * @throws IOException If there is an error writing the data.
+     * @throws COSVisitorException If there is an error generating the data.
+     */
+    protected void doWriteBody(COSDocument doc) throws IOException, COSVisitorException
+    {
+        COSDictionary trailer = doc.getTrailer();
+        COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT );
+        COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.INFO );
+        COSDictionary encrypt = (COSDictionary)trailer.getDictionaryObject( COSName.ENCRYPT );
+          if( root != null )
+          {
+              addObjectToWrite( root );
+          }
+          if( info != null )
+          {
+              addObjectToWrite( info );
+          }
+
+        while( objectsToWrite.size() > 0 )
+        {
+            COSBase nextObject = (COSBase)objectsToWrite.removeFirst();
+            objectsToWriteSet.remove(nextObject);
+            doWriteObject( nextObject );
+        }
+
+
+        willEncrypt = false;
+
+        if( encrypt != null )
+        {
+            addObjectToWrite( encrypt );
+        }
+
+        while( objectsToWrite.size() > 0 )
+        {
+            COSBase nextObject = (COSBase)objectsToWrite.removeFirst();
+            objectsToWriteSet.remove(nextObject);
+            doWriteObject( nextObject );
+        }
+    }
+
+    private void addObjectToWrite( COSBase object )
+    {
+        COSBase actual = object;
+        if( actual instanceof COSObject )
+        {
+            actual = ((COSObject)actual).getObject();
+        }
+
+        if( !writtenObjects.contains( object ) &&
+            !objectsToWriteSet.contains( object ) &&
+            !actualsAdded.contains( actual ) )
+        {
+            COSBase cosBase=null;
+            COSObjectKey cosObjectKey = null;
+            if(actual != null)
+                cosObjectKey= objectKeys.get(actual);
+          
+            if(cosObjectKey!=null)
+                cosBase = keyObject.get(cosObjectKey);
+          
+            if(actual != null && objectKeys.containsKey(actual) &&
+                    !object.isNeedToBeUpdate() && (cosBase!= null &&
+                    !cosBase.isNeedToBeUpdate()))
+            {
+                return;
+            }
+          
+            objectsToWrite.add( object );
+            objectsToWriteSet.add( object );
+            if( actual != null )
+            {
+                actualsAdded.add( actual );
+            }
+        }
+    }
+
+    /**
+     * This will write a COS object.
+     *
+     * @param obj The object to write.
+     *
+     * @throws COSVisitorException If there is an error visiting objects.
+     */
+    public void doWriteObject( COSBase obj ) throws COSVisitorException
+    {
+        try
+        {
+            writtenObjects.add( obj );
+            if(obj instanceof COSDictionary) 
+            {
+                COSDictionary dict = (COSDictionary)obj;
+                COSName item = (COSName)dict.getItem(COSName.TYPE);
+                if(COSName.SIG.equals(item)) {
+                    reachedSignature = true;
+                }
+            }
+            
+            // find the physical reference
+            currentObjectKey = getObjectKey( obj );
+            // add a x ref entry
+            addXRefEntry( new COSWriterXRefEntry(getStandardOutput().getPos(), obj, currentObjectKey));
+            // write the object
+            getStandardOutput().write(String.valueOf(currentObjectKey.getNumber()).getBytes("ISO-8859-1"));
+            getStandardOutput().write(SPACE);
+            getStandardOutput().write(String.valueOf(currentObjectKey.getGeneration()).getBytes("ISO-8859-1"));
+            getStandardOutput().write(SPACE);
+            getStandardOutput().write(OBJ);
+            getStandardOutput().writeEOL();
+            obj.accept( this );
+            getStandardOutput().writeEOL();
+            getStandardOutput().write(ENDOBJ);
+            getStandardOutput().writeEOL();
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * This will write the header to the PDF document.
+     *
+     * @param doc The document to get the data from.
+     *
+     * @throws IOException If there is an error writing to the stream.
+     */
+    protected void doWriteHeader(COSDocument doc) throws IOException
+    {
+        getStandardOutput().write( doc.getHeaderString().getBytes("ISO-8859-1") );
+        getStandardOutput().writeEOL();
+        getStandardOutput().write(COMMENT);
+        getStandardOutput().write(GARBAGE);
+        getStandardOutput().writeEOL();
+    }
+
+
+    /**
+     * This will write the trailer to the PDF document.
+     *
+     * @param doc The document to create the trailer for.
+     *
+     * @throws IOException If there is an IOError while writing the document.
+     * @throws COSVisitorException If there is an error while generating the data.
+     */
+    protected void doWriteTrailer(COSDocument doc) throws IOException, COSVisitorException
+    {
+        getStandardOutput().write(TRAILER);
+        getStandardOutput().writeEOL();
+
+        COSDictionary trailer = doc.getTrailer();
+        //sort xref, needed only if object keys not regenerated
+        Collections.sort(getXRefEntries());
+        COSWriterXRefEntry lastEntry = getXRefEntries().get( getXRefEntries().size()-1);
+        trailer.setInt(COSName.SIZE, (int)lastEntry.getKey().getNumber()+1);
+        // Only need to stay, if an incremental update will be performed
+        if (!incrementalUpdate)
+          trailer.removeItem( COSName.PREV );
+        // Remove a checksum if present
+        trailer.removeItem( COSName.getPDFName("DocChecksum") );
+        
+        /**
+        COSObject catalog = doc.getCatalog();
+        if (catalog != null)
+        {
+            trailer.setItem(COSName.getPDFName("Root"), catalog);
+        }
+        */
+        trailer.accept(this);
+
+        getStandardOutput().write(STARTXREF);
+        getStandardOutput().writeEOL();
+        getStandardOutput().write(String.valueOf(getStartxref()).getBytes("ISO-8859-1"));
+        getStandardOutput().writeEOL();
+        getStandardOutput().write(EOF);
+        getStandardOutput().writeEOL();
+    }
+
+    /**
+     * write the x ref section for the pdf file
+     *
+     * currently, the pdf is reconstructed from the scratch, so we write a single section
+     *
+     * todo support for incremental writing?
+     *
+     * @param doc The document to write the xref from.
+     *
+     * @throws IOException If there is an error writing the data to the stream.
+     */
+    protected void doWriteXRef(COSDocument doc) throws IOException
+    {
+        // sort xref, needed only if object keys not regenerated
+        Collections.sort(getXRefEntries());
+        COSWriterXRefEntry lastEntry = getXRefEntries().get( getXRefEntries().size()-1 );
+
+        // remember the position where x ref is written
+        setStartxref(getStandardOutput().getPos());
+        //
+        getStandardOutput().write(XREF);
+        getStandardOutput().writeEOL();
+        // write start object number and object count for this x ref section
+        // we assume starting from scratch
+        writeXrefRange(0, lastEntry.getKey().getNumber() + 1);
+        // write initial start object with ref to first deleted object and magic generation number
+        writeXrefEntry(COSWriterXRefEntry.getNullEntry());
+        // write entry for every object
+        long lastObjectNumber = 0;
+        for (Iterator<COSWriterXRefEntry> i = getXRefEntries().iterator(); i.hasNext();)
+        {
+            COSWriterXRefEntry entry = i.next();
+            while( lastObjectNumber<entry.getKey().getNumber()-1 )
+            {
+              writeXrefEntry(COSWriterXRefEntry.getNullEntry());
+            }
+            lastObjectNumber = entry.getKey().getNumber();
+            writeXrefEntry(entry);
+        }
+    }
+
+    protected void doWriteXRefInc(COSDocument doc) throws IOException
+    {
+        COSDictionary trailer = doc.getTrailer();
+        trailer.setLong(COSName.PREV, doc.getStartXref());
+        addXRefEntry(COSWriterXRefEntry.getNullEntry());
+
+        // sort xref, needed only if object keys not regenerated
+        Collections.sort(getXRefEntries());
+      
+        // remember the position where x ref was written
+        setStartxref(getStandardOutput().getPos());
+
+        getStandardOutput().write(XREF);
+        getStandardOutput().writeEOL();
+        // write start object number and object count for this x ref section
+        // we assume starting from scratch
+
+        Integer[] xRefRanges = getXRefRanges(getXRefEntries());
+        int xRefLength = xRefRanges.length;
+        int x = 0;
+        int j = 0;
+        while(x < xRefLength && (xRefLength % 2) == 0)
+        {
+            writeXrefRange(xRefRanges[x], xRefRanges[x + 1]);
+
+            for(int i = 0 ; i < xRefRanges[x + 1] ; ++i)
+            {
+                writeXrefEntry(xRefEntries.get(j++));
+            }
+            x += 2;
+        }
+    }
+
+    protected void doWriteSignature(COSDocument doc) throws IOException, SignatureException
+    {
+        // need to calculate the ByteRange
+        if (signaturePosition[0]>0 && byterangePosition[1] > 0)
+        {
+            int left = (int)getStandardOutput().getPos()-signaturePosition[1];
+            String newByteRange = "0 "+signaturePosition[0]+" "+signaturePosition[1]+" "+left+"]";
+            int leftByterange = byterangePosition[1]-byterangePosition[0]-newByteRange.length();
+            if(leftByterange<0)
+                throw new IOException("Can't write new ByteRange, not enough space");
+            getStandardOutput().setPos(byterangePosition[0]);
+            getStandardOutput().write(newByteRange.getBytes());
+            for(int i=0;i<leftByterange;++i)
+            {
+                getStandardOutput().write(0x20);
+            }
+        
+            getStandardOutput().setPos(0);
+            // Begin - extracting document
+            InputStream filterInputStream = new COSFilterInputStream(in, new int[] {0,signaturePosition[0],signaturePosition[1],left});
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            try {
+                byte[] buffer = new byte[1024];
+                int c;
+                while((c = filterInputStream.read(buffer)) != -1)
+                    bytes.write(buffer, 0, c);
+            } finally {
+                if(filterInputStream !=null)
+                    filterInputStream.close();
+            }
+
+            byte[] pdfContent = bytes.toByteArray();
+            // End - extracting document
+        
+            SignatureInterface signatureInterface = doc.getSignatureInterface();
+            byte[] sign = signatureInterface.sign(new ByteArrayInputStream(pdfContent));
+            String signature = new COSString(sign).getHexString();
+            int leftSignaturerange = signaturePosition[1]-signaturePosition[0]-signature.length();
+            if(leftSignaturerange<0)
+                throw new IOException("Can't write signature, not enough space");
+            getStandardOutput().setPos(signaturePosition[0]+1);
+            getStandardOutput().write(signature.getBytes());
+        }
+    }
+    
+    protected void writeXrefRange(long x, long y) throws IOException
+    {
+        getStandardOutput().write(String.valueOf(x).getBytes());
+        getStandardOutput().write(SPACE);
+        getStandardOutput().write(String.valueOf(y).getBytes());
+        getStandardOutput().writeEOL();
+    }
+
+    protected void writeXrefEntry(COSWriterXRefEntry entry) throws IOException
+    {
+        String offset = formatXrefOffset.format(entry.getOffset());
+        String generation = formatXrefGeneration.format(entry.getKey().getGeneration());
+        getStandardOutput().write(offset.getBytes("ISO-8859-1"));
+        getStandardOutput().write(SPACE);
+        getStandardOutput().write(generation.getBytes("ISO-8859-1"));
+        getStandardOutput().write(SPACE);
+        getStandardOutput().write(entry.isFree() ? XREF_FREE : XREF_USED);
+        getStandardOutput().writeCRLF();
+    }
+
+    /**
+     * check the xref entries and write out the ranges.  The format of the
+     * returned array is exactly the same as the pdf specification.  See section
+     * 7.5.4 of ISO32000-1:2008, example 1 (page 40) for reference.
+     * <p>
+     * example: 0 1 2 5 6 7 8 10
+     * <p>
+     * will create a array with follow ranges
+     * <p>
+     * 0 3 5 4 10 1
+     * <p>
+     * this mean that the element 0 is followed by two other related numbers 
+     * that represent a cluster of the size 3. 5 is follow by three other
+     * related numbers and create a cluster of size 4. etc.
+     * 
+     * @param xRefEntries list with the xRef entries that was written
+     * @return a integer array with the ranges
+     */
+    protected Integer[] getXRefRanges(List<COSWriterXRefEntry> xRefEntries)
+    {
+        int nr = 0;
+        int last = -2;
+        int count = 1;
+
+        ArrayList<Integer> list = new ArrayList<Integer>();
+        for( Object object : xRefEntries )
+        {
+            nr = (int)((COSWriterXRefEntry)object).getKey().getNumber();
+            if (nr == last + 1)
+            {
+                ++count;
+                last = nr;
+            }
+            else if (last == -2)
+            {
+                last = nr;
+            }
+            else
+            {
+                list.add(last - count + 1);
+                list.add(count);
+                last = nr;
+                count = 1;
+            }
+        }
+        // If no new entry is found, we need to write out the last result
+        if(xRefEntries.size() > 0)
+        {
+            list.add(last - count + 1);
+            list.add(count);
+        }
+        return list.toArray(new Integer[list.size()]);
+    }
+    
+    /**
+     * This will get the object key for the object.
+     *
+     * @param obj The object to get the key for.
+     *
+     * @return The object key for the object.
+     */
+    private COSObjectKey getObjectKey( COSBase obj )
+    {
+        COSBase actual = obj;
+        if( actual instanceof COSObject )
+        {
+            actual = ((COSObject)obj).getObject();
+        }
+        COSObjectKey key = null;
+        if( actual != null )
+        {
+            key = objectKeys.get(actual);
+        }
+        if( key == null )
+        {
+            key = objectKeys.get(obj);
+        }
+        if (key == null)
+        {
+            setNumber(getNumber()+1);
+            key = new COSObjectKey(getNumber(),0);
+            objectKeys.put(obj, key);
+            if( actual != null )
+            {
+                objectKeys.put(actual, key);
+            }
+        }
+        return key;
+    }
+
+    /**
+     * visitFromArray method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromArray( COSArray obj ) throws COSVisitorException
+    {
+        try
+        {
+            int count = 0;
+            getStandardOutput().write(ARRAY_OPEN);
+            for (Iterator<COSBase> i = obj.iterator(); i.hasNext();)
+            {
+                COSBase current = i.next();
+                if( current instanceof COSDictionary )
+                {
+                    addObjectToWrite( current );
+                    writeReference( current );
+                }
+                else if( current instanceof COSObject )
+                {
+                    COSBase subValue = ((COSObject)current).getObject();
+                    if( subValue instanceof COSDictionary || subValue == null )
+                    {
+                        addObjectToWrite( current );
+                        writeReference( current );
+                    }
+                    else
+                    {
+                        subValue.accept( this );
+                    }
+                }
+                else if( current == null )
+                {
+                    COSNull.NULL.accept( this );
+                }
+                else if( current instanceof COSString )
+                {
+                    COSString copy = new COSString(((COSString)current).getString());
+                    copy.accept(this);
+                }
+                else
+                {
+                    current.accept(this);
+                }
+                count++;
+                if (i.hasNext())
+                {
+                    if (count % 10 == 0)
+                    {
+                        getStandardOutput().writeEOL();
+                    }
+                    else
+                    {
+                        getStandardOutput().write(SPACE);
+                    }
+                }
+            }
+            getStandardOutput().write(ARRAY_CLOSE);
+            getStandardOutput().writeEOL();
+            return null;
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromBoolean method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromBoolean(COSBoolean obj) throws COSVisitorException
+    {
+
+        try
+        {
+            obj.writePDF( getStandardOutput() );
+            return null;
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromDictionary method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromDictionary(COSDictionary obj) throws COSVisitorException
+    {
+        try
+        {
+            getStandardOutput().write(DICT_OPEN);
+            getStandardOutput().writeEOL();
+            for (Map.Entry<COSName, COSBase> entry : obj.entrySet())
+            {
+                COSBase value = entry.getValue();
+                if (value != null)
+                {
+                    entry.getKey().accept(this);
+                    getStandardOutput().write(SPACE);
+                    if( value instanceof COSDictionary )
+                    {
+                        COSDictionary dict = (COSDictionary)value;
+                        
+                        // write all XObjects as direct objects, this will save some size
+                        COSBase item = dict.getItem(COSName.XOBJECT);
+                        if(item!=null)
+                        {
+                            item.setDirect(true);
+                        }
+                        item = dict.getItem(COSName.RESOURCES);
+                        if(item!=null)
+                        {
+                            item.setDirect(true);
+                        }
+
+                        if(dict.isDirect()) 
+                        {
+                            // If the object should be written direct, we need
+                            // to pass the dictionary to the visitor again.
+                            visitFromDictionary(dict);
+                        }
+                        else 
+                        {
+                            addObjectToWrite( dict );
+                            writeReference( dict );
+                        }
+                    }
+                    else if( value instanceof COSObject )
+                    {
+                        COSBase subValue = ((COSObject)value).getObject();
+                        if( subValue instanceof COSDictionary || subValue == null )
+                        {
+                            addObjectToWrite( value );
+                            writeReference( value );
+                        }
+                        else
+                        {
+                            subValue.accept( this );
+                        }
+                    }
+                    else
+                    {
+                        // If we reach the pdf signature, we need to determinate the position of the
+                        // content and byterange
+                        if(reachedSignature && COSName.CONTENTS.equals(entry.getKey()))
+                        {
+                            signaturePosition = new int[2];
+                            signaturePosition[0] = (int)getStandardOutput().getPos();
+                            value.accept(this);
+                            signaturePosition[1] = (int)getStandardOutput().getPos();
+                        }
+                        else if(reachedSignature && COSName.BYTERANGE.equals(entry.getKey()))
+                        {
+                            byterangePosition = new int[2];
+                            byterangePosition[0] = (int)getStandardOutput().getPos()+1;
+                            value.accept(this);
+                            byterangePosition[1] = (int)getStandardOutput().getPos()-1;
+                            reachedSignature = false;
+                        }
+                        else
+                        {
+                            value.accept(this);
+                        }
+                    }
+                    getStandardOutput().writeEOL();
+
+                }
+                else
+                {
+                    //then we won't write anything, there are a couple cases
+                    //were the value of an entry in the COSDictionary will
+                    //be a dangling reference that points to nothing
+                    //so we will just not write out the entry if that is the case
+                }
+            }
+            getStandardOutput().write(DICT_CLOSE);
+            getStandardOutput().writeEOL();
+            return null;
+        }
+        catch( IOException e )
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * The visit from document method.
+     *
+     * @param doc The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromDocument(COSDocument doc) throws COSVisitorException
+    {
+        try
+        {
+            if(!incrementalUpdate)
+                doWriteHeader(doc);
+            doWriteBody(doc);
+            if(incrementalUpdate)
+                doWriteXRefInc(doc);
+            else
+                doWriteXRef(doc);
+            doWriteTrailer(doc);
+            doWriteSignature(doc);
+            
+            return null;
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+        catch (SignatureException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromFloat method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromFloat(COSFloat obj) throws COSVisitorException
+    {
+
+        try
+        {
+            obj.writePDF( getStandardOutput() );
+            return null;
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromFloat method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromInt(COSInteger obj) throws COSVisitorException
+    {
+        try
+        {
+            obj.writePDF( getStandardOutput() );
+            return null;
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromName method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromName(COSName obj) throws COSVisitorException
+    {
+        try
+        {
+            obj.writePDF( getStandardOutput() );
+            return null;
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromNull method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromNull(COSNull obj) throws COSVisitorException
+    {
+        try
+        {
+            obj.writePDF( getStandardOutput() );
+            return null;
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromObjRef method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     */
+    public void writeReference(COSBase obj) throws COSVisitorException
+    {
+        try
+        {
+            COSObjectKey  key = getObjectKey(obj);
+            getStandardOutput().write(String.valueOf(key.getNumber()).getBytes("ISO-8859-1"));
+            getStandardOutput().write(SPACE);
+            getStandardOutput().write(String.valueOf(key.getGeneration()).getBytes("ISO-8859-1"));
+            getStandardOutput().write(SPACE);
+            getStandardOutput().write(REFERENCE);
+        }
+        catch (IOException e)
+        {
+            throw new COSVisitorException(e);
+        }
+    }
+
+    /**
+     * visitFromStream method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     *
+     * @return null
+     */
+    public Object visitFromStream(COSStream obj) throws COSVisitorException
+    {
+        InputStream input = null;
+
+        try
+        {
+            if(willEncrypt)
+            {
+                document.getSecurityHandler().encryptStream(
+                    obj,
+                    currentObjectKey.getNumber(),
+                    currentObjectKey.getGeneration());
+            }
+
+            input = obj.getFilteredStream();
+            // set the length of the stream and write stream dictionary
+            COSObject lengthObject = new COSObject( null );
+
+            obj.setItem(COSName.LENGTH, lengthObject);
+            //obj.accept(this);
+            // write the stream content
+            visitFromDictionary( obj );
+            getStandardOutput().write(STREAM);
+            getStandardOutput().writeCRLF();
+            byte[] buffer = new byte[1024];
+            int amountRead = 0;
+            int totalAmountWritten = 0;
+            while( (amountRead = input.read(buffer,0,1024)) != -1 )
+            {
+                getStandardOutput().write( buffer, 0, amountRead );
+                totalAmountWritten += amountRead;
+            }
+            lengthObject.setObject( COSInteger.get( totalAmountWritten ) );
+            getStandardOutput().writeCRLF();
+            getStandardOutput().write(ENDSTREAM);
+            getStandardOutput().writeEOL();
+            return null;
+        }
+        catch( Exception e )
+        {
+            throw new COSVisitorException(e);
+        }
+        finally
+        {
+            if (input != null)
+            {
+                try
+                {
+                    input.close();
+                }
+                catch (IOException e)
+                {
+                    throw new COSVisitorException(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * visitFromString method comment.
+     *
+     * @param obj The object that is being visited.
+     *
+     * @return null
+     *
+     * @throws COSVisitorException If there is an exception while visiting this object.
+     */
+    public Object visitFromString(COSString obj) throws COSVisitorException
+    {
+        try
+        {
+            if(willEncrypt)
+            {
+                document.getSecurityHandler().decryptString(
+                    obj,
+                    currentObjectKey.getNumber(),
+                    currentObjectKey.getGeneration());
+            }
+
+            obj.writePDF( getStandardOutput() );
+        }
+        catch (Exception e)
+        {
+            throw new COSVisitorException(e);
+        }
+        return null;
+    }
+
+    /**
+     * This will write the pdf document.
+     *
+     * @param doc The document to write.
+     *
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void write(COSDocument doc) throws COSVisitorException
+    {
+        PDDocument pdDoc = new PDDocument( doc );
+        write( pdDoc );
+    }
+
+    /**
+     * This will write the pdf document.
+     *
+     * @param doc The document to write.
+     *
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void write(PDDocument doc) throws COSVisitorException
+    {
+        document = doc;
+        if(incrementalUpdate)
+            prepareIncrement(doc);
+        
+        // if the document says we should remove encryption, then we shouldn't encrypt
+        if(doc.isAllSecurityToBeRemoved())
+        {
+            this.willEncrypt = false;
+            // also need to get rid of the "Encrypt" in the trailer so readers 
+            // don't try to decrypt a document which is not encrypted
+            COSDocument cosDoc = doc.getDocument();
+            COSDictionary trailer = cosDoc.getTrailer();
+            trailer.removeItem(COSName.ENCRYPT);
+        }
+        else
+        {
+            SecurityHandler securityHandler = document.getSecurityHandler();
+            if(securityHandler != null)
+            {
+                try
+                {
+                    securityHandler.prepareDocumentForEncryption(document);
+                    this.willEncrypt = true;
+                }
+                catch(IOException e)
+                {
+                    throw new COSVisitorException( e );
+                }
+                catch(CryptographyException e)
+                {
+                    throw new COSVisitorException( e );
+                }
+            }
+            else
+            {
+                    this.willEncrypt = false;
+            }        
+        }
+
+        COSDocument cosDoc = document.getDocument();
+        COSDictionary trailer = cosDoc.getTrailer();
+        COSArray idArray = (COSArray)trailer.getDictionaryObject( COSName.ID );
+        if( idArray == null || incrementalUpdate)
+        {
+            try
+            {
+
+                //algorithm says to use time/path/size/values in doc to generate
+                //the id.  We don't have path or size, so do the best we can
+                MessageDigest md = MessageDigest.getInstance( "MD5" );
+                md.update( Long.toString( System.currentTimeMillis()).getBytes("ISO-8859-1") );
+                COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.INFO );
+                if( info != null )
+                {
+                    Iterator<COSBase> values = info.getValues().iterator();
+                    while( values.hasNext() )
+                    {
+                        md.update( values.next().toString().getBytes("ISO-8859-1") );
+                    }
+                }
+                idArray = new COSArray();
+                COSString id = new COSString( md.digest() );
+                idArray.add( id );
+                idArray.add( id );
+                trailer.setItem( COSName.ID, idArray );
+            }
+            catch( NoSuchAlgorithmException e )
+            {
+                throw new COSVisitorException( e );
+            }
+            catch( UnsupportedEncodingException e )
+            {
+                throw new COSVisitorException( e );
+            }
+        }
+        cosDoc.accept(this);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSWriterXRefEntry.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/COSWriterXRefEntry.java
new file mode 100644 (file)
index 0000000..e5f0b99
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * 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.pdfbox.pdfwriter;
+
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * this is en entry in the xref section of the physical pdf document
+ * generated by the COSWriter.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.7 $
+ */
+public class COSWriterXRefEntry implements Comparable<COSWriterXRefEntry>
+{
+    private long offset;
+    private COSBase object;
+    private COSObjectKey key;
+    private boolean free = false;
+    private static COSWriterXRefEntry nullEntry;
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(COSWriterXRefEntry obj)
+    {
+        if (obj instanceof COSWriterXRefEntry)
+        {
+            return (int)(getKey().getNumber() - obj.getKey().getNumber());
+        }
+        else
+        {
+            return -1;
+        }
+    }
+    
+    /**
+     * This will return a null entry: 0000000000 65535 f
+     * 
+     * @return null COSWriterXRefEntry
+     */
+    public static COSWriterXRefEntry getNullEntry()
+    {
+      if (nullEntry == null)
+      {
+        nullEntry = new COSWriterXRefEntry(0, null, new COSObjectKey(0, 65535));
+        nullEntry.setFree(true);
+      }
+      return nullEntry;
+    }
+    
+    /**
+     * This will get the Object key.
+     *
+     * @return The object key.
+     */
+    public COSObjectKey  getKey()
+    {
+        return key;
+    }
+
+    /**
+     * This will get the offset into the document.
+     *
+     * @return The offset into the document.
+     */
+    public long getOffset()
+    {
+        return offset;
+    }
+
+    /**
+     * Gets the xref 'free' attribute.
+     *
+     * @return The free attribute.
+     */
+    public boolean isFree()
+    {
+        return free;
+    }
+
+    /**
+     * This will set the free attribute.
+     *
+     * @param newFree The newly freed attribute.
+     */
+    public void setFree(boolean newFree)
+    {
+        free = newFree;
+    }
+
+    /**
+     * This will set the object key.
+     *
+     * @param newKey The new object key.
+     */
+    private void setKey(COSObjectKey  newKey)
+    {
+        key = newKey;
+    }
+
+    /**
+     * The offset attribute.
+     *
+     * @param newOffset The new value for the offset.
+     */
+    public void setOffset(long newOffset)
+    {
+        offset = newOffset;
+    }
+
+    /**
+     * COSWriterXRefEntry constructor comment.
+     *
+     * @param start The start attribute.
+     * @param obj The COS object that this entry represents.
+     * @param keyValue The key to the COS object.
+     */
+    public COSWriterXRefEntry(long start, COSBase obj, COSObjectKey keyValue)
+    {
+        super();
+        setOffset(start);
+        setObject(obj);
+        setKey(keyValue);
+    }
+
+    /**
+     * This will get the object.
+     *
+     * @return The object.
+     */
+    public COSBase getObject()
+    {
+        return object;
+    }
+
+    /**
+     * This will set the object for this xref.
+     *
+     * @param newObject The object that is being set.
+     */
+    private void setObject(COSBase newObject)
+    {
+        object = newObject;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/ContentStreamWriter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/ContentStreamWriter.java
new file mode 100644 (file)
index 0000000..4602f76
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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.pdfbox.pdfwriter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * A class that will take a list of tokens and write out a stream with them.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class ContentStreamWriter
+{
+    private OutputStream output;
+    /**
+     * space character.
+     */
+    public static final byte[] SPACE = new byte[] { 32 };
+
+    /**
+     * standard line separator
+     */
+    public static final byte[] EOL = new byte[] { 0x0A };
+
+    /**
+     * This will create a new content stream writer.
+     *
+     * @param out The stream to write the data to.
+     */
+    public ContentStreamWriter( OutputStream out )
+    {
+        output = out;
+    }
+
+    /**
+     * This will write out the list of tokens to the stream.
+     *
+     * @param tokens The tokens to write to the stream.
+     * @param start The start index into the list of tokens.
+     * @param end The end index into the list of tokens.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void writeTokens( List tokens, int start, int end ) throws IOException
+    {
+        for( int i=start; i<end; i++ )
+        {
+            Object o = tokens.get( i );
+            writeObject( o );
+            //write a space between each object.
+            output.write( 32 );
+        }
+        output.flush();
+    }
+
+    private void writeObject( Object o ) throws IOException
+    {
+        if( o instanceof COSString )
+        {
+            ((COSString)o).writePDF( output );
+        }
+        else if( o instanceof COSFloat )
+        {
+            ((COSFloat)o).writePDF( output );
+        }
+        else if( o instanceof COSInteger )
+        {
+            ((COSInteger)o).writePDF( output );
+        }
+        else if( o instanceof COSBoolean )
+        {
+            ((COSBoolean)o).writePDF( output );
+        }
+        else if( o instanceof COSName )
+        {
+            ((COSName)o).writePDF( output );
+        }
+        else if( o instanceof COSArray )
+        {
+            COSArray array = (COSArray)o;
+            output.write(COSWriter.ARRAY_OPEN);
+            for( int i=0; i<array.size(); i++ )
+            {
+                writeObject( array.get( i ) );
+                output.write( SPACE );
+            }
+
+            output.write( COSWriter.ARRAY_CLOSE );
+        }
+        else if( o instanceof COSDictionary )
+        {
+            COSDictionary obj = (COSDictionary)o;
+            output.write( COSWriter.DICT_OPEN );
+            for (Map.Entry<COSName, COSBase> entry : obj.entrySet())
+            {
+                if (entry.getValue() != null)
+                {
+                    writeObject( entry.getKey() );
+                    output.write( SPACE );
+                    writeObject( entry.getValue() );
+                    output.write( SPACE );
+                }
+            }
+            output.write( COSWriter.DICT_CLOSE );
+            output.write( SPACE );
+        }
+        else if( o instanceof PDFOperator )
+        {
+            PDFOperator op = (PDFOperator)o;
+            if( op.getOperation().equals( "BI" ) )
+            {
+                output.write( "BI".getBytes("ISO-8859-1") );
+                ImageParameters params = op.getImageParameters();
+                COSDictionary dic = params.getDictionary();
+                for( COSName key : dic.keySet() )
+                {
+                    Object value = dic.getDictionaryObject( key );
+                    key.writePDF( output );
+                    output.write( SPACE );
+                    writeObject( value );
+                    output.write( EOL );
+                }
+                output.write( "ID".getBytes("ISO-8859-1") );
+                output.write( EOL );
+                output.write( op.getImageData() );
+            }
+            else
+            {
+                output.write( op.getOperation().getBytes("ISO-8859-1") );
+                output.write( EOL );
+            }
+        }
+        else
+        {
+            throw new IOException( "Error:Unknown type in content stream:" + o );
+        }
+    }
+
+    /**
+     * This will write out the list of tokens to the stream.
+     *
+     * @param tokens The tokens to write to the stream.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void writeTokens( List tokens ) throws IOException
+    {
+        writeTokens( tokens, 0, tokens.size() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdfwriter/package.html
new file mode 100644 (file)
index 0000000..1c1d0a1
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This is the persistence layer used to write the PDFBox documents to a stream.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDestinationNameTreeNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDestinationNameTreeNode.java
new file mode 100644 (file)
index 0000000..490e52d
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
+
+/**
+ * This class holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDDestinationNameTreeNode extends PDNameTreeNode
+{
+
+    /**
+     * Constructor.
+     */
+    public PDDestinationNameTreeNode()
+    {
+        super( PDPageDestination.class );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dic The COS dictionary.
+     */
+    public PDDestinationNameTreeNode( COSDictionary dic )
+    {
+        super( dic, PDPageDestination.class );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected Object convertCOSToPD( COSBase base ) throws IOException
+    {
+        COSBase destination = base;
+        if( base instanceof COSDictionary )
+        {
+            //the destination is sometimes stored in the D dictionary
+            //entry instead of being directly an array, so just dereference
+            //it for now
+            destination = ((COSDictionary)base).getDictionaryObject( COSName.D );
+        }
+        return PDDestination.create( destination );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected PDNameTreeNode createChildNode( COSDictionary dic )
+    {
+        return new PDDestinationNameTreeNode(dic);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocument.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocument.java
new file mode 100644 (file)
index 0000000..bb62d85
--- /dev/null
@@ -0,0 +1,1373 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import java.awt.print.PageFormat;
+import java.awt.print.Pageable;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.SignatureException;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdfparser.PDFParser;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.BadSecurityHandlerException;
+import org.apache.pdfbox.pdmodel.encryption.DecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.PDEncryptionDictionary;
+import org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy;
+import org.apache.pdfbox.pdmodel.encryption.SecurityHandler;
+import org.apache.pdfbox.pdmodel.encryption.SecurityHandlersManager;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
+
+/**
+ * This is the in-memory representation of the PDF document.  You need to call
+ * close() on this object when you are done using it!!
+ * <p>
+ * This class implements the {@link Pageable} interface, but since PDFBox
+ * version 1.3.0 you should be using the {@link PDPageable} adapter instead
+ * (see <a href="https://issues.apache.org/jira/browse/PDFBOX-788">PDFBOX-788</a>).
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.47 $
+ */
+public class PDDocument implements Pageable
+{
+
+    private COSDocument document;
+
+    //cached values
+    private PDDocumentInformation documentInformation;
+    private PDDocumentCatalog documentCatalog;
+    
+    //The encParameters will be cached here.  When the document is decrypted then
+    //the COSDocument will not have an "Encrypt" dictionary anymore and this object
+    //must be used.
+    private PDEncryptionDictionary encParameters = null;
+
+    /**
+     * The security handler used to decrypt / encrypt the document.
+     */
+    private SecurityHandler securityHandler = null;
+
+
+    /**
+     * This assocates object ids with a page number.  It's used to determine
+     * the page number for bookmarks (or page numbers for anything else for
+     * which you have an object id for that matter). 
+     */
+    private Map<String, Integer> pageMap = null;
+    
+    /**
+     * This will hold a flag which tells us if we should remove all security
+     * from this documents
+     */
+    private boolean allSecurityToBeRemoved = false;
+    
+    /**
+     * Constructor, creates a new PDF Document with no pages.  You need to add
+     * at least one page for the document to be valid.
+     *
+     * @throws IOException If there is an error creating this document.
+     */
+    public PDDocument() throws IOException
+    {
+        document = new COSDocument();
+
+        //First we need a trailer
+        COSDictionary trailer = new COSDictionary();
+        document.setTrailer( trailer );
+
+        //Next we need the root dictionary.
+        COSDictionary rootDictionary = new COSDictionary();
+        trailer.setItem( COSName.ROOT, rootDictionary );
+        rootDictionary.setItem( COSName.TYPE, COSName.CATALOG );
+        rootDictionary.setItem( COSName.VERSION, COSName.getPDFName( "1.4" ) );
+
+        //next we need the pages tree structure
+        COSDictionary pages = new COSDictionary();
+        rootDictionary.setItem( COSName.PAGES, pages );
+        pages.setItem( COSName.TYPE, COSName.PAGES );
+        COSArray kidsArray = new COSArray();
+        pages.setItem( COSName.KIDS, kidsArray );
+        pages.setItem( COSName.COUNT, COSInteger.ZERO );
+    }
+
+    private void generatePageMap() 
+    {
+        pageMap = new HashMap<String,Integer>();
+        // these page nodes could be references to pages, 
+        // or references to arrays which have references to pages
+        // or references to arrays which have references to arrays which have references to pages
+        // or ... (I think you get the idea...)
+        processListOfPageReferences(getDocumentCatalog().getPages().getKids());
+    }
+    
+    private void processListOfPageReferences(List<Object> pageNodes)
+    {
+        for(int i=0; i < pageNodes.size(); ++i) 
+        {
+            Object pageOrArray = pageNodes.get(i);
+            if(pageOrArray instanceof PDPage)
+            {
+                List pageArray = ((((PDPage)pageOrArray).getParent()).getKids());
+                parseCatalogObject((COSObject)pageArray.get(i));
+            }
+            else if(pageOrArray instanceof PDPageNode)
+            {
+                processListOfPageReferences(((PDPageNode)pageOrArray).getKids());
+            }
+        }
+    }
+             
+    /**
+     * This will either add the page passed in, or, if it's a pointer to an array
+     * of pages, it'll recursivly call itself and process everything in the list.
+     */
+    private void parseCatalogObject(COSObject thePageOrArrayObject) 
+    {
+        COSBase arrayCountBase = thePageOrArrayObject.getItem(COSName.COUNT);
+        int arrayCount = -1;
+        if(arrayCountBase instanceof COSInteger)
+        {
+            arrayCount = ((COSInteger)arrayCountBase).intValue();
+        }
+        COSBase kidsBase = thePageOrArrayObject.getItem(COSName.KIDS);
+        int kidsCount = -1;
+        if(kidsBase instanceof COSArray)
+        {
+            kidsCount = ((COSArray)kidsBase).size();
+        }
+     
+        if(arrayCount == -1 || kidsCount == -1) 
+        {
+            // these cases occur when we have a page, not an array of pages
+            String objStr = String.valueOf(thePageOrArrayObject.getObjectNumber().intValue());
+            String genStr = String.valueOf(thePageOrArrayObject.getGenerationNumber().intValue());
+            getPageMap().put(objStr+","+genStr, new Integer(getPageMap().size()+1));
+        } 
+        else 
+        {
+            // we either have an array of page pointers, or an array of arrays
+            if(arrayCount == kidsCount) 
+            {
+                // process the kids... they're all references to pages
+                COSArray kidsArray = ((COSArray)kidsBase);
+                for(int i=0; i<kidsArray.size(); ++i) 
+                {
+                    COSObject thisObject = (COSObject)kidsArray.get(i);
+                    String objStr = String.valueOf(thisObject.getObjectNumber().intValue());
+                    String genStr = String.valueOf(thisObject.getGenerationNumber().intValue());
+                    getPageMap().put(objStr+","+genStr, new Integer(getPageMap().size()+1));
+                }
+            } 
+            else 
+            {
+                // this object is an array of references to other arrays
+                COSArray list = null;
+                if(kidsBase instanceof COSArray)
+                {
+                    list = ((COSArray)kidsBase);
+                }
+                if(list != null) 
+                {
+                    for(int arrayCounter=0; arrayCounter < list.size(); ++arrayCounter) 
+                    {
+                        parseCatalogObject((COSObject)list.get(arrayCounter));
+                    }
+                }
+            }
+        }
+    }
+    /**
+     * This will return the Map containing the mapping from object-ids to pagenumbers.
+     * 
+     * @return the pageMap
+     */
+    public final Map<String,Integer> getPageMap() 
+    {
+        if (pageMap == null)
+        {
+            generatePageMap();
+        }
+        return pageMap;
+    }
+
+    /**
+     * This will add a page to the document.  This is a convenience method, that
+     * will add the page to the root of the hierarchy and set the parent of the
+     * page to the root.
+     *
+     * @param page The page to add to the document.
+     */
+    public void addPage( PDPage page )
+    {
+        PDPageNode rootPages = getDocumentCatalog().getPages();
+        rootPages.getKids().add( page );
+        page.setParent( rootPages );
+        rootPages.updateCount();
+    }
+
+    public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface) throws IOException, SignatureException
+    {
+        SignatureOptions defaultOptions = new SignatureOptions();
+        defaultOptions.setPage(1);
+        addSignature(sigObject, signatureInterface,defaultOptions);
+    }
+    
+    /**
+     * This will add a signature to the document. 
+     *
+     * @param sigObject is the PDSignature model
+     * @param signatureInterface is a interface which provides signing capabilities
+     * @param options 
+     * @throws IOException if there is an error creating required fields
+     */
+    public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface, SignatureOptions options) throws IOException, SignatureException
+    {
+        // Reserve content
+        // We need to reserve some space for the signature. Some signatures including
+        // big certificate chain and we need enough space to store it.
+        int preferedSignatureSize = options.getPreferedSignatureSize();
+        if (preferedSignatureSize > 0)
+        {
+            sigObject.setContents(new byte[preferedSignatureSize * 2 + 2]);
+        }
+        else
+        {
+            sigObject.setContents(new byte[0x2500 * 2 + 2]);
+        }
+        
+        // Reserve ByteRange
+        sigObject.setByteRange(new int[] {0,1000000000,1000000000,1000000000});
+      
+        getDocument().setSignatureInterface(signatureInterface);
+      
+        // #########################################
+        // # Create SignatureForm for signature    #
+        // # and appending it to the document      #
+        // #########################################
+
+        // Get the first page
+        PDDocumentCatalog root = getDocumentCatalog();
+        PDPageNode rootPages = root.getPages();
+        List<PDPage> kids = new ArrayList<PDPage>();
+        rootPages.getAllKids(kids);
+  
+        int size = (int)rootPages.getCount();
+        PDPage page = null;
+        if (size == 0)
+        {
+            throw new SignatureException(SignatureException.INVALID_PAGE_FOR_SIGNATURE, "The PDF file has no pages");
+        }
+        if (options.getPage()>size)
+        {
+            page = kids.get(size-1);
+        }
+        else if(options.getPage()<=0)
+        {
+            page = kids.get(0);
+        }
+        else
+        {
+            page = kids.get(options.getPage()-1);
+        }
+      
+
+        // Get the AcroForm from the Root-Dictionary and append the annotation
+        PDAcroForm acroForm = root.getAcroForm();
+        root.getCOSObject().setNeedToBeUpdate(true); 
+        
+        if (acroForm==null) 
+        {
+            acroForm = new PDAcroForm(this);
+            root.setAcroForm(acroForm);
+        } 
+        else 
+        {
+            acroForm.getCOSObject().setNeedToBeUpdate(true);
+        }
+      
+        /*
+         * For invisible signatures, the annotation has a rectangle array with values [ 0 0 0 0 ]. 
+         * This annotation is usually attached to the viewed page when the signature is created. 
+         * Despite not having an appearance, the annotation AP and N dictionaries may be present 
+         * in some versions of Acrobat. If present, N references the DSBlankXObj (blank) XObject.
+         */
+
+        // Create Annotation / Field for signature
+        PDSignatureField signatureField = new PDSignatureField(acroForm);
+        signatureField.setSignature(sigObject);              // append the signature object
+        signatureField.getWidget().setPage(page);            // backward linking
+      
+        // Set the AcroForm Fields
+        List acroFormFields = acroForm.getFields();
+        COSDictionary acroFormDict = acroForm.getDictionary();
+        acroFormDict.setDirect(true);
+        acroFormDict.setInt(COSName.SIG_FLAGS, 3);
+        acroFormFields.add(signatureField);
+
+        // Get the object from the visual signature
+        COSDocument visualSignature = options.getVisualSignature();
+
+        // Distinction of case for visual and non-visual signature
+        if (visualSignature == null) // non-visual signature
+        {
+            // Set rectangle for non-visual signature to 0 0 0 0
+            signatureField.getWidget().setRectangle(new PDRectangle());  // rectangle array [ 0 0 0 0 ]
+            // Clear AcroForm / Set DefaultRessource
+            acroFormDict.setItem(COSName.DR, null);
+            // Set empty Appearance-Dictionary
+            PDAppearanceDictionary ap = new PDAppearanceDictionary();
+            COSStream apsStream = new COSStream(getDocument().getScratchFile());
+            apsStream.createUnfilteredStream();
+            PDAppearanceStream aps = new PDAppearanceStream(apsStream);
+            COSDictionary cosObject = (COSDictionary)aps.getCOSObject();
+            cosObject.setItem(COSName.SUBTYPE, COSName.FORM);
+            cosObject.setItem(COSName.BBOX, new PDRectangle());
+            
+            ap.setNormalAppearance(aps);
+            ap.getDictionary().setDirect(true);
+            signatureField.getWidget().setAppearance(ap);
+        }
+        else // visual signature
+        {
+            // Obtain visual signature object
+            List<COSObject> cosObjects = visualSignature.getObjects();
+    
+            boolean annotNotFound = true;
+            boolean sigFieldNotFound = true;
+
+            for ( COSObject cosObject : cosObjects )
+            {
+                COSBase base = cosObject.getObject();
+                if (base != null && base instanceof COSDictionary)
+                {
+                    COSBase ft = ((COSDictionary)base).getItem(COSName.FT);
+                    COSBase type = ((COSDictionary)base).getItem(COSName.TYPE);
+                    COSBase apDict = ((COSDictionary)base).getItem(COSName.AP);
+            
+                    // Search for signature annotation
+                    if (annotNotFound && COSName.ANNOT.equals(type))
+                    {
+                        COSDictionary cosBaseDict = (COSDictionary)base;
+              
+                        // Read and set the Rectangle for visual signature
+                        COSArray rectAry = (COSArray)cosBaseDict.getItem(COSName.RECT);
+                        PDRectangle rect = new PDRectangle(rectAry);
+                        signatureField.getWidget().setRectangle(rect);
+                        annotNotFound = false;
+                    }
+            
+                    // Search for Signature-Field
+                    if (sigFieldNotFound && COSName.SIG.equals(ft) && apDict != null)
+                    {
+                        COSDictionary cosBaseDict = (COSDictionary)base;
+              
+                        // Appearance Dictionary auslesen und setzen
+                        PDAppearanceDictionary ap = new PDAppearanceDictionary((COSDictionary)cosBaseDict.getItem(COSName.AP));
+                        ap.getDictionary().setDirect(true);
+                        signatureField.getWidget().setAppearance(ap);
+              
+                        // AcroForm DefaultRessource auslesen und setzen
+                        COSBase dr = cosBaseDict.getItem(COSName.DR);
+                        dr.setDirect(true);
+                        dr.setNeedToBeUpdate(true);
+                        acroFormDict.setItem(COSName.DR, dr);
+                        sigFieldNotFound=false;
+                    }
+                }
+            }
+        
+            if (annotNotFound || sigFieldNotFound )
+            {
+                throw new SignatureException(SignatureException.VISUAL_SIGNATURE_INVALID, "Could not read all needed objects from template");
+            }
+        }
+      
+        // Get the annotations of the page and append the signature-annotation to it
+        List annotations = page.getAnnotations();
+        if (annotations == null) 
+        {
+            annotations = new COSArrayList();
+            page.setAnnotations(annotations);
+        }
+        // take care that page and acroforms do not share the same array (if so, we don't need to add it twice)
+        if (!(annotations instanceof COSArrayList) 
+                || !(acroFormFields instanceof COSArrayList) 
+                || !((COSArrayList)annotations).toList().equals(((COSArrayList)acroFormFields).toList()))
+        {
+            annotations.add(signatureField.getWidget());
+        }
+        page.getCOSObject().setNeedToBeUpdate(true);
+    }
+
+    /**
+     * Remove the page from the document.
+     *
+     * @param page The page to remove from the document.
+     *
+     * @return true if the page was found false otherwise.
+     */
+    public boolean removePage( PDPage page )
+    {
+        PDPageNode parent = page.getParent();
+        boolean retval = parent.getKids().remove( page );
+        if( retval )
+        {
+            //do a recursive updateCount starting at the root
+            //of the document
+            getDocumentCatalog().getPages().updateCount();
+        }
+        return retval;
+    }
+
+    /**
+     * Remove the page from the document.
+     *
+     * @param pageNumber 0 based index to page number.
+     * @return true if the page was found false otherwise.
+     */
+    public boolean removePage( int pageNumber )
+    {
+        boolean removed = false;
+        List allPages = getDocumentCatalog().getAllPages();
+        if( allPages.size() > pageNumber)
+        {
+            PDPage page = (PDPage)allPages.get( pageNumber );
+            removed = removePage( page );
+        }
+        return removed;
+    }
+
+    /**
+     * This will import and copy the contents from another location.  Currently
+     * the content stream is stored in a scratch file.  The scratch file is
+     * associated with the document.  If you are adding a page to this document
+     * from another document and want to copy the contents to this document's
+     * scratch file then use this method otherwise just use the addPage method.
+     *
+     * @param page The page to import.
+     * @return The page that was imported.
+     *
+     * @throws IOException If there is an error copying the page.
+     */
+    public PDPage importPage( PDPage page ) throws IOException
+    {
+        PDPage importedPage = new PDPage( new COSDictionary( page.getCOSDictionary() ) );
+        InputStream is = null;
+        OutputStream os = null;
+        try
+        {
+            PDStream src = page.getContents();
+            if(src != null)
+            {
+                PDStream dest = new PDStream( new COSStream( src.getStream(), document.getScratchFile() ) );
+                importedPage.setContents( dest );
+                os = dest.createOutputStream();
+
+                byte[] buf = new byte[10240];
+                int amountRead = 0;
+                is = src.createInputStream();
+                while((amountRead = is.read(buf,0,10240)) > -1)
+                {
+                    os.write(buf, 0, amountRead);
+                }
+            }
+            addPage( importedPage );
+        }
+        finally
+        {
+            if( is != null )
+            {
+                is.close();
+            }
+            if( os != null )
+            {
+                os.close();
+            }
+        }
+        return importedPage;
+
+    }
+
+    /**
+     * Constructor that uses an existing document.  The COSDocument that
+     * is passed in must be valid.
+     *
+     * @param doc The COSDocument that this document wraps.
+     */
+    public PDDocument( COSDocument doc )
+    {
+        document = doc;
+    }
+
+    /**
+     * This will get the low level document.
+     *
+     * @return The document that this layer sits on top of.
+     */
+    public COSDocument getDocument()
+    {
+        return document;
+    }
+
+    /**
+     * This will get the document info dictionary.  This is guaranteed to not return null.
+     *
+     * @return The documents /Info dictionary
+     */
+    public PDDocumentInformation getDocumentInformation()
+    {
+        if( documentInformation == null )
+        {
+            COSDictionary trailer = document.getTrailer();
+            COSDictionary infoDic = (COSDictionary)trailer.getDictionaryObject( COSName.INFO );
+            if( infoDic == null )
+            {
+                infoDic = new COSDictionary();
+                trailer.setItem( COSName.INFO, infoDic );
+            }
+            documentInformation = new PDDocumentInformation( infoDic );
+        }
+        return documentInformation;
+    }
+
+    /**
+     * This will set the document information for this document.
+     *
+     * @param info The updated document information.
+     */
+    public void setDocumentInformation( PDDocumentInformation info )
+    {
+        documentInformation = info;
+        document.getTrailer().setItem( COSName.INFO, info.getDictionary() );
+    }
+
+    /**
+     * This will get the document CATALOG.  This is guaranteed to not return null.
+     *
+     * @return The documents /Root dictionary
+     */
+    public PDDocumentCatalog getDocumentCatalog()
+    {
+        if( documentCatalog == null )
+        {
+            COSDictionary trailer = document.getTrailer();
+            COSBase dictionary = trailer.getDictionaryObject( COSName.ROOT );
+            if (dictionary instanceof COSDictionary) {
+                documentCatalog =
+                    new PDDocumentCatalog(this, (COSDictionary) dictionary);
+            } else {
+                documentCatalog = new PDDocumentCatalog(this);
+            }
+
+        }
+        return documentCatalog;
+    }
+
+    /**
+     * This will tell if this document is encrypted or not.
+     *
+     * @return true If this document is encrypted.
+     */
+    public boolean isEncrypted()
+    {
+        return document.isEncrypted();
+    }
+
+    /**
+     * This will get the encryption dictionary for this document.  This will still
+     * return the parameters if the document was decrypted.  If the document was
+     * never encrypted then this will return null.  As the encryption architecture
+     * in PDF documents is plugable this returns an abstract class, but the only
+     * supported subclass at this time is a PDStandardEncryption object.
+     *
+     * @return The encryption dictionary(most likely a PDStandardEncryption object)
+     *
+     * @throws IOException If there is an error determining which security handler to use.
+     */
+    public PDEncryptionDictionary getEncryptionDictionary() throws IOException
+    {
+        if( encParameters == null )
+        {
+            if( isEncrypted() )
+            {
+                encParameters = new PDEncryptionDictionary(document.getEncryptionDictionary());
+            }
+        }
+        return encParameters;
+    }
+
+    /**
+     * This will set the encryption dictionary for this document.
+     *
+     * @param encDictionary The encryption dictionary(most likely a PDStandardEncryption object)
+     *
+     * @throws IOException If there is an error determining which security handler to use.
+     */
+    public void setEncryptionDictionary( PDEncryptionDictionary encDictionary ) throws IOException
+    {
+        encParameters = encDictionary;
+    }
+
+    public PDSignature getSignatureDictionary() throws IOException 
+    {
+      COSDictionary signatureDictionary = document.getLastSignatureDictionary();
+
+      if (signatureDictionary!= null)
+      {
+        return new PDSignature(signatureDictionary);
+      }
+      return null;
+    }
+    
+    /**
+     * This will determine if this is the user password.  This only applies when
+     * the document is encrypted and uses standard encryption.
+     *
+     * @param password The plain text user password.
+     *
+     * @return true If the password passed in matches the user password used to encrypt the document.
+     *
+     * @throws IOException If there is an error determining if it is the user password.
+     * @throws CryptographyException If there is an error in the encryption algorithms.
+     *
+     * @deprecated
+     */
+    @Deprecated
+    public boolean isUserPassword( String password ) throws IOException, CryptographyException
+    {
+            return false;
+        /*boolean retval = false;
+        if( password == null )
+        {
+            password = "";
+        }
+        PDFEncryption encryptor = new PDFEncryption();
+        PDEncryptionDictionary encryptionDictionary = getEncryptionDictionary();
+        if( encryptionDictionary == null )
+        {
+            throw new IOException( "Error: Document is not encrypted" );
+        }
+        else
+        {
+            if( encryptionDictionary instanceof PDStandardEncryption )
+            {
+                COSString documentID = (COSString)document.getDocumentID().get(0);
+                PDStandardEncryption standard = (PDStandardEncryption)encryptionDictionary;
+                retval = encryptor.isUserPassword(
+                    password.getBytes("ISO-8859-1"),
+                    standard.getUserKey(),
+                    standard.getOwnerKey(),
+                    standard.getPermissions(),
+                    documentID.getBytes(),
+                    standard.getRevision(),
+                    standard.getLength()/8 );
+            }
+            else
+            {
+                throw new IOException( "Error: Encyption dictionary is not 'Standard'" +
+                    encryptionDictionary.getClass().getName() );
+            }
+        }
+        return retval;*/
+    }
+
+    /**
+     * This will determine if this is the owner password.  This only applies when
+     * the document is encrypted and uses standard encryption.
+     *
+     * @param password The plain text owner password.
+     *
+     * @return true If the password passed in matches the owner password used to encrypt the document.
+     *
+     * @throws IOException If there is an error determining if it is the user password.
+     * @throws CryptographyException If there is an error in the encryption algorithms.
+     *
+     * @deprecated
+     */
+    @Deprecated
+    public boolean isOwnerPassword( String password ) throws IOException, CryptographyException
+    {
+            return false;
+        /*boolean retval = false;
+        if( password == null )
+        {
+            password = "";
+        }
+        PDFEncryption encryptor = new PDFEncryption();
+        PDEncryptionDictionary encryptionDictionary = getEncryptionDictionary();
+        if( encryptionDictionary == null )
+        {
+            throw new IOException( "Error: Document is not encrypted" );
+        }
+        else
+        {
+            if( encryptionDictionary instanceof PDStandardEncryption )
+            {
+                COSString documentID = (COSString)document.getDocumentID().get( 0 );
+                PDStandardEncryption standard = (PDStandardEncryption)encryptionDictionary;
+                retval = encryptor.isOwnerPassword(
+                    password.getBytes("ISO-8859-1"),
+                    standard.getUserKey(),
+                    standard.getOwnerKey(),
+                    standard.getPermissions(),
+                    documentID.getBytes(),
+                    standard.getRevision(),
+                    standard.getLength()/8 );
+            }
+            else
+            {
+                throw new IOException( "Error: Encyption dictionary is not 'Standard'" +
+                    encryptionDictionary.getClass().getName() );
+            }
+        }
+        return retval;*/
+    }
+
+    /**
+     * This will decrypt a document. This method is provided for compatibility reasons only. User should use
+     * the new security layer instead and the openProtection method especially.
+     *
+     * @param password Either the user or owner password.
+     *
+     * @throws CryptographyException If there is an error decrypting the document.
+     * @throws IOException If there is an error getting the stream data.
+     * @throws InvalidPasswordException If the password is not a user or owner password.
+     *
+     */
+    public void decrypt( String password ) throws CryptographyException, IOException, InvalidPasswordException
+    {
+        try
+        {
+            StandardDecryptionMaterial m = new StandardDecryptionMaterial(password);
+            this.openProtection(m);
+            document.dereferenceObjectStreams();
+        }
+        catch(BadSecurityHandlerException e)
+        {
+            throw new CryptographyException(e);
+        }
+    }
+
+    /**
+     * This will tell if the document was decrypted with the master password.  This
+     * entry is invalid if the PDF was not decrypted.
+     *
+     * @return true if the pdf was decrypted with the master password.
+     *
+     * @deprecated use <code>getCurrentAccessPermission</code> instead
+     */
+    @Deprecated
+    public boolean wasDecryptedWithOwnerPassword()
+    {
+        return false;
+    }
+
+    /**
+     * This will <b>mark</b> a document to be encrypted.  The actual encryption
+     * will occur when the document is saved.
+     * This method is provided for compatibility reasons only. User should use
+     * the new security layer instead and the openProtection method especially.
+     *
+     * @param ownerPassword The owner password to encrypt the document.
+     * @param userPassword The user password to encrypt the document.
+     *
+     * @throws CryptographyException If an error occurs during encryption.
+     * @throws IOException If there is an error accessing the data.
+     *
+     */
+    public void encrypt( String ownerPassword, String userPassword )
+        throws CryptographyException, IOException
+    {
+        try
+        {
+            StandardProtectionPolicy policy =
+                new StandardProtectionPolicy(ownerPassword, userPassword, new AccessPermission());
+            this.protect(policy);
+        }
+        catch(BadSecurityHandlerException e)
+        {
+            throw new CryptographyException(e);
+        }
+    }
+
+
+    /**
+     * The owner password that was passed into the encrypt method. You should
+     * never use this method.  This will not longer be valid once encryption
+     * has occured.
+     *
+     * @return The owner password passed to the encrypt method.
+     *
+     * @deprecated Do not rely on this method anymore.
+     */
+    @Deprecated
+    public String getOwnerPasswordForEncryption()
+    {
+        return null;
+    }
+
+    /**
+     * The user password that was passed into the encrypt method.  You should
+     * never use this method.  This will not longer be valid once encryption
+     * has occured.
+     *
+     * @return The user password passed to the encrypt method.
+     *
+     * @deprecated Do not rely on this method anymore.
+     */
+    @Deprecated
+    public String getUserPasswordForEncryption()
+    {
+        return null;
+    }
+
+    /**
+     * Internal method do determine if the document will be encrypted when it is saved.
+     *
+     * @return True if encrypt has been called and the document
+     *              has not been saved yet.
+     *
+     * @deprecated Do not rely on this method anymore. It is the responsibility of
+     * COSWriter to hold this state
+     */
+    @Deprecated
+    public boolean willEncryptWhenSaving()
+    {
+        return false;
+    }
+
+    /**
+     * This shoule only be called by the COSWriter after encryption has completed.
+     *
+     * @deprecated Do not rely on this method anymore. It is the responsability of
+     * COSWriter to hold this state.
+     */
+    @Deprecated
+    public void clearWillEncryptWhenSaving()
+    {
+        //method is deprecated.
+    }
+
+    /**
+     * This will load a document from a url.
+     *
+     * @param url The url to load the PDF from.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( URL url ) throws IOException
+    {
+        return load( url.openStream() );
+    }
+    /**
+     * This will load a document from a url. Used for skipping corrupt
+     * pdf objects
+     *
+     * @param url The url to load the PDF from.
+     * @param force When true, the parser will skip corrupt pdf objects and 
+     * will continue parsing at the next object in the file
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load(URL url, boolean force) throws IOException
+    {
+        return load(url.openStream(), force);
+    }
+
+    /**
+     * This will load a document from a url.
+     *
+     * @param url The url to load the PDF from.
+     * @param scratchFile A location to store temp PDFBox data for this document.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( URL url, RandomAccess scratchFile ) throws IOException
+    {
+        return load( url.openStream(), scratchFile );
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param filename The name of the file to load.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( String filename ) throws IOException
+    {
+        return load( new FileInputStream( filename ) );
+    }
+    
+    /**
+     * This will load a document from a file. Allows for skipping corrupt pdf
+     * objects
+     *
+     * @param filename The name of the file to load.
+     * @param force When true, the parser will skip corrupt pdf objects and 
+     * will continue parsing at the next object in the file
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load(String filename, boolean force) throws IOException
+    {
+        return load(new FileInputStream( filename ), force);
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param filename The name of the file to load.
+     * @param scratchFile A location to store temp PDFBox data for this document.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( String filename, RandomAccess scratchFile ) throws IOException
+    {
+        return load( new FileInputStream( filename ), scratchFile );
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param file The name of the file to load.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( File file ) throws IOException
+    {
+        return load( new FileInputStream( file ) );
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param file The name of the file to load.
+     * @param scratchFile A location to store temp PDFBox data for this document.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( File file, RandomAccess scratchFile ) throws IOException
+    {
+        return load( new FileInputStream( file ), scratchFile );
+    }
+
+    /**
+     * This will load a document from an input stream.
+     *
+     * @param input The stream that contains the document.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( InputStream input ) throws IOException
+    {
+        return load( input, null );
+    }
+
+    /**
+     * This will load a document from an input stream.
+     * Allows for skipping corrupt pdf objects
+     *
+     * @param input The stream that contains the document.
+     * @param force When true, the parser will skip corrupt pdf objects and 
+     * will continue parsing at the next object in the file
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load(InputStream input, boolean force) throws IOException
+    {
+        return load(input, null, force);
+    }
+    
+    /**
+     * This will load a document from an input stream.
+     *
+     * @param input The stream that contains the document.
+     * @param scratchFile A location to store temp PDFBox data for this document.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load( InputStream input, RandomAccess scratchFile ) throws IOException
+    {
+        PDFParser parser = new PDFParser( new BufferedInputStream( input ) , scratchFile );
+        parser.parse();
+        return parser.getPDDocument();
+    }
+    
+    /**
+     * This will load a document from an input stream. Allows for skipping corrupt pdf objects
+     * 
+     * @param input The stream that contains the document.
+     * @param scratchFile A location to store temp PDFBox data for this document.
+     * @param force When true, the parser will skip corrupt pdf objects and 
+     * will continue parsing at the next object in the file
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static PDDocument load(InputStream input, RandomAccess scratchFile, boolean force) throws IOException
+    {
+        PDFParser parser = new PDFParser( new BufferedInputStream( input ), scratchFile, force);
+        parser.parse();
+        return parser.getPDDocument();
+    }
+
+    /**
+     * This will save this document to the filesystem.
+     *
+     * @param fileName The file to save as.
+     *
+     * @throws IOException If there is an error saving the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void save( String fileName ) throws IOException, COSVisitorException
+    {
+        save( new FileOutputStream( fileName ) );
+    }
+
+    /**
+     * This will save the document to an output stream.
+     *
+     * @param output The stream to write to.
+     *
+     * @throws IOException If there is an error writing the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void save( OutputStream output ) throws IOException, COSVisitorException
+    {
+        //update the count in case any pages have been added behind the scenes.
+        getDocumentCatalog().getPages().updateCount();
+        COSWriter writer = null;
+        try
+        {
+            writer = new COSWriter( output );
+            writer.write( this );
+            writer.close();
+        }
+        finally
+        {
+            if( writer != null )
+            {
+                writer.close();
+            }
+        }
+    }
+
+    public void saveIncremental( String fileName ) throws IOException, COSVisitorException
+    {
+        saveIncremental( new FileInputStream( fileName ) , new FileOutputStream( fileName , true) );
+    }
+    
+    public void saveIncremental( FileInputStream input, OutputStream output ) throws IOException, COSVisitorException
+    {
+        //update the count in case any pages have been added behind the scenes.
+        getDocumentCatalog().getPages().updateCount();
+        COSWriter writer = null;
+        try
+        {
+            writer = new COSWriter( output, input );
+            writer.write( this );
+            writer.close();
+        }
+        finally
+        {
+            if( writer != null )
+            {
+                writer.close();
+            }
+        }
+    }
+
+    /**
+     * This will return the total page count of the PDF document.  Note: This method
+     * is deprecated in favor of the getNumberOfPages method.  The getNumberOfPages is
+     * a required interface method of the Pageable interface.  This method will
+     * be removed in a future version of PDFBox!!
+     *
+     * @return The total number of pages in the PDF document.
+     * @deprecated Use the getNumberOfPages method instead!
+     */
+    @Deprecated
+    public int getPageCount()
+    {
+        return getNumberOfPages();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getNumberOfPages()
+    {
+        PDDocumentCatalog cat = getDocumentCatalog();
+        return (int)cat.getPages().getCount();
+    }
+
+    /**
+     * Returns the format of the page at the given index when using a
+     * default printer job returned by  {@link PrinterJob#getPrinterJob()}.
+     *
+     * @deprecated Use the {@link PDPageable} adapter class
+     * @param i page index, zero-based
+     * @return page format
+     * @throws IndexOutOfBoundsException if the page index is invalid
+     */
+    @Deprecated
+    public PageFormat getPageFormat(int pageIndex)
+    {
+        try {
+            PrinterJob printerJob = PrinterJob.getPrinterJob();
+            return new PDPageable(this, printerJob).getPageFormat(pageIndex);
+        } catch (PrinterException e) {
+            throw new RuntimeException(e);
+        }
+    } 
+
+    /**
+     * {@inheritDoc}
+     */
+    public Printable getPrintable(int pageIndex)
+    {
+        return (Printable)getDocumentCatalog().getAllPages().get( pageIndex );
+    }
+
+    /**
+     * @see PDDocument#print()
+     *
+     * @param printJob The printer job.
+     *
+     * @throws PrinterException If there is an error while sending the PDF to
+     * the printer, or you do not have permissions to print this document.
+     */
+    public void print(PrinterJob printJob) throws PrinterException
+    {
+        print(printJob, false);
+    }
+
+    /**
+     * This will send the PDF document to a printer.  The printing functionality
+     * depends on the org.apache.pdfbox.pdfviewer.PageDrawer functionality.  The PageDrawer
+     * is a work in progress and some PDFs will print correctly and some will
+     * not.  This is a convenience method to create the java.awt.print.PrinterJob.
+     * The PDDocument implements the java.awt.print.Pageable interface and
+     * PDPage implementes the java.awt.print.Printable interface, so advanced printing
+     * capabilities can be done by using those interfaces instead of this method.
+     *
+     * @throws PrinterException If there is an error while sending the PDF to
+     * the printer, or you do not have permissions to print this document.
+     */
+    public void print() throws PrinterException
+    {
+        print( PrinterJob.getPrinterJob() );
+    }
+
+    /**
+     * This will send the PDF to the default printer without prompting the user
+     * for any printer settings.
+     *
+     * @see PDDocument#print()
+     *
+     * @throws PrinterException If there is an error while printing.
+     */
+    public void silentPrint() throws PrinterException
+    {
+        silentPrint( PrinterJob.getPrinterJob() );
+    }
+
+    /**
+     * This will send the PDF to the default printer without prompting the user
+     * for any printer settings.
+     *
+     * @param printJob A printer job definition.
+     * @see PDDocument#print()
+     *
+     * @throws PrinterException If there is an error while printing.
+     */
+    public void silentPrint( PrinterJob printJob ) throws PrinterException
+    {
+        print(printJob, true);
+    }
+
+    private void print(PrinterJob job, boolean silent) throws PrinterException {
+        if (job == null) {
+            throw new PrinterException("The given printer job is null.");
+        } else {
+            job.setPageable(new PDPageable(this, job));
+            if (silent || job.printDialog()) {
+                job.print();
+            }
+        }
+    }
+
+    /**
+     * This will close the underlying COSDocument object.
+     *
+     * @throws IOException If there is an error releasing resources.
+     */
+    public void close() throws IOException
+    {
+        document.close();
+    }
+
+
+    /**
+     * Protects the document with the protection policy pp. The document content will be really encrypted
+     * when it will be saved. This method only marks the document for encryption.
+     *
+     * @see org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy
+     * @see org.apache.pdfbox.pdmodel.encryption.PublicKeyProtectionPolicy
+     *
+     * @param pp The protection policy.
+     *
+     * @throws BadSecurityHandlerException If there is an error during protection.
+     */
+    public void protect(ProtectionPolicy pp) throws BadSecurityHandlerException
+    {
+        SecurityHandler handler = SecurityHandlersManager.getInstance().getSecurityHandler(pp);
+        securityHandler = handler;
+    }
+
+    /**
+     * Tries to decrypt the document in memory using the provided decryption material.
+     *
+     *  @see org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial
+     *  @see org.apache.pdfbox.pdmodel.encryption.PublicKeyDecryptionMaterial
+     *
+     * @param pm The decryption material (password or certificate).
+     *
+     * @throws BadSecurityHandlerException If there is an error during decryption.
+     * @throws IOException If there is an error reading cryptographic information.
+     * @throws CryptographyException If there is an error during decryption.
+     */
+    public void openProtection(DecryptionMaterial pm)
+        throws BadSecurityHandlerException, IOException, CryptographyException
+    {
+        PDEncryptionDictionary dict = this.getEncryptionDictionary();
+        if(dict.getFilter() != null)
+        {
+            securityHandler = SecurityHandlersManager.getInstance().getSecurityHandler(dict.getFilter());
+            securityHandler.decryptDocument(this, pm);
+            document.dereferenceObjectStreams();
+            document.setEncryptionDictionary( null );
+        }
+        else
+        {
+            throw new RuntimeException("This document does not need to be decrypted");
+        }
+    }
+
+    /**
+     * Returns the access permissions granted when the document was decrypted.
+     * If the document was not decrypted this method returns the access permission
+     * for a document owner (ie can do everything).
+     * The returned object is in read only mode so that permissions cannot be changed.
+     * Methods providing access to content should rely on this object to verify if the current
+     * user is allowed to proceed.
+     *
+     * @return the access permissions for the current user on the document.
+     */
+
+    public AccessPermission getCurrentAccessPermission()
+    {
+        if(this.securityHandler == null)
+        {
+            return AccessPermission.getOwnerAccessPermission();
+        }
+        return securityHandler.getCurrentAccessPermission();
+    }
+
+    /**
+     * Get the security handler that is used for document encryption.
+     *
+     * @return The handler used to encrypt/decrypt the document.
+     */
+    public SecurityHandler getSecurityHandler()
+    {
+        return securityHandler;
+    }
+
+    public boolean isAllSecurityToBeRemoved() {
+        return allSecurityToBeRemoved;
+    }
+
+    public void setAllSecurityToBeRemoved(boolean allSecurityToBeRemoved) {
+        this.allSecurityToBeRemoved = allSecurityToBeRemoved;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java
new file mode 100644 (file)
index 0000000..14a6cf3
--- /dev/null
@@ -0,0 +1,630 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+import org.apache.pdfbox.pdmodel.common.PDPageLabels;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.PDDocumentCatalogAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDURIDictionary;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.pagenavigation.PDThread;
+import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences;
+
+/**
+ * This class represents the acroform of a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.21 $
+ */
+public class PDDocumentCatalog implements COSObjectable
+{
+    private COSDictionary root;
+    private PDDocument document;
+
+    private PDAcroForm acroForm = null;
+
+    /**
+     * Page mode where neither the outline nor the thumbnails
+     * are displayed.
+     */
+    public static final String PAGE_MODE_USE_NONE = "UseNone";
+    /**
+     * Show bookmarks when pdf is opened.
+     */
+    public static final String PAGE_MODE_USE_OUTLINES = "UseOutlines";
+    /**
+     * Show thumbnails when pdf is opened.
+     */
+    public static final String PAGE_MODE_USE_THUMBS = "UseThumbs";
+    /**
+     * Full screen mode with no menu bar, window controls.
+     */
+    public static final String PAGE_MODE_FULL_SCREEN = "FullScreen";
+    /**
+     * Optional content group panel is visible when opened.
+     */
+    public static final String PAGE_MODE_USE_OPTIONAL_CONTENT = "UseOC";
+    /**
+     * Attachments panel is visible.
+     */
+    public static final String PAGE_MODE_USE_ATTACHMENTS = "UseAttachments";
+
+    /**
+     * Display one page at a time.
+     */
+    public static final String PAGE_LAYOUT_SINGLE_PAGE = "SinglePage";
+    /**
+     * Display the pages in one column.
+     */
+    public static final String PAGE_LAYOUT_ONE_COLUMN = "OneColumn";
+    /**
+     * Display the pages in two columns, with odd numbered pagse on the left.
+     */
+    public static final String PAGE_LAYOUT_TWO_COLUMN_LEFT = "TwoColumnLeft";
+    /**
+     * Display the pages in two columns, with odd numbered pagse on the right.
+     */
+    public static final String PAGE_LAYOUT_TWO_COLUMN_RIGHT ="TwoColumnRight";
+    /**
+     * Display the pages two at a time, with odd-numbered pages on the left.
+     * @since PDF Version 1.5
+     */
+    public static final String PAGE_LAYOUT_TWO_PAGE_LEFT = "TwoPageLeft";
+    /**
+     * Display the pages two at a time, with odd-numbered pages on the right.
+     * @since PDF Version 1.5
+     */
+    public static final String PAGE_LAYOUT_TWO_PAGE_RIGHT = "TwoPageRight";
+
+
+
+    /**
+     * Constructor.
+     *
+     * @param doc The document that this catalog is part of.
+     */
+    public PDDocumentCatalog( PDDocument doc )
+    {
+        document = doc;
+        root = new COSDictionary();
+        root.setItem( COSName.TYPE, COSName.CATALOG );
+        document.getDocument().getTrailer().setItem( COSName.ROOT, root );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param doc The document that this catalog is part of.
+     * @param rootDictionary The root dictionary that this object wraps.
+     */
+    public PDDocumentCatalog( PDDocument doc, COSDictionary rootDictionary )
+    {
+        document = doc;
+        root = rootDictionary;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return root;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return root;
+    }
+
+    /**
+     * This will get the documents acroform.  This will return null if
+     * no acroform is part of the document.
+     *
+     * @return The documents acroform.
+     */
+    public PDAcroForm getAcroForm()
+    {
+        if( acroForm == null )
+        {
+            COSDictionary acroFormDic =
+                (COSDictionary)root.getDictionaryObject( COSName.ACRO_FORM );
+            if( acroFormDic != null )
+            {
+                acroForm = new PDAcroForm( document, acroFormDic );
+            }
+        }
+        return acroForm;
+    }
+
+    /**
+     * Set the acro form for this catalog.
+     *
+     * @param acro The new acro form.
+     */
+    public void setAcroForm( PDAcroForm acro )
+    {
+        root.setItem( COSName.ACRO_FORM, acro );
+    }
+
+    /**
+     * This will get the root node for the pages.
+     *
+     * @return The parent page node.
+     */
+    public PDPageNode getPages()
+    {
+        return new PDPageNode( (COSDictionary)root.getDictionaryObject( COSName.PAGES ) );
+    }
+
+    /**
+     * The PDF document contains a hierarchical structure of PDPageNode and PDPages, which
+     * is mostly just a way to store this information.  This method will return a flat list
+     * of all PDPage objects in this document.
+     *
+     * @return A list of PDPage objects.
+     */
+    public List getAllPages()
+    {
+        List retval = new ArrayList();
+        PDPageNode rootNode = getPages();
+        //old (slower):
+        //getPageObjects( rootNode, retval );
+        rootNode.getAllKids(retval);
+        return retval;
+    }
+
+    /**
+     * Get the viewer preferences associated with this document or null if they
+     * do not exist.
+     *
+     * @return The document's viewer preferences.
+     */
+    public PDViewerPreferences getViewerPreferences()
+    {
+        PDViewerPreferences retval = null;
+        COSDictionary dict = (COSDictionary)root.getDictionaryObject( COSName.VIEWER_PREFERENCES );
+        if( dict != null )
+        {
+            retval = new PDViewerPreferences( dict );
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the viewer preferences.
+     *
+     * @param prefs The new viewer preferences.
+     */
+    public void setViewerPreferences( PDViewerPreferences prefs )
+    {
+        root.setItem( COSName.VIEWER_PREFERENCES, prefs );
+    }
+
+    /**
+     * Get the outline associated with this document or null if it
+     * does not exist.
+     *
+     * @return The document's outline.
+     */
+    public PDDocumentOutline getDocumentOutline()
+    {
+        PDDocumentOutline retval = null;
+        COSDictionary dict = (COSDictionary)root.getDictionaryObject( COSName.OUTLINES );
+        if( dict != null )
+        {
+            retval = new PDDocumentOutline( dict );
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the document outlines.
+     *
+     * @param outlines The new document outlines.
+     */
+    public void setDocumentOutline( PDDocumentOutline outlines )
+    {
+        root.setItem( COSName.OUTLINES, outlines );
+    }
+
+    /**
+     * Get the list threads for this pdf document.
+     *
+     * @return A list of PDThread objects.
+     */
+    public List getThreads()
+    {
+        COSArray array = (COSArray)root.getDictionaryObject( COSName.THREADS );
+        if( array == null )
+        {
+            array = new COSArray();
+            root.setItem( COSName.THREADS, array );
+        }
+        List pdObjects = new ArrayList();
+        for( int i=0; i<array.size(); i++ )
+        {
+            pdObjects.add( new PDThread( (COSDictionary)array.getObject( i ) ) );
+        }
+        return new COSArrayList( pdObjects, array );
+    }
+
+    /**
+     * Set the list of threads for this pdf document.
+     *
+     * @param threads The list of threads, or null to clear it.
+     */
+    public void setThreads( List threads )
+    {
+        root.setItem( COSName.THREADS, COSArrayList.converterToCOSArray( threads ) );
+    }
+
+    /**
+     * Get the metadata that is part of the document catalog.  This will
+     * return null if there is no meta data for this object.
+     *
+     * @return The metadata for this object.
+     */
+    public PDMetadata getMetadata()
+    {
+        PDMetadata retval = null;
+        COSStream stream = (COSStream)root.getDictionaryObject( COSName.METADATA );
+        if( stream != null )
+        {
+            retval = new PDMetadata( stream );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the metadata for this object.  This can be null.
+     *
+     * @param meta The meta data for this object.
+     */
+    public void setMetadata( PDMetadata meta )
+    {
+        root.setItem( COSName.METADATA, meta );
+    }
+
+    /**
+     * Set the Document Open Action for this object.
+     *
+     * @param action The action you want to perform.
+     */
+    public void setOpenAction( PDDestinationOrAction action )
+    {
+        root.setItem( COSName.OPEN_ACTION, action );
+    }
+
+    /**
+     * Get the Document Open Action for this object.
+     *
+     * @return The action to perform when the document is opened.
+     *
+     * @throws IOException If there is an error creating the destination
+     * or action.
+     */
+    public PDDestinationOrAction getOpenAction() throws IOException
+    {
+        PDDestinationOrAction action = null;
+        COSBase actionObj = root.getDictionaryObject(COSName.OPEN_ACTION);
+
+        if( actionObj == null )
+        {
+            //no op
+        }
+        else if( actionObj instanceof COSDictionary )
+        {
+            action = PDActionFactory.createAction((COSDictionary)actionObj);
+        }
+        else if( actionObj instanceof COSArray )
+        {
+            action = PDDestination.create( actionObj );
+        }
+        else
+        {
+            throw new IOException( "Unknown OpenAction " + actionObj );
+        }
+
+        return action;
+    }
+    /**
+     * @return The Additional Actions for this Document
+     */
+    public PDDocumentCatalogAdditionalActions getActions()
+    {
+        COSDictionary addAct = (COSDictionary) root.getDictionaryObject( COSName.AA );
+        if (addAct == null)
+        {
+            addAct = new COSDictionary();
+            root.setItem(COSName.AA, addAct);
+        }
+        return new PDDocumentCatalogAdditionalActions(addAct);
+    }
+
+    /**
+     * Set the additional actions for the document.
+     *
+     * @param actions The actions that are associated with this document.
+     */
+    public void setActions( PDDocumentCatalogAdditionalActions actions )
+    {
+        root.setItem(COSName.AA, actions );
+    }
+
+    /**
+     * @return The names dictionary for this document or null if none exist.
+     */
+    public PDDocumentNameDictionary getNames()
+    {
+        PDDocumentNameDictionary nameDic = null;
+        COSDictionary names = (COSDictionary) root.getDictionaryObject(COSName.NAMES);
+        if(names != null)
+        {
+            nameDic = new PDDocumentNameDictionary(this,names);
+        }
+        return nameDic;
+    }
+
+    /**
+     * Set the names dictionary for the document.
+     *
+     * @param names The names dictionary that is associated with this document.
+     */
+    public void setNames( PDDocumentNameDictionary names )
+    {
+        root.setItem(COSName.NAMES, names );
+    }
+
+    /**
+     * Get info about doc's usage of tagged features.  This will return
+     * null if there is no information.
+     *
+     * @return The new mark info.
+     */
+    public PDMarkInfo getMarkInfo()
+    {
+        PDMarkInfo retval = null;
+        COSDictionary dic = (COSDictionary)root.getDictionaryObject( COSName.MARK_INFO );
+        if( dic != null )
+        {
+            retval = new PDMarkInfo( dic );
+        }
+        return retval;
+    }
+
+    /**
+     * Set information about the doc's usage of tagged features.
+     *
+     * @param markInfo The new MarkInfo data.
+     */
+    public void setMarkInfo( PDMarkInfo markInfo )
+    {
+        root.setItem( COSName.MARK_INFO, markInfo );
+    }
+
+    /**
+     * Set the page display mode, see the PAGE_MODE_XXX constants.
+     * @return A string representing the page mode.
+     */
+    public String getPageMode()
+    {
+        return root.getNameAsString( COSName.PAGE_MODE, PAGE_MODE_USE_NONE );
+    }
+
+    /**
+     * Set the page mode.  See the PAGE_MODE_XXX constants for valid values.
+     * @param mode The new page mode.
+     */
+    public void setPageMode( String mode )
+    {
+        root.setName( COSName.PAGE_MODE, mode );
+    }
+
+    /**
+     * Set the page layout, see the PAGE_LAYOUT_XXX constants.
+     * @return A string representing the page layout.
+     */
+    public String getPageLayout()
+    {
+        return root.getNameAsString( COSName.PAGE_LAYOUT, PAGE_LAYOUT_SINGLE_PAGE );
+    }
+
+    /**
+     * Set the page layout.  See the PAGE_LAYOUT_XXX constants for valid values.
+     * @param layout The new page layout.
+     */
+    public void setPageLayout( String layout )
+    {
+        root.setName( COSName.PAGE_LAYOUT, layout );
+    }
+
+    /**
+     * Document level information in the URI.
+     * @return Document level URI.
+     */
+    public PDURIDictionary getURI()
+    {
+        PDURIDictionary retval = null;
+        COSDictionary uri = (COSDictionary)root.getDictionaryObject( COSName.URI );
+        if( uri != null )
+        {
+            retval = new PDURIDictionary( uri );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the document level uri.
+     * @param uri The new document level uri.
+     */
+    public void setURI( PDURIDictionary uri )
+    {
+        root.setItem( COSName.URI, uri );
+    }
+
+    /**
+     * Get the document's structure tree root.
+     *
+     * @return The document's structure tree root or null if none exists.
+     */
+    public PDStructureTreeRoot getStructureTreeRoot()
+    {
+        PDStructureTreeRoot treeRoot = null;
+        COSDictionary dic = (COSDictionary)root.getDictionaryObject( COSName.STRUCT_TREE_ROOT );
+        if( dic != null )
+        {
+            treeRoot = new PDStructureTreeRoot( dic );
+        }
+        return treeRoot;
+    }
+
+    /**
+     * Set the document's structure tree root.
+     *
+     * @param treeRoot The new structure tree.
+     */
+    public void setStructureTreeRoot( PDStructureTreeRoot treeRoot )
+    {
+        root.setItem( COSName.STRUCT_TREE_ROOT, treeRoot );
+    }
+
+    /**
+     * The language for the document.
+     *
+     * @return The language for the document.
+     */
+    public String getLanguage()
+    {
+        return root.getString( COSName.LANG );
+    }
+
+    /**
+     * Set the Language for the document.
+     *
+     * @param language The new document language.
+     */
+    public void setLanguage( String language )
+    {
+        root.setString( COSName.LANG, language );
+    }
+
+    /**
+     * Returns the PDF specification version this document conforms to.
+     *
+     * @return The PDF version.
+     */
+    public String getVersion()
+    {
+        return root.getNameAsString(COSName.VERSION);
+    }
+
+    /**
+     * Sets the PDF specification version this document conforms to.
+     *
+     * @param version the PDF version (ex. "1.4")
+     */
+    public void setVersion(String version)
+    {
+        root.setName(COSName.VERSION, version);
+    }
+
+    /**
+     * Returns the page labels descriptor of the document.
+     *
+     * @return the page labels descriptor of the document.
+     *
+     * @throws IOException If there is a problem retrieving the page labels.
+     */
+    public PDPageLabels getPageLabels() throws IOException
+    {
+        PDPageLabels labels = null;
+        COSDictionary dict = (COSDictionary) root.getDictionaryObject(COSName.PAGE_LABELS);
+        if (dict != null)
+        {
+            labels = new PDPageLabels(document, dict);
+        }
+        return labels;
+    }
+
+    /**
+     * Set the page label descriptor for the document.
+     *
+     * @param labels the new page label descriptor to set.
+     */
+    public void setPageLabels(PDPageLabels labels)
+    {
+        root.setItem(COSName.PAGE_LABELS, labels);
+    }
+
+    /**
+     * Get the optional content properties dictionary associated with this document.
+     *
+     * @return the optional properties dictionary or null if it is not present
+     * @since PDF 1.5
+     */
+    public PDOptionalContentProperties getOCProperties()
+    {
+        PDOptionalContentProperties retval = null;
+        COSDictionary dict = (COSDictionary)root.getDictionaryObject(COSName.OCPROPERTIES);
+        if (dict != null)
+        {
+            retval = new PDOptionalContentProperties(dict);
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the optional content properties dictionary.
+     *
+     * @param ocProperties the optional properties dictionary
+     * @since PDF 1.5
+     */
+    public void setOCProperties(PDOptionalContentProperties ocProperties)
+    {
+        //TODO Check for PDF 1.5 or higher
+        root.setItem(COSName.OCPROPERTIES, ocProperties);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentInformation.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentInformation.java
new file mode 100644 (file)
index 0000000..3e4ff85
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import java.io.IOException;
+
+import java.util.Calendar;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This is the document metadata.  Each getXXX method will return the entry if
+ * it exists or null if it does not exist.  If you pass in null for the setXXX
+ * method then it will clear the value.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class PDDocumentInformation implements COSObjectable
+{
+    private COSDictionary info;
+
+    /**
+     * Default Constructor.
+     */
+    public PDDocumentInformation()
+    {
+        info = new COSDictionary();
+    }
+
+    /**
+     * Constructor that is used for a preexisting dictionary.
+     *
+     * @param dic The underlying dictionary.
+     */
+    public PDDocumentInformation( COSDictionary dic )
+    {
+        info = dic;
+    }
+
+    /**
+     * This will get the underlying dictionary that this object wraps.
+     *
+     * @return The underlying info dictionary.
+     */
+    public COSDictionary getDictionary()
+    {
+        return info;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return info;
+    }
+
+    /**
+     * This will get the title of the document.  This will return null if no title exists.
+     *
+     * @return The title of the document.
+     */
+    public String getTitle()
+    {
+        return info.getString( COSName.TITLE );
+    }
+
+    /**
+     * This will set the title of the document.
+     *
+     * @param title The new title for the document.
+     */
+    public void setTitle( String title )
+    {
+        info.setString( COSName.TITLE, title );
+    }
+
+    /**
+     * This will get the author of the document.  This will return null if no author exists.
+     *
+     * @return The author of the document.
+     */
+    public String getAuthor()
+    {
+        return info.getString( COSName.AUTHOR );
+    }
+
+    /**
+     * This will set the author of the document.
+     *
+     * @param author The new author for the document.
+     */
+    public void setAuthor( String author )
+    {
+        info.setString( COSName.AUTHOR, author );
+    }
+
+    /**
+     * This will get the subject of the document.  This will return null if no subject exists.
+     *
+     * @return The subject of the document.
+     */
+    public String getSubject()
+    {
+        return info.getString( COSName.SUBJECT );
+    }
+
+    /**
+     * This will set the subject of the document.
+     *
+     * @param subject The new subject for the document.
+     */
+    public void setSubject( String subject )
+    {
+        info.setString( COSName.SUBJECT, subject );
+    }
+
+    /**
+     * This will get the keywords of the document.  This will return null if no keywords exists.
+     *
+     * @return The keywords of the document.
+     */
+    public String getKeywords()
+    {
+        return info.getString( COSName.KEYWORDS );
+    }
+
+    /**
+     * This will set the keywords of the document.
+     *
+     * @param keywords The new keywords for the document.
+     */
+    public void setKeywords( String keywords )
+    {
+        info.setString( COSName.KEYWORDS, keywords );
+    }
+
+    /**
+     * This will get the creator of the document.  This will return null if no creator exists.
+     *
+     * @return The creator of the document.
+     */
+    public String getCreator()
+    {
+        return info.getString( COSName.CREATOR );
+    }
+
+    /**
+     * This will set the creator of the document.
+     *
+     * @param creator The new creator for the document.
+     */
+    public void setCreator( String creator )
+    {
+        info.setString( COSName.CREATOR, creator );
+    }
+
+    /**
+     * This will get the producer of the document.  This will return null if no producer exists.
+     *
+     * @return The producer of the document.
+     */
+    public String getProducer()
+    {
+        return info.getString( COSName.PRODUCER );
+    }
+
+    /**
+     * This will set the producer of the document.
+     *
+     * @param producer The new producer for the document.
+     */
+    public void setProducer( String producer )
+    {
+        info.setString( COSName.PRODUCER, producer );
+    }
+
+    /**
+     * This will get the creation date of the document.  This will return null if no creation date exists.
+     *
+     * @return The creation date of the document.
+     *
+     * @throws IOException If there is an error creating the date.
+     */
+    public Calendar getCreationDate() throws IOException
+    {
+        return info.getDate( COSName.CREATION_DATE );
+    }
+
+    /**
+     * This will set the creation date of the document.
+     *
+     * @param date The new creation date for the document.
+     */
+    public void setCreationDate( Calendar date )
+    {
+        info.setDate( COSName.CREATION_DATE, date );
+    }
+
+    /**
+     * This will get the modification date of the document.  This will return null if no modification date exists.
+     *
+     * @return The modification date of the document.
+     *
+     * @throws IOException If there is an error creating the date.
+     */
+    public Calendar getModificationDate() throws IOException
+    {
+        return info.getDate( COSName.MOD_DATE );
+    }
+
+    /**
+     * This will set the modification date of the document.
+     *
+     * @param date The new modification date for the document.
+     */
+    public void setModificationDate( Calendar date )
+    {
+        info.setDate( COSName.MOD_DATE, date );
+    }
+
+    /**
+     * This will get the trapped value for the document.
+     * This will return null if one is not found.
+     *
+     * @return The trapped value for the document.
+     */
+    public String getTrapped()
+    {
+        return info.getNameAsString( COSName.TRAPPED );
+    }
+
+    /**
+     * This will get the keys of all metadata information fields for the document.
+     *
+     * @return all metadata key strings.
+     * @since Apache PDFBox 1.3.0
+     */
+    public Set<String> getMetadataKeys()
+    {
+        Set<String> keys = new TreeSet<String>();
+        for (COSName key : info.keySet()) {
+            keys.add(key.getName());
+        }
+        return keys;
+    }
+
+    /**
+     *  This will get the value of a custom metadata information field for the document.
+     *  This will return null if one is not found.
+     *
+     * @param fieldName Name of custom metadata field from pdf document.
+     *
+     * @return String Value of metadata field
+     *
+     * @author  Gerardo Ortiz
+     */
+    public String getCustomMetadataValue(String fieldName)
+    {
+        return info.getString( fieldName );
+    }
+
+    /**
+     * Set the custom metadata value.
+     *
+     * @param fieldName The name of the custom metadata field.
+     * @param fieldValue The value to the custom metadata field.
+     */
+    public void setCustomMetadataValue( String fieldName, String fieldValue )
+    {
+        info.setString( fieldName, fieldValue );
+    }
+
+    /**
+     * This will set the trapped of the document.  This will be
+     * 'True', 'False', or 'Unknown'.
+     *
+     * @param value The new trapped value for the document.
+     */
+    public void setTrapped( String value )
+    {
+        if( value != null &&
+            !value.equals( "True" ) &&
+            !value.equals( "False" ) &&
+            !value.equals( "Unknown" ) )
+        {
+            throw new RuntimeException( "Valid values for trapped are " +
+                                        "'True', 'False', or 'Unknown'" );
+        }
+
+        info.setName( COSName.TRAPPED, value );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentNameDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDDocumentNameDictionary.java
new file mode 100644 (file)
index 0000000..276cfbe
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDDocumentNameDictionary implements COSObjectable
+{
+    private COSDictionary nameDictionary;
+    private PDDocumentCatalog catalog;
+
+    /**
+     * Constructor.
+     *
+     * @param cat The document catalog that this dictionary is part of.
+     */
+    public PDDocumentNameDictionary( PDDocumentCatalog cat )
+    {
+        nameDictionary = new COSDictionary();
+        catalog = cat;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param cat The document that this dictionary is part of.
+     * @param names The names dictionary.
+     */
+    public PDDocumentNameDictionary( PDDocumentCatalog cat, COSDictionary names )
+    {
+        catalog = cat;
+        nameDictionary = names;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return nameDictionary;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos dictionary for this object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return nameDictionary;
+    }
+
+    /**
+     * Get the destination named tree node.  The value in this name tree will be PDDestination
+     * objects.
+     *
+     * @return The destination name tree node.
+     */
+    public PDDestinationNameTreeNode getDests()
+    {
+        PDDestinationNameTreeNode dests = null;
+
+        COSDictionary dic = (COSDictionary)nameDictionary.getDictionaryObject( COSName.DESTS );
+
+        //The document catalog also contains the Dests entry sometimes
+        //so check there as well.
+        if( dic == null )
+        {
+            dic = (COSDictionary)catalog.getCOSDictionary().getDictionaryObject( COSName.DESTS );
+        }
+
+        if( dic != null )
+        {
+            dests = new PDDestinationNameTreeNode( dic );
+        }
+
+
+        return dests;
+    }
+
+    /**
+     * Set the named destinations that are associated with this document.
+     *
+     * @param dests The destination names.
+     */
+    public void setDests( PDDestinationNameTreeNode dests )
+    {
+        nameDictionary.setItem( COSName.DESTS, dests );
+        //The dests can either be in the document catalog or in the
+        //names dictionary, PDFBox will just maintain the one in the
+        //names dictionary for now unless there is a reason to do
+        //something else.
+        //clear the potentially out of date Dests reference.
+        catalog.getCOSDictionary().setItem( COSName.DESTS, (COSObjectable)null);
+    }
+
+    /**
+     * Get the embedded files named tree node.  The value in this name tree will be PDComplexFileSpecification
+     * objects.
+     *
+     * @return The embedded files name tree node.
+     */
+    public PDEmbeddedFilesNameTreeNode getEmbeddedFiles()
+    {
+        PDEmbeddedFilesNameTreeNode retval = null;
+
+        COSDictionary dic = (COSDictionary)nameDictionary.getDictionaryObject( COSName.EMBEDDED_FILES );
+
+        if( dic != null )
+        {
+            retval = new PDEmbeddedFilesNameTreeNode( dic );
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the named embedded files that are associated with this document.
+     *
+     * @param ef The new embedded files
+     */
+    public void setEmbeddedFiles( PDEmbeddedFilesNameTreeNode ef )
+    {
+        nameDictionary.setItem( COSName.EMBEDDED_FILES, ef );
+    }
+
+    /**
+     * Get the document level javascript entries.  The value in this name tree will be PDTextStream.
+     *
+     * @return The document level named javascript.
+     */
+    public PDJavascriptNameTreeNode getJavaScript()
+    {
+        PDJavascriptNameTreeNode retval = null;
+
+        COSDictionary dic = (COSDictionary)nameDictionary.getDictionaryObject( COSName.JAVA_SCRIPT );
+
+        if( dic != null )
+        {
+            retval = new PDJavascriptNameTreeNode( dic );
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the named javascript entries that are associated with this document.
+     *
+     * @param js The new Javascript entries.
+     */
+    public void setJavascript( PDJavascriptNameTreeNode js )
+    {
+        nameDictionary.setItem( COSName.JAVA_SCRIPT, js );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDEmbeddedFilesNameTreeNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDEmbeddedFilesNameTreeNode.java
new file mode 100644 (file)
index 0000000..8df0e29
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+
+/**
+ * This class holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDEmbeddedFilesNameTreeNode extends PDNameTreeNode
+{
+    /**
+     * Constructor.
+     */
+    public PDEmbeddedFilesNameTreeNode()
+    {
+        super( PDComplexFileSpecification.class );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dic The COS dictionary.
+     */
+    public PDEmbeddedFilesNameTreeNode( COSDictionary dic )
+    {
+        super( dic, PDComplexFileSpecification.class );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected Object convertCOSToPD( COSBase base ) throws IOException
+    {
+        COSBase destination = base;
+        return new PDComplexFileSpecification( (COSDictionary)destination );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected PDNameTreeNode createChildNode( COSDictionary dic )
+    {
+        return new PDEmbeddedFilesNameTreeNode(dic);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDJavascriptNameTreeNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDJavascriptNameTreeNode.java
new file mode 100644 (file)
index 0000000..9d05e0a
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+
+/**
+ * This class holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class PDJavascriptNameTreeNode extends PDNameTreeNode
+{
+    /**
+     * Constructor.
+     */
+    public PDJavascriptNameTreeNode()
+    {
+        super( PDTextStream.class );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dic The COS dictionary.
+     */
+    public PDJavascriptNameTreeNode( COSDictionary dic )
+    {
+        super( dic, PDTextStream.class );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected Object convertCOSToPD( COSBase base ) throws IOException
+    {
+        PDTextStream stream = null;
+        if( base instanceof COSString )
+        {
+            stream = new PDTextStream((COSString)base);
+        }
+        else if( base instanceof COSStream )
+        {
+            stream = new PDTextStream((COSStream)base);
+        }
+        else
+        {
+            throw new IOException( "Error creating Javascript object, expected either COSString or COSStream and not " 
+                    + base );
+        }
+        return stream;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected PDNameTreeNode createChildNode( COSDictionary dic )
+    {
+        return new PDJavascriptNameTreeNode(dic);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPage.java
new file mode 100644 (file)
index 0000000..c9a58c1
--- /dev/null
@@ -0,0 +1,848 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.interactive.action.PDPageAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.pagenavigation.PDThreadBead;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImagingOpException;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterIOException;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * This represents a single page in a PDF document.
+ * <p>
+ * This class implements the {@link Printable} interface, but since PDFBox
+ * version 1.3.0 you should be using the {@link PDPageable} adapter instead
+ * (see <a href="https://issues.apache.org/jira/browse/PDFBOX-788">PDFBOX-788</a>).
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.29 $
+ */
+public class PDPage implements COSObjectable, Printable
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDPage.class);
+
+    private static final int DEFAULT_USER_SPACE_UNIT_DPI = 72;
+    
+    private static final float MM_TO_UNITS = 1/(10*2.54f)*DEFAULT_USER_SPACE_UNIT_DPI;
+    
+    /**
+     * Fully transparent that can fall back to white when image type has no alpha.
+     */
+    private static final Color TRANSPARENT_WHITE = new Color( 255, 255, 255, 0 ); 
+    
+    private COSDictionary page;
+
+    /**
+     * A page size of LETTER or 8.5x11.
+     */
+    public static final PDRectangle PAGE_SIZE_LETTER = new PDRectangle( 8.5f*DEFAULT_USER_SPACE_UNIT_DPI, 11f*DEFAULT_USER_SPACE_UNIT_DPI );
+    /**
+     * A page size of A0 Paper.
+     */
+    public static final PDRectangle PAGE_SIZE_A0 = new PDRectangle( 841*MM_TO_UNITS, 1189*MM_TO_UNITS );
+    /**
+     * A page size of A1 Paper.
+     */
+    public static final PDRectangle PAGE_SIZE_A1 = new PDRectangle( 594*MM_TO_UNITS, 841*MM_TO_UNITS );
+    /**
+     * A page size of A2 Paper.
+     */
+    public static final PDRectangle PAGE_SIZE_A2 = new PDRectangle( 420*MM_TO_UNITS, 594*MM_TO_UNITS );
+    /**
+     * A page size of A3 Paper.
+     */
+    public static final PDRectangle PAGE_SIZE_A3 = new PDRectangle( 297*MM_TO_UNITS, 420*MM_TO_UNITS );
+    /**
+     * A page size of A4 Paper.
+     */
+    public static final PDRectangle PAGE_SIZE_A4 = new PDRectangle( 210*MM_TO_UNITS, 297*MM_TO_UNITS );
+    /**
+     * A page size of A5 Paper.
+     */
+    public static final PDRectangle PAGE_SIZE_A5 = new PDRectangle( 148*MM_TO_UNITS, 210*MM_TO_UNITS );
+    /**
+     * A page size of A6 Paper.
+     */
+    public static final PDRectangle PAGE_SIZE_A6 = new PDRectangle( 105*MM_TO_UNITS, 148*MM_TO_UNITS );
+
+    /**
+     * Creates a new instance of PDPage with a size of 8.5x11.
+     */
+    public PDPage()
+    {
+        page = new COSDictionary();
+        page.setItem( COSName.TYPE, COSName.PAGE );
+        setMediaBox( PAGE_SIZE_LETTER );
+    }
+
+    /**
+     * Creates a new instance of PDPage.
+        *
+     * @param size The MediaBox or the page.
+     */
+    public PDPage(PDRectangle size)
+    {
+        page = new COSDictionary();
+        page.setItem( COSName.TYPE, COSName.PAGE );
+        setMediaBox( size );
+    }
+
+    
+    
+    /**
+     * Creates a new instance of PDPage.
+     *
+     * @param pageDic The existing page dictionary.
+     */
+    public PDPage( COSDictionary pageDic )
+    {
+        page = pageDic;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return page;
+    }
+
+    /**
+     * This will get the underlying dictionary that this class acts on.
+     *
+     * @return The underlying dictionary for this class.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return page;
+    }
+
+
+    /**
+     * This is the parent page node.  The parent is a required element of the
+     * page.  This will be null until this page is added to the document.
+     *
+     * @return The parent to this page.
+     */
+    public PDPageNode getParent()
+    {
+        if( parent == null){
+            COSDictionary parentDic = (COSDictionary)page.getDictionaryObject( "Parent", "P" );
+            if( parentDic != null )
+            {
+                parent = new PDPageNode( parentDic );
+            }
+        }
+        return parent;
+    }
+
+    private PDPageNode parent = null;
+    
+    /**
+     * This will set the parent of this page.
+     *
+     * @param parent The parent to this page node.
+     */
+    public void setParent( PDPageNode parent )
+    {
+        this.parent = parent;
+        page.setItem( COSName.PARENT, parent.getDictionary() );
+    }
+
+    /**
+     * This will update the last modified time for the page object.
+     */
+    public void updateLastModified()
+    {
+        page.setDate( "LastModified", new GregorianCalendar() );
+    }
+
+    /**
+     * This will get the date that the content stream was last modified.  This
+     * may return null.
+     *
+     * @return The date the content stream was last modified.
+     *
+     * @throws IOException If there is an error accessing the date information.
+     */
+    public Calendar getLastModified() throws IOException
+    {
+        return page.getDate( "LastModified" );
+    }
+
+    /**
+     * This will get the resources at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findResources() should probably used.
+     * This will return null if no resources are available at this level.
+     *
+     * @return The resources at this level in the hierarchy.
+     */
+    public PDResources getResources()
+    {
+        PDResources retval = null;
+        COSDictionary resources = (COSDictionary)page.getDictionaryObject( COSName.RESOURCES );
+        if( resources != null )
+        {
+            retval = new PDResources( resources );
+        }
+        return retval;
+    }
+
+    /**
+     * This will find the resources for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The resources at this level in the hierarchy.
+     */
+    public PDResources findResources()
+    {
+        PDResources retval = getResources();
+        PDPageNode parent = getParent();
+        if( retval == null && parent != null )
+        {
+            retval = parent.findResources();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the resources for this page.
+     *
+     * @param resources The new resources for this page.
+     */
+    public void setResources( PDResources resources )
+    {
+        page.setItem( COSName.RESOURCES, resources );
+    }
+
+    /**
+     * A rectangle, expressed
+     * in default user space units, defining the boundaries of the physical
+     * medium on which the page is intended to be displayed or printed
+     *
+     * This will get the MediaBox at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findMediaBox() should probably used.
+     * This will return null if no MediaBox are available at this level.
+     *
+     * @return The MediaBox at this level in the hierarchy.
+     */
+    public PDRectangle getMediaBox()
+    {
+        if( mediaBox == null){
+            COSArray array = (COSArray)page.getDictionaryObject( COSName.MEDIA_BOX );
+            if( array != null )
+            {
+               mediaBox = new PDRectangle( array );
+            }
+        }
+        return mediaBox;
+    }
+
+    private PDRectangle mediaBox = null;
+    
+    /**
+     * This will find the MediaBox for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The MediaBox at this level in the hierarchy.
+     */
+    public PDRectangle findMediaBox()
+    {
+        PDRectangle retval = getMediaBox();
+        if( retval == null && getParent() != null )
+        {
+            retval = getParent().findMediaBox();
+        }
+        return retval;
+    }
+    
+    /**
+     * This will set the mediaBox for this page.
+     *
+     * @param mediaBox The new mediaBox for this page.
+     */
+    public void setMediaBox( PDRectangle mediaBox )
+    {
+        this.mediaBox = mediaBox;
+       if( mediaBox == null )
+        {
+            page.removeItem( COSName.MEDIA_BOX );
+        }
+        else
+        {
+            page.setItem( COSName.MEDIA_BOX, mediaBox.getCOSArray() );
+        }
+    }
+
+    /**
+     * A rectangle, expressed in default user space units,
+     * defining the visible region of default user space. When the page is displayed
+     * or printed, its contents are to be clipped (cropped) to this rectangle
+     * and then imposed on the output medium in some implementationdefined
+     * manner
+     *
+     * This will get the CropBox at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findCropBox() should probably used.
+     * This will return null if no CropBox is available at this level.
+     *
+     * @return The CropBox at this level in the hierarchy.
+     */
+    public PDRectangle getCropBox()
+    {
+        PDRectangle retval = null;
+        COSArray array = (COSArray)page.getDictionaryObject( COSName.CROP_BOX);
+        if( array != null )
+        {
+            retval = new PDRectangle( array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will find the CropBox for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The CropBox at this level in the hierarchy.
+     */
+    public PDRectangle findCropBox()
+    {
+        PDRectangle retval = getCropBox();
+        PDPageNode parent = getParent();
+        if( retval == null && parent != null )
+        {
+            retval = findParentCropBox( parent );
+        }
+
+        //default value for cropbox is the media box
+        if( retval == null )
+        {
+            retval = findMediaBox();
+        }
+        return retval;
+    }
+
+    /**
+     * This will search for a crop box in the parent and return null if it is not
+     * found.  It will NOT default to the media box if it cannot be found.
+     *
+     * @param node The node
+     */
+    private PDRectangle findParentCropBox( PDPageNode node )
+    {
+        PDRectangle rect = node.getCropBox();
+        PDPageNode parent = node.getParent();
+        if( rect == null && parent != null )
+        {
+            rect = findParentCropBox( parent );
+        }
+        return rect;
+    }
+
+    /**
+     * This will set the CropBox for this page.
+     *
+     * @param cropBox The new CropBox for this page.
+     */
+    public void setCropBox( PDRectangle cropBox )
+    {
+        if( cropBox == null )
+        {
+            page.removeItem( COSName.CROP_BOX );
+        }
+        else
+        {
+            page.setItem( COSName.CROP_BOX, cropBox.getCOSArray() );
+        }
+    }
+
+    /**
+     * A rectangle, expressed in default user space units, defining
+     * the region to which the contents of the page should be clipped
+     * when output in a production environment.  The default is the CropBox.
+     *
+     * @return The BleedBox attribute.
+     */
+    public PDRectangle getBleedBox()
+    {
+        PDRectangle retval = null;
+        COSArray array = (COSArray)page.getDictionaryObject( COSName.BLEED_BOX );
+        if( array != null )
+        {
+            retval = new PDRectangle( array );
+        }
+        else
+        {
+            retval = findCropBox();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the BleedBox for this page.
+     *
+     * @param bleedBox The new BleedBox for this page.
+     */
+    public void setBleedBox( PDRectangle bleedBox )
+    {
+        if( bleedBox == null )
+        {
+            page.removeItem( COSName.BLEED_BOX );
+        }
+        else
+        {
+            page.setItem( COSName.BLEED_BOX, bleedBox.getCOSArray() );
+        }
+    }
+
+    /**
+     * A rectangle, expressed in default user space units, defining
+     * the intended dimensions of the finished page after trimming.
+     * The default is the CropBox.
+     *
+     * @return The TrimBox attribute.
+     */
+    public PDRectangle getTrimBox()
+    {
+        PDRectangle retval = null;
+        COSArray array = (COSArray)page.getDictionaryObject( COSName.TRIM_BOX );
+        if( array != null )
+        {
+            retval = new PDRectangle( array );
+        }
+        else
+        {
+            retval = findCropBox();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the TrimBox for this page.
+     *
+     * @param trimBox The new TrimBox for this page.
+     */
+    public void setTrimBox( PDRectangle trimBox )
+    {
+        if( trimBox == null )
+        {
+            page.removeItem( COSName.TRIM_BOX );
+        }
+        else
+        {
+            page.setItem( COSName.TRIM_BOX, trimBox.getCOSArray() );
+        }
+    }
+
+    /**
+     * A rectangle, expressed in default user space units, defining
+     * the extent of the page's meaningful content (including potential
+     * white space) as intended by the page's creator  The default isthe CropBox.
+     *
+     * @return The ArtBox attribute.
+     */
+    public PDRectangle getArtBox()
+    {
+        PDRectangle retval = null;
+        COSArray array = (COSArray)page.getDictionaryObject( COSName.ART_BOX );
+        if( array != null )
+        {
+            retval = new PDRectangle( array );
+        }
+        else
+        {
+            retval = findCropBox();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the ArtBox for this page.
+     *
+     * @param artBox The new ArtBox for this page.
+     */
+    public void setArtBox( PDRectangle artBox )
+    {
+        if( artBox == null )
+        {
+            page.removeItem( COSName.ART_BOX );
+        }
+        else
+        {
+            page.setItem( COSName.ART_BOX, artBox.getCOSArray() );
+        }
+    }
+
+
+    //todo BoxColorInfo
+    //todo Contents
+
+    /**
+     * A value representing the rotation.  This will be null if not set at this level
+     * The number of degrees by which the page should
+     * be rotated clockwise when displayed or printed. The value must be a multiple
+     * of 90.
+     *
+     * This will get the rotation at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findRotation() should probably used.
+     * This will return null if no rotation is available at this level.
+     *
+     * @return The rotation at this level in the hierarchy.
+     */
+    public Integer getRotation()
+    {
+        Integer retval = null;
+        COSNumber value = (COSNumber)page.getDictionaryObject( COSName.ROTATE );
+        if( value != null )
+        {
+            retval = new Integer( value.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will find the rotation for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The rotation at this level in the hierarchy.
+     */
+    public int findRotation()
+    {
+        int retval = 0;
+        Integer rotation = getRotation();
+        if( rotation != null )
+        {
+            retval = rotation.intValue();
+        }
+        else
+        {
+            PDPageNode parent = getParent();
+            if( parent != null )
+            {
+                retval = parent.findRotation();
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the rotation for this page.
+     *
+     * @param rotation The new rotation for this page.
+     */
+    public void setRotation( int rotation )
+    {
+        page.setInt( COSName.ROTATE, rotation );
+    }
+
+    /**
+     * This will get the contents of the PDF Page, in the case that the contents
+     * of the page is an array then then the entire array of streams will be
+     * be wrapped and appear as a single stream.
+     *
+     * @return The page content stream.
+     *
+     * @throws IOException If there is an error obtaining the stream.
+     */
+    public PDStream getContents() throws IOException
+    {
+        return PDStream.createFromCOS( page.getDictionaryObject( COSName.CONTENTS ) );
+    }
+
+    /**
+     * This will set the contents of this page.
+     *
+     * @param contents The new contents of the page.
+     */
+    public void setContents( PDStream contents )
+    {
+        page.setItem( COSName.CONTENTS, contents );
+    }
+
+    /**
+     * This will get a list of PDThreadBead objects, which are article threads in the
+     * document.  This will return an empty list of there are no thread beads.
+     *
+     * @return A list of article threads on this page.
+     */
+    public List getThreadBeads()
+    {
+        COSArray beads = (COSArray)page.getDictionaryObject( COSName.B );
+        if( beads == null )
+        {
+            beads = new COSArray();
+        }
+        List pdObjects = new ArrayList();
+        for( int i=0; i<beads.size(); i++)
+        {
+            COSDictionary beadDic = (COSDictionary)beads.getObject( i );
+            PDThreadBead bead = null;
+            //in some cases the bead is null
+            if( beadDic != null )
+            {
+                bead = new PDThreadBead( beadDic );
+            }
+            pdObjects.add( bead );
+        }
+        return new COSArrayList(pdObjects, beads);
+
+    }
+
+    /**
+     * This will set the list of thread beads.
+     *
+     * @param beads A list of PDThreadBead objects or null.
+     */
+    public void setThreadBeads( List beads )
+    {
+        page.setItem( COSName.B, COSArrayList.converterToCOSArray( beads ) );
+    }
+
+    /**
+     * Get the metadata that is part of the document catalog.  This will
+     * return null if there is no meta data for this object.
+     *
+     * @return The metadata for this object.
+     */
+    public PDMetadata getMetadata()
+    {
+        PDMetadata retval = null;
+        COSStream stream = (COSStream)page.getDictionaryObject( COSName.METADATA );
+        if( stream != null )
+        {
+            retval = new PDMetadata( stream );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the metadata for this object.  This can be null.
+     *
+     * @param meta The meta data for this object.
+     */
+    public void setMetadata( PDMetadata meta )
+    {
+        page.setItem( COSName.METADATA, meta );
+    }
+
+    /**
+     * Convert this page to an output image with 8 bits per pixel and the double
+     * default screen resolution.
+     *
+     * @return A graphical representation of this page.
+     *
+     * @throws IOException If there is an error drawing to the image.
+     */
+    public BufferedImage convertToImage() throws IOException
+    {
+        //note we are doing twice as many pixels because
+        //the default size is not really good resolution,
+        //so create an image that is twice the size
+        //and let the client scale it down.
+        return convertToImage(8, 2 * DEFAULT_USER_SPACE_UNIT_DPI);
+    }
+    
+    /**
+     * Convert this page to an output image.
+     *
+     * @param imageType the image type (see {@link BufferedImage}.TYPE_*)
+     * @param resolution the resolution in dpi (dots per inch)
+     * @return A graphical representation of this page.
+     *
+     * @throws IOException If there is an error drawing to the image.
+     */
+    public BufferedImage convertToImage(int imageType, int resolution) throws IOException
+    {
+        PDRectangle mBox = findMediaBox();
+        float widthPt = mBox.getWidth();
+        float heightPt = mBox.getHeight();
+        float scaling = resolution / (float)DEFAULT_USER_SPACE_UNIT_DPI;
+        int widthPx = Math.round(widthPt * scaling);
+        int heightPx = Math.round(heightPt * scaling);
+        //TODO The following reduces accuracy. It should really be a Dimension2D.Float.
+        Dimension pageDimension = new Dimension( (int)widthPt, (int)heightPt );
+            
+        BufferedImage retval = new BufferedImage( widthPx, heightPx, imageType );
+        Graphics2D graphics = (Graphics2D)retval.getGraphics();
+        graphics.setBackground( TRANSPARENT_WHITE );
+        graphics.clearRect( 0, 0, retval.getWidth(), retval.getHeight() );
+        graphics.scale( scaling, scaling );
+        PageDrawer drawer = new PageDrawer();
+        drawer.drawPage( graphics, this, pageDimension );
+
+        //TODO This could be done directly by manipulating the transformation matrix before painting.
+        //That could result in a better image quality.
+        try 
+        {
+            int rotation = findRotation();
+            if (rotation == 90 || rotation == 270) 
+            {
+                 int w = retval.getWidth();    
+                 int h = retval.getHeight();    
+                 BufferedImage rotatedImg = new BufferedImage(w, h, retval.getType());    
+                 Graphics2D g = rotatedImg.createGraphics();    
+                 g.rotate(Math.toRadians(rotation), w/2, h/2);    
+                 g.drawImage(retval, null, 0, 0);    
+            }
+        } 
+        catch (ImagingOpException e)
+        {
+                log.warn("Unable to rotate page image", e);
+        }
+
+        return retval;
+    }
+
+    /**
+     * Get the page actions.
+     *
+     * @return The Actions for this Page
+     */
+    public PDPageAdditionalActions getActions()
+    {
+        COSDictionary addAct = (COSDictionary) page.getDictionaryObject(COSName.AA);
+        if (addAct == null)
+        {
+            addAct = new COSDictionary();
+            page.setItem(COSName.AA, addAct);
+        }
+        return new PDPageAdditionalActions(addAct);
+    }
+
+    /**
+     * Set the page actions.
+     *
+     * @param actions The actions for the page.
+     */
+    public void setActions( PDPageAdditionalActions actions )
+    {
+        page.setItem( COSName.AA, actions );
+    }
+
+    /**
+     * This will return a list of the Annotations for this page.
+     *
+     * @return List of the PDAnnotation objects.
+     *
+     * @throws IOException If there is an error while creating the annotations.
+     */
+    public List getAnnotations() throws IOException
+    {
+        COSArrayList retval = null;
+        COSArray annots = (COSArray)page.getDictionaryObject(COSName.ANNOTS);
+        if (annots == null)
+        {
+            annots = new COSArray();
+            page.setItem(COSName.ANNOTS, annots);
+            retval = new COSArrayList(new ArrayList(), annots);
+        }
+        else
+        {
+            List actuals = new ArrayList();
+
+            for (int i=0; i < annots.size(); i++)
+            {
+                COSBase item = annots.getObject(i);
+                actuals.add( PDAnnotation.createAnnotation( item ) );
+            }
+            retval = new COSArrayList(actuals, annots);
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the list of annotations.
+     *
+     * @param annots The new list of annotations.
+     */
+    public void setAnnotations( List annots )
+    {
+        page.setItem( COSName.ANNOTS, COSArrayList.converterToCOSArray( annots ) );
+    }
+
+    /**
+     * @deprecated Use the {@link PDPageable} adapter class
+     */
+    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
+        throws PrinterException
+    {
+        try
+        {
+            PageDrawer drawer = new PageDrawer();
+            PDRectangle cropBox = findCropBox();
+            drawer.drawPage( graphics, this, cropBox.createDimension() );
+            return PAGE_EXISTS;
+        }
+        catch( IOException io )
+        {
+            throw new PrinterIOException( io );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals( Object other )
+    {
+        return other instanceof PDPage && ((PDPage)other).getCOSObject() == this.getCOSObject();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return this.getCOSDictionary().hashCode();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPageNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPageNode.java
new file mode 100644 (file)
index 0000000..625c29f
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This represents a page node in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class PDPageNode implements COSObjectable
+{
+    private COSDictionary page;
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDPageNode.class);
+
+    /**
+     * Creates a new instance of PDPage.
+     */
+    public PDPageNode()
+    {
+        page = new COSDictionary();
+        page.setItem( COSName.TYPE, COSName.PAGES );
+        page.setItem( COSName.KIDS, new COSArray() );
+        page.setItem( COSName.COUNT, COSInteger.ZERO );
+    }
+
+    /**
+     * Creates a new instance of PDPage.
+     *
+     * @param pages The dictionary pages.
+     */
+    public PDPageNode( COSDictionary pages )
+    {
+        page = pages;
+    }
+
+    /**
+     * This will update the count attribute of the page node.  This only needs to
+     * be called if you add or remove pages.  The PDDocument will call this for you
+     * when you use the PDDocumnet persistence methods.  So, basically most clients
+     * will never need to call this.
+     *
+     * @return The update count for this node.
+     */
+    public long updateCount()
+    {
+        long totalCount = 0;
+        List kids = getKids();
+        Iterator kidIter = kids.iterator();
+        while( kidIter.hasNext() )
+        {
+            Object next = kidIter.next();
+            if( next instanceof PDPage )
+            {
+                totalCount++;
+            }
+            else
+            {
+                PDPageNode node = (PDPageNode)next;
+                totalCount += node.updateCount();
+            }
+        }
+        page.setLong( COSName.COUNT, totalCount );
+        return totalCount;
+    }
+
+    /**
+     * This will get the count of descendent page objects.
+     *
+     * @return The total number of descendent page objects.
+     */
+    public long getCount()
+    {
+        if(page == null)
+        {
+            return 0L;
+        }
+        COSBase num = page.getDictionaryObject(COSName.COUNT);
+        if(num == null)
+        {
+            return 0L;
+        }
+        return ((COSNumber) num).intValue();
+    }
+
+    /**
+     * This will get the underlying dictionary that this class acts on.
+     *
+     * @return The underlying dictionary for this class.
+     */
+    public COSDictionary getDictionary()
+    {
+        return page;
+    }
+
+    /**
+     * This is the parent page node.
+     *
+     * @return The parent to this page.
+     */
+    public PDPageNode getParent()
+    {
+        PDPageNode parent = null;
+        COSDictionary parentDic = (COSDictionary)page.getDictionaryObject(COSName.PARENT, COSName.P);
+        if( parentDic != null )
+        {
+            parent = new PDPageNode( parentDic );
+        }
+        return parent;
+    }
+
+    /**
+     * This will set the parent of this page.
+     *
+     * @param parent The parent to this page node.
+     */
+    public void setParent( PDPageNode parent )
+    {
+        page.setItem( COSName.PARENT, parent.getDictionary() );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return page;
+    }
+
+    /**
+     * This will return all kids of this node, either PDPageNode or PDPage.
+     *
+     * @return All direct descendents of this node.
+     */
+    public List getKids()
+    {
+        List actuals = new ArrayList();
+        COSArray kids = getAllKids(actuals, page, false);
+        return new COSArrayList( actuals, kids );
+    }
+
+    /**
+     * This will return all kids of this node as PDPage.
+     *
+     * @param result All direct and indirect descendents of this node are added to this list.
+     */
+    public void getAllKids(List result)
+    {
+        getAllKids(result, page, true);
+    }
+
+    /**
+     * This will return all kids of the given page node as PDPage.
+     *
+     * @param result All direct and optionally indirect descendents of this node are added to this list.
+     * @param page Page dictionary of a page node.
+     * @param recurse if true indirect descendents are processed recursively
+     */
+    private static COSArray getAllKids(List result, COSDictionary page, boolean recurse)
+    {
+        if(page == null)
+            return null;
+        COSArray kids = (COSArray)page.getDictionaryObject( COSName.KIDS );
+        if ( kids == null)
+        {
+            log.error("No Kids found in getAllKids(). Probably a malformed pdf.");
+            return null;
+        }
+        for( int i=0; i<kids.size(); i++ )
+        {
+            COSBase obj = kids.getObject( i );
+            if (obj instanceof COSDictionary)
+            {
+                COSDictionary kid = (COSDictionary)obj;
+                if( COSName.PAGE.equals( kid.getDictionaryObject( COSName.TYPE ) ) )
+                {
+                    result.add( new PDPage( kid ) );
+                }
+                else
+                {
+                    if (recurse)
+                    {
+                        getAllKids(result, kid, recurse);
+                    }
+                    else
+                    {
+                        result.add( new PDPageNode( kid ) );
+                    }
+                }
+            }
+        }
+        return kids;
+    }
+
+    /**
+     * This will get the resources at this page node and not look up the hierarchy.
+     * This attribute is inheritable, and findResources() should probably used.
+     * This will return null if no resources are available at this level.
+     *
+     * @return The resources at this level in the hierarchy.
+     */
+    public PDResources getResources()
+    {
+        PDResources retval = null;
+        COSDictionary resources = (COSDictionary)page.getDictionaryObject( COSName.RESOURCES );
+        if( resources != null )
+        {
+            retval = new PDResources( resources );
+        }
+        return retval;
+    }
+
+    /**
+     * This will find the resources for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The resources at this level in the hierarchy.
+     */
+    public PDResources findResources()
+    {
+        PDResources retval = getResources();
+        PDPageNode parent = getParent();
+        if( retval == null && parent != null )
+        {
+            retval = parent.findResources();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the resources for this page.
+     *
+     * @param resources The new resources for this page.
+     */
+    public void setResources( PDResources resources )
+    {
+        if( resources == null )
+        {
+            page.removeItem( COSName.RESOURCES );
+        }
+        else
+        {
+            page.setItem( COSName.RESOURCES, resources.getCOSDictionary() );
+        }
+    }
+
+    /**
+     * This will get the MediaBox at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findMediaBox() should probably used.
+     * This will return null if no MediaBox are available at this level.
+     *
+     * @return The MediaBox at this level in the hierarchy.
+     */
+    public PDRectangle getMediaBox()
+    {
+        PDRectangle retval = null;
+        COSArray array = (COSArray)page.getDictionaryObject( COSName.MEDIA_BOX );
+        if( array != null )
+        {
+            retval = new PDRectangle( array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will find the MediaBox for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The MediaBox at this level in the hierarchy.
+     */
+    public PDRectangle findMediaBox()
+    {
+        PDRectangle retval = getMediaBox();
+        PDPageNode parent = getParent();
+        if( retval == null && parent != null )
+        {
+            retval = parent.findMediaBox();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the mediaBox for this page.
+     *
+     * @param mediaBox The new mediaBox for this page.
+     */
+    public void setMediaBox( PDRectangle mediaBox )
+    {
+        if( mediaBox == null )
+        {
+            page.removeItem( COSName.MEDIA_BOX  );
+        }
+        else
+        {
+            page.setItem( COSName.MEDIA_BOX , mediaBox.getCOSArray() );
+        }
+    }
+
+    /**
+     * This will get the CropBox at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findCropBox() should probably used.
+     * This will return null if no CropBox is available at this level.
+     *
+     * @return The CropBox at this level in the hierarchy.
+     */
+    public PDRectangle getCropBox()
+    {
+        PDRectangle retval = null;
+        COSArray array = (COSArray)page.getDictionaryObject( COSName.CROP_BOX );
+        if( array != null )
+        {
+            retval = new PDRectangle( array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will find the CropBox for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The CropBox at this level in the hierarchy.
+     */
+    public PDRectangle findCropBox()
+    {
+        PDRectangle retval = getCropBox();
+        PDPageNode parent = getParent();
+        if( retval == null && parent != null )
+        {
+            retval = findParentCropBox( parent );
+        }
+
+        //default value for cropbox is the media box
+        if( retval == null )
+        {
+            retval = findMediaBox();
+        }
+        return retval;
+    }
+
+    /**
+     * This will search for a crop box in the parent and return null if it is not
+     * found.  It will NOT default to the media box if it cannot be found.
+     *
+     * @param node The node
+     */
+    private PDRectangle findParentCropBox( PDPageNode node )
+    {
+        PDRectangle rect = node.getCropBox();
+        PDPageNode parent = node.getParent();
+        if( rect == null && parent != null )
+        {
+            rect = findParentCropBox( node );
+        }
+        return rect;
+    }
+
+    /**
+     * This will set the CropBox for this page.
+     *
+     * @param cropBox The new CropBox for this page.
+     */
+    public void setCropBox( PDRectangle cropBox )
+    {
+        if( cropBox == null )
+        {
+            page.removeItem( COSName.CROP_BOX );
+        }
+        else
+        {
+            page.setItem( COSName.CROP_BOX, cropBox.getCOSArray() );
+        }
+    }
+
+    /**
+     * A value representing the rotation.  This will be null if not set at this level
+     * The number of degrees by which the page should
+     * be rotated clockwise when displayed or printed. The value must be a multiple
+     * of 90.
+     *
+     * This will get the rotation at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findRotation() should probably used.
+     * This will return null if no rotation is available at this level.
+     *
+     * @return The rotation at this level in the hierarchy.
+     */
+    public Integer getRotation()
+    {
+        Integer retval = null;
+        COSNumber value = (COSNumber)page.getDictionaryObject( COSName.ROTATE );
+        if( value != null )
+        {
+            retval = new Integer( value.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will find the rotation for this page by looking up the hierarchy until
+     * it finds them.
+     *
+     * @return The rotation at this level in the hierarchy.
+     */
+    public int findRotation()
+    {
+        int retval = 0;
+        Integer rotation = getRotation();
+        if( rotation != null )
+        {
+            retval = rotation.intValue();
+        }
+        else
+        {
+            PDPageNode parent = getParent();
+            if( parent != null )
+            {
+                retval = parent.findRotation();
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the rotation for this page.
+     *
+     * @param rotation The new rotation for this page.
+     */
+    public void setRotation( int rotation )
+    {
+        page.setInt( COSName.ROTATE, rotation );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPageable.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDPageable.java
new file mode 100644 (file)
index 0000000..5e82c99
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import static javax.print.attribute.standard.OrientationRequested.LANDSCAPE;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.print.PageFormat;
+import java.awt.print.Pageable;
+import java.awt.print.Paper;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.print.PrintService;
+import javax.print.attribute.standard.OrientationRequested;
+
+/**
+ * Adapter class that implements the {@link Pageable} and {@link Printable}
+ * interfaces for printing a given PDF document. Note that the given PDF
+ * document should not be modified (pages added, removed, etc.) while an
+ * instance of this class is being used.
+ *
+ * @since Apache PDFBox 1.3.0
+ * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-788">PDFBOX-788</a>
+ */
+public class PDPageable implements Pageable, Printable {
+
+    /**
+     * List of all pages in the given PDF document.
+     */
+    private final List<PDPage> pages = new ArrayList<PDPage>();
+
+    /**
+     * The printer job for printing the given PDF document.
+     */
+    private final PrinterJob job;
+
+    /**
+     * Creates a {@link Pageable} adapter for the given PDF document and
+     * printer job.
+     *
+     * @param document PDF document
+     * @param job printer job
+     * @throws IllegalArgumentException if an argument is <code>null</code>
+     * @throws PrinterException if the document permissions prevent printing
+     */
+    public PDPageable(PDDocument document, PrinterJob job)
+            throws IllegalArgumentException, PrinterException {
+        if (document == null || job == null) {
+            throw new IllegalArgumentException(
+                    "PDPageable(" + document + ", " + job + ")");
+        } else if (!document.getCurrentAccessPermission().canPrint()) {
+            throw new PrinterException(
+                "You do not have permission to print this document");
+        } else {
+            document.getDocumentCatalog().getPages().getAllKids(pages);
+            this.job = job;
+        }
+    }
+
+    /**
+     * Creates a {@link Pageable} adapter for the given PDF document using
+     * a default printer job returned by {@link PrinterJob#getPrinterJob()}.
+     *
+     * @param document PDF document
+     * @throws IllegalArgumentException if the argument is <code>null</code>
+     * @throws PrinterException if the document permissions prevent printing
+     */
+    public PDPageable(PDDocument document)
+            throws IllegalArgumentException, PrinterException {
+        this(document, PrinterJob.getPrinterJob());
+    }
+
+    /**
+     * Returns the printer job for printing the given PDF document.
+     *
+     * @return printer job
+     */
+    public PrinterJob getPrinterJob() {
+        return job;
+    }
+
+    //------------------------------------------------------------< Pageable >
+
+    /**
+     * Returns the number of pages in the given PDF document.
+     *
+     * @return number of pages
+     */
+    public int getNumberOfPages() {
+        return pages.size();
+    }
+
+    /**
+     * Returns the format of the page at the given index.
+     *
+     * @param i page index, zero-based
+     * @return page format
+     * @throws IndexOutOfBoundsException if the page index is invalid
+     */
+    public PageFormat getPageFormat(int i) throws IndexOutOfBoundsException {
+        PageFormat format = job.defaultPage();
+
+        PDPage page = pages.get(i); // can throw IOOBE
+        Dimension media = page.findMediaBox().createDimension();
+        Dimension crop = page.findCropBox().createDimension();
+
+        // Center the ImageableArea if the crop is smaller than the media
+        double diffWidth = 0.0;
+        double diffHeight = 0.0;
+        if (!media.equals(crop)) {
+            diffWidth = (media.getWidth() - crop.getWidth()) / 2.0;
+            diffHeight = (media.getHeight() - crop.getHeight()) / 2.0;
+        }
+
+        Paper paper = format.getPaper();
+        PrintService service = job.getPrintService(); // can be null
+        Class<OrientationRequested> orientation = OrientationRequested.class;
+        if (service != null
+                && service.getDefaultAttributeValue(orientation) == LANDSCAPE) {
+           format.setOrientation(PageFormat.LANDSCAPE);
+           paper.setImageableArea(
+                   diffHeight, diffWidth, crop.getHeight(), crop.getWidth());
+           paper.setSize(media.getHeight(), media.getWidth());
+        } else {
+           format.setOrientation(PageFormat.PORTRAIT);
+           paper.setImageableArea(
+                   diffWidth, diffHeight, crop.getWidth(), crop.getHeight());
+           paper.setSize(media.getWidth(), media.getHeight());
+        }
+        format.setPaper(paper);
+
+        return format;
+    }
+
+    /**
+     * Returns a {@link Printable} for the page at the given index.
+     * Currently this method simply returns the underlying {@link PDPage}
+     * object that directly implements the {@link Printable} interface, but
+     * future versions may choose to return a different adapter instance.
+     *
+     * @param i page index, zero-based
+     * @return printable
+     * @throws IndexOutOfBoundsException if the page index is invalid
+     */
+    public Printable getPrintable(int i) throws IndexOutOfBoundsException {
+        return pages.get(i);
+    }
+
+    //-----------------------------------------------------------< Printable >
+
+    /**
+     * Prints the page at the given index.
+     *
+     * @param graphics printing target
+     * @param format page format
+     * @param i page index, zero-based
+     * @return {@link Printable#PAGE_EXISTS} if the page was printed,
+     *         or {@link Printable#NO_SUCH_PAGE} if page index was invalid
+     * @throws PrinterException if printing failed
+     */
+    @SuppressWarnings("deprecation")
+    public int print(Graphics graphics, PageFormat format, int i)
+            throws PrinterException {
+        if (0 <= i && i < pages.size()) {
+            return pages.get(i).print(graphics, format, i);
+        } else {
+            return NO_SUCH_PAGE;
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDResources.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/PDResources.java
new file mode 100644 (file)
index 0000000..5f05404
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * 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.pdfbox.pdmodel;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.font.PDFontFactory;
+import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.pdmodel.markedcontent.PDPropertyList;
+
+/**
+ * This represents a set of resources available at the page/pages/stream level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.16 $
+ */
+public class PDResources implements COSObjectable
+{
+    private COSDictionary resources;
+
+    /**
+     * Default constructor.
+     */
+    public PDResources()
+    {
+        resources = new COSDictionary();
+    }
+
+    /**
+     * Prepopulated resources.
+     *
+     * @param resourceDictionary The cos dictionary for this resource.
+     */
+    public PDResources( COSDictionary resourceDictionary )
+    {
+        resources = resourceDictionary;
+    }
+
+    /**
+     * This will get the underlying dictionary.
+     *
+     * @return The dictionary for these resources.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return resources;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return resources;
+    }
+
+    /**
+     * This will get the map of fonts.  This will never return null.  The keys are string
+     * and the values are PDFont objects.
+     *
+     * @param fontCache A map of existing PDFont objects to reuse.
+     * @return The map of fonts.
+     *
+     * @throws IOException If there is an error getting the fonts.
+     */
+    public Map getFonts( Map fontCache ) throws IOException
+    {
+        Map retval = null;
+        COSDictionary fonts = (COSDictionary)resources.getDictionaryObject( COSName.FONT );
+
+        if( fonts == null )
+        {
+            fonts = new COSDictionary();
+            resources.setItem( COSName.FONT, fonts );
+        }
+
+        Map actuals = new HashMap();
+        retval = new COSDictionaryMap( actuals, fonts );
+        for( COSName fontName : fonts.keySet() )
+        {
+            COSBase font = fonts.getDictionaryObject( fontName );
+            //data-000174.pdf contains a font that is a COSArray, looks to be an error in the
+            //PDF, we will just ignore entries that are not dictionaries.
+            if( font instanceof COSDictionary )
+            {
+                COSDictionary fontDictionary = (COSDictionary)font;
+                actuals.put( fontName.getName(), PDFontFactory.createFont( fontDictionary, fontCache ));
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the map of fonts.  This will never return null.  The keys are string
+     * and the values are PDFont objects.
+     *
+     * @return The map of fonts.
+     *
+     * @throws IOException If there is an error getting the fonts.
+     */
+    public Map getFonts() throws IOException
+    {
+        return getFonts( null );
+    }
+
+    /**
+     * This will get the map of PDXObjects that are in the resource dictionary.
+     *
+     * @return The map of xobjects.
+     *
+     * @throws IOException If there is an error creating the xobjects.
+     */
+    public Map getXObjects() throws IOException
+    {
+        Map retval = null;
+        COSDictionary xobjects = (COSDictionary)resources.getDictionaryObject( COSName.XOBJECT );
+
+        if( xobjects == null )
+        {
+            xobjects = new COSDictionary();
+            resources.setItem( COSName.XOBJECT, xobjects );
+        }
+
+        Map actuals = new HashMap();
+        retval = new COSDictionaryMap( actuals, xobjects );
+        for( COSName objName : xobjects.keySet() )
+        {
+            COSBase cosObject = xobjects.getDictionaryObject(objName);
+            PDXObject xobject = PDXObject.createXObject( cosObject );
+            if( xobject !=null )
+            {
+                actuals.put( objName.getName(), xobject);
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the map of images.  An empty map will be returned if there
+     * are no underlying images.
+     * So far the keys are COSName of the image
+     * and the value is the corresponding PDXObjectImage.
+     *
+     * @author By BM
+     * @return The map of images.
+     * @throws IOException If there is an error writing the picture.
+     */
+    public Map getImages() throws IOException
+    {
+        Map retval = null;
+        COSDictionary images = (COSDictionary)resources.getDictionaryObject( COSName.XOBJECT );
+
+        if( images == null )
+        {
+            images = new COSDictionary();
+            resources.setItem( COSName.XOBJECT, images );
+        }
+
+        Map actuals = new HashMap();
+        retval = new COSDictionaryMap( actuals, images );
+        for( COSName imageName : images.keySet() )
+        {
+            COSStream image = (COSStream)(images.getDictionaryObject(imageName));
+
+            COSName subType =(COSName)image.getDictionaryObject(COSName.SUBTYPE);
+            if( subType.equals(COSName.IMAGE) )
+            {
+                PDXObjectImage ximage = (PDXObjectImage)PDXObject.createXObject( image );
+                if( ximage !=null )
+                {
+                    actuals.put( imageName.getName(), ximage);
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the map of fonts.
+     *
+     * @param fonts The new map of fonts.
+     */
+    public void setFonts( Map fonts )
+    {
+        resources.setItem( COSName.FONT, COSDictionaryMap.convert( fonts ) );
+    }
+
+    /**
+     * This will get the map of colorspaces.  This will return null if the underlying
+     * resources dictionary does not have a colorspace dictionary.  The keys are string
+     * and the values are PDColorSpace objects.
+     *
+     * @return The map of colorspaces.
+     *
+     * @throws IOException If there is an error getting the colorspaces.
+     */
+    public Map getColorSpaces() throws IOException
+    {
+        Map retval = null;
+        COSDictionary colorspaces = (COSDictionary)resources.getDictionaryObject( COSName.getPDFName( "ColorSpace" ) );
+
+        if( colorspaces != null )
+        {
+            Map actuals = new HashMap();
+            retval = new COSDictionaryMap( actuals, colorspaces );
+            for( COSName csName : colorspaces.keySet() )
+            {
+                COSBase cs = colorspaces.getDictionaryObject( csName );
+                actuals.put( csName.getName(), PDColorSpaceFactory.createColorSpace( cs ) );
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the map of colorspaces.
+     *
+     * @param colorspaces The new map of colorspaces.
+     */
+    public void setColorSpaces( Map colorspaces )
+    {
+        resources.setItem( COSName.COLORSPACE, COSDictionaryMap.convert( colorspaces ) );
+    }
+
+    /**
+     * This will get the map of graphic states.  This will return null if the underlying
+     * resources dictionary does not have a graphics dictionary.  The keys are the graphic state
+     * name as a String and the values are PDExtendedGraphicsState objects.
+     *
+     * @return The map of extended graphic state objects.
+     */
+    public Map getGraphicsStates()
+    {
+        Map retval = null;
+        COSDictionary states = (COSDictionary)resources.getDictionaryObject( COSName.EXT_G_STATE );
+
+        if( states != null )
+        {
+            Map actuals = new HashMap();
+            retval = new COSDictionaryMap( actuals, states );
+            for( COSName name : states.keySet() )
+            {
+                COSDictionary dictionary = (COSDictionary)states.getDictionaryObject( name );
+                actuals.put( name.getName(), new PDExtendedGraphicsState( dictionary ) );
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the map of graphics states.
+     *
+     * @param states The new map of states.
+     */
+    public void setGraphicsStates( Map states )
+    {
+        Iterator iter = states.keySet().iterator();
+        COSDictionary dic = new COSDictionary();
+        while( iter.hasNext() )
+        {
+            String name = (String)iter.next();
+            PDExtendedGraphicsState state = (PDExtendedGraphicsState)states.get( name );
+            dic.setItem( COSName.getPDFName( name ), state.getCOSObject() );
+        }
+        resources.setItem( COSName.EXT_G_STATE, dic );
+    }
+
+    /**
+     * Returns the dictionary mapping resource names to property list dictionaries for marked
+     * content.
+     * @return the property list
+     */
+    public PDPropertyList getProperties()
+    {
+        PDPropertyList retval = null;
+        COSDictionary props = (COSDictionary)resources.getDictionaryObject(COSName.PROPERTIES);
+
+        if (props != null)
+        {
+            retval = new PDPropertyList(props);
+        }
+        return retval;
+    }
+
+    /**
+     * Sets the dictionary mapping resource names to property list dictionaries for marked
+     * content.
+     * @param props the property list
+     */
+    public void setProperties(PDPropertyList props)
+    {
+        resources.setItem(COSName.PROPERTIES, props.getCOSObject());
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSArrayList.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSArrayList.java
new file mode 100644 (file)
index 0000000..488e799
--- /dev/null
@@ -0,0 +1,676 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * This is an implementation of a List that will sync its contents to a COSArray.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ */
+public class COSArrayList implements List
+{
+    private COSArray array;
+    private List actual;
+
+    private COSDictionary parentDict;
+    private COSName dictKey;
+
+    /**
+     * Default constructor.
+     */
+    public COSArrayList()
+    {
+        array = new COSArray();
+        actual = new ArrayList();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param actualList The list of standard java objects
+     * @param cosArray The COS array object to sync to.
+     */
+    public COSArrayList( List actualList, COSArray cosArray )
+    {
+        actual = actualList;
+        array = cosArray;
+    }
+
+    /**
+     * This is a really special constructor.  Sometimes the PDF spec says
+     * that a dictionary entry can either be a single item or an array of those
+     * items.  But in the PDModel interface we really just want to always return
+     * a java.util.List.  In the case were we get the list and never modify it
+     * we don't want to convert to COSArray and put one element, unless we append
+     * to the list.  So here we are going to create this object with a single
+     * item instead of a list, but allow more items to be added and then converted
+     * to an array.
+     *
+     * @param actualObject The PDModel object.
+     * @param item The COS Model object.
+     * @param dictionary The dictionary that holds the item, and will hold the array if an item is added.
+     * @param dictionaryKey The key into the dictionary to set the item.
+     */
+    public COSArrayList( Object actualObject, COSBase item, COSDictionary dictionary, COSName dictionaryKey )
+    {
+        array = new COSArray();
+        array.add( item );
+        actual = new ArrayList();
+        actual.add( actualObject );
+
+        parentDict = dictionary;
+        dictKey = dictionaryKey;
+    }
+
+    /**
+     * @deprecated use the {@link #COSArrayList(Object, COSBase, COSDictionary, COSName)} method instead
+     */
+    public COSArrayList( Object actualObject, COSBase item, COSDictionary dictionary, String dictionaryKey )
+    {
+        this( actualObject, item, dictionary, COSName.getPDFName(dictionaryKey) );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int size()
+    {
+        return actual.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isEmpty()
+    {
+        return actual.isEmpty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean contains(Object o)
+    {
+        return actual.contains(o);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator iterator()
+    {
+        return actual.iterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object[] toArray()
+    {
+        return actual.toArray();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object[] toArray(Object[] a)
+    {
+        return actual.toArray(a);
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean add(Object o)
+    {
+        //when adding if there is a parentDict then change the item
+        //in the dictionary from a single item to an array.
+        if( parentDict != null )
+        {
+            parentDict.setItem( dictKey, array );
+            //clear the parent dict so it doesn't happen again, there might be
+            //a usecase for keeping the parentDict around but not now.
+            parentDict = null;
+        }
+        //string is a special case because we can't subclass to be COSObjectable
+        if( o instanceof String )
+        {
+            array.add( new COSString( (String)o ) );
+        }
+        else if( o instanceof DualCOSObjectable )
+        {
+            DualCOSObjectable dual = (DualCOSObjectable)o;
+            array.add( dual.getFirstCOSObject() );
+            array.add( dual.getSecondCOSObject() );
+        }
+        else
+        {
+            if(array != null)
+            {
+                array.add(((COSObjectable)o).getCOSObject());
+            }
+        }
+        return actual.add(o);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean remove(Object o)
+    {
+        boolean retval = true;
+        int index = actual.indexOf( o );
+        if( index >= 0 )
+        {
+            actual.remove( index );
+            array.remove( index );
+        }
+        else
+        {
+            retval = false;
+        }
+        return retval;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsAll(Collection c)
+    {
+        return actual.containsAll( c );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addAll(Collection c)
+    {
+        //when adding if there is a parentDict then change the item
+        //in the dictionary from a single item to an array.
+        if( parentDict != null && c.size() > 0)
+        {
+            parentDict.setItem( dictKey, array );
+            //clear the parent dict so it doesn't happen again, there might be
+            //a usecase for keeping the parentDict around but not now.
+            parentDict = null;
+        }
+        array.addAll( toCOSObjectList( c ) );
+        return actual.addAll( c );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addAll(int index, Collection c)
+    {
+        //when adding if there is a parentDict then change the item
+        //in the dictionary from a single item to an array.
+        if( parentDict != null && c.size() > 0)
+        {
+            parentDict.setItem( dictKey, array );
+            //clear the parent dict so it doesn't happen again, there might be
+            //a usecase for keeping the parentDict around but not now.
+            parentDict = null;
+        }
+
+        if( c.size() >0 && c.toArray()[0] instanceof DualCOSObjectable )
+        {
+            array.addAll( index*2, toCOSObjectList( c ) );
+        }
+        else
+        {
+            array.addAll( index, toCOSObjectList( c ) );
+        }
+        return actual.addAll( index, c );
+    }
+
+    /**
+     * This will take an array of COSNumbers and return a COSArrayList of
+     * java.lang.Integer values.
+     *
+     * @param intArray The existing integer Array.
+     *
+     * @return A list that is part of the core Java collections.
+     */
+    public static List convertIntegerCOSArrayToList( COSArray intArray )
+    {
+        List numbers = new ArrayList();
+        for( int i=0; i<intArray.size(); i++ )
+        {
+            numbers.add( new Integer( ((COSNumber)intArray.get( i )).intValue() ) );
+        }
+        return new COSArrayList( numbers, intArray );
+    }
+
+    /**
+     * This will take an array of COSNumbers and return a COSArrayList of
+     * java.lang.Float values.
+     *
+     * @param floatArray The existing float Array.
+     *
+     * @return The list of Float objects.
+     */
+    public static List convertFloatCOSArrayToList( COSArray floatArray )
+    {
+        List retval = null;
+        if( floatArray != null )
+        {
+            List numbers = new ArrayList();
+            for( int i=0; i<floatArray.size(); i++ )
+            {
+                numbers.add( new Float( ((COSNumber)floatArray.get( i )).floatValue() ) );
+            }
+            retval = new COSArrayList( numbers, floatArray );
+        }
+        return retval;
+    }
+
+    /**
+     * This will take an array of COSName and return a COSArrayList of
+     * java.lang.String values.
+     *
+     * @param nameArray The existing name Array.
+     *
+     * @return The list of String objects.
+     */
+    public static List convertCOSNameCOSArrayToList( COSArray nameArray )
+    {
+        List retval = null;
+        if( nameArray != null )
+        {
+            List names = new ArrayList();
+            for( int i=0; i<nameArray.size(); i++ )
+            {
+                names.add( ((COSName)nameArray.getObject( i )).getName() );
+            }
+            retval = new COSArrayList( names, nameArray );
+        }
+        return retval;
+    }
+
+    /**
+     * This will take an array of COSString and return a COSArrayList of
+     * java.lang.String values.
+     *
+     * @param stringArray The existing name Array.
+     *
+     * @return The list of String objects.
+     */
+    public static List convertCOSStringCOSArrayToList( COSArray stringArray )
+    {
+        List retval = null;
+        if( stringArray != null )
+        {
+            List string = new ArrayList();
+            for( int i=0; i<stringArray.size(); i++ )
+            {
+                string.add( ((COSString)stringArray.getObject( i )).getString() );
+            }
+            retval = new COSArrayList( string, stringArray );
+        }
+        return retval;
+    }
+
+    /**
+     * This will take an list of string objects and return a COSArray of COSName
+     * objects.
+     *
+     * @param strings A list of strings
+     *
+     * @return An array of COSName objects
+     */
+    public static COSArray convertStringListToCOSNameCOSArray( List strings )
+    {
+        COSArray retval = new COSArray();
+        for( int i=0; i<strings.size(); i++ )
+        {
+            Object next = strings.get( i );
+            if( next instanceof COSName )
+            {
+                retval.add( (COSName)next );
+            }
+            else
+            {
+                retval.add( COSName.getPDFName( (String)next ) );
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will take an list of string objects and return a COSArray of COSName
+     * objects.
+     *
+     * @param strings A list of strings
+     *
+     * @return An array of COSName objects
+     */
+    public static COSArray convertStringListToCOSStringCOSArray( List strings )
+    {
+        COSArray retval = new COSArray();
+        for( int i=0; i<strings.size(); i++ )
+        {
+            retval.add( new COSString( (String)strings.get( i ) ) );
+        }
+        return retval;
+    }
+
+    /**
+     * This will convert a list of COSObjectables to an
+     * array list of COSBase objects.
+     *
+     * @param cosObjectableList A list of COSObjectable.
+     *
+     * @return A list of COSBase.
+     */
+    public static COSArray converterToCOSArray( List cosObjectableList )
+    {
+        COSArray array = null;
+        if( cosObjectableList != null )
+        {
+            if( cosObjectableList instanceof COSArrayList )
+            {
+                //if it is already a COSArrayList then we don't want to recreate the array, we want to reuse it.
+                array = ((COSArrayList)cosObjectableList).array;
+            }
+            else
+            {
+                array = new COSArray();
+                Iterator iter = cosObjectableList.iterator();
+                while( iter.hasNext() )
+                {
+                    Object next = iter.next();
+                    if( next instanceof String )
+                    {
+                        array.add( new COSString( (String)next ) );
+                    }
+                    else if( next instanceof Integer || next instanceof Long )
+                    {
+                        array.add( COSInteger.get( ((Number)next).longValue() ) );
+                    }
+                    else if( next instanceof Float || next instanceof Double )
+                    {
+                        array.add( new COSFloat( ((Number)next).floatValue() ) );
+                    }
+                    else if( next instanceof COSObjectable )
+                    {
+                        COSObjectable object = (COSObjectable)next;
+                        array.add( object.getCOSObject() );
+                    }
+                    else if( next instanceof DualCOSObjectable )
+                    {
+                        DualCOSObjectable object = (DualCOSObjectable)next;
+                        array.add( object.getFirstCOSObject() );
+                        array.add( object.getSecondCOSObject() );
+                    }
+                    else if( next == null )
+                    {
+                        array.add( COSNull.NULL );
+                    }
+                    else
+                    {
+                        throw new RuntimeException( "Error: Don't know how to convert type to COSBase '" +
+                        next.getClass().getName() + "'" );
+                    }
+                }
+            }
+        }
+        return array;
+    }
+
+    private List toCOSObjectList( Collection list )
+    {
+        List cosObjects = new ArrayList();
+        Iterator iter = list.iterator();
+        while( iter.hasNext() )
+        {
+            Object next = iter.next();
+            if( next instanceof String )
+            {
+                cosObjects.add( new COSString( (String)next ) );
+            }
+            else if( next instanceof DualCOSObjectable )
+            {
+                DualCOSObjectable object = (DualCOSObjectable)next;
+                array.add( object.getFirstCOSObject() );
+                array.add( object.getSecondCOSObject() );
+            }
+            else
+            {
+                COSObjectable cos = (COSObjectable)next;
+                cosObjects.add( cos.getCOSObject() );
+            }
+        }
+        return cosObjects;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean removeAll(Collection c)
+    {
+        array.removeAll( toCOSObjectList( c ) );
+        return actual.removeAll( c );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean retainAll(Collection c)
+    {
+        array.retainAll( toCOSObjectList( c ) );
+        return actual.retainAll( c );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void clear()
+    {
+        //when adding if there is a parentDict then change the item
+        //in the dictionary from a single item to an array.
+        if( parentDict != null )
+        {
+            parentDict.setItem( dictKey, (COSBase)null );
+        }
+        actual.clear();
+        array.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object o)
+    {
+        return actual.equals( o );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return actual.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object get(int index)
+    {
+        return actual.get( index );
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object set(int index, Object element)
+    {
+        if( element instanceof String )
+        {
+            COSString item = new COSString( (String)element );
+            if( parentDict != null && index == 0 )
+            {
+                parentDict.setItem( dictKey, item );
+            }
+            array.set( index, item );
+        }
+        else if( element instanceof DualCOSObjectable )
+        {
+            DualCOSObjectable dual = (DualCOSObjectable)element;
+            array.set( index*2, dual.getFirstCOSObject() );
+            array.set( index*2+1, dual.getSecondCOSObject() );
+        }
+        else
+        {
+            if( parentDict != null && index == 0 )
+            {
+                parentDict.setItem( dictKey, ((COSObjectable)element).getCOSObject() );
+            }
+            array.set( index, ((COSObjectable)element).getCOSObject() );
+        }
+        return actual.set( index, element );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void add(int index, Object element)
+    {
+        //when adding if there is a parentDict then change the item
+        //in the dictionary from a single item to an array.
+        if( parentDict != null )
+        {
+            parentDict.setItem( dictKey, array );
+            //clear the parent dict so it doesn't happen again, there might be
+            //a usecase for keeping the parentDict around but not now.
+            parentDict = null;
+        }
+        actual.add( index, element );
+        if( element instanceof String )
+        {
+            array.add( index, new COSString( (String)element ) );
+        }
+        else if( element instanceof DualCOSObjectable )
+        {
+            DualCOSObjectable dual = (DualCOSObjectable)element;
+            array.add( index*2, dual.getFirstCOSObject() );
+            array.add( index*2+1, dual.getSecondCOSObject() );
+        }
+        else
+        {
+            array.add( index, ((COSObjectable)element).getCOSObject() );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object remove(int index)
+    {
+        if( array.size() > index && array.get( index ) instanceof DualCOSObjectable )
+        {
+            //remove both objects
+            array.remove( index );
+            array.remove( index );
+        }
+        else
+        {
+            array.remove( index );
+        }
+        return actual.remove( index );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int indexOf(Object o)
+    {
+        return actual.indexOf( o );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int lastIndexOf(Object o)
+    {
+        return actual.indexOf( o );
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ListIterator listIterator()
+    {
+        return actual.listIterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ListIterator listIterator(int index)
+    {
+        return actual.listIterator( index );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List subList(int fromIndex, int toIndex)
+    {
+        return actual.subList( fromIndex, toIndex );
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "COSArrayList{" + array.toString() + "}";
+    }
+    
+    /**
+     * This will return then underlying COSArray.
+     * 
+     * @return the COSArray
+     */
+    public COSArray toList() 
+    {
+        return array;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSDictionaryMap.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSDictionaryMap.java
new file mode 100644 (file)
index 0000000..8e00f1b
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This is a Map that will automatically sync the contents to a COSDictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class COSDictionaryMap implements Map
+{
+    private COSDictionary map;
+    private Map actuals;
+
+    /**
+     * Constructor for this map.
+     *
+     * @param actualsMap The map with standard java objects as values.
+     * @param dicMap The map with COSBase objects as values.
+     */
+    public COSDictionaryMap( Map actualsMap, COSDictionary dicMap )
+    {
+        actuals = actualsMap;
+        map = dicMap;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int size()
+    {
+        return map.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isEmpty()
+    {
+        return size() == 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsKey(Object key)
+    {
+        return map.keySet().contains( key );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsValue(Object value)
+    {
+        return actuals.containsValue( value );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object get(Object key)
+    {
+        return actuals.get( key );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object put(Object key, Object value)
+    {
+        COSObjectable object = (COSObjectable)value;
+
+        map.setItem( COSName.getPDFName( (String)key ), object.getCOSObject() );
+        return actuals.put( key, value );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object remove(Object key)
+    {
+        map.removeItem( COSName.getPDFName( (String)key ) );
+        return actuals.remove( key );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void putAll(Map t)
+    {
+        throw new RuntimeException( "Not yet implemented" );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void clear()
+    {
+        map.clear();
+        actuals.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set keySet()
+    {
+        return actuals.keySet();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection values()
+    {
+        return actuals.values();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set entrySet()
+    {
+        return Collections.unmodifiableSet(actuals.entrySet());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object o)
+    {
+        boolean retval = false;
+        if( o instanceof COSDictionaryMap )
+        {
+            COSDictionaryMap other = (COSDictionaryMap)o;
+            retval = other.map.equals( this.map );
+        }
+        return retval;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return actuals.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return map.hashCode();
+    }
+
+    /**
+     * This will take a map&lt;java.lang.String,org.apache.pdfbox.pdmodel.COSObjectable&gt;
+     * and convert it into a COSDictionary&lt;COSName,COSBase&gt;.
+     *
+     * @param someMap A map containing COSObjectables
+     *
+     * @return A proper COSDictionary
+     */
+    public static COSDictionary convert( Map someMap )
+    {
+        Iterator iter = someMap.keySet().iterator();
+        COSDictionary dic = new COSDictionary();
+        while( iter.hasNext() )
+        {
+            String name = (String)iter.next();
+            COSObjectable object = (COSObjectable)someMap.get( name );
+            dic.setItem( COSName.getPDFName( name ), object.getCOSObject() );
+        }
+        return dic;
+    }
+
+    /**
+     * This will take a COS dictionary and convert it into COSDictionaryMap.  All cos
+     * objects will be converted to their primitive form.
+     *
+     * @param map The COS mappings.
+     * @return A standard java map.
+     * @throws IOException If there is an error during the conversion.
+     */
+    public static COSDictionaryMap convertBasicTypesToMap( COSDictionary map ) throws IOException
+    {
+        COSDictionaryMap retval = null;
+        if( map != null )
+        {
+            Map actualMap = new HashMap();
+            for( COSName key : map.keySet() )
+            {
+                COSBase cosObj = map.getDictionaryObject( key );
+                Object actualObject = null;
+                if( cosObj instanceof COSString )
+                {
+                    actualObject = ((COSString)cosObj).getString();
+                }
+                else if( cosObj instanceof COSInteger )
+                {
+                    actualObject = new Integer( ((COSInteger)cosObj).intValue() );
+                }
+                else if( cosObj instanceof COSName )
+                {
+                    actualObject = ((COSName)cosObj).getName();
+                }
+                else if( cosObj instanceof COSFloat )
+                {
+                    actualObject = new Float( ((COSFloat)cosObj).floatValue() );
+                }
+                else if( cosObj instanceof COSBoolean )
+                {
+                    actualObject = ((COSBoolean)cosObj).getValue() ? Boolean.TRUE : Boolean.FALSE;
+                }
+                else
+                {
+                    throw new IOException( "Error:unknown type of object to convert:" + cosObj );
+                }
+                actualMap.put( key.getName(), actualObject );
+            }
+            retval = new COSDictionaryMap( actualMap, map );
+        }
+
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSObjectable.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSObjectable.java
new file mode 100644 (file)
index 0000000..470f8ed
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This is an interface used to get/create the underlying COSObject.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public interface COSObjectable
+{
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject();
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSStreamArray.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/COSStreamArray.java
new file mode 100644 (file)
index 0000000..055502d
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.SequenceInputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.ICOSVisitor;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccess;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+
+/**
+ * This will take an array of streams and sequence them together.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class COSStreamArray extends COSStream
+{
+    private COSArray streams;
+
+    /**
+     * The first stream will be used to delegate some of the methods for this
+     * class.
+     */
+    private COSStream firstStream;
+
+    /**
+     * Constructor.
+     *
+     * @param array The array of COSStreams to concatenate together.
+     */
+    public COSStreamArray( COSArray array )
+    {
+        super( new COSDictionary(), null );
+        streams = array;
+        if( array.size() > 0 )
+        {
+            firstStream = (COSStream)array.getObject( 0 );
+        }
+    }
+
+    /**
+     * This will get a stream (or the reference to a stream) from the array.
+     *
+     * @param index The index of the requested stream
+     * @return The stream object or a reference to a stream
+     */
+    public COSBase get( int index )
+    {
+        return streams.get( index );
+    }
+
+    /**
+     * This will get the number of streams in the array.
+     *
+     * @return the number of streams
+     */
+    public int getStreamCount()
+    {
+        return streams.size();
+    }
+
+    /**
+     * This will get the scratch file associated with this stream.
+     *
+     * @return The scratch file where this stream is being stored.
+     */
+    public RandomAccess getScratchFile()
+    {
+        return firstStream.getScratchFile();
+    }
+
+    /**
+     * This will get an object from this streams dictionary.
+     *
+     * @param key The key to the object.
+     *
+     * @return The dictionary object with the key or null if one does not exist.
+     */
+    public COSBase getItem( COSName key )
+    {
+        return firstStream.getItem( key );
+    }
+
+   /**
+     * This will get an object from this streams dictionary and dereference it
+     * if necessary.
+     *
+     * @param key The key to the object.
+     *
+     * @return The dictionary object with the key or null if one does not exist.
+     */
+    public COSBase getDictionaryObject( COSName key )
+    {
+        return firstStream.getDictionaryObject( key );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "COSStream{}";
+    }
+
+    /**
+     * This will get all the tokens in the stream.
+     *
+     * @return All of the tokens in the stream.
+     *
+     * @throws IOException If there is an error parsing the stream.
+     */
+    public List getStreamTokens() throws IOException
+    {
+        List retval = null;
+        if( streams.size() > 0 )
+        {
+            PDFStreamParser parser = new PDFStreamParser( this );
+            parser.parse();
+            retval = parser.getTokens();
+        }
+        else
+        {
+            retval = new ArrayList();
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the dictionary that is associated with this stream.
+     *
+     * @return the object that is associated with this stream.
+     */
+    public COSDictionary getDictionary()
+    {
+        return firstStream;
+    }
+
+    /**
+     * This will get the stream with all of the filters applied.
+     *
+     * @return the bytes of the physical (endoced) stream
+     *
+     * @throws IOException when encoding/decoding causes an exception
+     */
+    public InputStream getFilteredStream() throws IOException
+    {
+        throw new IOException( "Error: Not allowed to get filtered stream from array of streams." );
+    }
+
+    /**
+     * This will get the logical content stream with none of the filters.
+     *
+     * @return the bytes of the logical (decoded) stream
+     *
+     * @throws IOException when encoding/decoding causes an exception
+     */
+    public InputStream getUnfilteredStream() throws IOException
+    {
+        Vector<InputStream> inputStreams = new Vector<InputStream>();
+        byte[] inbetweenStreamBytes = "\n".getBytes("ISO-8859-1");
+
+        for( int i=0;i<streams.size(); i++ )
+        {
+            COSStream stream = (COSStream)streams.getObject( i );
+            inputStreams.add( stream.getUnfilteredStream() );
+            //handle the case where there is no whitespace in the
+            //between streams in the contents array, without this
+            //it is possible that two operators will get concatenated
+            //together
+            inputStreams.add( new ByteArrayInputStream( inbetweenStreamBytes ) );
+        }
+
+        return new SequenceInputStream( inputStreams.elements() );
+    }
+
+    /**
+     * visitor pattern double dispatch method.
+     *
+     * @param visitor The object to notify when visiting this object.
+     * @return any object, depending on the visitor implementation, or null
+     * @throws COSVisitorException If an error occurs while visiting this object.
+     */
+    public Object accept(ICOSVisitor visitor) throws COSVisitorException
+    {
+        return streams.accept( visitor );
+    }
+
+
+    /**
+     * This will return the filters to apply to the byte stream
+     * the method will return.
+     * - null if no filters are to be applied
+     * - a COSName if one filter is to be applied
+     * - a COSArray containing COSNames if multiple filters are to be applied
+     *
+     * @return the COSBase object representing the filters
+     */
+    public COSBase getFilters()
+    {
+        return firstStream.getFilters();
+    }
+
+    /**
+     * This will create a new stream for which filtered byte should be
+     * written to.  You probably don't want this but want to use the
+     * createUnfilteredStream, which is used to write raw bytes to.
+     *
+     * @return A stream that can be written to.
+     *
+     * @throws IOException If there is an error creating the stream.
+     */
+    public OutputStream createFilteredStream() throws IOException
+    {
+        return firstStream.createFilteredStream();
+    }
+
+    /**
+     * This will create a new stream for which filtered byte should be
+     * written to.  You probably don't want this but want to use the
+     * createUnfilteredStream, which is used to write raw bytes to.
+     *
+     * @param expectedLength An entry where a length is expected.
+     *
+     * @return A stream that can be written to.
+     *
+     * @throws IOException If there is an error creating the stream.
+     */
+    public OutputStream createFilteredStream( COSBase expectedLength ) throws IOException
+    {
+        return firstStream.createFilteredStream( expectedLength );
+    }
+
+    /**
+     * set the filters to be applied to the stream.
+     *
+     * @param filters The filters to set on this stream.
+     *
+     * @throws IOException If there is an error clearing the old filters.
+     */
+    public void setFilters(COSBase filters) throws IOException
+    {
+        //should this be allowed?  Should this
+        //propagate to all streams in the array?
+        firstStream.setFilters( filters );
+    }
+
+    /**
+     * This will create an output stream that can be written to.
+     *
+     * @return An output stream which raw data bytes should be written to.
+     *
+     * @throws IOException If there is an error creating the stream.
+     */
+    public OutputStream createUnfilteredStream() throws IOException
+    {
+        return firstStream.createUnfilteredStream();
+    }
+
+    /**
+     * Appends a new stream to the array that represents this object's stream.
+     *
+     * @param streamToAppend The stream to append.
+     */
+    public void appendStream(COSStream streamToAppend)
+    {
+        streams.add(streamToAppend);
+    }
+    
+    /**
+     * Insert the given stream at the beginning of the existing stream array.
+     * @param streamToBeInserted
+     */
+    public void insertCOSStream(PDStream streamToBeInserted)
+    {
+        COSArray tmp = new COSArray();
+        tmp.add(streamToBeInserted);
+        tmp.addAll(streams);
+        streams.clear();
+        streams = tmp;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/DualCOSObjectable.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/DualCOSObjectable.java
new file mode 100644 (file)
index 0000000..1a9aac7
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This is an interface to represent a PDModel object that holds two COS objects.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public interface DualCOSObjectable
+{
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getFirstCOSObject();
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getSecondCOSObject();
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDDestinationOrAction.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDDestinationOrAction.java
new file mode 100644 (file)
index 0000000..0e8777f
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.common;
+
+/**
+ * This is an interface used for some elements such as the document
+ * OpenAction that can be either a Destination or an Action.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ *
+ * @version $Revision: 1.2 $
+ */
+public interface PDDestinationOrAction extends COSObjectable
+{
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDDictionaryWrapper.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDDictionaryWrapper.java
new file mode 100644 (file)
index 0000000..3d53cd9
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A wrapper for a COS dictionary.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public class PDDictionaryWrapper implements COSObjectable
+{
+
+    private final COSDictionary dictionary;
+
+    /**
+     * Default constructor
+     */
+    public PDDictionaryWrapper()
+    {
+        this.dictionary = new COSDictionary();
+    }
+
+    /**
+     * Creates a new instance with a given COS dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDDictionaryWrapper(COSDictionary dictionary)
+    {
+        this.dictionary = dictionary;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * Gets the COS dictionary.
+     * 
+     * @return the COS dictionary
+     */
+    protected COSDictionary getCOSDictionary()
+    {
+        return this.dictionary;
+    }
+
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj instanceof PDDictionaryWrapper)
+        {
+            return this.dictionary.equals(((PDDictionaryWrapper) obj).dictionary);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return this.dictionary.hashCode();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMatrix.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMatrix.java
new file mode 100644 (file)
index 0000000..0f38c1e
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+/**
+ * This class will be used for matrix manipulation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDMatrix implements COSObjectable
+{
+    private COSArray matrix;
+    // the number of row elements depends on the number of elements
+    // within the given matrix
+    // 3x3 e.g. Matrix of a CalRGB colorspace dictionary
+    // 3x2 e.g. FontMatrix of a type 3 font
+    private int numberOfRowElements = 3;
+    
+    /**
+     * Constructor.
+     */
+    public PDMatrix()
+    {
+        matrix = new COSArray();
+        matrix.add( new COSFloat( 1.0f ) );
+        matrix.add( new COSFloat( 0.0f ) );
+        matrix.add( new COSFloat( 0.0f ) );
+        matrix.add( new COSFloat( 0.0f ) );
+        matrix.add( new COSFloat( 1.0f ) );
+        matrix.add( new COSFloat( 0.0f ) );
+        matrix.add( new COSFloat( 0.0f ) );
+        matrix.add( new COSFloat( 0.0f ) );
+        matrix.add( new COSFloat( 1.0f ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param array The array that describes the matrix.
+     */
+    public PDMatrix( COSArray array )
+    {
+        if ( array.size() == 6) 
+        {
+            numberOfRowElements = 2;
+        }
+        matrix = array;
+    }
+
+    /**
+     * This will get the underlying array value.
+     *
+     * @return The cos object that this object wraps.
+     */
+    public COSArray getCOSArray()
+    {
+        return matrix;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return matrix;
+    }
+
+
+    /**
+     * This will get a matrix value at some point.
+     *
+     * @param row The row to get the value from.
+     * @param column The column to get the value from.
+     *
+     * @return The value at the row/column position.
+     */
+    public float getValue( int row, int column )
+    {
+        return ((COSNumber)matrix.get( row*numberOfRowElements + column )).floatValue();
+    }
+
+    /**
+     * This will set a value at a position.
+     *
+     * @param row The row to set the value at.
+     * @param column the column to set the value at.
+     * @param value The value to set at the position.
+     */
+    public void setValue( int row, int column, float value )
+    {
+        matrix.set( row*numberOfRowElements+column, new COSFloat( value ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMemoryStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMemoryStream.java
new file mode 100644 (file)
index 0000000..e58e00e
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * A PDStream represents a stream in a PDF document.  Streams are tied to a single
+ * PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDMemoryStream extends PDStream
+{
+    private byte[] data;
+
+    /**
+     * This will create a new PDStream object.
+     *
+     * @param buffer The data for this in memory stream.
+     */
+    public PDMemoryStream( byte[] buffer )
+    {
+        data = buffer;
+    }
+
+
+
+    /**
+     * If there are not compression filters on the current stream then this
+     * will add a compression filter, flate compression for example.
+     */
+    public void addCompression()
+    {
+        //no compression to add
+    }
+
+
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        throw new UnsupportedOperationException( "not supported for memory stream" );
+    }
+
+    /**
+     * This will get a stream that can be written to.
+     *
+     * @return An output stream to write data to.
+     *
+     * @throws IOException If an IO error occurs during writing.
+     */
+    public OutputStream createOutputStream() throws IOException
+    {
+        throw new UnsupportedOperationException( "not supported for memory stream" );
+    }
+
+    /**
+     * This will get a stream that can be read from.
+     *
+     * @return An input stream that can be read from.
+     *
+     * @throws IOException If an IO error occurs during reading.
+     */
+    public InputStream createInputStream() throws IOException
+    {
+        return new ByteArrayInputStream( data );
+    }
+
+    /**
+     * This will get a stream with some filters applied but not others.  This is useful
+     * when doing images, ie filters = [flate,dct], we want to remove flate but leave dct
+     *
+     * @param stopFilters A list of filters to stop decoding at.
+     * @return A stream with decoded data.
+     * @throws IOException If there is an error processing the stream.
+     */
+    public InputStream getPartiallyFilteredStream( List stopFilters ) throws IOException
+    {
+        return createInputStream();
+    }
+
+    /**
+     * Get the cos stream associated with this object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSStream getStream()
+    {
+        throw new UnsupportedOperationException( "not supported for memory stream" );
+    }
+
+    /**
+     * This will get the length of the filtered/compressed stream.  This is readonly in the
+     * PD Model and will be managed by this class.
+     *
+     * @return The length of the filtered stream.
+     */
+    public int getLength()
+    {
+        return data.length;
+    }
+
+    /**
+     * This will get the list of filters that are associated with this stream.  Or
+     * null if there are none.
+     * @return A list of all encoding filters to apply to this stream.
+     */
+    public List getFilters()
+    {
+        return null;
+    }
+
+    /**
+     * This will set the filters that are part of this stream.
+     *
+     * @param filters The filters that are part of this stream.
+     */
+    public void setFilters( List filters )
+    {
+        throw new UnsupportedOperationException( "not supported for memory stream" );
+    }
+
+    /**
+     * Get the list of decode parameters.  Each entry in the list will refer to
+     * an entry in the filters list.
+     *
+     * @return The list of decode parameters.
+     *
+     * @throws IOException if there is an error retrieving the parameters.
+     */
+    public List getDecodeParams() throws IOException
+    {
+        return null;
+    }
+
+    /**
+     * This will set the list of decode params.
+     *
+     * @param decodeParams The list of decode params.
+     */
+    public void setDecodeParams( List decodeParams )
+    {
+        //do nothing
+    }
+
+    /**
+     * This will get the file specification for this stream.  This is only
+     * required for external files.
+     *
+     * @return The file specification.
+     */
+    public PDFileSpecification getFile()
+    {
+        return null;
+    }
+
+    /**
+     * Set the file specification.
+     * @param f The file specification.
+     */
+    public void setFile( PDFileSpecification f )
+    {
+        //do nothing.
+    }
+
+    /**
+     * This will get the list of filters that are associated with this stream.  Or
+     * null if there are none.
+     * @return A list of all encoding filters to apply to this stream.
+     */
+    public List getFileFilters()
+    {
+        return null;
+    }
+
+    /**
+     * This will set the filters that are part of this stream.
+     *
+     * @param filters The filters that are part of this stream.
+     */
+    public void setFileFilters( List filters )
+    {
+        //do nothing.
+    }
+
+    /**
+     * Get the list of decode parameters.  Each entry in the list will refer to
+     * an entry in the filters list.
+     *
+     * @return The list of decode parameters.
+     *
+     * @throws IOException if there is an error retrieving the parameters.
+     */
+    public List getFileDecodeParams() throws IOException
+    {
+        return null;
+    }
+
+    /**
+     * This will set the list of decode params.
+     *
+     * @param decodeParams The list of decode params.
+     */
+    public void setFileDecodeParams( List decodeParams )
+    {
+        //do nothing
+    }
+
+    /**
+     * This will copy the stream into a byte array.
+     *
+     * @return The byte array of the filteredStream
+     * @throws IOException When getFilteredStream did not work
+     */
+    public byte[] getByteArray() throws IOException
+    {
+        return data;
+    }
+
+    /**
+     * Get the metadata that is part of the document catalog.  This will
+     * return null if there is no meta data for this object.
+     *
+     * @return The metadata for this object.
+     */
+    public PDMetadata getMetadata()
+    {
+        return null;
+    }
+
+    /**
+     * Set the metadata for this object.  This can be null.
+     *
+     * @param meta The meta data for this object.
+     */
+    public void setMetadata( PDMetadata meta )
+    {
+        //do nothing
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMetadata.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDMetadata.java
new file mode 100644 (file)
index 0000000..4f501f9
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.jempbox.xmp.XMPMetadata;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * This class represents metadata for various objects in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDMetadata extends PDStream
+{
+
+    /**
+     * This will create a new PDMetadata object.
+     *
+     * @param document The document that the stream will be part of.
+     */
+    public PDMetadata( PDDocument document )
+    {
+        super( document );
+        getStream().setName( "Type", "Metadata" );
+        getStream().setName( "Subtype", "XML" );
+    }
+
+    /**
+     * Constructor.  Reads all data from the input stream and embeds it into the
+     * document, this will close the InputStream.
+     *
+     * @param doc The document that will hold the stream.
+     * @param str The stream parameter.
+     * @param filtered True if the stream already has a filter applied.
+     * @throws IOException If there is an error creating the stream in the document.
+     */
+    public PDMetadata( PDDocument doc, InputStream str, boolean filtered ) throws IOException
+    {
+        super( doc, str, filtered );
+        getStream().setName( "Type", "Metadata" );
+        getStream().setName( "Subtype", "XML" );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param str The stream parameter.
+     */
+    public PDMetadata( COSStream str )
+    {
+        super( str );
+    }
+
+    /**
+     * Extract the XMP metadata and create and build an in memory object.
+     * To persist changes back to the PDF you must call importXMPMetadata.
+     *
+     * @return A parsed XMP object.
+     *
+     * @throws IOException If there is an error parsing the XMP data.
+     */
+    public XMPMetadata exportXMPMetadata() throws IOException
+    {
+        return XMPMetadata.load( createInputStream() );
+    }
+
+    /**
+     * Import an XMP stream into the PDF document.
+     *
+     * @param xmp The XMP data.
+     *
+     * @throws IOException If there is an error generating the XML document.
+     * @throws TransformerException If there is an error generating the XML document.
+     */
+    public void importXMPMetadata( XMPMetadata xmp )
+        throws IOException, TransformerException
+    {
+        xmp.save( createOutputStream() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNameTreeNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNameTreeNode.java
new file mode 100644 (file)
index 0000000..62759c2
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This class represends a PDF Name tree.  See the PDF Reference 1.5 section 3.8.5
+ * for more details.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDNameTreeNode implements COSObjectable
+{
+    private static final Log log = LogFactory.getLog(PDNameTreeNode.class);
+    private COSDictionary node;
+    private Class valueType = null;
+
+    /**
+     * Constructor.
+     *
+     * @param valueClass The PD Model type of object that is the value.
+     */
+    public PDNameTreeNode( Class valueClass )
+    {
+        node = new COSDictionary();
+        valueType = valueClass;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict The dictionary that holds the name information.
+     * @param valueClass The PD Model type of object that is the value.
+     */
+    public PDNameTreeNode( COSDictionary dict, Class valueClass )
+    {
+        node = dict;
+        valueType = valueClass;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return node;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return node;
+    }
+
+    /**
+     * Return the children of this node.  This list will contain PDNameTreeNode objects.
+     *
+     * @return The list of children or null if there are no children.
+     */
+    public List getKids()
+    {
+
+        List retval = null;
+        COSArray kids = (COSArray)node.getDictionaryObject( COSName.KIDS );
+        if( kids != null )
+        {
+            List pdObjects = new ArrayList();
+            for( int i=0; i<kids.size(); i++ )
+            {
+                pdObjects.add( createChildNode( (COSDictionary)kids.getObject(i) ) );
+            }
+            retval = new COSArrayList(pdObjects,kids);
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the children of this named tree.
+     *
+     * @param kids The children of this named tree.
+     */
+    public void setKids( List kids )
+    {
+        if (kids != null && kids.size() > 0)
+        {
+            PDNameTreeNode firstKid = (PDNameTreeNode) kids.get(0);
+            PDNameTreeNode lastKid = (PDNameTreeNode) kids.get(kids.size() - 1);
+            String lowerLimit = firstKid.getLowerLimit();
+            this.setLowerLimit(lowerLimit);
+            String upperLimit = lastKid.getUpperLimit();
+            this.setUpperLimit(upperLimit);
+        }
+        node.setItem( "Kids", COSArrayList.converterToCOSArray( kids ) );
+    }
+
+    /**
+     * The name to retrieve.
+     *
+     * @param name The name in the tree.
+     *
+     * @return The value of the name in the tree.
+     *
+     * @throws IOException If an there is a problem creating the destinations.
+     */
+    public Object getValue( String name ) throws IOException
+    {
+        Object retval = null;
+        Map names = getNames();
+        if( names != null )
+        {
+            retval = names.get( name );
+        }
+        else
+        {
+            List kids = getKids();
+            if (kids != null) 
+            {
+                for( int i=0; i<kids.size() && retval == null; i++ )
+                {
+                    PDNameTreeNode childNode = (PDNameTreeNode)kids.get( i );
+                    if( childNode.getLowerLimit().compareTo( name ) <= 0 &&
+                        childNode.getUpperLimit().compareTo( name ) >= 0 )
+                    {
+                        retval = childNode.getValue( name );
+                    }
+                }
+            }
+            else
+            {
+                log.warn("NameTreeNode does not have \"names\" nor \"kids\" objects.");
+            }
+        }
+        return retval;
+    }
+
+
+    /**
+     * This will return a map of names. The key will be a string, and the
+     * value will depend on where this class is being used.
+     *
+     * @return ordered map of cos objects
+     * @throws IOException If there is an error while creating the sub types.
+     */
+    public Map<String, Object> getNames() throws IOException
+    {
+        COSArray namesArray = (COSArray)node.getDictionaryObject( COSName.NAMES );
+        if( namesArray != null )
+        {
+            Map<String, Object> names = new LinkedHashMap<String, Object>();
+            for( int i=0; i<namesArray.size(); i+=2 )
+            {
+                COSString key = (COSString)namesArray.getObject(i);
+                COSBase cosValue = namesArray.getObject( i+1 );
+                names.put( key.getString(), convertCOSToPD( cosValue ) );
+            }
+            return Collections.unmodifiableMap(names);
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Method to convert the COS value in the name tree to the PD Model object.  The
+     * default implementation will simply use reflection to create the correct object
+     * type.  Subclasses can do whatever they want.
+     *
+     * @param base The COS object to convert.
+     * @return The converted PD Model object.
+     * @throws IOException If there is an error during creation.
+     */
+    protected Object convertCOSToPD( COSBase base ) throws IOException
+    {
+        Object retval = null;
+        try
+        {
+            Constructor ctor = valueType.getConstructor( new Class[] { base.getClass() } );
+            retval = ctor.newInstance( new Object[] { base } );
+        }
+        catch( Throwable t )
+        {
+            throw new IOException( "Error while trying to create value in named tree:" + t.getMessage());
+
+        }
+        return retval;
+    }
+
+    /**
+     * Create a child node object.
+     *
+     * @param dic The dictionary for the child node object to refer to.
+     * @return The new child node object.
+     */
+    protected PDNameTreeNode createChildNode( COSDictionary dic )
+    {
+        return new PDNameTreeNode(dic,valueType);
+    }
+
+    /**
+     * Set the names of for this node.  The keys should be java.lang.String and the
+     * values must be a COSObjectable.  This method will set the appropriate upper and lower
+     * limits based on the keys in the map.
+     *
+     * @param names map of names to objects, or <code>null</code>
+     */
+    public void setNames( Map<String, ? extends COSObjectable> names )
+    {
+        if( names == null )
+        {
+            node.setItem( "Names", (COSObjectable)null );
+            node.setItem( COSName.LIMITS, (COSObjectable)null);
+        }
+        else
+        {
+            COSArray array = new COSArray();
+            List<String> keys = new ArrayList<String>(names.keySet());
+            Collections.sort(keys);
+            for (String key : keys) {
+                array.add(new COSString(key));
+                array.add(names.get(key));
+            }
+            setLowerLimit(keys.get(0));
+            setUpperLimit(keys.get(keys.size() - 1));
+            node.setItem("Names", array);
+        }
+    }
+
+    /**
+     * Get the highest value for a key in the name map.
+     *
+     * @return The highest value for a key in the map.
+     */
+    public String getUpperLimit()
+    {
+        String retval = null;
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr != null )
+        {
+            retval = arr.getString( 1 );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the highest value for the key in the map.
+     *
+     * @param upper The new highest value for a key in the map.
+     */
+    private void setUpperLimit( String upper )
+    {
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr == null )
+        {
+            arr = new COSArray();
+            arr.add( null );
+            arr.add( null );
+            node.setItem(COSName.LIMITS, arr);
+        }
+        arr.setString( 1, upper );
+    }
+
+    /**
+     * Get the lowest value for a key in the name map.
+     *
+     * @return The lowest value for a key in the map.
+     */
+    public String getLowerLimit()
+    {
+        String retval = null;
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr != null )
+        {
+            retval = arr.getString( 0 );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the lowest value for the key in the map.
+     *
+     * @param lower The new lowest value for a key in the map.
+     */
+    private void setLowerLimit( String lower )
+    {
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr == null )
+        {
+            arr = new COSArray();
+            arr.add( null );
+            arr.add( null );
+            node.setItem(COSName.LIMITS, arr);
+        }
+        arr.setString( 0, lower );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNamedTextStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNamedTextStream.java
new file mode 100644 (file)
index 0000000..bfcae5c
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A named text stream is a combination of a name and a PDTextStream object.  This
+ * is used in name trees.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDNamedTextStream implements DualCOSObjectable
+{
+    private COSName streamName;
+    private PDTextStream stream;
+
+    /**
+     * Constructor.
+     */
+    public PDNamedTextStream()
+    {
+        //default constructor
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param name The name of the stream.
+     * @param str The stream.
+     */
+    public PDNamedTextStream( COSName name, COSBase str )
+    {
+        streamName = name;
+        stream = PDTextStream.createTextStream( str );
+    }
+
+    /**
+     * The name of the named text stream.
+     *
+     * @return The stream name.
+     */
+    public String getName()
+    {
+        String name = null;
+        if( streamName != null )
+        {
+            name = streamName.getName();
+        }
+        return name;
+    }
+
+    /**
+     * This will set the name of the named text stream.
+     *
+     * @param name The name of the named text stream.
+     */
+    public void setName( String name )
+    {
+        streamName = COSName.getPDFName( name );
+    }
+
+    /**
+     * This will get the stream.
+     *
+     * @return The stream associated with this name.
+     */
+    public PDTextStream getStream()
+    {
+        return stream;
+    }
+
+    /**
+     * This will set the stream.
+     *
+     * @param str The stream associated with this name.
+     */
+    public void setStream( PDTextStream str )
+    {
+        stream = str;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getFirstCOSObject()
+    {
+        return streamName;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getSecondCOSObject()
+    {
+        COSBase retval = null;
+        if( stream != null )
+        {
+            retval = stream.getCOSObject();
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNumberTreeNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDNumberTreeNode.java
new file mode 100644 (file)
index 0000000..5e8972d
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class represents a PDF Number tree. See the PDF Reference 1.7 section
+ * 7.9.7 for more details.
+ * 
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>,
+ *         <a href="igor.podolskiy@ievvwi.uni-stuttgart.de">Igor Podolskiy</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDNumberTreeNode implements COSObjectable
+{
+    private COSDictionary node;
+    private Class valueType = null;
+
+    /**
+     * Constructor.
+     *
+     * @param valueClass The PD Model type of object that is the value.
+     */
+    public PDNumberTreeNode( Class valueClass )
+    {
+        node = new COSDictionary();
+        valueType = valueClass;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict The dictionary that holds the name information.
+     * @param valueClass The PD Model type of object that is the value.
+     */
+    public PDNumberTreeNode( COSDictionary dict, Class valueClass )
+    {
+        node = dict;
+        valueType = valueClass;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return node;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return node;
+    }
+
+    /**
+     * Return the children of this node.  This list will contain PDNumberTreeNode objects.
+     *
+     * @return The list of children or null if there are no children.
+     */
+    public List getKids()
+    {
+
+        List retval = null;
+        COSArray kids = (COSArray)node.getDictionaryObject( COSName.KIDS );
+        if( kids != null )
+        {
+            List pdObjects = new ArrayList();
+            for( int i=0; i<kids.size(); i++ )
+            {
+                pdObjects.add( createChildNode( (COSDictionary)kids.getObject(i) ) );
+            }
+            retval = new COSArrayList(pdObjects,kids);
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the children of this number tree.
+     *
+     * @param kids The children of this number tree.
+     */
+    public void setKids( List kids )
+    {
+        node.setItem( "Kids", COSArrayList.converterToCOSArray( kids ) );
+    }
+
+    /**
+     * Returns the value corresponding to an index in the number tree.
+     *
+     * @param index The index in the number tree.
+     *
+     * @return The value corresponding to the index.
+     * 
+     * @throws IOException If there is a problem creating the values.
+     */
+    public Object getValue( Integer index ) throws IOException
+    {
+        Object retval = null;
+        Map names = getNumbers();
+        if( names != null )
+        {
+            retval = names.get( index );
+        }
+        else
+        {
+            List kids = getKids();
+            for( int i=0; i<kids.size() && retval == null; i++ )
+            {
+                PDNumberTreeNode childNode = (PDNumberTreeNode)kids.get( i );
+                if( childNode.getLowerLimit().compareTo( index ) <= 0 &&
+                        childNode.getUpperLimit().compareTo( index ) >= 0 )
+                {
+                    retval = childNode.getValue( index );
+                }
+            }
+        }
+        return retval;
+    }
+
+
+    /**
+     * This will return a map of numbers.  The key will be a java.lang.Integer, the value will
+     * depend on where this class is being used.
+     *
+     * @return A map of COS objects.
+     * 
+     * @throws IOException If there is a problem creating the values.
+     */
+    public Map getNumbers()  throws IOException
+    {
+        Map indices = null;
+        COSArray namesArray = (COSArray)node.getDictionaryObject( COSName.NUMS );
+        if( namesArray != null )
+        {
+            indices = new HashMap();
+            for( int i=0; i<namesArray.size(); i+=2 )
+            {
+                COSInteger key = (COSInteger)namesArray.getObject(i);
+                COSBase cosValue = namesArray.getObject( i+1 );
+                Object pdValue = convertCOSToPD( cosValue );
+
+                indices.put( Integer.valueOf(key.intValue()), pdValue );
+            }
+            indices = Collections.unmodifiableMap(indices);
+        }
+
+        return indices;
+    }
+
+    /**
+     * Method to convert the COS value in the name tree to the PD Model object.  The
+     * default implementation will simply use reflection to create the correct object
+     * type.  Subclasses can do whatever they want.
+     *
+     * @param base The COS object to convert.
+     * @return The converted PD Model object.
+     * @throws IOException If there is an error during creation.
+     */
+    protected Object convertCOSToPD( COSBase base ) throws IOException
+    {
+        Object retval = null;
+        try
+        {
+            Constructor ctor = valueType.getConstructor( new Class[] { base.getClass() } );
+            retval = ctor.newInstance( new Object[] { base } );
+        }
+        catch( Throwable t )
+        {
+            throw new IOException( "Error while trying to create value in number tree:" + t.getMessage());
+
+        }
+        return retval;
+    }
+
+    /**
+     * Create a child node object.
+     *
+     * @param dic The dictionary for the child node object to refer to.
+     * @return The new child node object.
+     */
+    protected PDNumberTreeNode createChildNode( COSDictionary dic )
+    {
+        return new PDNumberTreeNode(dic,valueType);
+    }
+
+    /**
+     * Set the names of for this node.  The keys should be java.lang.String and the
+     * values must be a COSObjectable.  This method will set the appropriate upper and lower
+     * limits based on the keys in the map.
+     *
+     * @param numbers The map of names to objects.
+     */
+    public void setNumbers( Map numbers )
+    {
+        if( numbers == null )
+        {
+            node.setItem( COSName.NUMS, (COSObjectable)null );
+            node.setItem( "Limits", (COSObjectable)null);
+        }
+        else
+        {
+            List keys = new ArrayList( numbers.keySet() );
+            Collections.sort( keys );
+            COSArray array = new COSArray();
+            for( int i=0; i<keys.size(); i++ )
+            {
+                Integer key = (Integer)keys.get(i);
+                array.add( COSInteger.get( key ) );
+                COSObjectable obj = (COSObjectable)numbers.get( key );
+                array.add( obj );
+            }
+            Integer lower = null;
+            Integer upper = null;
+            if( keys.size() > 0 )
+            {
+                lower = (Integer)keys.get( 0 );
+                upper = (Integer)keys.get( keys.size()-1 );
+            }
+            setUpperLimit( upper );
+            setLowerLimit( lower );
+            node.setItem( "Names", array );
+        }
+    }
+
+    /**
+     * Get the highest value for a key in the name map.
+     *
+     * @return The highest value for a key in the map.
+     */
+    public Integer getUpperLimit()
+    {
+        Integer retval = null;
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr != null )
+        {
+            retval = Integer.valueOf(arr.getInt( 1 ));
+        }
+        return retval;
+    }
+
+    /**
+     * Set the highest value for the key in the map.
+     *
+     * @param upper The new highest value for a key in the map.
+     */
+    private void setUpperLimit( Integer upper )
+    {
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr == null )
+        {
+            arr = new COSArray();
+            arr.add( null );
+            arr.add( null );
+        }
+        arr.setInt( 1, upper.intValue() );
+    }
+
+    /**
+     * Get the lowest value for a key in the name map.
+     *
+     * @return The lowest value for a key in the map.
+     */
+    public Integer getLowerLimit()
+    {
+        Integer retval = null;
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr != null )
+        {
+            retval = Integer.valueOf(arr.getInt( 0 ));
+        }
+        return retval;
+    }
+
+    /**
+     * Set the lowest value for the key in the map.
+     *
+     * @param lower The new lowest value for a key in the map.
+     */
+    private void setLowerLimit( Integer lower )
+    {
+        COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+        if( arr == null )
+        {
+            arr = new COSArray();
+            arr.add( null );
+            arr.add( null );
+        }
+        arr.setInt( 0, lower.intValue() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDObjectStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDObjectStream.java
new file mode 100644 (file)
index 0000000..b18953b
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+
+
+/**
+ * A PDStream represents a stream in a PDF document.  Streams are tied to a single
+ * PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDObjectStream extends PDStream
+{
+
+    /**
+     * Constructor.
+     *
+     * @param str The stream parameter.
+     */
+    public PDObjectStream( COSStream str )
+    {
+        super( str );
+    }
+
+    /**
+     * This will create a new PDStream object.
+     *
+     * @param document The document that the stream will be part of.
+     * @return A new stream object.
+     */
+    public static PDObjectStream createStream( PDDocument document )
+    {
+        COSStream cosStream = new COSStream( document.getDocument().getScratchFile() );
+        PDObjectStream strm = new PDObjectStream( cosStream );
+        strm.getStream().setName( "Type", "ObjStm" );
+        return strm;
+    }
+
+    /**
+     * Get the type of this object, should always return "ObjStm".
+     *
+     * @return The type of this object.
+     */
+    public String getType()
+    {
+        return getStream().getNameAsString( "Type" );
+    }
+
+    /**
+     * Get the number of compressed object.
+     *
+     * @return The number of compressed objects.
+     */
+    public int getNumberOfObjects()
+    {
+        return getStream().getInt( "N", 0 );
+    }
+
+    /**
+     * Set the number of objects.
+     *
+     * @param n The new number of objects.
+     */
+    public void setNumberOfObjects( int n )
+    {
+        getStream().setInt( "N", n );
+    }
+
+    /**
+     * The byte offset (in the decoded stream) of the first compressed object.
+     *
+     * @return The byte offset to the first object.
+     */
+    public int getFirstByteOffset()
+    {
+        return getStream().getInt( "First", 0 );
+    }
+
+    /**
+     * The byte offset (in the decoded stream) of the first compressed object.
+     *
+     * @param n The byte offset to the first object.
+     */
+    public void setFirstByteOffset( int n )
+    {
+        getStream().setInt( "First", n );
+    }
+
+    /**
+     * A reference to an object stream, of which the current object stream is
+     * considered an extension.
+     *
+     * @return The object that this stream is an extension.
+     */
+    public PDObjectStream getExtends()
+    {
+        PDObjectStream retval = null;
+        COSStream stream = (COSStream)getStream().getDictionaryObject( COSName.EXTENDS );
+        if( stream != null )
+        {
+            retval = new PDObjectStream( stream );
+        }
+        return retval;
+
+    }
+
+    /**
+     * A reference to an object stream, of which the current object stream is
+     * considered an extension.
+     *
+     * @param stream The object stream extension.
+     */
+    public void setExtends( PDObjectStream stream )
+    {
+        getStream().setItem( COSName.EXTENDS, stream );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDPageLabelRange.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDPageLabelRange.java
new file mode 100644 (file)
index 0000000..b94d95e
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * Contains information for a page label range.
+ * 
+ * @author <a href="mailto:igor.podolskiy@ievvwi.uni-stuttgart.de">Igor
+ *         Podolskiy</a>
+ * 
+ * @see PDPageLabels
+ * 
+ * @version $Revision$
+ */
+public class PDPageLabelRange implements COSObjectable
+{
+
+    private COSDictionary root;
+
+    // Page label dictonary (PDF32000-1:2008 Section 12.4.2, Table 159)
+    private static final COSName KEY_START = COSName.getPDFName("St");
+    private static final COSName KEY_PREFIX = COSName.P;
+    private static final COSName KEY_STYLE = COSName.getPDFName("S");
+
+    // Style entry values (PDF32000-1:2008 Section 12.4.2, Table 159)
+
+    /**
+     * Decimal page numbering style (1, 2, 3, ...).
+     */
+    public static final String STYLE_DECIMAL = "D";
+
+    /**
+     * Roman numbers (upper case) numbering style (I, II, III, IV, ...).
+     */
+    public static final String STYLE_ROMAN_UPPER = "R";
+
+    /**
+     * Roman numbers (lower case) numbering style (i, ii, iii, iv, ...).
+     */
+    public static final String STYLE_ROMAN_LOWER = "r";
+
+    /**
+     * Letter (upper case) numbering style (A, B, ..., Z, AA, BB, ..., ZZ, AAA,
+     * ...).
+     */
+    public static final String STYLE_LETTERS_UPPER = "A";
+
+    /**
+     * Letter (lower case) numbering style (a, b, ..., z, aa, bb, ..., zz, aaa,
+     * ...).
+     */
+    public static final String STYLE_LETTERS_LOWER = "a";
+
+    /**
+     * Creates a new empty page label range object.
+     */
+    public PDPageLabelRange()
+    {
+        this(new COSDictionary());
+    }
+
+    /**
+     * Creates a new page label range object from the given dictionary.
+     * 
+     * @param dict
+     *            the base dictionary for the new object.
+     */
+    public PDPageLabelRange(COSDictionary dict)
+    {
+        root = dict;
+    }
+
+    /**
+     * Returns the underlying dictionary.
+     * 
+     * @return the underlying dictionary.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return root;
+    }
+
+    public COSBase getCOSObject()
+    {
+        return root;
+    }
+
+    /**
+     * Returns the numbering style for this page range.
+     * 
+     * @return one of the STYLE_* constants
+     */
+    public String getStyle()
+    {
+        return root.getNameAsString(KEY_STYLE);
+    }
+
+    /**
+     * Sets the numbering style for this page range.
+     * 
+     * @param style
+     *            one of the STYLE_* constants or {@code null} if no page
+     *            numbering is desired.
+     */
+    public void setStyle(String style)
+    {
+        if (style != null)
+        {
+            root.setName(KEY_STYLE, style);
+        }
+        else
+        {
+            root.removeItem(KEY_STYLE);
+        }
+    }
+
+    /**
+     * Returns the start value for page numbering in this page range.
+     * 
+     * @return a positive integer the start value for numbering.
+     */
+    public int getStart()
+    {
+        return root.getInt(KEY_START, 1);
+    }
+
+    /**
+     * Sets the start value for page numbering in this page range.
+     * 
+     * @param start
+     *            a positive integer representing the start value.
+     * @throws IllegalArgumentException
+     *             if {@code start} is not a positive integer
+     */
+    public void setStart(int start)
+    {
+        if (start <= 0)
+        {
+            throw new IllegalArgumentException(
+                    "The page numbering start value must be a positive integer");
+        }
+        root.setInt(KEY_START, start);
+    }
+
+    /**
+     * Returns the page label prefix for this page range.
+     * 
+     * @return the page label prefix for this page range, or {@code null} if no
+     *         prefix has been defined.
+     */
+    public String getPrefix()
+    {
+        return root.getString(KEY_PREFIX);
+    }
+
+    /**
+     * Sets the page label prefix for this page range.
+     * 
+     * @param prefix
+     *            the page label prefix for this page range, or {@code null} to
+     *            unset the prefix.
+     */
+    public void setPrefix(String prefix)
+    {
+        if (prefix != null)
+        {
+            root.setString(KEY_PREFIX, prefix);
+        }
+        else
+        {
+            root.removeItem(KEY_PREFIX);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDPageLabels.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDPageLabels.java
new file mode 100644 (file)
index 0000000..73e8cad
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * Represents the page label dictionary of a document.
+ * 
+ * @author <a href="mailto:igor.podolskiy@ievvwi.uni-stuttgart.de">Igor
+ *         Podolskiy</a>
+ * @version $Revision$
+ */
+public class PDPageLabels implements COSObjectable
+{
+
+    private SortedMap<Integer, PDPageLabelRange> labels;
+
+    private PDDocument doc;
+
+    /**
+     * Creates an empty page label dictionary for the given document.
+     * 
+     * <p>
+     * Note that the page label dictionary won't be automatically added to the
+     * document; you will still need to do it manually (see
+     * {@link PDDocumentCatalog#setPageLabels(PDPageLabels)}.
+     * </p>
+     * 
+     * @param document
+     *            The document the page label dictionary is created for.
+     * @see PDDocumentCatalog#setPageLabels(PDPageLabels)
+     */
+    public PDPageLabels(PDDocument document)
+    {
+        labels = new TreeMap<Integer, PDPageLabelRange>();
+        this.doc = document;
+        PDPageLabelRange defaultRange = new PDPageLabelRange();
+        defaultRange.setStyle(PDPageLabelRange.STYLE_DECIMAL);
+        labels.put(0, defaultRange);
+    }
+
+    /**
+     * Creates an page label dictionary for a document using the information in
+     * the given COS dictionary.
+     * 
+     * <p>
+     * Note that the page label dictionary won't be automatically added to the
+     * document; you will still need to do it manually (see
+     * {@link PDDocumentCatalog#setPageLabels(PDPageLabels)}.
+     * </p>
+     * 
+     * @param document
+     *            The document the page label dictionary is created for.
+     * @param dict
+     *            an existing page label dictionary
+     * @see PDDocumentCatalog#setPageLabels(PDPageLabels)
+     * @throws IOException
+     *             If something goes wrong during the number tree conversion.
+     */
+    public PDPageLabels(PDDocument document, COSDictionary dict) throws IOException
+    {
+        this(document);
+        if (dict == null)
+        {
+            return;
+        }
+        PDNumberTreeNode root = new PDNumberTreeNode(dict, COSDictionary.class);
+        findLabels(root);
+    }
+    
+    private void findLabels(PDNumberTreeNode node) throws IOException {
+        if (node.getKids() != null) {
+            List<PDNumberTreeNode> kids = node.getKids();
+            for (PDNumberTreeNode kid : kids) {
+                findLabels(kid);
+            }
+        }
+        else if (node.getNumbers() != null) {
+            Map<Integer, COSDictionary> numbers = node.getNumbers();
+            for (Entry<Integer, COSDictionary> i : numbers.entrySet())
+            {
+                if(i.getKey() >= 0)
+                    labels.put(i.getKey(), new PDPageLabelRange(i.getValue()));
+            }
+        }
+    }
+
+
+    /**
+     * Returns the number of page label ranges.
+     * 
+     * <p>
+     * This will be always &gt;= 1, as the required default entry for the page
+     * range starting at the first page is added automatically by this
+     * implementation (see PDF32000-1:2008, p. 375).
+     * </p>
+     * 
+     * @return the number of page label ranges.
+     */
+    public int getPageRangeCount()
+    {
+        return labels.size();
+    }
+
+    /**
+     * Returns the page label range starting at the given page, or {@code null}
+     * if no such range is defined.
+     * 
+     * @param startPage
+     *            the 0-based page index representing the start page of the page
+     *            range the item is defined for.
+     * @return the page label range or {@code null} if no label range is defined
+     *         for the given start page.
+     */
+    public PDPageLabelRange getPageLabelRange(int startPage)
+    {
+        return labels.get(startPage);
+    }
+
+    /**
+     * Sets the page label range beginning at the specified start page.
+     * 
+     * @param startPage
+     *            the 0-based index of the page representing the start of the
+     *            page label range.
+     * @param item
+     *            the page label item to set.
+     */
+    public void setLabelItem(int startPage, PDPageLabelRange item)
+    {
+        labels.put(startPage, item);
+    }
+
+    public COSBase getCOSObject()
+    {
+        COSDictionary dict = new COSDictionary();
+        COSArray arr = new COSArray();
+        for (Entry<Integer, PDPageLabelRange> i : labels.entrySet())
+        {
+            arr.add(COSInteger.get(i.getKey()));
+            arr.add(i.getValue());
+        }
+        dict.setItem(COSName.NUMS, arr);
+        return dict;
+    }
+
+    /**
+     * Returns a mapping with computed page labels as keys and corresponding
+     * 0-based page indices as values. The returned map will contain at most as
+     * much entries as the document has pages.
+     * 
+     * <p>
+     * <strong>NOTE:</strong> If the document contains duplicate page labels,
+     * the returned map will contain <em>less</em> entries than the document has
+     * pages. The page index returned in this case is the <em>highest</em> index
+     * among all pages sharing the same label.
+     * </p>
+     * 
+     * @return a mapping from labels to 0-based page indices.
+     */
+    public Map<String, Integer> getPageIndicesByLabels()
+    {
+        final Map<String, Integer> labelMap = 
+            new HashMap<String, Integer>(doc.getNumberOfPages());
+        computeLabels(new LabelHandler()
+        {
+            public void newLabel(int pageIndex, String label)
+            {
+                labelMap.put(label, pageIndex);
+            }
+        });
+        return labelMap;
+    }
+
+    /**
+     * Returns a mapping with 0-based page indices as keys and corresponding
+     * page labels as values as an array. The array will have exactly as much
+     * entries as the document has pages.
+     * 
+     * @return an array mapping from 0-based page indices to labels.
+     */
+    public String[] getLabelsByPageIndices()
+    {
+        final String[] map = new String[doc.getNumberOfPages()];
+        computeLabels(new LabelHandler()
+        {
+            public void newLabel(int pageIndex, String label)
+            {
+                if(pageIndex < doc.getNumberOfPages())
+                { 
+                    map[pageIndex] = label;
+                }
+            }
+        });
+        return map;
+    }
+
+    /**
+     * Internal interface for the control flow support.
+     * 
+     * @author Igor Podolskiy
+     */
+    private static interface LabelHandler
+    {
+        public void newLabel(int pageIndex, String label);
+    }
+
+    private void computeLabels(LabelHandler handler)
+    {
+        Iterator<Entry<Integer, PDPageLabelRange>> iterator = 
+            labels.entrySet().iterator();
+        if (!iterator.hasNext())
+        {
+            return;
+        }
+        int pageIndex = 0;
+        Entry<Integer, PDPageLabelRange> lastEntry = iterator.next();
+        while (iterator.hasNext())
+        {
+            Entry<Integer, PDPageLabelRange> entry = iterator.next();
+            int numPages = entry.getKey() - lastEntry.getKey();
+            LabelGenerator gen = new LabelGenerator(lastEntry.getValue(),
+                    numPages);
+            while (gen.hasNext())
+            {
+                handler.newLabel(pageIndex, gen.next());
+                pageIndex++;
+            }
+            lastEntry = entry;
+        }
+        LabelGenerator gen = new LabelGenerator(lastEntry.getValue(), 
+                doc.getNumberOfPages() - lastEntry.getKey());
+        while (gen.hasNext())
+        {
+            handler.newLabel(pageIndex, gen.next());
+            pageIndex++;
+        }
+    }
+
+    /**
+     * Generates the labels in a page range.
+     * 
+     * @author Igor Podolskiy
+     * 
+     */
+    private static class LabelGenerator implements Iterator<String>
+    {
+        private PDPageLabelRange labelInfo;
+        private int numPages;
+        private int currentPage;
+
+        public LabelGenerator(PDPageLabelRange label, int pages)
+        {
+            this.labelInfo = label;
+            this.numPages = pages;
+            this.currentPage = 0;
+        }
+
+        public boolean hasNext()
+        {
+            return currentPage < numPages;
+        }
+
+        public String next()
+        {
+            if (!hasNext())
+            {
+                throw new NoSuchElementException();
+            }
+            StringBuilder buf = new StringBuilder();
+            if (labelInfo.getPrefix() != null)
+            {
+                buf.append(labelInfo.getPrefix());
+            }
+            if (labelInfo.getStyle() != null)
+            {
+                buf.append(getNumber(labelInfo.getStart() + currentPage,
+                        labelInfo.getStyle()));
+            }
+            currentPage++;
+            return buf.toString();
+        }
+
+        private String getNumber(int pageIndex, String style)
+        {
+            if (PDPageLabelRange.STYLE_DECIMAL.equals(style))
+            {
+                return Integer.toString(pageIndex);
+            }
+            else if (PDPageLabelRange.STYLE_LETTERS_LOWER.equals(style))
+            {
+                return makeLetterLabel(pageIndex);
+            }
+            else if (PDPageLabelRange.STYLE_LETTERS_UPPER.equals(style))
+            {
+                return makeLetterLabel(pageIndex).toUpperCase();
+            }
+            else if (PDPageLabelRange.STYLE_ROMAN_LOWER.equals(style))
+            {
+                return makeRomanLabel(pageIndex);
+            }
+            else if (PDPageLabelRange.STYLE_ROMAN_UPPER.equals(style))
+            {
+                return makeRomanLabel(pageIndex).toUpperCase();
+            }
+            else
+            {
+                // Fall back to decimals.
+                return Integer.toString(pageIndex);
+            }
+        }
+
+        /**
+         * Lookup table used by the {@link #makeRomanLabel(int)} method.
+         */
+        private static final String[][] ROMANS = new String[][] {
+            { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" },
+            { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" },
+            { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }
+        };
+
+        private static String makeRomanLabel(int pageIndex)
+        {
+            StringBuilder buf = new StringBuilder();
+            int power = 0;
+            while (power < 3 && pageIndex > 0)
+            {
+                buf.insert(0, ROMANS[power][pageIndex % 10]);
+                pageIndex = pageIndex / 10;
+                power++;
+            }
+            // Prepend as many m as there are thousands (which is
+            // incorrect by the roman numeral rules for numbers > 3999,
+            // but is unbounded and Adobe Acrobat does it this way).
+            // This code is somewhat inefficient for really big numbers,
+            // but those don't occur too often (and the numbers in those cases
+            // would be incomprehensible even if we and Adobe
+            // used strict Roman rules).
+            for (int i = 0; i < pageIndex; i++)
+            {
+                buf.insert(0, 'm');
+            }
+            return buf.toString();
+        }
+
+        /**
+         * A..Z, AA..ZZ, AAA..ZZZ ... labeling as described in PDF32000-1:2008,
+         * Table 159, Page 375.
+         */
+        private static String makeLetterLabel(int num)
+        {
+            StringBuilder buf = new StringBuilder();
+            int numLetters = num / 26 + Integer.signum(num % 26);
+            int letter = num % 26 + 26 * (1 - Integer.signum(num % 26)) + 64;
+            for (int i = 0; i < numLetters; i++)
+            {
+                buf.appendCodePoint(letter);
+            }
+            return buf.toString();
+        }
+
+        public void remove()
+        {
+            // This is a generator, no removing allowed.
+            throw new UnsupportedOperationException();
+        }
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDRange.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDRange.java
new file mode 100644 (file)
index 0000000..e4ad1f9
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+/**
+ * This class will be used to signify a range.  a(min) <= a* <= a(max)
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDRange implements COSObjectable
+{
+    private COSArray rangeArray;
+    private int startingIndex;
+
+    /**
+     * Constructor with an initial range of 0..1.
+     */
+    public PDRange()
+    {
+        rangeArray = new COSArray();
+        rangeArray.add( new COSFloat( 0.0f ) );
+        rangeArray.add( new COSFloat( 1.0f ) );
+        startingIndex = 0;
+    }
+
+    /**
+     * Constructor assumes a starting index of 0.
+     *
+     * @param range The array that describes the range.
+     */
+    public PDRange( COSArray range )
+    {
+        rangeArray = range;
+    }
+
+    /**
+     * Constructor with an index into an array.  Because some arrays specify
+     * multiple ranges ie [ 0,1,  0,2,  2,3 ] It is convenient for this
+     * class to take an index into an array.  So if you want this range to
+     * represent 0,2 in the above example then you would say <code>new PDRange( array, 1 )</code>.
+     *
+     * @param range The array that describes the index
+     * @param index The range index into the array for the start of the range.
+     */
+    public PDRange( COSArray range, int index )
+    {
+        rangeArray = range;
+        startingIndex = index;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return rangeArray;
+    }
+
+    /**
+     * This will get the underlying array value.
+     *
+     * @return The cos object that this object wraps.
+     */
+    public COSArray getCOSArray()
+    {
+        return rangeArray;
+    }
+
+    /**
+     * This will get the minimum value of the range.
+     *
+     * @return The min value.
+     */
+    public float getMin()
+    {
+        COSNumber min = (COSNumber)rangeArray.getObject( startingIndex*2 );
+        return min.floatValue();
+    }
+
+    /**
+     * This will set the minimum value for the range.
+     *
+     * @param min The new minimum for the range.
+     */
+    public void setMin( float min )
+    {
+        rangeArray.set( startingIndex*2, new COSFloat( min ) );
+    }
+
+    /**
+     * This will get the maximum value of the range.
+     *
+     * @return The max value.
+     */
+    public float getMax()
+    {
+        COSNumber max = (COSNumber)rangeArray.getObject( startingIndex*2+1 );
+        return max.floatValue();
+    }
+
+    /**
+     * This will set the maximum value for the range.
+     *
+     * @param max The new maximum for the range.
+     */
+    public void setMax( float max )
+    {
+        rangeArray.set( startingIndex*2+1, new COSFloat( max ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDRectangle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDRectangle.java
new file mode 100644 (file)
index 0000000..a6fd488
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.fontbox.util.BoundingBox;
+
+import java.awt.Dimension;
+
+/**
+ * This represents a rectangle in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class PDRectangle implements COSObjectable
+{
+    private COSArray rectArray;
+
+    /**
+     * Constructor.
+     *
+     * Initializes to 0,0,0,0
+     */
+    public PDRectangle()
+    {
+        rectArray = new COSArray();
+        rectArray.add( new COSFloat( 0.0f ) );
+        rectArray.add( new COSFloat( 0.0f ) );
+        rectArray.add( new COSFloat( 0.0f ) );
+        rectArray.add( new COSFloat( 0.0f ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param width The width of the rectangle.
+     * @param height The height of the rectangle.
+     */
+    public PDRectangle( float width, float height )
+    {
+        rectArray = new COSArray();
+        rectArray.add( new COSFloat( 0.0f ) );
+        rectArray.add( new COSFloat( 0.0f ) );
+        rectArray.add( new COSFloat( width ) );
+        rectArray.add( new COSFloat( height ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param box The non PD bouding box.
+     */
+    public PDRectangle( BoundingBox box )
+    {
+        rectArray = new COSArray();
+        rectArray.add( new COSFloat( box.getLowerLeftX() ) );
+        rectArray.add( new COSFloat( box.getLowerLeftY() ) );
+        rectArray.add( new COSFloat( box.getUpperRightX() ) );
+        rectArray.add( new COSFloat( box.getUpperRightY() ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param array An array of numbers as specified in the PDF Reference for a rectangle type.
+     */
+    public PDRectangle( COSArray array )
+    {
+        rectArray = array;
+    }
+
+    /**
+     * Method to determine if the x/y point is inside this rectangle.
+     * @param x The x-coordinate to test.
+     * @param y The y-coordinate to test.
+     * @return True if the point is inside this rectangle.
+     */
+    public boolean contains( float x, float y )
+    {
+        float llx = getLowerLeftX();
+        float urx = getUpperRightX();
+        float lly = getLowerLeftY();
+        float ury = getUpperRightY();
+        return x >= llx && x <= urx &&
+               y >= lly && y <= ury;
+    }
+
+    /**
+     * This will create a translated rectangle based off of this rectangle, such
+     * that the new rectangle retains the same dimensions(height/width), but the
+     * lower left x,y values are zero. <br />
+     * 100, 100, 400, 400 (llx, lly, urx, ury ) <br />
+     * will be translated to 0,0,300,300
+     *
+     * @return A new rectangle that has been translated back to the origin.
+     */
+    public PDRectangle createRetranslatedRectangle()
+    {
+        PDRectangle retval = new PDRectangle();
+        retval.setUpperRightX( getWidth() );
+        retval.setUpperRightY( getHeight() );
+        return retval;
+    }
+
+    /**
+     * This will get the underlying array for this rectangle.
+     *
+     * @return The cos array.
+     */
+    public COSArray getCOSArray()
+    {
+        return rectArray;
+    }
+
+    /**
+     * This will get the lower left x coordinate.
+     *
+     * @return The lower left x.
+     */
+    public float getLowerLeftX()
+    {
+        return ((COSNumber)rectArray.get(0)).floatValue();
+    }
+
+    /**
+     * This will set the lower left x coordinate.
+     *
+     * @param value The lower left x.
+     */
+    public void setLowerLeftX(float value)
+    {
+        rectArray.set(0, new COSFloat( value ) );
+    }
+
+    /**
+     * This will get the lower left y coordinate.
+     *
+     * @return The lower left y.
+     */
+    public float getLowerLeftY()
+    {
+        return ((COSNumber)rectArray.get(1)).floatValue();
+    }
+
+    /**
+     * This will set the lower left y coordinate.
+     *
+     * @param value The lower left y.
+     */
+    public void setLowerLeftY(float value)
+    {
+        rectArray.set(1, new COSFloat( value ) );
+    }
+
+    /**
+     * This will get the upper right x coordinate.
+     *
+     * @return The upper right x .
+     */
+    public float getUpperRightX()
+    {
+        return ((COSNumber)rectArray.get(2)).floatValue();
+    }
+
+    /**
+     * This will set the upper right x coordinate.
+     *
+     * @param value The upper right x .
+     */
+    public void setUpperRightX(float value)
+    {
+        rectArray.set(2, new COSFloat( value ) );
+    }
+
+    /**
+     * This will get the upper right y coordinate.
+     *
+     * @return The upper right y.
+     */
+    public float getUpperRightY()
+    {
+        return ((COSNumber)rectArray.get(3)).floatValue();
+    }
+
+    /**
+     * This will set the upper right y coordinate.
+     *
+     * @param value The upper right y.
+     */
+    public void setUpperRightY(float value)
+    {
+        rectArray.set(3, new COSFloat( value ) );
+    }
+
+    /**
+     * This will get the width of this rectangle as calculated by
+     * upperRightX - lowerLeftX.
+     *
+     * @return The width of this rectangle.
+     */
+    public float getWidth()
+    {
+        return getUpperRightX() - getLowerLeftX();
+    }
+
+    /**
+     * This will get the height of this rectangle as calculated by
+     * upperRightY - lowerLeftY.
+     *
+     * @return The height of this rectangle.
+     */
+    public float getHeight()
+    {
+        return getUpperRightY() - getLowerLeftY();
+    }
+
+    /**
+     * A convenience method to create a dimension object for AWT operations.
+     *
+     * @return A dimension that matches the width and height of this rectangle.
+     */
+    public Dimension createDimension()
+    {
+        return new Dimension( (int)getWidth(), (int)getHeight() );
+    }
+
+    /**
+    * This will move the rectangle the given relative amount.
+    *
+    * @param horizontalAmount positive values will move rectangle to the right, negative's to the left.
+    * @param verticalAmount positive values will move the rectangle up, negative's down.
+    */
+    public void move(float horizontalAmount, float verticalAmount)
+    {
+        setUpperRightX(getUpperRightX() + horizontalAmount);
+        setLowerLeftX(getLowerLeftX() + horizontalAmount);
+        setUpperRightY(getUpperRightY() + verticalAmount);
+        setLowerLeftY(getLowerLeftY() + verticalAmount);
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return rectArray;
+    }
+
+
+    /**
+     * This will return a string representation of this rectangle.
+     *
+     * @return This object as a string.
+     */
+    public String toString()
+    {
+        return "[" + getLowerLeftX() + "," + getLowerLeftY() + "," +
+                     getUpperRightX() + "," + getUpperRightY() +"]";
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDStream.java
new file mode 100644 (file)
index 0000000..d1e6fe1
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterManager;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * A PDStream represents a stream in a PDF document.  Streams are tied to a single
+ * PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.17 $
+ */
+public class PDStream implements COSObjectable
+{
+    private COSStream stream;
+
+    /**
+     * This will create a new PDStream object.
+     */
+    protected PDStream()
+    {
+        //should only be called by PDMemoryStream
+    }
+
+    /**
+     * This will create a new PDStream object.
+     *
+     * @param document The document that the stream will be part of.
+     */
+    public PDStream( PDDocument document )
+    {
+        stream = new COSStream( document.getDocument().getScratchFile() );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param str The stream parameter.
+     */
+    public PDStream( COSStream str )
+    {
+        stream = str;
+    }
+
+    /**
+     * Constructor.  Reads all data from the input stream and embeds it into the
+     * document, this will close the InputStream.
+     *
+     * @param doc The document that will hold the stream.
+     * @param str The stream parameter.
+     * @throws IOException If there is an error creating the stream in the document.
+     */
+    public PDStream( PDDocument doc, InputStream str ) throws IOException
+    {
+        this( doc, str, false );
+    }
+
+    /**
+     * Constructor.  Reads all data from the input stream and embeds it into the
+     * document, this will close the InputStream.
+     *
+     * @param doc The document that will hold the stream.
+     * @param str The stream parameter.
+     * @param filtered True if the stream already has a filter applied.
+     * @throws IOException If there is an error creating the stream in the document.
+     */
+    public PDStream( PDDocument doc, InputStream str, boolean filtered ) throws IOException
+    {
+        OutputStream output = null;
+        try
+        {
+            stream = new COSStream( doc.getDocument().getScratchFile() );
+            if( filtered )
+            {
+                output = stream.createFilteredStream();
+            }
+            else
+            {
+                output = stream.createUnfilteredStream();
+            }
+            byte[] buffer = new byte[ 1024 ];
+            int amountRead = -1;
+            while( (amountRead = str.read(buffer)) != -1 )
+            {
+                output.write( buffer, 0, amountRead );
+            }
+        }
+        finally
+        {
+            if( output != null )
+            {
+                output.close();
+            }
+            if( str != null )
+            {
+                str.close();
+            }
+        }
+    }
+
+    /**
+     * If there are not compression filters on the current stream then this
+     * will add a compression filter, flate compression for example.
+     */
+    public void addCompression()
+    {
+        List filters = getFilters();
+        if( filters == null )
+        {
+            filters = new ArrayList();
+            filters.add( COSName.FLATE_DECODE );
+            setFilters( filters );
+        }
+    }
+
+    /**
+     * Create a pd stream from either a regular COSStream on a COSArray of cos streams.
+     * @param base Either a COSStream or COSArray.
+     * @return A PDStream or null if base is null.
+     * @throws IOException If there is an error creating the PDStream.
+     */
+    public static PDStream createFromCOS( COSBase base ) throws IOException
+    {
+        PDStream retval = null;
+        if( base instanceof COSStream )
+        {
+            retval = new PDStream( (COSStream)base );
+        }
+        else if( base instanceof COSArray )
+        {
+            if (((COSArray)base).size() > 0) {
+                retval = new PDStream( new COSStreamArray( (COSArray)base ) );
+            }
+        }
+        else
+        {
+            if( base != null )
+            {
+                throw new IOException( "Contents are unknown type:" + base.getClass().getName() );
+            }
+        }
+        return retval;
+    }
+
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return stream;
+    }
+
+    /**
+     * This will get a stream that can be written to.
+     *
+     * @return An output stream to write data to.
+     *
+     * @throws IOException If an IO error occurs during writing.
+     */
+    public OutputStream createOutputStream() throws IOException
+    {
+        return stream.createUnfilteredStream();
+    }
+
+    /**
+     * This will get a stream that can be read from.
+     *
+     * @return An input stream that can be read from.
+     *
+     * @throws IOException If an IO error occurs during reading.
+     */
+    public InputStream createInputStream() throws IOException
+    {
+        return stream.getUnfilteredStream();
+    }
+
+    /**
+     * This will get a stream with some filters applied but not others.  This is useful
+     * when doing images, ie filters = [flate,dct], we want to remove flate but leave dct
+     *
+     * @param stopFilters A list of filters to stop decoding at.
+     * @return A stream with decoded data.
+     * @throws IOException If there is an error processing the stream.
+     */
+    public InputStream getPartiallyFilteredStream( List stopFilters ) throws IOException
+    {
+        FilterManager manager = stream.getFilterManager();
+        InputStream is = stream.getFilteredStream();
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        List filters = getFilters();
+        String nextFilter = null;
+        boolean done = false;
+        for( int i=0; i<filters.size() && !done; i++ )
+        {
+            os.reset();
+            nextFilter = (String)filters.get( i );
+            if( stopFilters.contains( nextFilter ) )
+            {
+                done = true;
+            }
+            else
+            {
+                Filter filter = manager.getFilter( COSName.getPDFName(nextFilter) );
+                filter.decode( is, os, stream, i );
+                is = new ByteArrayInputStream( os.toByteArray() );
+            }
+        }
+        return is;
+    }
+
+    /**
+     * Get the cos stream associated with this object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSStream getStream()
+    {
+        return stream;
+    }
+
+    /**
+     * This will get the length of the filtered/compressed stream.  This is readonly in the
+     * PD Model and will be managed by this class.
+     *
+     * @return The length of the filtered stream.
+     */
+    public int getLength()
+    {
+        return stream.getInt( "Length", 0 );
+    }
+
+    /**
+     * This will get the list of filters that are associated with this stream.  Or
+     * null if there are none.
+     * @return A list of all encoding filters to apply to this stream.
+     */
+    public List getFilters()
+    {
+        List retval = null;
+        COSBase filters = stream.getFilters();
+        if( filters instanceof COSName )
+        {
+            COSName name = (COSName)filters;
+            retval = new COSArrayList( name.getName(), name, stream, COSName.FILTER );
+        }
+        else if( filters instanceof COSArray )
+        {
+            retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the filters that are part of this stream.
+     *
+     * @param filters The filters that are part of this stream.
+     */
+    public void setFilters( List filters )
+    {
+        COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters );
+        stream.setItem( COSName.FILTER, obj );
+    }
+
+    /**
+     * Get the list of decode parameters.  Each entry in the list will refer to
+     * an entry in the filters list.
+     *
+     * @return The list of decode parameters.
+     *
+     * @throws IOException if there is an error retrieving the parameters.
+     */
+    public List getDecodeParms() throws IOException
+    {
+        List retval = null;
+
+        COSBase dp = stream.getDictionaryObject( COSName.DECODE_PARMS );
+        if( dp == null )
+        {
+            //See PDF Ref 1.5 implementation note 7, the DP is sometimes used instead.
+            dp = stream.getDictionaryObject( COSName.DP );
+        }
+        if( dp instanceof COSDictionary )
+        {
+            Map map = COSDictionaryMap.convertBasicTypesToMap( (COSDictionary)dp );
+            retval = new COSArrayList(map, dp, stream, COSName.DECODE_PARMS );
+        }
+        else if( dp instanceof COSArray )
+        {
+            COSArray array = (COSArray)dp;
+            List actuals = new ArrayList();
+            for( int i=0; i<array.size(); i++ )
+            {
+                actuals.add(
+                    COSDictionaryMap.convertBasicTypesToMap(
+                        (COSDictionary)array.getObject( i ) ) );
+            }
+            retval = new COSArrayList(actuals, array);
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the list of decode parameterss.
+     *
+     * @param decodeParams The list of decode parameterss.
+     */
+    public void setDecodeParms( List decodeParams )
+    {
+        stream.setItem(
+            COSName.DECODE_PARMS, COSArrayList.converterToCOSArray( decodeParams ) );
+    }
+
+    /**
+     * This will get the file specification for this stream.  This is only
+     * required for external files.
+     *
+     * @return The file specification.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public PDFileSpecification getFile() throws IOException
+    {
+        COSBase f = stream.getDictionaryObject( COSName.F );
+        PDFileSpecification retval = PDFileSpecification.createFS( f );
+        return retval;
+    }
+
+    /**
+     * Set the file specification.
+     * @param f The file specification.
+     */
+    public void setFile( PDFileSpecification f )
+    {
+        stream.setItem( COSName.F, f );
+    }
+
+    /**
+     * This will get the list of filters that are associated with this stream.  Or
+     * null if there are none.
+     * @return A list of all encoding filters to apply to this stream.
+     */
+    public List getFileFilters()
+    {
+        List retval = null;
+        COSBase filters = stream.getDictionaryObject( COSName.F_FILTER );
+        if( filters instanceof COSName )
+        {
+            COSName name = (COSName)filters;
+            retval = new COSArrayList( name.getName(), name, stream, COSName.F_FILTER );
+        }
+        else if( filters instanceof COSArray )
+        {
+            retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the filters that are part of this stream.
+     *
+     * @param filters The filters that are part of this stream.
+     */
+    public void setFileFilters( List filters )
+    {
+        COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters );
+        stream.setItem( COSName.F_FILTER, obj );
+    }
+
+    /**
+     * Get the list of decode parameters.  Each entry in the list will refer to
+     * an entry in the filters list.
+     *
+     * @return The list of decode parameters.
+     *
+     * @throws IOException if there is an error retrieving the parameters.
+     */
+    public List getFileDecodeParams() throws IOException
+    {
+        List retval = null;
+
+        COSBase dp = stream.getDictionaryObject( COSName.F_DECODE_PARMS );
+        if( dp instanceof COSDictionary )
+        {
+            Map map = COSDictionaryMap.convertBasicTypesToMap( (COSDictionary)dp );
+            retval = new COSArrayList(map, dp, stream, COSName.F_DECODE_PARMS );
+        }
+        else if( dp instanceof COSArray )
+        {
+            COSArray array = (COSArray)dp;
+            List actuals = new ArrayList();
+            for( int i=0; i<array.size(); i++ )
+            {
+                actuals.add(
+                    COSDictionaryMap.convertBasicTypesToMap(
+                        (COSDictionary)array.getObject( i ) ) );
+            }
+            retval = new COSArrayList(actuals, array);
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the list of decode params.
+     *
+     * @param decodeParams The list of decode params.
+     */
+    public void setFileDecodeParams( List decodeParams )
+    {
+        stream.setItem(
+            "FDecodeParams", COSArrayList.converterToCOSArray( decodeParams ) );
+    }
+
+    /**
+     * This will copy the stream into a byte array.
+     *
+     * @return The byte array of the filteredStream
+     * @throws IOException When getFilteredStream did not work
+     */
+    public byte[] getByteArray() throws IOException
+    {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        byte[] buf = new byte[1024];
+        InputStream is = null;
+        try
+        {
+            is = createInputStream();
+            int amountRead = -1;
+            while( (amountRead = is.read( buf )) != -1)
+            {
+                output.write( buf, 0, amountRead );
+            }
+        }
+        finally
+        {
+            if( is != null )
+            {
+                is.close();
+            }
+        }
+        return output.toByteArray();
+    }
+
+    /**
+     * A convenience method to get this stream as a string.  Uses
+     * the default system encoding.
+     *
+     * @return a String representation of this (input) stream.
+     *
+     * @throws IOException if there is an error while converting the stream
+     *                     to a string.
+     */
+    public String getInputStreamAsString() throws IOException
+    {
+        byte[] bStream = getByteArray();
+        return new String(bStream, "ISO-8859-1");
+    }
+
+    /**
+     * Get the metadata that is part of the document catalog.  This will
+     * return null if there is no meta data for this object.
+     *
+     * @return The metadata for this object.
+     */
+    public PDMetadata getMetadata()
+    {
+        PDMetadata retval = null;
+        COSStream mdStream = (COSStream)stream.getDictionaryObject( COSName.METADATA );
+        if( mdStream != null )
+        {
+            retval = new PDMetadata( mdStream );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the metadata for this object.  This can be null.
+     *
+     * @param meta The meta data for this object.
+     */
+    public void setMetadata( PDMetadata meta )
+    {
+        stream.setItem( COSName.METADATA, meta );
+    }
+
+    /**
+     * Get the decoded stream length.
+     *
+     * @since Apache PDFBox 1.1.0
+     * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-636">PDFBOX-636</a>
+     * @return the decoded stream length
+     */
+    public int getDecodedStreamLength()
+    {
+        return this.stream.getInt(COSName.DL);
+    }
+
+    /**
+     * Set the decoded stream length.
+     *
+     * @since Apache PDFBox 1.1.0
+     * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-636">PDFBOX-636</a>
+     * @param decodedStreamLength the decoded stream length
+     */
+    public void setDecodedStreamLength(int decodedStreamLength)
+    {
+        this.stream.setInt(COSName.DL, decodedStreamLength);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDTextStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDTextStream.java
new file mode 100644 (file)
index 0000000..e648367
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.common;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * A PDTextStream class is used when the PDF specification supports either
+ * a string or a stream for the value of an object.  This is usually when
+ * a value could be large or small, for example a JavaScript method.  This
+ * class will help abstract that and give a single unified interface to
+ * those types of fields.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDTextStream implements COSObjectable
+{
+    private COSString string;
+    private COSStream stream;
+
+    /**
+     * Constructor.
+     *
+     * @param str The string parameter.
+     */
+    public PDTextStream( COSString str )
+    {
+        string = str;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param str The string parameter.
+     */
+    public PDTextStream( String str )
+    {
+        string = new COSString( str );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param str The stream parameter.
+     */
+    public PDTextStream( COSStream str )
+    {
+        stream = str;
+    }
+
+    /**
+     * This will create the text stream object.  base must either be a string
+     * or a stream.
+     *
+     * @param base The COS text stream object.
+     *
+     * @return A PDTextStream that wraps the base object.
+     */
+    public static PDTextStream createTextStream( COSBase base )
+    {
+        PDTextStream retval = null;
+        if( base instanceof COSString )
+        {
+            retval = new PDTextStream( (COSString) base );
+        }
+        else if( base instanceof COSStream )
+        {
+            retval = new PDTextStream( (COSStream)base );
+        }
+        return retval;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        COSBase retval = null;
+        if( string == null )
+        {
+            retval = stream;
+        }
+        else
+        {
+            retval = string;
+        }
+        return retval;
+    }
+
+    /**
+     * This will get this value as a string.  If this is a stream then it
+     * will load the entire stream into memory, so you should only do this when
+     * the stream is a manageable size.
+     *
+     * @return This value as a string.
+     *
+     * @throws IOException If an IO error occurs while accessing the stream.
+     */
+    public String getAsString() throws IOException
+    {
+        String retval = null;
+        if( string != null )
+        {
+            retval = string.getString();
+        }
+        else
+        {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            byte[] buffer = new byte[ 1024 ];
+            int amountRead = -1;
+            InputStream is = stream.getUnfilteredStream();
+            while( (amountRead = is.read( buffer ) ) != -1 )
+            {
+                out.write( buffer, 0, amountRead );
+            }
+            retval = new String( out.toByteArray(), "ISO-8859-1" );
+        }
+        return retval;
+    }
+
+    /**
+     * This is the preferred way of getting data with this class as it uses
+     * a stream object.
+     *
+     * @return The stream object.
+     *
+     * @throws IOException If an IO error occurs while accessing the stream.
+     */
+    public InputStream getAsStream() throws IOException
+    {
+        InputStream retval = null;
+        if( string != null )
+        {
+            retval = new ByteArrayInputStream( string.getBytes() );
+        }
+        else
+        {
+            retval = stream.getUnfilteredStream();
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDTypedDictionaryWrapper.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/PDTypedDictionaryWrapper.java
new file mode 100644 (file)
index 0000000..edf1e0f
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.pdfbox.pdmodel.common;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A wrapper for a COS dictionary including Type information.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public class PDTypedDictionaryWrapper extends PDDictionaryWrapper
+{
+
+    /**
+     * Creates a new instance with a given type.
+     * 
+     * @param type the type (Type)
+     */
+    public PDTypedDictionaryWrapper(String type)
+    {
+        super();
+        this.getCOSDictionary().setName(COSName.TYPE, type);
+    }
+
+    /**
+     * Creates a new instance with a given COS dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDTypedDictionaryWrapper(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Gets the type.
+     * 
+     * @return the type
+     */
+    public String getType()
+    {
+        return this.getCOSDictionary().getNameAsString(COSName.TYPE);
+    }
+
+    // There is no setType(String) method because changing the Type would most
+    // probably also change the type of PD object.
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDComplexFileSpecification.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDComplexFileSpecification.java
new file mode 100644 (file)
index 0000000..2534176
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * 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.pdfbox.pdmodel.common.filespecification;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+
+/**
+ * This represents a file specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDComplexFileSpecification extends PDFileSpecification
+{
+    private COSDictionary fs;
+
+    /**
+     * Default Constructor.
+     */
+    public PDComplexFileSpecification()
+    {
+        fs = new COSDictionary();
+        fs.setName( "Type", "Filespec" );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict The dictionary that fulfils this file specification.
+     */
+    public PDComplexFileSpecification( COSDictionary dict )
+    {
+        fs = dict;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return fs;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return fs;
+    }
+
+    /**
+     * This will get the file name.
+     *
+     * @return The file name.
+     */
+    public String getFile()
+    {
+        return fs.getString( "F" );
+    }
+
+    /**
+     * This will set the file name.
+     *
+     * @param file The name of the file.
+     */
+    public void setFile( String file )
+    {
+        fs.setString( "F", file );
+    }
+
+    /**
+     * This will get the name representing a Dos file.
+     *
+     * @return The file name.
+     */
+    public String getFileDos()
+    {
+        return fs.getString( "DOS" );
+    }
+
+    /**
+     * This will set name representing a dos file.
+     *
+     * @param file The name of the file.
+     */
+    public void setFileDos( String file )
+    {
+        fs.setString( "DOS", file );
+    }
+
+    /**
+     * This will get the name representing a Mac file.
+     *
+     * @return The file name.
+     */
+    public String getFileMac()
+    {
+        return fs.getString( "Mac" );
+    }
+
+    /**
+     * This will set name representing a Mac file.
+     *
+     * @param file The name of the file.
+     */
+    public void setFileMac( String file )
+    {
+        fs.setString( "Mac", file );
+    }
+
+    /**
+     * This will get the name representing a Unix file.
+     *
+     * @return The file name.
+     */
+    public String getFileUnix()
+    {
+        return fs.getString( "Unix" );
+    }
+
+    /**
+     * This will set name representing a Unix file.
+     *
+     * @param file The name of the file.
+     */
+    public void setFileUnix( String file )
+    {
+        fs.setString( "Unix", file );
+    }
+
+    /**
+     * Tell if the underlying file is volatile and should not be cached by the
+     * reader application.  Default: false
+     *
+     * @param fileIsVolatile The new value for the volatility of the file.
+     */
+    public void setVolatile( boolean fileIsVolatile )
+    {
+        fs.setBoolean( "V", fileIsVolatile );
+    }
+
+    /**
+     * Get if the file is volatile.  Default: false
+     *
+     * @return True if the file is volatile attribute is set.
+     */
+    public boolean isVolatile()
+    {
+        return fs.getBoolean( "V", false );
+    }
+
+    /**
+     * Get the embedded file.
+     *
+     * @return The embedded file for this file spec.
+     */
+    public PDEmbeddedFile getEmbeddedFile()
+    {
+        PDEmbeddedFile file = null;
+        COSStream stream = (COSStream)fs.getObjectFromPath( "EF/F" );
+        if( stream != null )
+        {
+            file = new PDEmbeddedFile( stream );
+        }
+        return file;
+    }
+
+    /**
+     * Set the embedded file for this spec.
+     *
+     * @param file The file to be embedded.
+     */
+    public void setEmbeddedFile( PDEmbeddedFile file )
+    {
+        COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "EF" );
+        if( ef == null && file != null )
+        {
+            ef = new COSDictionary();
+            fs.setItem( "EF", ef );
+        }
+        if( ef != null )
+        {
+            ef.setItem( "F", file );
+        }
+    }
+
+    /**
+     * Get the embedded dos file.
+     *
+     * @return The embedded file for this file spec.
+     */
+    public PDEmbeddedFile getEmbeddedFileDos()
+    {
+        PDEmbeddedFile file = null;
+        COSStream stream = (COSStream)fs.getObjectFromPath( "EF/DOS" );
+        if( stream != null )
+        {
+            file = new PDEmbeddedFile( stream );
+        }
+        return file;
+    }
+
+    /**
+     * Set the embedded dos file for this spec.
+     *
+     * @param file The dos file to be embedded.
+     */
+    public void setEmbeddedFileDos( PDEmbeddedFile file )
+    {
+        COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "DOS" );
+        if( ef == null && file != null )
+        {
+            ef = new COSDictionary();
+            fs.setItem( "EF", ef );
+        }
+        if( ef != null )
+        {
+            ef.setItem( "DOS", file );
+        }
+    }
+
+    /**
+     * Get the embedded Mac file.
+     *
+     * @return The embedded file for this file spec.
+     */
+    public PDEmbeddedFile getEmbeddedFileMac()
+    {
+        PDEmbeddedFile file = null;
+        COSStream stream = (COSStream)fs.getObjectFromPath( "EF/Mac" );
+        if( stream != null )
+        {
+            file = new PDEmbeddedFile( stream );
+        }
+        return file;
+    }
+
+    /**
+     * Set the embedded Mac file for this spec.
+     *
+     * @param file The Mac file to be embedded.
+     */
+    public void setEmbeddedFileMac( PDEmbeddedFile file )
+    {
+        COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "Mac" );
+        if( ef == null && file != null )
+        {
+            ef = new COSDictionary();
+            fs.setItem( "EF", ef );
+        }
+        if( ef != null )
+        {
+            ef.setItem( "Mac", file );
+        }
+    }
+
+    /**
+     * Get the embedded Unix file.
+     *
+     * @return The embedded file for this file spec.
+     */
+    public PDEmbeddedFile getEmbeddedFileUnix()
+    {
+        PDEmbeddedFile file = null;
+        COSStream stream = (COSStream)fs.getObjectFromPath( "EF/Unix" );
+        if( stream != null )
+        {
+            file = new PDEmbeddedFile( stream );
+        }
+        return file;
+    }
+
+    /**
+     * Set the embedded Unix file for this spec.
+     *
+     * @param file The Unix file to be embedded.
+     */
+    public void setEmbeddedFileUnix( PDEmbeddedFile file )
+    {
+        COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "Unix" );
+        if( ef == null && file != null )
+        {
+            ef = new COSDictionary();
+            fs.setItem( "EF", ef );
+        }
+        if( ef != null )
+        {
+            ef.setItem( "Unix", file );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDEmbeddedFile.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDEmbeddedFile.java
new file mode 100644 (file)
index 0000000..e4fa739
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * 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.pdfbox.pdmodel.common.filespecification;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This represents an embedded file in a file specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDEmbeddedFile extends PDStream
+{
+
+    /**
+     * Constructor.
+     *
+     * @param document {@inheritDoc}
+     */
+    public PDEmbeddedFile( PDDocument document )
+    {
+        super( document );
+        getStream().setName( "Type", "EmbeddedFile" );
+
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param str The stream parameter.
+     */
+    public PDEmbeddedFile( COSStream str )
+    {
+        super( str );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param doc {@inheritDoc}
+     * @param str {@inheritDoc}
+     *
+     * @throws IOException {@inheritDoc}
+     */
+    public PDEmbeddedFile( PDDocument doc, InputStream str  ) throws IOException
+    {
+        super( doc, str );
+        getStream().setName( "Type", "EmbeddedFile" );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param doc {@inheritDoc}
+     * @param str {@inheritDoc}
+     * @param filtered {@inheritDoc}
+     *
+     * @throws IOException {@inheritDoc}
+     */
+    public PDEmbeddedFile( PDDocument doc, InputStream str, boolean filtered ) throws IOException
+    {
+        super( doc, str, filtered );
+        getStream().setName( "Type", "EmbeddedFile" );
+    }
+
+    /**
+     * Set the subtype for this embedded file.  This should be a mime type value.  Optional.
+     *
+     * @param mimeType The mimeType for the file.
+     */
+    public void setSubtype( String mimeType )
+    {
+        getStream().setName( "Subtype", mimeType );
+    }
+
+    /**
+     * Get the subtype(mimetype) for the embedded file.
+     *
+     * @return The type of embedded file.
+     */
+    public String getSubtype()
+    {
+        return getStream().getNameAsString( "Subtype" );
+    }
+
+    /**
+     * Get the size of the embedded file.
+     *
+     * @return The size of the embedded file.
+     */
+    public int getSize()
+    {
+        return getStream().getEmbeddedInt( "Params", "Size" );
+    }
+
+    /**
+     * Set the size of the embedded file.
+     *
+     * @param size The size of the embedded file.
+     */
+    public void setSize( int size )
+    {
+        getStream().setEmbeddedInt( "Params", "Size", size );
+    }
+
+    /**
+     * Get the creation date of the embedded file.
+     *
+     * @return The Creation date.
+     * @throws IOException If there is an error while constructing the date.
+     */
+    public Calendar getCreationDate() throws IOException
+    {
+        return getStream().getEmbeddedDate( "Params", "CreationDate" );
+    }
+
+    /**
+     * Set the creation date.
+     *
+     * @param creation The new creation date.
+     */
+    public void setCreationDate( Calendar creation )
+    {
+        getStream().setEmbeddedDate( "Params", "CreationDate", creation );
+    }
+
+    /**
+     * Get the mod date of the embedded file.
+     *
+     * @return The mod date.
+     * @throws IOException If there is an error while constructing the date.
+     */
+    public Calendar getModDate() throws IOException
+    {
+        return getStream().getEmbeddedDate( "Params", "ModDate" );
+    }
+
+    /**
+     * Set the mod date.
+     *
+     * @param mod The new creation mod.
+     */
+    public void setModDate( Calendar mod )
+    {
+        getStream().setEmbeddedDate( "Params", "ModDate", mod );
+    }
+
+    /**
+     * Get the check sum of the embedded file.
+     *
+     * @return The check sum of the file.
+     */
+    public String getCheckSum()
+    {
+        return getStream().getEmbeddedString( "Params", "CheckSum" );
+    }
+
+    /**
+     * Set the check sum.
+     *
+     * @param checksum The checksum of the file.
+     */
+    public void setCheckSum( String checksum )
+    {
+        getStream().setEmbeddedString( "Params", "CheckSum", checksum );
+    }
+
+    /**
+     * Get the mac subtype.
+     *
+     * @return The mac subtype.
+     */
+    public String getMacSubtype()
+    {
+        String retval = null;
+        COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+        if( params != null )
+        {
+            retval = params.getEmbeddedString( "Mac", "Subtype" );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the mac subtype.
+     *
+     * @param macSubtype The mac subtype.
+     */
+    public void setMacSubtype( String macSubtype )
+    {
+        COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+        if( params == null && macSubtype != null )
+        {
+            params = new COSDictionary();
+            getStream().setItem( "Params", params );
+        }
+        if( params != null )
+        {
+            params.setEmbeddedString( "Mac", "Subtype", macSubtype );
+        }
+    }
+
+    /**
+     * Get the mac Creator.
+     *
+     * @return The mac Creator.
+     */
+    public String getMacCreator()
+    {
+        String retval = null;
+        COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+        if( params != null )
+        {
+            retval = params.getEmbeddedString( "Mac", "Creator" );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the mac Creator.
+     *
+     * @param macCreator The mac Creator.
+     */
+    public void setMacCreator( String macCreator )
+    {
+        COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+        if( params == null && macCreator != null )
+        {
+            params = new COSDictionary();
+            getStream().setItem( "Params", params );
+        }
+        if( params != null )
+        {
+            params.setEmbeddedString( "Mac", "Creator", macCreator );
+        }
+    }
+
+    /**
+     * Get the mac ResFork.
+     *
+     * @return The mac ResFork.
+     */
+    public String getMacResFork()
+    {
+        String retval = null;
+        COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+        if( params != null )
+        {
+            retval = params.getEmbeddedString( "Mac", "ResFork" );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the mac ResFork.
+     *
+     * @param macResFork The mac ResFork.
+     */
+    public void setMacResFork( String macResFork )
+    {
+        COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+        if( params == null && macResFork != null )
+        {
+            params = new COSDictionary();
+            getStream().setItem( "Params", params );
+        }
+        if( params != null )
+        {
+            params.setEmbeddedString( "Mac", "ResFork", macResFork);
+        }
+    }
+
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDFileSpecification.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDFileSpecification.java
new file mode 100644 (file)
index 0000000..9503d8a
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.pdfbox.pdmodel.common.filespecification;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents a file specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public abstract class PDFileSpecification implements COSObjectable
+{
+
+    /**
+     * A file specfication can either be a COSString or a COSDictionary.  This
+     * will create the file specification either way.
+     *
+     * @param base The cos object that describes the fs.
+     *
+     * @return The file specification for the COSBase object.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public static PDFileSpecification createFS( COSBase base ) throws IOException
+    {
+        PDFileSpecification retval = null;
+        if( base == null )
+        {
+            //then simply return null
+        }
+        else if( base instanceof COSString )
+        {
+            retval = new PDSimpleFileSpecification( (COSString)base );
+        }
+        else if( base instanceof COSDictionary )
+        {
+            retval = new PDComplexFileSpecification( (COSDictionary)base );
+        }
+        else
+        {
+            throw new IOException( "Error: Unknown file specification " + base );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the file name.
+     *
+     * @return The file name.
+     */
+    public abstract String getFile();
+
+    /**
+     * This will set the file name.
+     *
+     * @param file The name of the file.
+     */
+    public abstract void setFile( String file );
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDSimpleFileSpecification.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/PDSimpleFileSpecification.java
new file mode 100644 (file)
index 0000000..ad27a68
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.pdfbox.pdmodel.common.filespecification;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * A file specification that is just a string.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDSimpleFileSpecification extends PDFileSpecification
+{
+    private COSString file;
+
+    /**
+     * Constructor.
+     *
+     */
+    public PDSimpleFileSpecification()
+    {
+        file = new COSString( "" );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fileName The file that this spec represents.
+     */
+    public PDSimpleFileSpecification( COSString fileName )
+    {
+        file = fileName;
+    }
+
+    /**
+     * This will get the file name.
+     *
+     * @return The file name.
+     */
+    public String getFile()
+    {
+    return file.getString();
+    }
+
+    /**
+     * This will set the file name.
+     *
+     * @param fileName The name of the file.
+     */
+    public void setFile( String fileName )
+    {
+    file = new COSString( fileName );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return file;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/filespecification/package.html
new file mode 100644 (file)
index 0000000..0b42edc
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The file specification package defines classes that are used for the PDF File Specification logic.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunction.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunction.java
new file mode 100644 (file)
index 0000000..71b11ce
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * 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.pdfbox.pdmodel.common.function;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This class represents a function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public abstract class PDFunction implements COSObjectable
+{
+
+    private PDStream functionStream = null;
+    private COSDictionary functionDictionary = null;
+    private COSArray domain = null;
+    private COSArray range = null;
+
+    /**
+     * Constructor.
+     *
+     * @param functionStream The function stream.
+     */
+    public PDFunction( COSBase function )
+    {
+        if (function instanceof COSStream)
+        {
+            functionStream = new PDStream( (COSStream)function );
+            functionStream.getStream().setName( COSName.TYPE, "Function" );
+        }
+        else if (function instanceof COSDictionary)
+        {
+            functionDictionary = (COSDictionary)function;
+        }
+    }
+
+    /**
+     * Returns the function type.
+     * 
+     * Possible values are:
+     * 
+     * 0 - Sampled function
+     * 2 - Exponential interpolation function
+     * 3 - Stitching function
+     * 4 - PostScript calculator function
+     * 
+     * @return the function type.
+     */
+    public abstract int getFunctionType();
+    
+    /**
+     * Returns the COSObject.
+     *
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        if (functionStream != null)
+        {
+            return functionStream.getCOSObject();
+        }
+        else 
+        {
+            return functionDictionary;
+        }
+    }
+
+    /**
+     * Returns the stream.
+     * @return The stream for this object.
+     */
+    public COSDictionary getDictionary()
+    {
+        if (functionStream != null)
+        {
+            return functionStream.getStream();
+        }
+        else 
+        {
+            return functionDictionary;
+        }
+    }
+
+    /**
+     * Returns the underlying PDStream.
+     * @return The stream.
+     */
+    protected PDStream getPDStream()
+    {
+        return functionStream;
+    }
+    /**
+     * Create the correct PD Model function based on the COS base function.
+     *
+     * @param function The COS function dictionary.
+     *
+     * @return The PDModel Function object.
+     *
+     * @throws IOException If we are unable to create the PDFunction object.
+     */
+    public static PDFunction create( COSBase function ) throws IOException
+    {
+        PDFunction retval = null;
+        if( function instanceof COSObject )
+        {
+            function = ((COSObject)function).getCOSObject();
+        }
+        COSDictionary functionDictionary = (COSDictionary)function;
+        int functionType =  functionDictionary.getInt( COSName.FUNCTION_TYPE );
+        if( functionType == 0 )
+        {
+            retval = new PDFunctionType0(functionDictionary);
+        }
+        else if( functionType == 2 )
+        {
+            retval = new PDFunctionType2(functionDictionary);
+        }
+        else if( functionType == 3 )
+        {
+            retval = new PDFunctionType3(functionDictionary);
+        }
+        else if( functionType == 4 )
+        {
+            retval = new PDFunctionType4(functionDictionary);
+        }
+        else
+        {
+            throw new IOException( "Error: Unknown function type " + functionType );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the number of output parameters that
+     * have a range specified.  A range for output parameters
+     * is optional so this may return zero for a function
+     * that does have output parameters, this will simply return the
+     * number that have the rnage specified.
+     *
+     * @return The number of input parameters that have a range
+     * specified.
+     */
+    public int getNumberOfOutputParameters()
+    {
+        COSArray rangeValues = getRangeValues();
+        return rangeValues.size() / 2;
+    }
+
+    /**
+     * This will get the range for a certain output parameters.  This is will never
+     * return null.  If it is not present then the range 0 to 0 will
+     * be returned.
+     *
+     * @param n The output parameter number to get the range for.
+     *
+     * @return The range for this component.
+     */
+    public PDRange getRangeForOutput(int n)
+    {
+        COSArray rangeValues = getRangeValues();
+        return new PDRange( rangeValues, n );
+    }
+
+    /**
+     * This will set the range values.
+     *
+     * @param range The new range values.
+     */
+    public void setRangeValues(COSArray rangeValues)
+    {
+        range = rangeValues;
+        getDictionary().setItem(COSName.RANGE, rangeValues);
+    }
+
+    /**
+     * This will get the number of input parameters that
+     * have a domain specified.
+     *
+     * @return The number of input parameters that have a domain
+     * specified.
+     */
+    public int getNumberOfInputParameters()
+    {
+        COSArray array = getDomainValues();
+        return array.size() / 2;
+    }
+
+    /**
+     * This will get the range for a certain input parameter.  This is will never
+     * return null.  If it is not present then the range 0 to 0 will
+     * be returned.
+     *
+     * @param n The parameter number to get the domain for.
+     *
+     * @return The domain range for this component.
+     */
+    public PDRange getDomainForInput(int n) 
+    {
+        COSArray domainValues = getDomainValues();
+        return new PDRange( domainValues, n );
+    }
+
+    /**
+     * This will set the domain values.
+     *
+     * @param range The new domain values.
+     */
+    public void setDomainValues(COSArray domainValues)
+    {
+        domain = domainValues;
+        getDictionary().setItem(COSName.DOMAIN, domainValues);
+    }
+
+    /**
+     * Evaluates the function at the given input.
+     * ReturnValue = f(input)
+     *
+     * @param input The array of input values for the function. In many cases will be an array of a single value, but not always.
+     * @return The of outputs the function returns based on those inputs. In many cases will be an array of a single value, but not always.
+     */
+    public abstract COSArray eval(COSArray input) throws IOException;
+    
+    /**
+     * Returns all ranges for the output values as COSArray .
+     * Required for type 0 and type 4 functions
+     * @return the ranges array. 
+     */
+    protected COSArray getRangeValues() 
+    {
+        if (range == null) 
+        {
+            range = (COSArray)getDictionary().getDictionaryObject( COSName.RANGE );
+        }
+        return range;
+    }
+
+    /**
+     * Returns all domains for the input values as COSArray.
+     * Required for all function types.
+     * @return the domains array. 
+     */
+    private COSArray getDomainValues()
+    {
+        if (domain == null) 
+        {
+            domain = (COSArray)getDictionary().getDictionaryObject( COSName.DOMAIN );
+        }
+        return domain;
+    }
+
+    /**
+     * Clip the given input values to the ranges.
+     * 
+     * @param inputArray the input values
+     * @return the clipped values
+     */
+    protected COSArray clipToRange(COSArray inputArray) 
+    {
+        COSArray rangesArray = getRangeValues();
+        COSArray result = null;
+        if (rangesArray != null) 
+        {
+            float[] inputValues = inputArray.toFloatArray();
+            float[] rangeValues = rangesArray.toFloatArray();
+            result = new COSArray();
+            int numberOfRanges = rangeValues.length/2;
+            for (int i=0; i<numberOfRanges; i++)
+                result.add(new COSFloat( clipToRange(inputValues[i], rangeValues[2*i], rangeValues[2*i+1])));
+        }
+        else
+        {
+            result = inputArray;
+        }
+        return result;
+    }
+
+    /**
+     * Clip the given input value to the given range.
+     * 
+     * @param x the input value
+     * @param rangeMin the min value of the range
+     * @param rangeMax the max value of the range
+
+     * @return the clipped value
+     */
+    protected float clipToRange(float x, float rangeMin, float rangeMax) 
+    {
+        return Math.min(Math.max(x, rangeMin), rangeMax);
+    }
+
+    /**
+     * For a given value of x, interpolate calculates the y value 
+     * on the line defined by the two points (xRangeMin , xRangeMax ) 
+     * and (yRangeMin , yRangeMax ).
+     * 
+     * @param x
+     * @param xRangeMin
+     * @param xRangeMax
+     * @param yRangeMin
+     * @param yRangeMax
+     * @return the interpolated y value
+     */
+    protected float interpolate(float x, float xRangeMin, float xRangeMax, float yRangeMin, float yRangeMax) 
+    {
+        return yRangeMin + ((x - xRangeMin) * (yRangeMax - yRangeMin)/(xRangeMax - xRangeMin));
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType0.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType0.java
new file mode 100644 (file)
index 0000000..15883bc
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * 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.pdfbox.pdmodel.common.function;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+/**
+ * This class represents a type 0 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType0 extends PDFunction
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDFunctionType0.class);
+
+    /**
+     * An array of 2 × m numbers specifying the linear mapping of input values 
+     * into the domain of the function’s sample table. 
+     * Default value: [ 0 (Size0 − 1) 0 (Size1 − 1) … ].
+     */
+    private COSArray encode = null;
+    /**
+     * An array of 2 × n numbers specifying the linear mapping of sample values 
+     * into the range appropriate for the function’s output values. 
+     * Default value: same as the value of Range
+     */
+    private COSArray decode = null;
+    /**
+     * An array of m positive integers specifying the number of samples in each 
+     * input dimension of the sample table.
+     */
+    private COSArray size = null;
+    /**
+     * The samples of the function.
+     */
+    private int[][] samples = null;
+    
+    /**
+     * Constructor.
+     *
+     * @param functionStream The function .
+     */
+    public PDFunctionType0(COSBase function)
+    {
+        super( function );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getFunctionType()
+    {
+        return 0;
+    }
+    /**
+     * The "Size" entry, which is the number of samples in
+     * each input dimension of the sample table.
+     *
+     * @return A List of java.lang.Integer objects.
+     */
+    public COSArray getSize()
+    {
+        if (size == null)
+        {
+            size = (COSArray)getDictionary().getDictionaryObject( COSName.SIZE );
+        }
+        return size;
+    }
+
+    /**
+     * Get all sample values of this function.
+     * 
+     * @return an array with all samples.
+     */
+    public int[][] getSamples()
+    {
+        if (samples == null)
+        {
+            int arraySize = 1;
+            int numberOfInputValues = getNumberOfInputParameters();
+            int numberOfOutputValues = getNumberOfOutputParameters();
+            COSArray sizes = getSize();
+            for (int i=0;i<numberOfInputValues;i++)
+            {
+                arraySize *= sizes.getInt(i);
+            }
+            samples = new int[arraySize][getNumberOfOutputParameters()];
+            int bitsPerSample = getBitsPerSample();
+            int index = 0;
+            int arrayIndex = 0;
+            try {
+                byte[] samplesArray = getPDStream().getByteArray();
+                for (int i=0;i<numberOfInputValues;i++) 
+                {
+                    int sizeInputValues = sizes.getInt(i);
+                    for (int j=0;j<sizeInputValues;j++)
+                    {
+                        int bitsLeft = 0;
+                        int bitsToRead = bitsPerSample;
+                        int currentValue = 0;
+                        for (int k=0;k<numberOfOutputValues;k++)
+                        {
+                            if (bitsLeft == 0)
+                            {
+                                currentValue = (samplesArray[arrayIndex++]+256)%256;
+                                bitsLeft = 8;
+                            }
+                            int value = 0;
+                            while (bitsToRead > 0)
+                            {
+                                int bits = Math.min(bitsToRead, bitsLeft);
+                                value = value << bits;
+                                int valueToAdd = currentValue >> (8 - bits); 
+                                value |= valueToAdd;
+                                bitsToRead -= bits;
+                                bitsLeft -= bits;
+                                if (bitsLeft == 0 && bitsToRead > 0)
+                                {
+                                    currentValue = (samplesArray[arrayIndex++]+256)%256;
+                                    bitsLeft = 8;
+                                }
+                            }
+                            samples[index][k] = value;
+                            bitsToRead = bitsPerSample;
+                        }
+                        index++;
+                    }
+                }
+            }
+            catch (IOException exception)
+            {
+                log.error("IOException while reading the sample values of this function.");
+            }
+        }
+        return samples;
+    }
+    /**
+     * Get the number of bits that the output value will take up.  
+     * 
+     * Valid values are 1,2,4,8,12,16,24,32.
+     *
+     * @return Number of bits for each output value.
+     */
+    public int getBitsPerSample()
+    {
+        return getDictionary().getInt( COSName.BITS_PER_SAMPLE );
+    }
+
+    /**
+     * Set the number of bits that the output value will take up.  Valid values
+     * are 1,2,4,8,12,16,24,32.
+     *
+     * @param bps The number of bits for each output value.
+     */
+    public void setBitsPerSample( int bps )
+    {
+        getDictionary().setInt( COSName.BITS_PER_SAMPLE, bps );
+    }
+    
+    /**
+     * Returns all encode values as COSArray.
+     * 
+     * @return the encode array. 
+     */
+    private COSArray getEncodeValues() 
+    {
+        if (encode == null)
+        {
+            encode = (COSArray)getDictionary().getDictionaryObject( COSName.ENCODE );
+            // the default value is [0 (size[0]-1) 0 (size[1]-1) ...]
+            if (encode == null)
+            {
+                encode = new COSArray();
+                COSArray sizeValues = getSize();
+                int sizeValuesSize = sizeValues.size();
+                for (int i=0; i <sizeValuesSize; i++)
+                {
+                    encode.add( COSInteger.ZERO );
+                    encode.add( COSInteger.get( sizeValues.getInt(i) - 1) );
+                }
+            }
+        }
+        return encode;
+    }
+
+    /**
+     * Returns all decode values as COSArray.
+     * 
+     * @return the decode array. 
+     */
+    private COSArray getDecodeValues() 
+    {
+        if (decode == null)
+        {
+            decode = (COSArray)getDictionary().getDictionaryObject( COSName.DECODE );
+            // if decode is null, the default values are the range values
+            if (decode == null)
+            {
+                decode = getRangeValues();
+            }
+        }
+        return decode;
+    }
+
+    /**
+     * Get the encode for the input parameter.
+     *
+     * @param paramNum The function parameter number.
+     *
+     * @return The encode parameter range or null if none is set.
+     */
+    public PDRange getEncodeForParameter( int paramNum )
+    {
+        PDRange retval = null;
+        COSArray encodeValues = getEncodeValues();
+        if( encodeValues != null && encodeValues.size() >= paramNum*2+1 )
+        {
+            retval = new PDRange(encodeValues, paramNum );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the encode values.
+     *
+     * @param range The new encode values.
+     */
+    public void setEncodeValues(COSArray encodeValues)
+    {
+        encode = encodeValues;
+        getDictionary().setItem(COSName.ENCODE, encodeValues);
+    }
+
+    /**
+     * Get the decode for the input parameter.
+     *
+     * @param paramNum The function parameter number.
+     *
+     * @return The decode parameter range or null if none is set.
+     */
+    public PDRange getDecodeForParameter( int paramNum )
+    {
+        PDRange retval = null;
+        COSArray decodeValues = getDecodeValues();
+        if( decodeValues != null && decodeValues.size() >= paramNum*2+1 )
+        {
+            retval = new PDRange(decodeValues, paramNum );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the decode values.
+     *
+     * @param range The new decode values.
+     */
+    public void setDecodeValues(COSArray decodeValues)
+    {
+        decode = decodeValues;
+        getDictionary().setItem(COSName.DECODE, decodeValues);
+    }
+    
+    /**
+    * {@inheritDoc}
+    */
+    public COSArray eval(COSArray input) throws IOException
+    {
+        //This involves linear interpolation based on a set of sample points.
+        //Theoretically it's not that difficult ... see section 3.9.1 of the PDF Reference.
+        float[] inputValues = input.toFloatArray();
+        float[] sizeValues = getSize().toFloatArray();
+        int bitsPerSample = getBitsPerSample();
+        int numberOfInputValues = inputValues.length;
+        int numberOfOutputValues = getNumberOfOutputParameters();
+        int[] intInputValuesPrevious = new int[numberOfInputValues];
+        int[] intInputValuesNext = new int[numberOfInputValues];
+        for (int i=0; i<numberOfInputValues; i++) {
+            PDRange domain = getDomainForInput(i);
+            PDRange encode = getEncodeForParameter(i);
+            inputValues[i] = clipToRange(inputValues[i], domain.getMin(), domain.getMax());
+            inputValues[i] = interpolate(inputValues[i], domain.getMin(), domain.getMax(), encode.getMin(), encode.getMax());
+            inputValues[i] = clipToRange(inputValues[i], 0, sizeValues[i]-1);
+            intInputValuesPrevious[i] = (int)Math.floor(inputValues[i]);
+            intInputValuesNext[i] = (int)Math.ceil(inputValues[i]);
+        }
+        float[] outputValuesPrevious = null;
+        float[] outputValuesNext = null;
+        outputValuesPrevious = getSample(intInputValuesPrevious);
+        outputValuesNext = getSample(intInputValuesNext);
+        float[] outputValues = new float[numberOfOutputValues];
+        for (int i=0;i<numberOfOutputValues;i++) 
+        {
+            PDRange range = getRangeForOutput(i);
+            PDRange decode = getDecodeForParameter(i);
+            // TODO using only a linear interpolation. 
+            // See "Order" entry in table 3.36 of the PDF reference
+            outputValues[i] = (outputValuesPrevious[i] + outputValuesNext[i]) / 2;
+            outputValues[i] = interpolate(outputValues[i], 0, (float)Math.pow(2, bitsPerSample), decode.getMin(), decode.getMax());
+            outputValues[i] = clipToRange(outputValues[i], range.getMin(), range.getMax());
+        }
+
+        COSArray result = new COSArray();
+        result.setFloatArray(outputValues);
+        return result;
+    }
+    
+    /**
+     * Get the samples for the given input values.
+     * 
+     * @param inputValues an array containing the input values
+     * @return an array with the corresponding samples
+     */
+    private float[] getSample(int[] inputValues)
+    {
+        int[][] sampleValues = getSamples();
+        COSArray sizes = getSize();
+        int numberOfInputValues = getNumberOfInputParameters();
+        int index = 0;
+        int previousSize = 1;
+        for (int i=0;i<numberOfInputValues;i++)
+        {
+            index += inputValues[i];
+            previousSize *= sizes.getInt(i);
+        }
+        int numberOfOutputValues = getNumberOfOutputParameters();
+        float[] result = new float[numberOfOutputValues];
+        for (int i=0;i<numberOfOutputValues;i++)
+        {
+            result[i] = sampleValues[index][i];
+        }
+        return result;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType2.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType2.java
new file mode 100644 (file)
index 0000000..4c704ec
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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.pdfbox.pdmodel.common.function;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import java.io.IOException;
+import java.lang.Math;
+
+/**
+ * This class represents a type 2 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType2 extends PDFunction
+{
+    
+    /**
+     * The C0 values of the exponential function.
+     */
+    private COSArray C0;
+    /**
+     * The C1 values of the exponential function.
+     */
+    private COSArray C1;
+
+    /**
+     * Constructor.
+     *
+     * @param functionStream The function .
+     */
+    public PDFunctionType2(COSBase function)
+    {
+        super( function );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getFunctionType()
+    {
+        return 2;
+    }
+
+    /**
+    * {@inheritDoc}
+    */
+    public COSArray eval(COSArray input) throws IOException
+    {
+        //This function performs exponential interpolation.
+        //It uses only a single value as its input, but may produce a multi-valued output.
+        //See PDF Reference section 3.9.2.
+                
+        double inputValue = input.toFloatArray()[0];
+        double exponent = getN();
+        COSArray c0 = getC0();
+        COSArray c1 = getC1();
+        COSArray functionResult = new COSArray();
+        int c0Size = c0.size();
+        for (int j=0;j<c0Size;j++)
+        {
+            //y[j] = C0[j] + x^N*(C1[j] - C0[j])
+            float result = ((COSNumber)c0.get(j)).floatValue() + (float)Math.pow(inputValue,exponent)*(((COSNumber)c1.get(j)).floatValue() - ((COSNumber)c0.get(j)).floatValue());
+            functionResult.add( new COSFloat( result));
+        }
+        // clip to range if available
+        return clipToRange(functionResult);
+    }
+    
+    /**
+     * Returns the C0 values of the function, 0 if empty.
+     * @return a COSArray with the C0 values
+     */
+    public COSArray getC0()
+    {
+        if(C0 == null)
+        {
+            C0 = (COSArray)getDictionary().getDictionaryObject( COSName.C0 );
+            if ( C0 == null )
+            {
+                // C0 is optional, default = 0
+                C0 = new COSArray();
+                C0.add( new COSFloat( 0 ) );
+            }
+        }
+        return C0;
+    }
+    
+    /**
+     * Returns the C1 values of the function, 1 if empty.
+     * @return a COSArray with the C1 values
+     */
+    public COSArray getC1()
+    {
+        if(C1 == null)
+        {
+            C1 = (COSArray)getDictionary().getDictionaryObject( COSName.C1 );
+            if( C1 == null )
+            {
+                // C1 is optional, default = 1
+                C1 = new COSArray();
+                C1.add( new COSFloat( 1 ) );
+            }      
+        }            
+        return C1;
+    }
+    
+    /**
+     * Returns the exponent of the function.
+     * @return the float value of the exponent
+     */
+    public float getN()
+    {
+        return getDictionary().getFloat(COSName.N);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType3.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType3.java
new file mode 100644 (file)
index 0000000..2a9d7c8
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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.pdfbox.pdmodel.common.function;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+import java.io.IOException;
+
+/**
+ * This class represents a type 3 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType3 extends PDFunction
+{
+
+    private COSArray functions = null;
+    private COSArray encode = null;
+    private COSArray bounds = null;
+    
+    /**
+     * Constructor.
+     *
+     * @param functionStream The function .
+     */
+    public PDFunctionType3(COSBase function)
+    {
+        super( function );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getFunctionType()
+    {
+        return 3;
+    }
+    
+    /**
+    * {@inheritDoc}
+    */
+    public COSArray eval(COSArray input) throws IOException
+    {
+        //This function is known as a "stitching" function. Based on the input, it decides which child function to call.
+        //See PDF Reference section 3.9.3.
+        PDFunction function = null;
+        float x = ((COSNumber)input.get(0)).floatValue();
+        PDRange domain = getDomainForInput(1);
+        // clip input value to domain
+        x = clipToRange(x, domain.getMin(), domain.getMax());
+
+        float[] boundsValues = getBounds().toFloatArray();
+        int boundsSize = boundsValues.length;
+        if (boundsSize == 0 || x < boundsValues[0])
+        {
+            function = PDFunction.create(getFunctions().get(0));
+            PDRange encode = getEncodeForParameter(0);
+            if (boundsSize == 0)
+            {
+                x = interpolate(x, domain.getMin(), domain.getMax(), encode.getMin(), encode.getMax());
+            }
+            else
+            {
+                x = interpolate(x, domain.getMin(), boundsValues[0], encode.getMin(), encode.getMax());
+            }
+        }
+        else
+        {
+            for (int i=0; i<boundsSize-1; i++)
+            {
+                if ( x >= boundsValues[i] && x < boundsValues[i+1] )
+                {
+                    function = PDFunction.create(getFunctions().get(i+1));
+                    PDRange encode = getEncodeForParameter(i+1);
+                    x = interpolate(x, boundsValues[i], boundsValues[i+1], encode.getMin(), encode.getMax());
+                    break;
+                }
+            }
+            if(function==null) //must be in last partition
+            {
+                function = PDFunction.create(getFunctions().get(boundsSize+1));
+                PDRange encode = getEncodeForParameter(boundsSize+1);
+                x = interpolate(x, boundsValues[boundsSize-1], domain.getMax(), encode.getMin(), encode.getMax());
+            }
+        }
+        COSArray functionValues = new COSArray();
+        functionValues.add(new COSFloat(x));
+        COSArray functionResult = function.eval(functionValues);
+        // clip to range if available
+        return clipToRange(functionResult);
+    }
+    
+    /**
+     * Returns all functions values as COSArray.
+     * 
+     * @return the functions array. 
+     */
+    public COSArray getFunctions()
+    {
+        if (functions == null)
+        {
+            functions = (COSArray)(getDictionary().getDictionaryObject( COSName.FUNCTIONS ));
+        }
+        return functions;
+    }
+    
+    /**
+     * Returns all bounds values as COSArray.
+     * 
+     * @return the bounds array. 
+     */
+    public COSArray getBounds()
+    {
+        if (bounds == null) 
+        {
+            bounds = (COSArray)(getDictionary().getDictionaryObject( COSName.BOUNDS ));
+        }
+        return bounds;
+    }
+    
+    /**
+     * Returns all encode values as COSArray.
+     * 
+     * @return the encode array. 
+     */
+    public COSArray getEncode()
+    {
+        if (encode == null)
+        {
+            encode = (COSArray)(getDictionary().getDictionaryObject( COSName.ENCODE ));
+        }
+        return encode;
+    }
+    
+    /**
+     * Get the encode for the input parameter.
+     *
+     * @param paramNum The function parameter number.
+     *
+     * @return The encode parameter range or null if none is set.
+     */
+    private PDRange getEncodeForParameter(int n) 
+    {
+        COSArray encodeValues = getEncode();
+        return new PDRange( encodeValues, n );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType4.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/PDFunctionType4.java
new file mode 100644 (file)
index 0000000..259e58c
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.common.function;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+import java.io.IOException;
+
+/**
+ * This class represents a type 4 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType4 extends PDFunction
+{
+
+    /**
+     * Constructor.
+     *
+     * @param functionStream The function .
+     */
+    public PDFunctionType4(COSBase function)
+    {
+        super( function );
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getFunctionType()
+    {
+        return 4;
+    }
+
+    /**
+    * {@inheritDoc}
+    */
+    public COSArray eval(COSArray input) throws IOException
+    {
+        //Implementation here will require evaluation of PostScript functions.
+        //See section 3.9.4 of the PDF Reference.
+        throw new IOException("Not Implemented");
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/function/package.html
new file mode 100644 (file)
index 0000000..a36a976
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains functions that are available in the PDF specification.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/common/package.html
new file mode 100644 (file)
index 0000000..4d3bd10
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+High level PD classes that are used throughout several packages are placed in the PDModel common package.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDAttributeObject.java
new file mode 100644 (file)
index 0000000..0043f13
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDDictionaryWrapper;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDExportFormatAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDLayoutAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDListAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDPrintFieldAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDTableAttributeObject;
+
+/**
+ * An attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public abstract class PDAttributeObject extends PDDictionaryWrapper
+{
+
+    /**
+     * Creates an attribute object.
+     * 
+     * @param dictionary the dictionary
+     * @return the attribute object
+     */
+    public static PDAttributeObject create(COSDictionary dictionary)
+    {
+        String owner = dictionary.getNameAsString(COSName.O);
+        if (PDUserAttributeObject.OWNER_USER_PROPERTIES.equals(owner))
+        {
+            return new PDUserAttributeObject(dictionary);
+        }
+        else if (PDListAttributeObject.OWNER_LIST.equals(owner))
+        {
+            return new PDListAttributeObject(dictionary);
+        }
+        else if (PDPrintFieldAttributeObject.OWNER_PRINT_FIELD.equals(owner))
+        {
+            return new PDPrintFieldAttributeObject(dictionary);
+        }
+        else if (PDTableAttributeObject.OWNER_TABLE.equals(owner))
+        {
+            return new PDTableAttributeObject(dictionary);
+        }
+        else if (PDLayoutAttributeObject.OWNER_LAYOUT.equals(owner))
+        {
+            return new PDLayoutAttributeObject(dictionary);
+        }
+        else if (PDExportFormatAttributeObject.OWNER_XML_1_00.equals(owner)
+            || PDExportFormatAttributeObject.OWNER_HTML_3_20.equals(owner)
+            || PDExportFormatAttributeObject.OWNER_HTML_4_01.equals(owner)
+            || PDExportFormatAttributeObject.OWNER_OEB_1_00.equals(owner)
+            || PDExportFormatAttributeObject.OWNER_RTF_1_05.equals(owner)
+            || PDExportFormatAttributeObject.OWNER_CSS_1_00.equals(owner)
+            || PDExportFormatAttributeObject.OWNER_CSS_2_00.equals(owner))
+        {
+            return new PDExportFormatAttributeObject(dictionary);
+        }
+        return new PDDefaultAttributeObject(dictionary);
+    }
+
+    private PDStructureElement structureElement;
+
+    /**
+     * Gets the structure element.
+     * 
+     * @return the structure element
+     */
+    private PDStructureElement getStructureElement()
+    {
+        return this.structureElement;
+    }
+
+    /**
+     * Sets the structure element.
+     * 
+     * @param structureElement the structure element
+     */
+    protected void setStructureElement(PDStructureElement structureElement)
+    {
+        this.structureElement = structureElement;
+    }
+
+
+    /**
+     * Default constructor.
+     */
+    public PDAttributeObject()
+    {
+    }
+
+    /**
+     * Creates a new attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Returns the owner of the attributes.
+     * 
+     * @return the owner of the attributes
+     */
+    public String getOwner()
+    {
+        return this.getCOSDictionary().getNameAsString(COSName.O);
+    }
+
+    /**
+     * Sets the owner of the attributes.
+     * 
+     * @param owner the owner of the attributes
+     */
+    protected void setOwner(String owner)
+    {
+        this.getCOSDictionary().setName(COSName.O, owner);
+    }
+
+    /**
+     * Detects whether there are no properties in the attribute object.
+     * 
+     * @return <code>true</code> if the attribute object is empty,
+     *  <code>false</code> otherwise
+     */
+    public boolean isEmpty()
+    {
+        // only entry is the owner?
+        return (this.getCOSDictionary().size() == 1) && (this.getOwner() != null);
+    }
+
+
+    /**
+     * Notifies the attribute object change listeners if the attribute is changed.
+     * 
+     * @param oldBase old value
+     * @param newBase new value
+     */
+    protected void potentiallyNotifyChanged(COSBase oldBase, COSBase newBase)
+    {
+        if (this.isValueChanged(oldBase, newBase))
+        {
+            this.notifyChanged();
+        }
+    }
+
+    /**
+     * Is the value changed?
+     * 
+     * @param oldValue old value
+     * @param newValue new value
+     * @return <code>true</code> if the value is changed, <code>false</code>
+     * otherwise
+     */
+    private boolean isValueChanged(COSBase oldValue, COSBase newValue)
+    {
+        if (oldValue == null)
+        {
+            if (newValue == null)
+            {
+                return false;
+            }
+            return true;
+        }
+        return !oldValue.equals(newValue);
+    }
+
+    /**
+     * Notifies the attribute object change listeners about a change in this
+     * attribute object.
+     */
+    protected void notifyChanged()
+    {
+        if (this.getStructureElement() != null)
+        {
+            this.getStructureElement().attributeChanged(this);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return new StringBuilder("O=").append(this.getOwner()).toString();
+    }
+
+    /**
+     * Creates a String representation of an Object array.
+     * 
+     * @param array the Object array
+     * @return the String representation
+     */
+    protected static String arrayToString(Object[] array)
+    {
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < array.length; i++)
+        {
+            if (i > 0)
+            {
+                sb.append(", ");
+            }
+            sb.append(array[i]);
+        }
+        return sb.append(']').toString();
+    }
+
+    /**
+     * Creates a String representation of a float array.
+     * 
+     * @param array the float array
+     * @return the String representation
+     */
+    protected static String arrayToString(float[] array)
+    {
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < array.length; i++)
+        {
+            if (i > 0)
+            {
+                sb.append(", ");
+            }
+            sb.append(array[i]);
+        }
+        return sb.append(']').toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDDefaultAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDDefaultAttributeObject.java
new file mode 100644 (file)
index 0000000..9b49d37
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A default attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDDefaultAttributeObject extends PDAttributeObject
+{
+
+    /**
+     * Default constructor.
+     */
+    public PDDefaultAttributeObject()
+    {
+    }
+
+    /**
+     * Creates a default attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDDefaultAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Gets the attribute names.
+     * 
+     * @return the attribute names
+     */
+    public List<String> getAttributeNames()
+    {
+        List<String> attrNames = new ArrayList<String>();
+        for (Entry<COSName, COSBase> entry : this.getCOSDictionary().entrySet())
+        {
+            COSName key = entry.getKey();
+            if (!COSName.O.equals(key))
+            {
+                attrNames.add(key.getName());
+            }
+        }
+        return attrNames;
+    }
+
+    /**
+     * Gets the attribute value for a given name.
+     * 
+     * @param attrName the given attribute name
+     * @return the attribute value for a given name
+     */
+    public COSBase getAttributeValue(String attrName)
+    {
+        return this.getCOSDictionary().getDictionaryObject(attrName);
+    }
+
+    /**
+     * Gets the attribute value for a given name.
+     * 
+     * @param attrName the given attribute name
+     * @param defaultValue the default value
+     * @return the attribute value for a given name
+     */
+    protected COSBase getAttributeValue(String attrName, COSBase defaultValue)
+    {
+        COSBase value = this.getCOSDictionary().getDictionaryObject(attrName);
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        return value;
+    }
+
+    /**
+     * Sets an attribute.
+     * 
+     * @param attrName the attribute name
+     * @param attrValue the attribute value
+     */
+    public void setAttribute(String attrName, COSBase attrValue)
+    {
+        COSBase old = this.getAttributeValue(attrName);
+        this.getCOSDictionary().setItem(COSName.getPDFName(attrName), attrValue);
+        this.potentiallyNotifyChanged(old, attrValue);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder().append(super.toString())
+            .append(", attributes={");
+        Iterator<String> it = this.getAttributeNames().iterator();
+        while (it.hasNext())
+        {
+            String name = it.next();
+            sb.append(name).append('=').append(this.getAttributeValue(name));
+            if (it.hasNext())
+            {
+                sb.append(", ");
+            }
+        }
+        return sb.append('}').toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkInfo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkInfo.java
new file mode 100644 (file)
index 0000000..9bf1d6b
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * The MarkInfo provides additional information relevant to specialized
+ * uses of structured documents.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDMarkInfo implements COSObjectable
+{
+    private COSDictionary dictionary;
+
+    /**
+     * Default Constructor.
+     *
+     */
+    public PDMarkInfo()
+    {
+        dictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor for an existing MarkInfo element.
+     *
+     * @param dic The existing dictionary.
+     */
+    public PDMarkInfo( COSDictionary dic )
+    {
+        dictionary = dic;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return dictionary;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * Tells if this is a tagged PDF.
+     *
+     * @return true If this is a tagged PDF.
+     */
+    public boolean isMarked()
+    {
+        return dictionary.getBoolean( "Marked", false );
+    }
+
+    /**
+     * Set if this is a tagged PDF.
+     *
+     * @param value The new marked value.
+     */
+    public void setMarked( boolean value )
+    {
+        dictionary.setBoolean( "Marked", value );
+    }
+
+    /**
+     * Tells if structure elements use user properties.
+     *
+     * @return A boolean telling if the structure elements use user properties.
+     */
+    public boolean usesUserProperties()
+    {
+        return dictionary.getBoolean( "UserProperties", false );
+    }
+
+    /**
+     * Set if the structure elements contain user properties.
+     *
+     * @param userProps The new value for this property.
+     */
+    public void setUserProperties( boolean userProps )
+    {
+        dictionary.setBoolean( "UserProperties", userProps );
+    }
+
+    /**
+     * Tells if this PDF contain 'suspect' tags.  See PDF Reference 1.6
+     * section 10.6 "Logical Structure" for more information about this property.
+     *
+     * @return true if the suspect flag has been set.
+     */
+    public boolean isSuspect()
+    {
+        return dictionary.getBoolean( "Suspects", false );
+    }
+
+    /**
+     * Set the value of the suspects property.  See PDF Reference 1.6
+     * section 10.6 "Logical Structure" for more information about this
+     * property.
+     *
+     * @param suspect The new "Suspects" value.
+     */
+    public void setSuspect( boolean suspect )
+    {
+        dictionary.setBoolean( "Suspects", false );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkedContentReference.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkedContentReference.java
new file mode 100644 (file)
index 0000000..e7a25e0
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * A marked-content reference.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDMarkedContentReference implements COSObjectable
+{
+
+    public static final String TYPE = "MCR";
+
+    private COSDictionary dictionary;
+
+    protected COSDictionary getCOSDictionary()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * Default constructor
+     */
+    public PDMarkedContentReference()
+    {
+        this.dictionary = new COSDictionary();
+        this.dictionary.setName(COSName.TYPE, TYPE);
+    }
+
+    /**
+     * Constructor for an existing marked content reference.
+     * 
+     * @param pageDic the page dictionary
+     * @param mcid the marked content indentifier
+     */
+    public PDMarkedContentReference(COSDictionary dictionary)
+    {
+        this.dictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * Gets the page.
+     * 
+     * @return the page
+     */
+    public PDPage getPage()
+    {
+        COSDictionary pg = (COSDictionary) this.getCOSDictionary()
+            .getDictionaryObject(COSName.PG);
+        if (pg != null)
+        {
+            return new PDPage(pg);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the page.
+     * 
+     * @param page the page
+     */
+    public void setPage(PDPage page)
+    {
+        this.getCOSDictionary().setItem(COSName.PG, page);
+    }
+
+    /**
+     * Gets the marked content identifier.
+     * 
+     * @return the marked content identifier
+     */
+    public int getMCID()
+    {
+        return this.getCOSDictionary().getInt(COSName.MCID);
+    }
+
+    /**
+     * Sets the marked content identifier.
+     * 
+     * @param mcid the marked content identifier
+     */
+    public void setMCID(int mcid)
+    {
+        this.getCOSDictionary().setInt(COSName.MCID, mcid);
+    }
+
+
+    @Override
+    public String toString()
+    {
+        return new StringBuilder()
+            .append("mcid=").append(this.getMCID()).toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDObjectReference.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDObjectReference.java
new file mode 100644 (file)
index 0000000..ee9c73f
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+
+/**
+ * An object reference.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDObjectReference implements COSObjectable
+{
+
+    public static final String TYPE = "OBJR";
+
+    private COSDictionary dictionary;
+
+    protected COSDictionary getCOSDictionary()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * Default Constructor.
+     *
+     */
+    public PDObjectReference()
+    {
+        this.dictionary = new COSDictionary();
+        this.dictionary.setName(COSName.TYPE, TYPE);
+    }
+
+    /**
+     * Constructor for an existing object reference.
+     *
+     * @param dictionary The existing dictionary.
+     */
+    public PDObjectReference(COSDictionary dictionary)
+    {
+        this.dictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * Gets a higher-level object for the referenced object.
+     * Currently this method may return a {@link PDAnnotation},
+     * a {@link PDXObject} or <code>null</code>.
+     * 
+     * @return a higher-level object for the referenced object
+     */
+    public COSObjectable getReferencedObject()
+    {
+        COSBase obj = this.getCOSDictionary().getDictionaryObject(COSName.OBJ);
+        try
+        {
+            return PDAnnotation.createAnnotation(obj);
+        }
+        catch (IOException e)
+        {
+            // No Annotation
+            try
+            {
+                return PDXObject.createXObject(obj);
+            }
+            catch (IOException e1)
+            {
+                // No XObject
+                // TODO what else can be the target of the object reference?
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets the referenced annotation.
+     * 
+     * @param annotation the referenced annotation
+     */
+    public void setReferencedObject(PDAnnotation annotation)
+    {
+        this.getCOSDictionary().setItem(COSName.OBJ, annotation);
+    }
+
+    /**
+     * Sets the referenced XObject.
+     * 
+     * @param xobject the referenced XObject
+     */
+    public void setReferencedObject(PDXObject xobject)
+    {
+        this.getCOSDictionary().setItem(COSName.OBJ, xobject);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureElement.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureElement.java
new file mode 100644 (file)
index 0000000..2b7fd64
--- /dev/null
@@ -0,0 +1,755 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;
+
+/**
+ * A structure element.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>,
+ *  <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDStructureElement extends PDStructureNode
+{
+    public static final String TYPE = "StructElem";
+
+
+    /**
+     * Constructor with required values.
+     *
+     * @param structureType the structure type
+     * @param parent the parent structure node
+     */
+    public PDStructureElement(String structureType, PDStructureNode parent)
+    {
+        super(TYPE);
+        this.setStructureType(structureType);
+        this.setParent(parent);
+    }
+
+    /**
+     * Constructor for an existing structure element.
+     *
+     * @param dic The existing dictionary.
+     */
+    public PDStructureElement( COSDictionary dic )
+    {
+        super(dic);
+    }
+
+
+    /**
+     * Returns the structure type (S).
+     * 
+     * @return the structure type
+     */
+    public String getStructureType()
+    {
+        return this.getCOSDictionary().getNameAsString(COSName.S);
+    }
+
+    /**
+     * Sets the structure type (S).
+     * 
+     * @param structureType the structure type
+     */
+    public void setStructureType(String structureType)
+    {
+        this.getCOSDictionary().setName(COSName.S, structureType);
+    }
+
+    /**
+     * Returns the parent in the structure hierarchy (P).
+     * 
+     * @return the parent in the structure hierarchy
+     */
+    public PDStructureNode getParent()
+    {
+        COSDictionary p = (COSDictionary) this.getCOSDictionary()
+            .getDictionaryObject(COSName.P);
+        if (p == null)
+        {
+            return null;
+        }
+        return PDStructureNode.create((COSDictionary) p);
+    }
+
+    /**
+     * Sets the parent in the structure hierarchy (P).
+     * 
+     * @param structureNode the parent in the structure hierarchy
+     */
+    public void setParent(PDStructureNode structureNode)
+    {
+        this.getCOSDictionary().setItem(COSName.P, structureNode);
+    }
+
+    /**
+     * Returns the element identifier (ID).
+     * 
+     * @return the element identifier
+     */
+    public String getElementIdentifier()
+    {
+        return this.getCOSDictionary().getString(COSName.ID);
+    }
+
+    /**
+     * Sets the element identifier (ID).
+     * 
+     * @param id the element identifier
+     */
+    public void setElementIdentifier(String id)
+    {
+        this.getCOSDictionary().setString(COSName.ID, id);
+    }
+
+    /**
+     * Returns the page on which some or all of the content items designated by
+     *  the K entry shall be rendered (Pg).
+     * 
+     * @return the page on which some or all of the content items designated by
+     *  the K entry shall be rendered
+     */
+    public PDPage getPage()
+    {
+        COSDictionary pageDic = (COSDictionary) this.getCOSDictionary()
+            .getDictionaryObject(COSName.PG);
+        if (pageDic == null)
+        {
+            return null;
+        }
+        return new PDPage(pageDic);
+    }
+
+    /**
+     * Sets the page on which some or all of the content items designated by
+     *  the K entry shall be rendered (Pg).
+     * @param page the page on which some or all of the content items designated
+     *  by the K entry shall be rendered.
+     */
+    public void setPage(PDPage page)
+    {
+        this.getCOSDictionary().setItem(COSName.PG, page);
+    }
+
+    /**
+     * Returns the attributes together with their revision numbers (A).
+     * 
+     * @return the attributes
+     */
+    public Revisions<PDAttributeObject> getAttributes()
+    {
+        Revisions<PDAttributeObject> attributes =
+            new Revisions<PDAttributeObject>();
+        COSBase a = this.getCOSDictionary().getDictionaryObject(COSName.A);
+        if (a instanceof COSArray)
+        {
+            COSArray aa = (COSArray) a;
+            Iterator<COSBase> it = aa.iterator();
+            PDAttributeObject ao = null;
+            while (it.hasNext())
+            {
+                COSBase item = it.next();
+                if (item instanceof COSDictionary)
+                {
+                    ao = PDAttributeObject.create((COSDictionary) item);
+                    ao.setStructureElement(this);
+                    attributes.addObject(ao, 0);
+                }
+                else if (item instanceof COSInteger)
+                {
+                    attributes.setRevisionNumber(ao,
+                        ((COSInteger) item).intValue());
+                }
+            }
+        }
+        if (a instanceof COSDictionary)
+        {
+            PDAttributeObject ao = PDAttributeObject.create((COSDictionary) a);
+            ao.setStructureElement(this);
+            attributes.addObject(ao, 0);
+        }
+        return attributes;
+    }
+
+    /**
+     * Sets the attributes together with their revision numbers (A).
+     * 
+     * @param attributes the attributes
+     */
+    public void setAttributes(Revisions<PDAttributeObject> attributes)
+    {
+        COSName key = COSName.A;
+        if ((attributes.size() == 1) && (attributes.getRevisionNumber(0) == 0))
+        {
+            PDAttributeObject attributeObject = attributes.getObject(0);
+            attributeObject.setStructureElement(this);
+            this.getCOSDictionary().setItem(key, attributeObject);
+            return;
+        }
+        COSArray array = new COSArray();
+        for (int i = 0; i < attributes.size(); i++)
+        {
+            PDAttributeObject attributeObject = attributes.getObject(i);
+            attributeObject.setStructureElement(this);
+            int revisionNumber = attributes.getRevisionNumber(i);
+            if (revisionNumber < 0)
+            {
+                // TODO throw Exception because revision number must be > -1?
+            }
+            array.add(attributeObject);
+            array.add(COSInteger.get(revisionNumber));
+        }
+        this.getCOSDictionary().setItem(key, array);
+    }
+
+    /**
+     * Adds an attribute object.
+     * 
+     * @param attributeObject the attribute object
+     */
+    public void addAttribute(PDAttributeObject attributeObject)
+    {
+        COSName key = COSName.A;
+        attributeObject.setStructureElement(this);
+        COSBase a = this.getCOSDictionary().getDictionaryObject(key);
+        COSArray array = null;
+        if (a instanceof COSArray)
+        {
+            array = (COSArray) a;
+        }
+        else
+        {
+            array = new COSArray();
+            if (a != null)
+            {
+                array.add(a);
+                array.add(COSInteger.get(0));
+            }
+        }
+        this.getCOSDictionary().setItem(key, array);
+        array.add(attributeObject);
+        array.add(COSInteger.get(this.getRevisionNumber()));
+    }
+
+    /**
+     * Removes an attribute object.
+     * 
+     * @param attributeObject the attribute object
+     */
+    public void removeAttribute(PDAttributeObject attributeObject)
+    {
+        COSName key = COSName.A;
+        COSBase a = this.getCOSDictionary().getDictionaryObject(key);
+        if (a instanceof COSArray)
+        {
+            COSArray array = (COSArray) a;
+            array.remove(attributeObject.getCOSObject());
+            if ((array.size() == 2) && (array.getInt(1) == 0))
+            {
+                this.getCOSDictionary().setItem(key, array.getObject(0));
+            }
+        }
+        else
+        {
+            COSBase directA = a;
+            if (a instanceof COSObject)
+            {
+                directA = ((COSObject) a).getObject();
+            }
+            if (attributeObject.getCOSObject().equals(directA))
+            {
+                this.getCOSDictionary().setItem(key, null);
+            }
+        }
+        attributeObject.setStructureElement(null);
+    }
+
+    /**
+     * Updates the revision number for the given attribute object.
+     * 
+     * @param attributeObject the attribute object
+     */
+    public void attributeChanged(PDAttributeObject attributeObject)
+    {
+        COSName key = COSName.A;
+        COSBase a = this.getCOSDictionary().getDictionaryObject(key);
+        if (a instanceof COSArray)
+        {
+            COSArray array = (COSArray) a;
+            for (int i = 0; i < array.size(); i++)
+            {
+                COSBase entry = array.getObject(i);
+                if (entry.equals(attributeObject.getCOSObject()))
+                {
+                    COSBase next = array.get(i + 1);
+                    if (next instanceof COSInteger)
+                    {
+                        array.set(i + 1, COSInteger.get(this.getRevisionNumber()));
+                    }
+                }
+            }
+        }
+        else
+        {
+            COSArray array = new COSArray();
+            array.add(a);
+            array.add(COSInteger.get(this.getRevisionNumber()));
+            this.getCOSDictionary().setItem(key, array);
+        }
+    }
+
+    /**
+     * Returns the class names together with their revision numbers (C).
+     * 
+     * @return the class names
+     */
+    public Revisions<String> getClassNames()
+    {
+        COSName key = COSName.C;
+        Revisions<String> classNames = new Revisions<String>();
+        COSBase c = this.getCOSDictionary().getDictionaryObject(key);
+        if (c instanceof COSName)
+        {
+            classNames.addObject(((COSName) c).getName(), 0);
+        }
+        if (c instanceof COSArray)
+        {
+            COSArray array = (COSArray) c;
+            Iterator<COSBase> it = array.iterator();
+            String className = null;
+            while (it.hasNext())
+            {
+                COSBase item = it.next();
+                if (item instanceof COSName)
+                {
+                    className = ((COSName) item).getName();
+                    classNames.addObject(className, 0);
+                }
+                else if (item instanceof COSInteger)
+                {
+                    classNames.setRevisionNumber(className,
+                        ((COSInteger) item).intValue());
+                }
+            }
+        }
+        return classNames;
+    }
+
+    /**
+     * Sets the class names together with their revision numbers (C).
+     * 
+     * @param classNames the class names
+     */
+    public void setClassNames(Revisions<String> classNames)
+    {
+        if (classNames == null)
+        {
+            return;
+        }
+        COSName key = COSName.C;
+        if ((classNames.size() == 1) && (classNames.getRevisionNumber(0) == 0))
+        {
+            String className = classNames.getObject(0);
+            this.getCOSDictionary().setName(key, className);
+            return;
+        }
+        COSArray array = new COSArray();
+        for (int i = 0; i < classNames.size(); i++)
+        {
+            String className = classNames.getObject(i);
+            int revisionNumber = classNames.getRevisionNumber(i);
+            if (revisionNumber < 0)
+            {
+                // TODO throw Exception because revision number must be > -1?
+            }
+            array.add(COSName.getPDFName(className));
+            array.add(COSInteger.get(revisionNumber));
+        }
+        this.getCOSDictionary().setItem(key, array);
+    }
+
+    /**
+     * Adds a class name.
+     * 
+     * @param className the class name
+     */
+    public void addClassName(String className)
+    {
+        if (className == null)
+        {
+            return;
+        }
+        COSName key = COSName.C;
+        COSBase c = this.getCOSDictionary().getDictionaryObject(key);
+        COSArray array = null;
+        if (c instanceof COSArray)
+        {
+            array = (COSArray) c;
+        }
+        else
+        {
+            array = new COSArray();
+            if (c != null)
+            {
+                array.add(c);
+                array.add(COSInteger.get(0));
+            }
+        }
+        this.getCOSDictionary().setItem(key, array);
+        array.add(COSName.getPDFName(className));
+        array.add(COSInteger.get(this.getRevisionNumber()));
+    }
+
+    /**
+     * Removes a class name.
+     * 
+     * @param className the class name
+     */
+    public void removeClassName(String className)
+    {
+        if (className == null)
+        {
+            return;
+        }
+        COSName key = COSName.C;
+        COSBase c = this.getCOSDictionary().getDictionaryObject(key);
+        COSName name = COSName.getPDFName(className);
+        if (c instanceof COSArray)
+        {
+            COSArray array = (COSArray) c;
+            array.remove(name);
+            if ((array.size() == 2) && (array.getInt(1) == 0))
+            {
+                this.getCOSDictionary().setItem(key, array.getObject(0));
+            }
+        }
+        else
+        {
+            COSBase directC = c;
+            if (c instanceof COSObject)
+            {
+                directC = ((COSObject) c).getObject();
+            }
+            if (name.equals(directC))
+            {
+                this.getCOSDictionary().setItem(key, null);
+            }
+        }
+    }
+
+    /**
+     * Returns the revision number (R).
+     * 
+     * @return the revision number
+     */
+    public int getRevisionNumber()
+    {
+        return this.getCOSDictionary().getInt(COSName.R, 0);
+    }
+
+    /**
+     * Sets the revision number (R).
+     * 
+     * @param revisionNumber the revision number
+     */
+    public void setRevisionNumber(int revisionNumber)
+    {
+        if (revisionNumber < 0)
+        {
+            // TODO throw Exception because revision number must be > -1?
+        }
+        this.getCOSDictionary().setInt(COSName.R, revisionNumber);
+    }
+
+    /**
+     * Increments th revision number
+     */
+    public void incrementRevisionNumber()
+    {
+        this.setRevisionNumber(this.getRevisionNumber() + 1);
+    }
+
+    /**
+     * Returns the title (T).
+     * 
+     * @return the title
+     */
+    public String getTitle()
+    {
+        return this.getCOSDictionary().getString(COSName.T);
+    }
+
+    /**
+     * Sets the title (T).
+     * 
+     * @param title the title
+     */
+    public void setTitle(String title)
+    {
+        this.getCOSDictionary().setString(COSName.T, title);
+    }
+
+    /**
+     * Returns the language (Lang).
+     * 
+     * @return the language
+     */
+    public String getLanguage()
+    {
+        return this.getCOSDictionary().getString(COSName.LANG);
+    }
+
+    /**
+     * Sets the language (Lang).
+     * 
+     * @param language the language
+     */
+    public void setLanguage(String language)
+    {
+        this.getCOSDictionary().setString(COSName.LANG, language);
+    }
+
+    /**
+     * Returns the alternate description (Alt).
+     * 
+     * @return the alternate description
+     */
+    public String getAlternateDescription()
+    {
+        return this.getCOSDictionary().getString(COSName.ALT);
+    }
+
+    /**
+     * Sets the alternate description (Alt).
+     * 
+     * @param alternateDescription the alternate description
+     */
+    public void setAlternateDescription(String alternateDescription)
+    {
+        this.getCOSDictionary().setString(COSName.ALT, alternateDescription);
+    }
+
+    /**
+     * Returns the expanded form (E).
+     * 
+     * @return the expanded form
+     */
+    public String getExpandedForm()
+    {
+        return this.getCOSDictionary().getString(COSName.E);
+    }
+
+    /**
+     * Sets the expanded form (E).
+     * 
+     * @param expandedForm the expanded form
+     */
+    public void setExpandedForm(String expandedForm)
+    {
+        this.getCOSDictionary().setString(COSName.E, expandedForm);
+    }
+
+    /**
+     * Returns the actual text (ActualText).
+     * 
+     * @return the actual text
+     */
+    public String getActualText()
+    {
+        return this.getCOSDictionary().getString(COSName.ACTUAL_TEXT);
+    }
+
+    /**
+     * Sets the actual text (ActualText).
+     * 
+     * @param actualText the actual text
+     */
+    public void setActualText(String actualText)
+    {
+        this.getCOSDictionary().setString(COSName.ACTUAL_TEXT, actualText);
+    }
+
+    /**
+     * Returns the standard structure type, the actual structure type is mapped
+     * to in the role map.
+     * 
+     * @return the standard structure type
+     */
+    public String getStandardStructureType()
+    {
+        String type = this.getStructureType();
+        String mappedType;
+        while (true)
+        {
+            mappedType = this.getRoleMap().get(type);
+            if ((mappedType == null) || type.equals(mappedType))
+            {
+                break;
+            }
+            type = mappedType;
+        }
+        return type;
+    }
+
+    /**
+     * Appends a marked-content sequence kid.
+     * 
+     * @param markedContent the marked-content sequence
+     */
+    public void appendKid(PDMarkedContent markedContent)
+    {
+        if (markedContent == null)
+        {
+            return;
+        }
+        this.appendKid(COSInteger.get(markedContent.getMCID()));
+    }
+
+    /**
+     * Appends a marked-content reference kid.
+     * 
+     * @param markedContentReference the marked-content reference
+     */
+    public void appendKid(PDMarkedContentReference markedContentReference)
+    {
+        this.appendObjectableKid(markedContentReference);
+    }
+
+    /**
+     * Appends an object reference kid.
+     * 
+     * @param objectReference the object reference
+     */
+    public void appendKid(PDObjectReference objectReference)
+    {
+        this.appendObjectableKid(objectReference);
+    }
+
+    /**
+     * Inserts a marked-content identifier kid before a reference kid.
+     * 
+     * @param markedContentIdentifier the marked-content identifier
+     * @param refKid the reference kid
+     */
+    public void insertBefore(COSInteger markedContentIdentifier, Object refKid)
+    {
+        this.insertBefore((COSBase) markedContentIdentifier, refKid);
+    }
+
+    /**
+     * Inserts a marked-content reference kid before a reference kid.
+     * 
+     * @param markedContentReference the marked-content reference
+     * @param refKid the reference kid
+     */
+    public void insertBefore(PDMarkedContentReference markedContentReference,
+        Object refKid)
+    {
+        this.insertObjectableBefore(markedContentReference, refKid);
+    }
+
+    /**
+     * Inserts an object reference kid before a reference kid.
+     * 
+     * @param objectReference the object reference
+     * @param refKid the reference kid
+     */
+    public void insertBefore(PDObjectReference objectReference, Object refKid)
+    {
+        this.insertObjectableBefore(objectReference, refKid);
+    }
+
+    /**
+     * Removes a marked-content identifier kid.
+     * 
+     * @param markedContentIdentifier the marked-content identifier
+     */
+    public void removeKid(COSInteger markedContentIdentifier)
+    {
+        this.removeKid((COSBase) markedContentIdentifier);
+    }
+
+    /**
+     * Removes a marked-content reference kid.
+     * 
+     * @param markedContentReference the marked-content reference
+     */
+    public void removeKid(PDMarkedContentReference markedContentReference)
+    {
+        this.removeObjectableKid(markedContentReference);
+    }
+
+    /**
+     * Removes an object reference kid.
+     * 
+     * @param objectReference the object reference
+     */
+    public void removeKid(PDObjectReference objectReference)
+    {
+        this.removeObjectableKid(objectReference);
+    }
+
+
+    /**
+     * Returns the structure tree root.
+     * 
+     * @return the structure tree root
+     */
+    private PDStructureTreeRoot getStructureTreeRoot()
+    {
+        PDStructureNode parent = this.getParent();
+        while (parent instanceof PDStructureElement)
+        {
+            parent = ((PDStructureElement) parent).getParent();
+        }
+        if (parent instanceof PDStructureTreeRoot)
+        {
+            return (PDStructureTreeRoot) parent;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the role map.
+     * 
+     * @return the role map
+     */
+    private Map<String, String> getRoleMap()
+    {
+        PDStructureTreeRoot root = this.getStructureTreeRoot();
+        if (root != null)
+        {
+            return root.getRoleMap();
+        }
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureNode.java
new file mode 100644 (file)
index 0000000..82f8cc5
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+
+/**
+ * A node in the structure tree.
+ * 
+ * @author Koch
+ * @version $Revision: $
+ */
+public abstract class PDStructureNode implements COSObjectable
+{
+
+    /**
+     * Creates a node in the structure tree. Can be either a structure tree root,
+     *  or a structure element.
+     * 
+     * @param node the node dictionary
+     * @return the structure node
+     */
+    public static PDStructureNode create(COSDictionary node)
+    {
+        String type = node.getNameAsString(COSName.TYPE);
+        if ("StructTreeRoot".equals(type))
+        {
+            return new PDStructureTreeRoot(node);
+        }
+        if ((type == null) || "StructElem".equals(type))
+        {
+            return new PDStructureElement(node);
+        }
+        throw new IllegalArgumentException("Dictionary must not include a Type entry with a value that is neither StructTreeRoot nor StructElem.");
+    }
+
+
+    private COSDictionary dictionary;
+
+    protected COSDictionary getCOSDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param type the type
+     */
+    protected PDStructureNode(String type)
+    {
+        this.dictionary = new COSDictionary();
+        this.dictionary.setName(COSName.TYPE, type);
+    }
+
+    /**
+     * Constructor for an existing structure node.
+     *
+     * @param dictionary The existing dictionary.
+     */
+    protected PDStructureNode(COSDictionary dictionary)
+    {
+        this.dictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * Returns the type.
+     * 
+     * @return the type
+     */
+    public String getType()
+    {
+        return this.getCOSDictionary().getNameAsString(COSName.TYPE);
+    }
+
+    /**
+     * Returns a list of objects for the kids (K).
+     * 
+     * @return a list of objects for the kids
+     */
+    public List<Object> getKids()
+    {
+        List<Object> kidObjects = new ArrayList<Object>();
+        COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+        if (k instanceof COSArray)
+        {
+            Iterator<COSBase> kids = ((COSArray) k).iterator();
+            while (kids.hasNext())
+            {
+                COSBase kid = kids.next();
+                Object kidObject = this.createObject(kid);
+                if (kidObject != null)
+                {
+                    kidObjects.add(kidObject);
+                }
+            }
+        }
+        else
+        {
+            Object kidObject = this.createObject(k);
+            if (kidObject != null)
+            {
+                kidObjects.add(kidObject);
+            }
+        }
+        return kidObjects;
+    }
+
+    /**
+     * Sets the kids (K).
+     * 
+     * @param kids the kids
+     */
+    public void setKids(List<Object> kids)
+    {
+        this.getCOSDictionary().setItem(COSName.K,
+            COSArrayList.converterToCOSArray(kids));
+    }
+
+    /**
+     * Appends a structure element kid.
+     * 
+     * @param structureElement the structure element
+     */
+    public void appendKid(PDStructureElement structureElement)
+    {
+        this.appendObjectableKid(structureElement);
+        structureElement.setParent(this);
+    }
+
+    /**
+     * Appends an objectable kid.
+     * 
+     * @param objectable the objectable
+     */
+    protected void appendObjectableKid(COSObjectable objectable)
+    {
+        if (objectable == null)
+        {
+            return;
+        }
+        this.appendKid(objectable.getCOSObject());
+    }
+
+    /**
+     * Appends a COS base kid.
+     * 
+     * @param object the COS base
+     */
+    protected void appendKid(COSBase object)
+    {
+        if (object == null)
+        {
+            return;
+        }
+        COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+        if (k == null)
+        {
+            // currently no kid: set new kid as kids
+            this.getCOSDictionary().setItem(COSName.K, object);
+        }
+        else if (k instanceof COSArray)
+        {
+            // currently more than one kid: add new kid to existing array
+            COSArray array = (COSArray) k;
+            array.add(object);
+        }
+        else
+        {
+            // currently one kid: put current and new kid into array and set array as kids
+            COSArray array = new COSArray();
+            array.add(k);
+            array.add(object);
+            this.getCOSDictionary().setItem(COSName.K, array);
+        }
+    }
+
+    /**
+     * Inserts a structure element kid before a reference kid.
+     * 
+     * @param newKid the structure element
+     * @param refKid the reference kid
+     */
+    public void insertBefore(PDStructureElement newKid, Object refKid)
+    {
+        this.insertObjectableBefore(newKid, refKid);
+    }
+
+    /**
+     * Inserts an objectable kid before a reference kid.
+     * 
+     * @param newKid the objectable
+     * @param refKid the reference kid
+     */
+    protected void insertObjectableBefore(COSObjectable newKid, Object refKid)
+    {
+        if (newKid == null)
+        {
+            return;
+        }
+        this.insertBefore(newKid.getCOSObject(), refKid);
+    }
+
+    /**
+     * Inserts an COS base kid before a reference kid.
+     * 
+     * @param newKid the COS base
+     * @param refKid the reference kid
+     */
+    protected void insertBefore(COSBase newKid, Object refKid)
+    {
+        if ((newKid == null) || (refKid == null))
+        {
+            return;
+        }
+        COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+        if (k == null)
+        {
+            return;
+        }
+        COSBase refKidBase = null;
+        if (refKid instanceof COSObjectable)
+        {
+            refKidBase = ((COSObjectable) refKid).getCOSObject();
+        }
+        else if (refKid instanceof COSInteger)
+        {
+            refKidBase = (COSInteger) refKid;
+        }
+        if (k instanceof COSArray)
+        {
+            COSArray array = (COSArray) k;
+            int refIndex = array.indexOfObject(refKidBase);
+            array.add(refIndex, newKid.getCOSObject());
+        }
+        else
+        {
+            boolean onlyKid = k.equals(refKidBase);
+            if (!onlyKid && (k instanceof COSObject))
+            {
+                COSBase kObj = ((COSObject) k).getObject();
+                onlyKid = kObj.equals(refKidBase);
+            }
+            if (onlyKid)
+            {
+                COSArray array = new COSArray();
+                array.add(newKid);
+                array.add(refKidBase);
+                this.getCOSDictionary().setItem(COSName.K, array);
+            }
+        }
+    }
+
+    /**
+     * Removes a structure element kid.
+     * 
+     * @param structureElement the structure element
+     * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
+     */
+    public boolean removeKid(PDStructureElement structureElement)
+    {
+        boolean removed = this.removeObjectableKid(structureElement);
+        if (removed)
+        {
+            structureElement.setParent(null);
+        }
+        return removed;
+    }
+
+    /**
+     * Removes an objectable kid.
+     * 
+     * @param objectable the objectable
+     * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
+     */
+    protected boolean removeObjectableKid(COSObjectable objectable)
+    {
+        if (objectable == null)
+        {
+            return false;
+        }
+        return this.removeKid(objectable.getCOSObject());
+    }
+
+    /**
+     * Removes a COS base kid.
+     * 
+     * @param object the COS base
+     * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
+     */
+    protected boolean removeKid(COSBase object)
+    {
+        if (object == null)
+        {
+            return false;
+        }
+        COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+        if (k == null)
+        {
+            // no kids: objectable is not a kid
+            return false;
+        }
+        else if (k instanceof COSArray)
+        {
+            // currently more than one kid: remove kid from existing array
+            COSArray array = (COSArray) k;
+            boolean removed = array.removeObject(object);
+            // if now only one kid: set remaining kid as kids
+            if (array.size() == 1)
+            {
+                this.getCOSDictionary().setItem(COSName.K, array.getObject(0));
+            }
+            return removed;
+        }
+        else
+        {
+            // currently one kid: if current kid equals given object, remove kids entry
+            boolean onlyKid = k.equals(object);
+            if (!onlyKid && (k instanceof COSObject))
+            {
+                COSBase kObj = ((COSObject) k).getObject();
+                onlyKid = kObj.equals(object);
+            }
+            if (onlyKid)
+            {
+                this.getCOSDictionary().setItem(COSName.K, null);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Creates an object for a kid of this structure node.
+     * The type of object depends on the type of the kid. It can be
+     * <ul>
+     * <li>a {@link PDStructureElement},</li>
+     * <li>a {@link PDAnnotation},</li>
+     * <li>a {@link PDXObject},</li>
+     * <li>a {@link PDMarkedContentReference}</li>
+     * <li>a {@link Integer}</li>
+     * </ul>
+     * 
+     * @param kid the kid
+     * @return the object
+     */
+    protected Object createObject(COSBase kid)
+    {
+        COSDictionary kidDic = null;
+        if (kid instanceof COSDictionary)
+        {
+            kidDic = (COSDictionary) kid;
+        }
+        else if (kid instanceof COSObject)
+        {
+            COSBase base = ((COSObject) kid).getObject();
+            if (base instanceof COSDictionary)
+            {
+                kidDic = (COSDictionary) base;
+            }
+        }
+        if (kidDic != null)
+        {
+            String type = kidDic.getNameAsString(COSName.TYPE);
+            if ((type == null) || PDStructureElement.TYPE.equals(type))
+            {
+                // A structure element dictionary denoting another structure
+                // element
+                return new PDStructureElement(kidDic);
+            }
+            else if (PDObjectReference.TYPE.equals(type))
+            {
+                // An object reference dictionary denoting a PDF object
+                return new PDObjectReference(kidDic);
+            }
+            else if (PDMarkedContentReference.TYPE.equals(type))
+            {
+                // A marked-content reference dictionary denoting a
+                // marked-content sequence
+                return new PDMarkedContentReference(kidDic);
+            }
+        }
+        else if (kid instanceof COSInteger)
+        {
+            // An integer marked-content identifier denoting a
+            // marked-content sequence
+            COSInteger mcid = (COSInteger) kid;
+            return mcid.intValue();
+        }
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java
new file mode 100644 (file)
index 0000000..6e67799
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+
+/**
+ * A root of a structure tree.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>,
+ *  <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDStructureTreeRoot extends PDStructureNode
+{
+
+    public static final String TYPE = "StructTreeRoot";
+
+
+    /**
+     * Default Constructor.
+     *
+     */
+    public PDStructureTreeRoot()
+    {
+        super(TYPE);
+    }
+
+    /**
+     * Constructor for an existing structure element.
+     *
+     * @param dic The existing dictionary.
+     */
+    public PDStructureTreeRoot( COSDictionary dic )
+    {
+        super(dic);
+    }
+
+
+    /**
+     * Returns the ID tree.
+     * 
+     * @return the ID tree
+     */
+    public PDNameTreeNode getIDTree()
+    {
+        COSDictionary idTreeDic = (COSDictionary) this.getCOSDictionary()
+            .getDictionaryObject(COSName.ID_TREE);
+        if (idTreeDic != null)
+        {
+            return new PDNameTreeNode(idTreeDic, PDStructureElement.class);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the ID tree.
+     * 
+     * @param idTree the ID tree
+     */
+    public void setIDTree(PDNameTreeNode idTree)
+    {
+        this.getCOSDictionary().setItem(COSName.ID_TREE, idTree);
+    }
+
+    /**
+     * Returns the next key in the parent tree.
+     * 
+     * @return the next key in the parent tree
+     */
+    public int getParentTreeNextKey()
+    {
+        return this.getCOSDictionary().getInt(COSName.PARENT_TREE_NEXT_KEY);
+    }
+
+    /**
+     * Returns the role map.
+     * 
+     * @return the role map
+     */
+    @SuppressWarnings("unchecked")
+    public Map<String, String> getRoleMap()
+    {
+        COSBase rm = this.getCOSDictionary().getDictionaryObject(COSName.ROLE_MAP);
+        if (rm instanceof COSDictionary)
+        {
+            try
+            {
+                return COSDictionaryMap.convertBasicTypesToMap((COSDictionary) rm);
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+            }
+        }
+        return new Hashtable<String, String>();
+    }
+
+    /**
+     * Sets the role map.
+     * 
+     * @param roleMap the role map
+     */
+    public void setRoleMap(Map<String, String> roleMap)
+    {
+        COSDictionary rmDic = new COSDictionary();
+        for (String key : roleMap.keySet())
+        {
+            rmDic.setName(key, roleMap.get(key));
+        }
+        this.getCOSDictionary().setItem(COSName.ROLE_MAP, rmDic);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserAttributeObject.java
new file mode 100644 (file)
index 0000000..e34ae1d
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A User attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDUserAttributeObject extends PDAttributeObject
+{
+
+    /**
+     * Attribute owner for user properties
+     */
+    public static final String OWNER_USER_PROPERTIES = "UserProperties";
+
+
+    /**
+     * Default constructor
+     */
+    public PDUserAttributeObject()
+    {
+        this.setOwner(OWNER_USER_PROPERTIES);
+    }
+
+    /**
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDUserAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Returns the user properties.
+     * 
+     * @return the user properties
+     */
+    public List<PDUserProperty> getOwnerUserProperties()
+    {
+        COSArray p = (COSArray) this.getCOSDictionary()
+            .getDictionaryObject(COSName.P);
+        List<PDUserProperty> properties = new ArrayList<PDUserProperty>(p.size());
+        for (int i = 0; i < p.size(); i++)
+        {
+            properties.add(
+                new PDUserProperty((COSDictionary) p.getObject(i), this));
+        }
+        return properties;
+    }
+
+    /**
+     * Sets the user properties.
+     * 
+     * @param userProperties the user properties
+     */
+    public void setUserProperties(List<PDUserProperty> userProperties)
+    {
+        COSArray p = new COSArray();
+        for (PDUserProperty userProperty : userProperties)
+        {
+            p.add(userProperty);
+        }
+        this.getCOSDictionary().setItem(COSName.P, p);
+    }
+
+    /**
+     * Adds a user property.
+     * 
+     * @param userProperty the user property
+     */
+    public void addUserProperty(PDUserProperty userProperty)
+    {
+        COSArray p = (COSArray) this.getCOSDictionary()
+            .getDictionaryObject(COSName.P);
+        p.add(userProperty);
+        this.notifyChanged();
+    }
+
+    /**
+     * Removes a user property.
+     * 
+     * @param userProperty the user property
+     */
+    public void removeUserProperty(PDUserProperty userProperty)
+    {
+        if (userProperty == null)
+        {
+            return;
+        }
+        COSArray p = (COSArray) this.getCOSDictionary()
+            .getDictionaryObject(COSName.P);
+        p.remove(userProperty.getCOSObject());
+        this.notifyChanged();
+    }
+
+    public void userPropertyChanged(PDUserProperty userProperty)
+    {
+        
+    }
+
+    @Override
+    public String toString()
+    {
+        return new StringBuilder().append(super.toString())
+            .append(", userProperties=")
+            .append(this.getOwnerUserProperties()).toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserProperty.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserProperty.java
new file mode 100644 (file)
index 0000000..1da3809
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDDictionaryWrapper;
+
+/**
+ * A user property.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDUserProperty extends PDDictionaryWrapper
+{
+
+    private final PDUserAttributeObject userAttributeObject;
+
+    /**
+     * Creates a new user property.
+     * 
+     * @param the user attribute object
+     */
+    public PDUserProperty(PDUserAttributeObject userAttributeObject)
+    {
+        this.userAttributeObject = userAttributeObject;
+    }
+
+    /**
+     * Creates a user property with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     * @param the user attribute object
+     */
+    public PDUserProperty(COSDictionary dictionary,
+        PDUserAttributeObject userAttributeObject)
+    {
+        super(dictionary);
+        this.userAttributeObject = userAttributeObject;
+    }
+
+
+    /**
+     * Returns the property name.
+     * 
+     * @return the property name
+     */
+    public String getName()
+    {
+        return this.getCOSDictionary().getNameAsString(COSName.N);
+    }
+
+    /**
+     * Sets the property name.
+     * 
+     * @param name the property name
+     */
+    public void setName(String name)
+    {
+        this.potentiallyNotifyChanged(this.getName(), name);
+        this.getCOSDictionary().setName(COSName.N, name);
+    }
+
+    /**
+     * Returns the property value.
+     * 
+     * @return the property value
+     */
+    public COSBase getValue()
+    {
+        return this.getCOSDictionary().getDictionaryObject(COSName.V);
+    }
+
+    /**
+     * Sets the property value.
+     * 
+     * @param value the property value
+     */
+    public void setValue(COSBase value)
+    {
+        this.potentiallyNotifyChanged(this.getValue(), value);
+        this.getCOSDictionary().setItem(COSName.V, value);
+    }
+
+    /**
+     * Returns the string for the property value.
+     * 
+     * @return the string for the property value
+     */
+    public String getFormattedValue()
+    {
+        return this.getCOSDictionary().getString(COSName.F);
+    }
+
+    /**
+     * Sets the string for the property value.
+     * 
+     * @param formattedValue the string for the property value
+     */
+    public void setFormattedValue(String formattedValue)
+    {
+        this.potentiallyNotifyChanged(this.getFormattedValue(), formattedValue);
+        this.getCOSDictionary().setString(COSName.F, formattedValue);
+    }
+
+    /**
+     * Shall the property be hidden?
+     * 
+     * @return <code>true</code> if the property shall be hidden,
+     * <code>false</code> otherwise
+     */
+    public boolean isHidden()
+    {
+        return this.getCOSDictionary().getBoolean(COSName.H, false);
+    }
+
+    /**
+     * Specifies whether the property shall be hidden.
+     * 
+     * @param hidden <code>true</code> if the property shall be hidden,
+     * <code>false</code> otherwise
+     */
+    public void setHidden(boolean hidden)
+    {
+        this.potentiallyNotifyChanged(this.isHidden(), hidden);
+        this.getCOSDictionary().setBoolean(COSName.H, hidden);
+    }
+
+
+    @Override
+    public String toString()
+    {
+        return new StringBuilder("Name=").append(this.getName())
+            .append(", Value=").append(this.getValue())
+            .append(", FormattedValue=").append(this.getFormattedValue())
+            .append(", Hidden=").append(this.isHidden()).toString();
+    }
+
+
+    /**
+     * Notifies the user attribute object if the user property is changed.
+     * 
+     * @param oldEntry old entry
+     * @param newEntry new entry
+     */
+    private void potentiallyNotifyChanged(Object oldEntry, Object newEntry)
+    {
+        if (this.isEntryChanged(oldEntry, newEntry))
+        {
+            this.userAttributeObject.userPropertyChanged(this);
+        }
+    }
+
+    /**
+     * Is the value changed?
+     * 
+     * @param oldEntry old entry
+     * @param newEntry new entry
+     * @return <code>true</code> if the entry is changed, <code>false</code>
+     * otherwise
+     */
+    private boolean isEntryChanged(Object oldEntry, Object newEntry)
+    {
+        if (oldEntry == null)
+        {
+            if (newEntry == null)
+            {
+                return false;
+            }
+            return true;
+        }
+        return !oldEntry.equals(newEntry);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/Revisions.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/Revisions.java
new file mode 100644 (file)
index 0000000..68761a4
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 
+ * @author Koch
+ * @version $Revision: $
+ *
+ * @param <T> the type of object to store the revision numbers with
+ */
+public class Revisions<T>
+{
+
+    private List<T> objects;
+    private List<Integer> revisionNumbers;
+
+    private List<T> getObjects()
+    {
+        if (this.objects == null)
+        {
+            this.objects = new ArrayList<T>();
+        }
+        return this.objects;
+    }
+
+    private List<Integer> getRevisionNumbers()
+    {
+        if (this.revisionNumbers == null)
+        {
+            this.revisionNumbers = new ArrayList<Integer>();
+        }
+        return this.revisionNumbers;
+    }
+
+
+    /**
+     * 
+     */
+    public Revisions()
+    {
+    }
+
+
+    /**
+     * Returns the object at the specified position.
+     * 
+     * @param index the position
+     * @return the object
+     * @throws IndexOutOfBoundsException if the index is out of range
+     */
+    public T getObject(int index) throws IndexOutOfBoundsException
+    {
+        return this.getObjects().get(index);
+    }
+
+    /**
+     * Returns the revision number at the specified position.
+     * 
+     * @param index the position
+     * @return the revision number
+     * @throws IndexOutOfBoundsException if the index is out of range
+     */
+    public int getRevisionNumber(int index) throws IndexOutOfBoundsException
+    {
+        return this.getRevisionNumbers().get(index);
+    }
+
+    /**
+     * Adds an object with a specified revision number.
+     * 
+     * @param object the object
+     * @param revisionNumber the revision number
+     */
+    public void addObject(T object, int revisionNumber)
+    {
+        this.getObjects().add(object);
+        this.getRevisionNumbers().add(revisionNumber);
+    }
+
+    /**
+     * Sets the revision number of a specified object.
+     * 
+     * @param object the object
+     * @param revisionNumber the revision number
+     */
+    protected void setRevisionNumber(T object, int revisionNumber)
+    {
+        int index = this.getObjects().indexOf(object);
+        if (index > -1)
+        {
+            this.getRevisionNumbers().set(index, revisionNumber);
+        }
+    }
+
+    /**
+     * Returns the size.
+     * 
+     * @return the size
+     */
+    public int size()
+    {
+        return this.getObjects().size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < this.getObjects().size(); i++)
+        {
+            if (i > 0)
+            {
+                sb.append("; ");
+            }
+            sb.append("object=").append(this.getObjects().get(i))
+                .append(", revisionNumber=").append(this.getRevisionNumber(i));
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/package.html
new file mode 100644 (file)
index 0000000..a7d32e7
--- /dev/null
@@ -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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The logical structure package provides a mechanism for incorporating
+structural information about a document's content into a PDF file.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/markedcontent/PDMarkedContent.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/markedcontent/PDMarkedContent.java
new file mode 100644 (file)
index 0000000..0376f16
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.markedcontent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDArtifactMarkedContent;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.util.TextPosition;
+
+/**
+ * A marked content.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDMarkedContent
+{
+
+    /**
+     * Creates a marked-content sequence.
+     * 
+     * @param tag the tag
+     * @param properties the properties
+     * @return the marked-content sequence
+     */
+    public static PDMarkedContent create(COSName tag, COSDictionary properties)
+    {
+        if (COSName.ARTIFACT.equals(tag))
+        {
+            new PDArtifactMarkedContent(properties);
+        }
+        return new PDMarkedContent(tag, properties);
+    }
+
+
+    private String tag;
+    private COSDictionary properties;
+    private List<Object> contents;
+
+
+    /**
+     * Creates a new marked content object.
+     * 
+     * @param tag the tag
+     * @param properties the properties
+     */
+    public PDMarkedContent(COSName tag, COSDictionary properties)
+    {
+        this.tag = tag == null ? null : tag.getName();
+        this.properties = properties;
+        this.contents = new ArrayList<Object>();
+    }
+
+
+    /**
+     * Gets the tag.
+     * 
+     * @return the tag
+     */
+    public String getTag()
+    {
+        return this.tag;
+    }
+
+    /**
+     * Gets the properties.
+     * 
+     * @return the properties
+     */
+    public COSDictionary getProperties()
+    {
+        return this.properties;
+    }
+
+    /**
+     * Gets the marked-content identifier.
+     * 
+     * @return the marked-content identifier
+     */
+    public int getMCID()
+    {
+        return this.getProperties() == null ? null :
+            this.getProperties().getInt(COSName.MCID);
+    }
+
+    /**
+     * Gets the language (Lang).
+     * 
+     * @return the language
+     */
+    public String getLanguage()
+    {
+        return this.getProperties() == null ? null :
+            this.getProperties().getNameAsString(COSName.LANG);
+    }
+
+    /**
+     * Gets the actual text (ActualText).
+     * 
+     * @return the actual text
+     */
+    public String getActualText()
+    {
+        return this.getProperties() == null ? null :
+            this.getProperties().getString(COSName.ACTUAL_TEXT);
+    }
+
+    /**
+     * Gets the alternate description (Alt).
+     * 
+     * @return the alternate description
+     */
+    public String getAlternateDescription()
+    {
+        return this.getProperties() == null ? null :
+            this.getProperties().getString(COSName.ALT);
+    }
+
+    /**
+     * Gets the expanded form (E).
+     * 
+     * @return the expanded form
+     */
+    public String getExpandedForm()
+    {
+        return this.getProperties() == null ? null :
+            this.getProperties().getString(COSName.E);
+    }
+
+    /**
+     * Gets the contents of the marked content sequence. Can be
+     * <ul>
+     *   <li>{@link TextPosition},</li>
+     *   <li>{@link PDMarkedContent}, or</li>
+     *   <li>{@link PDXObject}.</li>
+     * </ul>
+     * 
+     * @return the contents of the marked content sequence
+     */
+    public List<Object> getContents()
+    {
+        return this.contents;
+    }
+
+    /**
+     * Adds a text position to the contents.
+     * 
+     * @param text the text position
+     */
+    public void addText(TextPosition text)
+    {
+        this.getContents().add(text);
+    }
+
+    /**
+     * Adds a marked content to the contents.
+     * 
+     * @param markedContent the marked content
+     */
+    public void addMarkedContent(PDMarkedContent markedContent)
+    {
+        this.getContents().add(markedContent);
+    }
+
+    /**
+     * Adds an XObject to the contents.
+     * 
+     * @param xobject the XObject
+     */
+    public void addXObject(PDXObject xobject)
+    {
+        this.getContents().add(xobject);
+    }
+
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder("tag=").append(this.tag)
+            .append(", properties=").append(this.properties);
+        sb.append(", contents=").append(this.contents);
+        return sb.toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/markedcontent/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/markedcontent/package.html
new file mode 100644 (file)
index 0000000..35ba675
--- /dev/null
@@ -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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The marked content package provides a mechanism for modeling marked-content
+sequences.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/prepress/PDBoxStyle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/prepress/PDBoxStyle.java
new file mode 100644 (file)
index 0000000..6753227
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.prepress;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+
+/**
+ * The Box Style specifies visual characteristics for displaying box areas.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDBoxStyle implements COSObjectable
+{
+    /**
+     * Style for guideline.
+     */
+    public static final String GUIDELINE_STYLE_SOLID = "S";
+    /**
+     * Style for guideline.
+     */
+    public static final String GUIDELINE_STYLE_DASHED = "D";
+
+    private COSDictionary dictionary;
+
+    /**
+     * Default Constructor.
+     *
+     */
+    public PDBoxStyle()
+    {
+        dictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor for an existing BoxStyle element.
+     *
+     * @param dic The existing dictionary.
+     */
+    public PDBoxStyle( COSDictionary dic )
+    {
+        dictionary = dic;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return dictionary;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * Get the color to be used for the guidelines.  This is guaranteed to
+     * not return null.  The color space will always be DeviceRGB and the
+     * default color is [0,0,0].
+     *
+     *@return The guideline color.
+     */
+    public PDColorState getGuidelineColor()
+    {
+        COSArray colorValues = (COSArray)dictionary.getDictionaryObject( "C" );
+        if( colorValues == null )
+        {
+            colorValues = new COSArray();
+            colorValues.add( COSInteger.ZERO );
+            colorValues.add( COSInteger.ZERO );
+            colorValues.add( COSInteger.ZERO );
+            dictionary.setItem( "C", colorValues );
+        }
+        PDColorState instance = new PDColorState( colorValues );
+        instance.setColorSpace( PDDeviceRGB.INSTANCE );
+        return instance;
+    }
+
+    /**
+     * Set the color space instance for this box style.  This must be a
+     * PDDeviceRGB!
+     *
+     * @param color The new colorspace value.
+     */
+    public void setGuideLineColor( PDColorState color )
+    {
+        COSArray values = null;
+        if( color != null )
+        {
+            values = color.getCOSColorSpaceValue();
+        }
+        dictionary.setItem( "C", values );
+    }
+
+    /**
+     * Get the width of the of the guideline in default user space units.
+     * The default is 1.
+     *
+     * @return The width of the guideline.
+     */
+    public float getGuidelineWidth()
+    {
+        return dictionary.getFloat( "W", 1 );
+    }
+
+    /**
+     * Set the guideline width.
+     *
+     * @param width The width in default user space units.
+     */
+    public void setGuidelineWidth( float width )
+    {
+        dictionary.setFloat( "W", width );
+    }
+
+    /**
+     * Get the style for the guideline.  The default is "S" for solid.
+     *
+     * @return The guideline style.
+     * @see PDBoxStyle#GUIDELINE_STYLE_DASHED
+     * @see PDBoxStyle#GUIDELINE_STYLE_SOLID
+     */
+    public String getGuidelineStyle()
+    {
+        return dictionary.getNameAsString( "S", GUIDELINE_STYLE_SOLID );
+    }
+
+    /**
+     * Set the style for the box.
+     *
+     * @param style The style for the box line.
+     * @see PDBoxStyle#GUIDELINE_STYLE_DASHED
+     * @see PDBoxStyle#GUIDELINE_STYLE_SOLID
+     */
+    public void setGuidelineStyle( String style )
+    {
+        dictionary.setName( "S", style );
+    }
+
+    /**
+     * Get the line dash pattern for this box style.  This is guaranteed to not
+     * return null.  The default is [3],0.
+     *
+     * @return The line dash pattern.
+     */
+    public PDLineDashPattern getLineDashPattern()
+    {
+        PDLineDashPattern pattern = null;
+        COSArray d = (COSArray)dictionary.getDictionaryObject( "D" );
+        if( d == null )
+        {
+            d = new COSArray();
+            d.add( COSInteger.THREE );
+            dictionary.setItem( "D", d );
+        }
+        COSArray lineArray = new COSArray();
+        lineArray.add( d );
+        //dash phase is not specified and assumed to be zero.
+        lineArray.add( COSInteger.ZERO );
+        pattern = new PDLineDashPattern( lineArray );
+        return pattern;
+    }
+
+    /**
+     * Set the line dash pattern associated with this box style.
+     *
+     * @param pattern The patter for this box style.
+     */
+    public void setLineDashPattern( PDLineDashPattern pattern )
+    {
+        COSArray array = null;
+        if( pattern != null )
+        {
+            array = pattern.getCOSDashPattern();
+        }
+        dictionary.setItem( "D", array );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/prepress/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/prepress/package.html
new file mode 100644 (file)
index 0000000..879f0d5
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains classes for prepress support in PDFBox.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDArtifactMarkedContent.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDArtifactMarkedContent.java
new file mode 100644 (file)
index 0000000..f1dd125
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;
+
+/**
+ * An artifact marked content.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public class PDArtifactMarkedContent extends PDMarkedContent
+{
+
+    public PDArtifactMarkedContent(COSDictionary properties)
+    {
+        super(COSName.ARTIFACT, properties);
+    }
+
+
+    /**
+     * Gets the type (Type).
+     * 
+     * @return the type
+     */
+    public String getType()
+    {
+        return this.getProperties().getNameAsString(COSName.TYPE);
+    }
+
+    /**
+     * Gets the artifact's bounding box (BBox).
+     * 
+     * @return the artifact's bounding box
+     */
+    public PDRectangle getBBox()
+    {
+        PDRectangle retval = null;
+        COSArray a = (COSArray) this.getProperties().getDictionaryObject(
+            COSName.BBOX);
+        if (a != null)
+        {
+            retval = new PDRectangle(a);
+        }
+        return retval;
+    }
+
+    /**
+     * Is the artifact attached to the top edge?
+     * 
+     * @return <code>true</code> if the artifact is attached to the top edge,
+     * <code>false</code> otherwise
+     */
+    public boolean isTopAttached()
+    {
+        return this.isAttached("Top");
+    }
+
+    /**
+     * Is the artifact attached to the bottom edge?
+     * 
+     * @return <code>true</code> if the artifact is attached to the bottom edge,
+     * <code>false</code> otherwise
+     */
+    public boolean isBottomAttached()
+    {
+        return this.isAttached("Bottom");
+    }
+
+    /**
+     * Is the artifact attached to the left edge?
+     * 
+     * @return <code>true</code> if the artifact is attached to the left edge,
+     * <code>false</code> otherwise
+     */
+    public boolean isLeftAttached()
+    {
+        return this.isAttached("Left");
+    }
+
+    /**
+     * Is the artifact attached to the right edge?
+     * 
+     * @return <code>true</code> if the artifact is attached to the right edge,
+     * <code>false</code> otherwise
+     */
+    public boolean isRightAttached()
+    {
+        return this.isAttached("Right");
+    }
+
+    /**
+     * Gets the subtype (Subtype).
+     * 
+     * @return the subtype
+     */
+    public String getSubtype()
+    {
+        return this.getProperties().getNameAsString(COSName.SUBTYPE);
+    }
+
+
+    /**
+     * Is the artifact attached to the given edge?
+     * 
+     * @param edge the edge
+     * @return <code>true</code> if the artifact is attached to the given edge,
+     * <code>false</code> otherwise
+     */
+    private boolean isAttached(String edge)
+    {
+        COSArray a = (COSArray) this.getProperties().getDictionaryObject(
+            COSName.ATTACHED);
+        if (a != null)
+        {
+            for (int i = 0; i < a.size(); i++)
+            {
+                if (edge.equals(a.getName(i)))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDExportFormatAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDExportFormatAttributeObject.java
new file mode 100644 (file)
index 0000000..db20316
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
+
+/**
+ * An Export Format attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDExportFormatAttributeObject extends PDLayoutAttributeObject
+{
+
+    /**
+     *  standard attribute owner: XML-1.00
+     */
+    public static final String OWNER_XML_1_00 = "XML-1.00";
+    /**
+     *  standard attribute owner: HTML-3.2
+     */
+    public static final String OWNER_HTML_3_20 = "HTML-3.2";
+    /**
+     *  standard attribute owner: HTML-4.01
+     */
+    public static final String OWNER_HTML_4_01 = "HTML-4.01";
+    /**
+     *  standard attribute owner: OEB-1.00
+     */
+    public static final String OWNER_OEB_1_00 = "OEB-1.00";
+    /**
+     *  standard attribute owner: RTF-1.05
+     */
+    public static final String OWNER_RTF_1_05 = "RTF-1.05";
+    /**
+     *  standard attribute owner: CSS-1.00
+     */
+    public static final String OWNER_CSS_1_00 = "CSS-1.00";
+    /**
+     *  standard attribute owner: CSS-2.00
+     */
+    public static final String OWNER_CSS_2_00 = "CSS-2.00";
+
+
+    /**
+     * Default constructor.
+     */
+    public PDExportFormatAttributeObject(String owner)
+    {
+        this.setOwner(owner);
+    }
+
+    /**
+     * Creates a new ExportFormat attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDExportFormatAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Gets the list numbering (ListNumbering). The default value is
+     * {@link #LIST_NUMBERING_NONE}.
+     * 
+     * @return the list numbering
+     */
+    public String getListNumbering()
+    {
+        return this.getName(PDListAttributeObject.LIST_NUMBERING,
+            PDListAttributeObject.LIST_NUMBERING_NONE);
+    }
+
+    /**
+     * Sets the list numbering (ListNumbering). The value shall be one of the
+     * following:
+     * <ul>
+     *   <li>{@link #LIST_NUMBERING_NONE},</li>
+     *   <li>{@link #LIST_NUMBERING_DISC},</li>
+     *   <li>{@link #LIST_NUMBERING_CIRCLE},</li>
+     *   <li>{@link #LIST_NUMBERING_SQUARE},</li>
+     *   <li>{@link #LIST_NUMBERING_DECIMAL},</li>
+     *   <li>{@link #LIST_NUMBERING_UPPER_ROMAN},</li>
+     *   <li>{@link #LIST_NUMBERING_LOWER_ROMAN},</li>
+     *   <li>{@link #LIST_NUMBERING_UPPER_ALPHA},</li>
+     *   <li>{@link #LIST_NUMBERING_LOWER_ALPHA}.</li>
+     * </ul>
+     * 
+     * @param listNumbering the list numbering
+     */
+    public void setListNumbering(String listNumbering)
+    {
+        this.setName(PDListAttributeObject.LIST_NUMBERING, listNumbering);
+    }
+
+    /**
+     * Gets the number of rows in the enclosing table that shall be spanned by
+     * the cell (RowSpan). The default value is 1.
+     * 
+     * @return the row span
+     */
+    public int getRowSpan()
+    {
+        return this.getInteger(PDTableAttributeObject.ROW_SPAN, 1);
+    }
+
+    /**
+     * Sets the number of rows in the enclosing table that shall be spanned by
+     * the cell (RowSpan).
+     * 
+     * @param rowSpan the row span
+     */
+    public void setRowSpan(int rowSpan)
+    {
+        this.setInteger(PDTableAttributeObject.ROW_SPAN, rowSpan);
+    }
+
+    /**
+     * Gets the number of columns in the enclosing table that shall be spanned
+     * by the cell (ColSpan). The default value is 1.
+     * 
+     * @return the column span
+     */
+    public int getColSpan()
+    {
+        return this.getInteger(PDTableAttributeObject.COL_SPAN, 1);
+    }
+
+    /**
+     * Sets the number of columns in the enclosing table that shall be spanned
+     * by the cell (ColSpan).
+     * 
+     * @param colSpan the column span
+     */
+    public void setColSpan(int colSpan)
+    {
+        this.setInteger(PDTableAttributeObject.COL_SPAN, colSpan);
+    }
+
+    /**
+     * Gets the headers (Headers). An array of byte strings, where each string
+     * shall be the element identifier (see the
+     * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+     * element that shall be used as a header associated with this cell.
+     * 
+     * @return the headers.
+     */
+    public String[] getHeaders()
+    {
+        return this.getArrayOfString(PDTableAttributeObject.HEADERS);
+    }
+
+    /**
+     * Sets the headers (Headers). An array of byte strings, where each string
+     * shall be the element identifier (see the
+     * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+     * element that shall be used as a header associated with this cell.
+     * 
+     * @param headers the headers
+     */
+    public void setHeaders(String[] headers)
+    {
+        this.setArrayOfString(PDTableAttributeObject.HEADERS, headers);
+    }
+
+    /**
+     * Gets the scope (Scope). It shall reflect whether the header cell applies
+     * to the rest of the cells in the row that contains it, the column that
+     * contains it, or both the row and the column that contain it.
+     * 
+     * @return the scope
+     */
+    public String getScope()
+    {
+        return this.getName(PDTableAttributeObject.SCOPE);
+    }
+
+    /**
+     * Sets the scope (Scope). It shall reflect whether the header cell applies
+     * to the rest of the cells in the row that contains it, the column that
+     * contains it, or both the row and the column that contain it. The value
+     * shall be one of the following:
+     * <ul>
+     *   <li>{@link #SCOPE_ROW},</li>
+     *   <li>{@link #SCOPE_COLUMN}, or</li>
+     *   <li>{@link #SCOPE_BOTH}.</li>
+     * </ul>
+     * 
+     * @param scope the scope
+     */
+    public void setScope(String scope)
+    {
+        this.setName(PDTableAttributeObject.SCOPE, scope);
+    }
+
+    /**
+     * Gets the summary of the table’s purpose and structure.
+     * 
+     * @return the summary
+     */
+    public String getSummary()
+    {
+        return this.getString(PDTableAttributeObject.SUMMARY);
+    }
+
+    /**
+     * Sets the summary of the table’s purpose and structure.
+     * 
+     * @param summary the summary
+     */
+    public void setSummary(String summary)
+    {
+        this.setString(PDTableAttributeObject.SUMMARY, summary);
+    }
+
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder().append(super.toString());
+        if (this.isSpecified(PDListAttributeObject.LIST_NUMBERING))
+        {
+            sb.append(", ListNumbering=").append(this.getListNumbering());
+        }
+        if (this.isSpecified(PDTableAttributeObject.ROW_SPAN))
+        {
+            sb.append(", RowSpan=").append(String.valueOf(this.getRowSpan()));
+        }
+        if (this.isSpecified(PDTableAttributeObject.COL_SPAN))
+        {
+            sb.append(", ColSpan=").append(String.valueOf(this.getColSpan()));
+        }
+        if (this.isSpecified(PDTableAttributeObject.HEADERS))
+        {
+            sb.append(", Headers=").append(arrayToString(this.getHeaders()));
+        }
+        if (this.isSpecified(PDTableAttributeObject.SCOPE))
+        {
+            sb.append(", Scope=").append(this.getScope());
+        }
+        if (this.isSpecified(PDTableAttributeObject.SUMMARY))
+        {
+            sb.append(", Summary=").append(this.getSummary());
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDFourColours.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDFourColours.java
new file mode 100644 (file)
index 0000000..3e0d882
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+
+/**
+ * An object for four colours.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDFourColours implements COSObjectable
+{
+
+    private COSArray array;
+
+    public PDFourColours()
+    {
+        this.array = new COSArray();
+        this.array.add(COSNull.NULL);
+        this.array.add(COSNull.NULL);
+        this.array.add(COSNull.NULL);
+        this.array.add(COSNull.NULL);
+    }
+
+    public PDFourColours(COSArray array)
+    {
+        this.array = array;
+        // ensure that array has 4 items
+        if (this.array.size() < 4)
+        {
+            for (int i = (this.array.size() - 1); i < 4; i++)
+            {
+                this.array.add(COSNull.NULL);
+            }
+        }
+    }
+
+
+    /**
+     * Gets the colour for the before edge.
+     * 
+     * @return the colour for the before edge
+     */
+    public PDGamma getBeforeColour()
+    {
+        return this.getColourByIndex(0);
+    }
+
+    /**
+     * Sets the colour for the before edge.
+     * 
+     * @param colour the colour for the before edge
+     */
+    public void setBeforeColour(PDGamma colour)
+    {
+        this.setColourByIndex(0, colour);
+    }
+
+    /**
+     * Gets the colour for the after edge.
+     * 
+     * @return the colour for the after edge
+     */
+    public PDGamma getAfterColour()
+    {
+        return this.getColourByIndex(1);
+    }
+
+    /**
+     * Sets the colour for the after edge.
+     * 
+     * @param colour the colour for the after edge
+     */
+    public void setAfterColour(PDGamma colour)
+    {
+        this.setColourByIndex(1, colour);
+    }
+
+    /**
+     * Gets the colour for the start edge.
+     * 
+     * @return the colour for the start edge
+     */
+    public PDGamma getStartColour()
+    {
+        return this.getColourByIndex(2);
+    }
+
+    /**
+     * Sets the colour for the start edge.
+     * 
+     * @param colour the colour for the start edge
+     */
+    public void setStartColour(PDGamma colour)
+    {
+        this.setColourByIndex(2, colour);
+    }
+
+    /**
+     * Gets the colour for the end edge.
+     * 
+     * @return the colour for the end edge
+     */
+    public PDGamma getEndColour()
+    {
+        return this.getColourByIndex(3);
+    }
+
+    /**
+     * Sets the colour for the end edge.
+     * 
+     * @param colour the colour for the end edge
+     */
+    public void setEndColour(PDGamma colour)
+    {
+        this.setColourByIndex(3, colour);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.array;
+    }
+
+
+    /**
+     * Gets the colour by edge index.
+     * 
+     * @param index edge index
+     * @return the colour
+     */
+    private PDGamma getColourByIndex(int index)
+    {
+        PDGamma retval = null;
+        COSBase item = this.array.getObject(index);
+        if (item instanceof COSArray)
+        {
+            retval = new PDGamma((COSArray) item);
+        }
+        return retval;
+    }
+
+    /**
+     * Sets the colour by edge index.
+     * 
+     * @param index the edge index
+     * @param colour the colour
+     */
+    private void setColourByIndex(int index, PDGamma colour)
+    {
+        COSBase base;
+        if (colour == null)
+        {
+            base = COSNull.NULL;
+        }
+        else
+        {
+            base = colour.getCOSArray();
+        }
+        this.array.set(index, base);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDLayoutAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDLayoutAttributeObject.java
new file mode 100644 (file)
index 0000000..fd9a4c2
--- /dev/null
@@ -0,0 +1,1715 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+
+/**
+ * A Layout attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDLayoutAttributeObject extends PDStandardAttributeObject
+{
+
+    /**
+     * standard attribute owner: Layout
+     */
+    public static final String OWNER_LAYOUT = "Layout";
+
+    private static final String PLACEMENT                    = "Placement";
+    private static final String WRITING_MODE                 = "WritingMode";
+    private static final String BACKGROUND_COLOR             = "BackgroundColor";
+    private static final String BORDER_COLOR                 = "BorderColor";
+    private static final String BORDER_STYLE                 = "BorderStyle";
+    private static final String BORDER_THICKNESS             = "BorderThickness";
+    private static final String PADDING                      = "Padding";
+    private static final String COLOR                        = "Color";
+    private static final String SPACE_BEFORE                 = "SpaceBefore";
+    private static final String SPACE_AFTER                  = "SpaceAfter";
+    private static final String START_INDENT                 = "StartIndent";
+    private static final String END_INDENT                   = "EndIndent";
+    private static final String TEXT_INDENT                  = "TextIndent";
+    private static final String TEXT_ALIGN                   = "TextAlign";
+    private static final String BBOX                         = "BBox";
+    private static final String WIDTH                        = "Width";
+    private static final String HEIGHT                       = "Height";
+    private static final String BLOCK_ALIGN                  = "BlockAlign";
+    private static final String INLINE_ALIGN                 = "InlineAlign";
+    private static final String T_BORDER_STYLE               = "TBorderStyle";
+    private static final String T_PADDING                    = "TPadding";
+    private static final String BASELINE_SHIFT               = "BaselineShift";
+    private static final String LINE_HEIGHT                  = "LineHeight";
+    private static final String TEXT_DECORATION_COLOR        = "TextDecorationColor";
+    private static final String TEXT_DECORATION_THICKNESS    = "TextDecorationThickness";
+    private static final String TEXT_DECORATION_TYPE         = "TextDecorationType";
+    private static final String RUBY_ALIGN                   = "RubyAlign";
+    private static final String RUBY_POSITION                = "RubyPosition";
+    private static final String GLYPH_ORIENTATION_VERTICAL   = "GlyphOrientationVertical";
+    private static final String COLUMN_COUNT                 = "ColumnCount";
+    private static final String COLUMN_GAP                   = "ColumnGap";
+    private static final String COLUMN_WIDTHS                = "ColumnWidths";
+
+    /**
+     * Placement: Block: Stacked in the block-progression direction within an
+     * enclosing reference area or parent BLSE.
+     */
+    public static final String PLACEMENT_BLOCK                 = "Block";
+    /**
+     * Placement: Inline: Packed in the inline-progression direction within an
+     * enclosing BLSE.
+     */
+    public static final String PLACEMENT_INLINE                = "Inline";
+    /**
+     * Placement: Before: Placed so that the before edge of the element’s
+     * allocation rectangle coincides with that of the nearest enclosing
+     * reference area. The element may float, if necessary, to achieve the
+     * specified placement. The element shall be treated as a block occupying
+     * the full extent of the enclosing reference area in the inline direction.
+     * Other content shall be stacked so as to begin at the after edge of the
+     * element’s allocation rectangle.
+     */
+    public static final String PLACEMENT_BEFORE                = "Before";
+    /**
+     * Placement: Start: Placed so that the start edge of the element’s
+     * allocation rectangle coincides with that of the nearest enclosing
+     * reference area. The element may float, if necessary, to achieve the
+     * specified placement. Other content that would intrude into the element’s
+     * allocation rectangle shall be laid out as a runaround.
+     */
+    public static final String PLACEMENT_START                 = "Start";
+    /**
+     * Placement: End: Placed so that the end edge of the element’s allocation
+     * rectangle coincides with that of the nearest enclosing reference area.
+     * The element may float, if necessary, to achieve the specified placement.
+     * Other content that would intrude into the element’s allocation rectangle
+     * shall be laid out as a runaround.
+     */
+    public static final String PLACEMENT_END                   = "End";
+    /**
+     * WritingMode: LrTb: Inline progression from left to right; block
+     * progression from top to bottom. This is the typical writing mode for
+     * Western writing systems.
+     */
+    public static final String WRITING_MODE_LRTB               = "LrTb";
+    /**
+     * WritingMode: RlTb: Inline progression from right to left; block
+     * progression from top to bottom. This is the typical writing mode for
+     * Arabic and Hebrew writing systems.
+     */
+    public static final String WRITING_MODE_RLTB               = "RlTb";
+    /**
+     * WritingMode: TbRl: Inline progression from top to bottom; block
+     * progression from right to left. This is the typical writing mode for
+     * Chinese and Japanese writing systems.
+     */
+    public static final String WRITING_MODE_TBRL               = "TbRl";
+    /**
+     * BorderStyle: None: No border. Forces the computed value of
+     * BorderThickness to be 0.
+     */
+    public static final String BORDER_STYLE_NONE               = "None";
+    /**
+     * BorderStyle: Hidden: Same as {@link #BORDER_STYLE_NONE}, except in terms
+     * of border conflict resolution for table elements.
+     */
+    public static final String BORDER_STYLE_HIDDEN             = "Hidden";
+    /**
+     * BorderStyle: Dotted: The border is a series of dots.
+     */
+    public static final String BORDER_STYLE_DOTTED             = "Dotted";
+    /**
+     * BorderStyle: Dashed: The border is a series of short line segments.
+     */
+    public static final String BORDER_STYLE_DASHED             = "Dashed";
+    /**
+     * BorderStyle: Solid: The border is a single line segment.
+     */
+    public static final String BORDER_STYLE_SOLID              = "Solid";
+    /**
+     * BorderStyle: Double: The border is two solid lines. The sum of the two
+     * lines and the space between them equals the value of BorderThickness.
+     */
+    public static final String BORDER_STYLE_DOUBLE             = "Double";
+    /**
+     * BorderStyle: Groove: The border looks as though it were carved into the
+     * canvas.
+     */
+    public static final String BORDER_STYLE_GROOVE             = "Groove";
+    /**
+     * BorderStyle: Ridge: The border looks as though it were coming out of the
+     * canvas (the opposite of {@link #BORDER_STYLE_GROOVE}).
+     */
+    public static final String BORDER_STYLE_RIDGE              = "Ridge";
+    /**
+     * BorderStyle: Inset: The border makes the entire box look as though it
+     * were embedded in the canvas.
+     */
+    public static final String BORDER_STYLE_INSET              = "Inset";
+    /**
+     * BorderStyle: Outset: The border makes the entire box look as though it
+     * were coming out of the canvas (the opposite of {@link #BORDER_STYLE_INSET}.
+     */
+    public static final String BORDER_STYLE_OUTSET             = "Outset";
+    /**
+     * TextAlign: Start: Aligned with the start edge.
+     */
+    public static final String TEXT_ALIGN_START                = "Start";
+    /**
+     * TextAlign: Center: Centered between the start and end edges.
+     */
+    public static final String TEXT_ALIGN_CENTER               = "Center";
+    /**
+     * TextAlign: End: Aligned with the end edge.
+     */
+    public static final String TEXT_ALIGN_END                  = "End";
+    /**
+     * TextAlign: Justify: Aligned with both the start and end edges, with
+     * internal spacing within each line expanded, if necessary, to achieve such
+     * alignment. The last (or only) line shall be aligned with the start edge
+     * only.
+     */
+    public static final String TEXT_ALIGN_JUSTIFY              = "Justify";
+    /**
+     * Width: Auto
+     */
+    public static final String WIDTH_AUTO                      = "Auto";
+    /**
+     * Height: Auto
+     */
+    public static final String HEIGHT_AUTO                     = "Auto";
+    /**
+     * BlockAlign: Before: Before edge of the first child’s allocation rectangle
+     * aligned with that of the table cell’s content rectangle.
+     */
+    public static final String BLOCK_ALIGN_BEFORE              = "Before";
+    /**
+     * BlockAlign: Middle: Children centered within the table cell. The distance
+     * between the before edge of the first child’s allocation rectangle and
+     * that of the table cell’s content rectangle shall be the same as the
+     * distance between the after edge of the last child’s allocation rectangle
+     * and that of the table cell’s content rectangle.
+     */
+    public static final String BLOCK_ALIGN_MIDDLE              = "Middle";
+    /**
+     * BlockAlign: After: After edge of the last child’s allocation rectangle
+     * aligned with that of the table cell’s content rectangle.
+     */
+    public static final String BLOCK_ALIGN_AFTER               = "After";
+    /**
+     * BlockAlign: Justify: Children aligned with both the before and after
+     * edges of the table cell’s content rectangle. The first child shall be
+     * placed as described for {@link #BLOCK_ALIGN_BEFORE} and the last child as
+     * described for {@link #BLOCK_ALIGN_AFTER}, with equal spacing between the
+     * children. If there is only one child, it shall be aligned with the before
+     * edge only, as for {@link #BLOCK_ALIGN_BEFORE}.
+     */
+    public static final String BLOCK_ALIGN_JUSTIFY             = "Justify";
+    /**
+     * InlineAlign: Start: Start edge of each child’s allocation rectangle
+     * aligned with that of the table cell’s content rectangle.
+     */
+    public static final String INLINE_ALIGN_START              = "Start";
+    /**
+     * InlineAlign: Center: Each child centered within the table cell. The
+     * distance between the start edges of the child’s allocation rectangle and
+     * the table cell’s content rectangle shall be the same as the distance
+     * between their end edges.
+     */
+    public static final String INLINE_ALIGN_CENTER             = "Center";
+    /**
+     * InlineAlign: End: End edge of each child’s allocation rectangle aligned
+     * with that of the table cell’s content rectangle.
+     */
+    public static final String INLINE_ALIGN_END                = "End";
+    /**
+     * LineHeight: NormalAdjust the line height to include any nonzero value
+     * specified for BaselineShift.
+     */
+    public static final String LINE_HEIGHT_NORMAL              = "Normal";
+    /**
+     * LineHeight: Auto: Adjustment for the value of BaselineShift shall not be
+     * made.
+     */
+    public static final String LINE_HEIGHT_AUTO                = "Auto";
+    /**
+     * TextDecorationType: None: No text decoration
+     */
+    public static final String TEXT_DECORATION_TYPE_NONE       = "None";
+    /**
+     * TextDecorationType: Underline: A line below the text
+     */
+    public static final String TEXT_DECORATION_TYPE_UNDERLINE  = "Underline";
+    /**
+     * TextDecorationType: Overline: A line above the text
+     */
+    public static final String TEXT_DECORATION_TYPE_OVERLINE   = "Overline";
+    /**
+     * TextDecorationType: LineThrough: A line through the middle of the text
+     */
+    public static final String TEXT_DECORATION_TYPE_LINE_THROUGH = "LineThrough";
+    /**
+     * RubyAlign: Start: The content shall be aligned on the start edge in the
+     * inline-progression direction.
+     */
+    public static final String RUBY_ALIGN_START                = "Start";
+    /**
+     * RubyAlign: Center: The content shall be centered in the
+     * inline-progression direction.
+     */
+    public static final String RUBY_ALIGN_CENTER               = "Center";
+    /**
+     * RubyAlign: End: The content shall be aligned on the end edge in the
+     * inline-progression direction.
+     */
+    public static final String RUBY_ALIGN_END                  = "End";
+    /**
+     * RubyAlign: Justify:  The content shall be expanded to fill the available
+     * width in the inline-progression direction.
+     */
+    public static final String RUBY_ALIGN_JUSTIFY              = "Justify";
+    /**
+     * RubyAlign: Distribute: The content shall be expanded to fill the
+     * available width in the inline-progression direction. However, space shall
+     * also be inserted at the start edge and end edge of the text. The spacing
+     * shall be distributed using a 1:2:1 (start:infix:end) ratio. It shall be
+     * changed to a 0:1:1 ratio if the ruby appears at the start of a text line
+     * or to a 1:1:0 ratio if the ruby appears at the end of the text line.
+     */
+    public static final String RUBY_ALIGN_DISTRIBUTE           = "Distribute";
+    /**
+     * RubyPosition: Before: The RT content shall be aligned along the before
+     * edge of the element.
+     */
+    public static final String RUBY_POSITION_BEFORE            = "Before";
+    /**
+     * RubyPosition: After: The RT content shall be aligned along the after edge
+     * of the element.
+     */
+    public static final String RUBY_POSITION_AFTER             = "After";
+    /**
+     * RubyPosition: Warichu: The RT and associated RP elements shall be
+     * formatted as a warichu, following the RB element.
+     */
+    public static final String RUBY_POSITION_WARICHU           = "Warichu";
+    /**
+     * RubyPosition: Inline: The RT and associated RP elements shall be
+     * formatted as a parenthesis comment, following the RB element.
+     */
+    public static final String RUBY_POSITION_INLINE            = "Inline";
+    /**
+     * GlyphOrientationVertical: Auto
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_AUTO = "Auto";
+    /**
+     * GlyphOrientationVertical: -180°
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_MINUS_180_DEGREES = "-180";
+    /**
+     * GlyphOrientationVertical: -90°
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_MINUS_90_DEGREES = "-90";
+    /**
+     * GlyphOrientationVertical: 0°
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_ZERO_DEGREES = "0";
+    /**
+     * GlyphOrientationVertical: 90°
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_90_DEGREES = "90";
+    /**
+     * GlyphOrientationVertical: 180°
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_180_DEGREES = "180";
+    /**
+     * GlyphOrientationVertical: 270°
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_270_DEGREES = "270";
+    /**
+     * GlyphOrientationVertical: 360°
+     */
+    public static final String GLYPH_ORIENTATION_VERTICAL_360_DEGREES = "360";
+
+
+    /**
+     * Default constructor.
+     */
+    public PDLayoutAttributeObject()
+    {
+        this.setOwner(OWNER_LAYOUT);
+    }
+
+    /**
+     * Creates a new Layout attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDLayoutAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Gets the positioning of the element with respect to the enclosing
+     * reference area and other content (Placement). The default value is
+     * {@link #PLACEMENT_INLINE}.
+     * 
+     * @return the placement
+     */
+    public String getPlacement()
+    {
+        return this.getName(PLACEMENT, PLACEMENT_INLINE);
+    }
+
+    /**
+     * Sets the positioning of the element with respect to the enclosing
+     * reference area and other content (Placement). The value should be one of:
+     * <ul>
+     *   <li>{@link #PLACEMENT_BLOCK},</li>
+     *   <li>{@link #PLACEMENT_INLINE},</li>
+     *   <li>{@link #PLACEMENT_BEFORE},</li>
+     *   <li>{@link #PLACEMENT_START},</li>
+     *   <li>{@link #PLACEMENT_END}.</li>
+     * <ul>
+     * 
+     * @param placement the placement
+     */
+    public void setPlacement(String placement)
+    {
+        this.setName(PLACEMENT, placement);
+    }
+
+    /**
+     * Gets the writing mode (WritingMode). The default value is
+     * {@link #WRITING_MODE_LRTB}.
+     * 
+     * @return the writing mode
+     */
+    public String getWritingMode()
+    {
+        return this.getName(WRITING_MODE, WRITING_MODE_LRTB);
+    }
+
+    /**
+     * Sets the writing mode (WritingMode). The value should be one of:
+     * <ul>
+     *   <li>{@link #WRITING_MODE_LRTB},</li>
+     *   <li>{@link #WRITING_MODE_RLTB},</li>
+     *   <li>{@link #WRITING_MODE_TBRL}.</li>
+     * </ul>
+     * 
+     * @param writingMode the writing mode
+     */
+    public void setWritingMode(String writingMode)
+    {
+        this.setName(WRITING_MODE, writingMode);
+    }
+
+    /**
+     * Gets the background colour (BackgroundColor).
+     * 
+     * @return the background colour
+     */
+    public PDGamma getBackgroundColor()
+    {
+        return this.getColor(BACKGROUND_COLOR);
+    }
+
+    /**
+     * Sets the background colour (BackgroundColor).
+     * 
+     * @param backgroundColor the background colour
+     */
+    public void setBackgroundColor(PDGamma backgroundColor)
+    {
+        this.setColor(BACKGROUND_COLOR, backgroundColor);
+    }
+
+    /**
+     * Gets the border colour (BorderColor).
+     * 
+     * @return a single border colour ({@link PDGamma}) or four border colours
+     *  ({@link PDFourColours})
+     */
+    public Object getBorderColors()
+    {
+        return this.getColorOrFourColors(BORDER_COLOR);
+    }
+
+    /**
+     * Sets the same border colour for all four sides (BorderColor).
+     * 
+     * @param borderColor the border colour
+     */
+    public void setAllBorderColors(PDGamma borderColor)
+    {
+        this.setColor(BORDER_COLOR, borderColor);
+    }
+
+    /**
+     * Sets the border colours for four sides separately (BorderColor).
+     * 
+     * @param borderColors the border colours
+     */
+    public void setBorderColors(PDFourColours borderColors)
+    {
+        this.setFourColors(BORDER_COLOR, borderColors);
+    }
+
+    /**
+     * Gets the border style (BorderStyle). The default value is
+     * {@link #BORDER_STYLE_NONE}.
+     * 
+     * @return the border styles (a String or an array of four Strings)
+     */
+    public Object getBorderStyle()
+    {
+        return this.getNameOrArrayOfName(BORDER_STYLE, BORDER_STYLE_NONE);
+    }
+
+    /**
+     * Sets the same border style for all four sides (BorderStyle). The value
+     * should be one of:
+     * <ul>
+     *   <li>{@link #BORDER_STYLE_NONE},</li>
+     *   <li>{@link #BORDER_STYLE_HIDDEN},</li>
+     *   <li>{@link #BORDER_STYLE_DOTTED},</li>
+     *   <li>{@link #BORDER_STYLE_DASHED},</li>
+     *   <li>{@link #BORDER_STYLE_SOLID},</li>
+     *   <li>{@link #BORDER_STYLE_DOUBLE},</li>
+     *   <li>{@link #BORDER_STYLE_GROOVE},</li>
+     *   <li>{@link #BORDER_STYLE_RIDGE},</li>
+     *   <li>{@link #BORDER_STYLE_INSET},</li>
+     *   <li>{@link #BORDER_STYLE_OUTSET}.</li>
+     * </ul>
+     * 
+     * @param borderStyle the border style
+     */
+    public void setAllBorderStyles(String borderStyle)
+    {
+        this.setName(BORDER_STYLE, borderStyle);
+    }
+
+    /**
+     * Sets the border styles for four sides separately (BorderStyle). The
+     * values should be of:
+     * <ul>
+     *   <li>{@link #BORDER_STYLE_NONE},</li>
+     *   <li>{@link #BORDER_STYLE_HIDDEN},</li>
+     *   <li>{@link #BORDER_STYLE_DOTTED},</li>
+     *   <li>{@link #BORDER_STYLE_DASHED},</li>
+     *   <li>{@link #BORDER_STYLE_SOLID},</li>
+     *   <li>{@link #BORDER_STYLE_DOUBLE},</li>
+     *   <li>{@link #BORDER_STYLE_GROOVE},</li>
+     *   <li>{@link #BORDER_STYLE_RIDGE},</li>
+     *   <li>{@link #BORDER_STYLE_INSET},</li>
+     *   <li>{@link #BORDER_STYLE_OUTSET}.</li>
+     * </ul>
+     * 
+     * @param borderStyles the border styles (an array of four Strings)
+     */
+    public void setBorderStyles(String[] borderStyles)
+    {
+        this.setArrayOfName(BORDER_STYLE, borderStyles);
+    }
+
+    /**
+     * Gets the border thickness (BorderThickness).
+     * 
+     * @return the border thickness (a Float or an array of four floats)
+     */
+    public Object getBorderThickness()
+    {
+        return this.getNumberOrArrayOfNumber(BORDER_THICKNESS, UNSPECIFIED);
+    }
+
+    /**
+     * Sets the same border thickness for all four sides (BorderThickness).
+     * 
+     * @param borderThickness the border thickness
+     */
+    public void setAllBorderThicknesses(float borderThickness)
+    {
+        this.setNumber(BORDER_THICKNESS, borderThickness);
+    }
+
+    /**
+     * Sets the same border thickness for all four sides (BorderThickness).
+     * 
+     * @param borderThickness the border thickness
+     */
+    public void setAllBorderThicknesses(int borderThickness)
+    {
+        this.setNumber(BORDER_THICKNESS, borderThickness);
+    }
+
+    /**
+     * Sets the border thicknesses for four sides separately (BorderThickness).
+     * 
+     * @param borderThicknesses the border thickness (an array of four floats)
+     */
+    public void setBorderThicknesses(float[] borderThicknesses)
+    {
+        this.setArrayOfNumber(BORDER_THICKNESS, borderThicknesses);
+    }
+
+    /**
+     * Gets the padding (Padding). The default value is 0.
+     * 
+     * @return the padding (a Float or an array of float)
+     */
+    public Object getPadding()
+    {
+        return this.getNumberOrArrayOfNumber(PADDING, 0.f);
+    }
+
+    /**
+     * Sets the same padding for all four sides (Padding).
+     * 
+     * @param padding the padding
+     */
+    public void setAllPaddings(float padding)
+    {
+        this.setNumber(PADDING, padding);
+    }
+
+    /**
+     * Sets the same padding for all four sides (Padding).
+     * 
+     * @param padding the padding
+     */
+    public void setAllPaddings(int padding)
+    {
+        this.setNumber(PADDING, padding);
+    }
+
+    /**
+     * Sets the paddings for four sides separately (Padding).
+     * 
+     * @param paddings the paddings (an array of four floats)
+     */
+    public void setPaddings(float[] paddings)
+    {
+        this.setArrayOfNumber(PADDING, paddings);
+    }
+
+    /**
+     * Gets the color to be used for drawing text and the default value for the
+     * colour of table borders and text decorations (Color).
+     * 
+     * @return the colour
+     */
+    public PDGamma getColor()
+    {
+        return this.getColor(COLOR);
+    }
+
+    /**
+     * Sets the color to be used for drawing text and the default value for the
+     * colour of table borders and text decorations (Color).
+     * 
+     * @param color the colour
+     */
+    public void setColor(PDGamma color)
+    {
+        this.setColor(COLOR, color);
+    }
+
+    /**
+     * Gets the amount of extra space preceding the before edge of the BLSE in
+     * the block-progression direction (SpaceBefore). The default value is 0.
+     * 
+     * @return the space before
+     */
+    public float getSpaceBefore()
+    {
+        return this.getNumber(SPACE_BEFORE, 0.f);
+    }
+
+    /**
+     * Sets the amount of extra space preceding the before edge of the BLSE in
+     * the block-progression direction (SpaceBefore).
+     * 
+     * @param spaceBefore the space before
+     */
+    public void setSpaceBefore(float spaceBefore)
+    {
+        this.setNumber(SPACE_BEFORE, spaceBefore);
+    }
+
+    /**
+     * Sets the amount of extra space preceding the before edge of the BLSE in
+     * the block-progression direction (SpaceBefore).
+     * 
+     * @param spaceBefore the space before
+     */
+    public void setSpaceBefore(int spaceBefore)
+    {
+        this.setNumber(SPACE_BEFORE, spaceBefore);
+    }
+
+    /**
+     * Gets the amount of extra space following the after edge of the BLSE in
+     * the block-progression direction (SpaceAfter). The default value is 0.
+     * 
+     * @return the space after
+     */
+    public float getSpaceAfter()
+    {
+        return this.getNumber(SPACE_AFTER, 0.f);
+    }
+
+    /**
+     * Sets the amount of extra space following the after edge of the BLSE in
+     * the block-progression direction (SpaceAfter).
+     * 
+     * @param spaceAfter the space after
+     */
+    public void setSpaceAfter(float spaceAfter)
+    {
+        this.setNumber(SPACE_AFTER, spaceAfter);
+    }
+
+    /**
+     * Sets the amount of extra space following the after edge of the BLSE in
+     * the block-progression direction (SpaceAfter).
+     * 
+     * @param spaceAfter the space after
+     */
+    public void setSpaceAfter(int spaceAfter)
+    {
+        this.setNumber(SPACE_AFTER, spaceAfter);
+    }
+
+    /**
+     * Gets the distance from the start edge of the reference area to that of
+     * the BLSE in the inline-progression direction (StartIndent). The default value is 0.
+     * 
+     * @return the start indent
+     */
+    public float getStartIndent()
+    {
+        return this.getNumber(START_INDENT, 0.f);
+    }
+
+    /**
+     * Sets the distance from the start edge of the reference area to that of
+     * the BLSE in the inline-progression direction (StartIndent).
+     * 
+     * @param startIndent the start indent
+     */
+    public void setStartIndent(float startIndent)
+    {
+        this.setNumber(START_INDENT, startIndent);
+    }
+
+    /**
+     * Sets the distance from the start edge of the reference area to that of
+     * the BLSE in the inline-progression direction (StartIndent).
+     * 
+     * @param startIndent the start indent
+     */
+    public void setStartIndent(int startIndent)
+    {
+        this.setNumber(START_INDENT, startIndent);
+    }
+
+    /**
+     * Gets the distance from the end edge of the BLSE to that of the reference
+     * area in the inline-progression direction (EndIndent). The default value
+     * is 0.
+     * 
+     * @return the end indent
+     */
+    public float getEndIndent()
+    {
+        return this.getNumber(END_INDENT, 0.f);
+    }
+
+    /**
+     * Sets the distance from the end edge of the BLSE to that of the reference
+     * area in the inline-progression direction (EndIndent).
+     * 
+     * @param endIndent the end indent
+     */
+    public void setEndIndent(float endIndent)
+    {
+        this.setNumber(END_INDENT, endIndent);
+    }
+
+    /**
+     * Sets the distance from the end edge of the BLSE to that of the reference
+     * area in the inline-progression direction (EndIndent).
+     * 
+     * @param endIndent the end indent
+     */
+    public void setEndIndent(int endIndent)
+    {
+        this.setNumber(END_INDENT, endIndent);
+    }
+
+    /**
+     * Gets the additional distance in the inline-progression direction from the
+     * start edge of the BLSE, as specified by StartIndent, to that of the first
+     * line of text (TextIndent). The default value is 0.
+     * 
+     * @return the text indent
+     */
+    public float getTextIndent()
+    {
+        return this.getNumber(TEXT_INDENT, 0.f);
+    }
+
+    /**
+     * Sets the additional distance in the inline-progression direction from the
+     * start edge of the BLSE, as specified by StartIndent, to that of the first
+     * line of text (TextIndent).
+     * 
+     * @param textIndent the text indent
+     */
+    public void setTextIndent(float textIndent)
+    {
+        this.setNumber(TEXT_INDENT, textIndent);
+    }
+
+    /**
+     * Sets the additional distance in the inline-progression direction from the
+     * start edge of the BLSE, as specified by StartIndent, to that of the first
+     * line of text (TextIndent).
+     * 
+     * @param textIndent the text indent
+     */
+    public void setTextIndent(int textIndent)
+    {
+        this.setNumber(TEXT_INDENT, textIndent);
+    }
+
+    /**
+     * Gets the alignment, in the inline-progression direction, of text and
+     * other content within lines of the BLSE (TextAlign). The default value is
+     * {@link #TEXT_ALIGN_START}.
+     * 
+     * @return the text alignment
+     */
+    public String getTextAlign()
+    {
+        return this.getName(TEXT_ALIGN, TEXT_ALIGN_START);
+    }
+
+    /**
+     * Sets the alignment, in the inline-progression direction, of text and
+     * other content within lines of the BLSE (TextAlign). The value should be
+     * one of:
+     * <ul>
+     *   <li>{@link #TEXT_ALIGN_START},</li>
+     *   <li>{@link #TEXT_ALIGN_CENTER},</li>
+     *   <li>{@link #TEXT_ALIGN_END},</li>
+     *   <li>{@link #TEXT_ALIGN_JUSTIFY}.</li>
+     * </ul>
+     * 
+     * @param textIndent the text alignment
+     */
+    public void setTextAlign(String textIndent)
+    {
+        this.setName(TEXT_ALIGN, textIndent);
+    }
+
+    /**
+     * Gets the bounding box.
+     * 
+     * @return the bounding box.
+     */
+    public PDRectangle getBBox()
+    {
+        COSArray array =
+            (COSArray) this.getCOSDictionary().getDictionaryObject(BBOX);
+        if (array != null)
+        {
+            return new PDRectangle(array);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the bounding box.
+     * 
+     * @param bbox the bounding box
+     */
+    public void setBBox(PDRectangle bbox)
+    {
+        String name = BBOX;
+        COSBase oldValue = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setItem(name, bbox);
+        COSBase newValue = bbox == null ? null : bbox.getCOSObject();
+        this.potentiallyNotifyChanged(oldValue, newValue);
+    }
+
+    /**
+     * Gets the width of the element’s content rectangle in the
+     * inline-progression direction (Width). The default value is
+     * {@link #WIDTH_AUTO}.
+     * 
+     * @return the width (a Float or a String)
+     */
+    public Object getWidth()
+    {
+        return this.getNumberOrName(WIDTH, WIDTH_AUTO);
+    }
+
+    /**
+     * Sets the width of the element’s content rectangle in the
+     * inline-progression direction (Width) to {@link #WIDTH_AUTO}.
+     */
+    public void setWidthAuto()
+    {
+        this.setName(WIDTH, WIDTH_AUTO);
+    }
+
+    /**
+     * Sets the width of the element’s content rectangle in the
+     * inline-progression direction (Width).
+     * 
+     * @param width the width
+     */
+    public void setWidth(float width)
+    {
+        this.setNumber(WIDTH, width);
+    }
+
+    /**
+     * Sets the width of the element’s content rectangle in the
+     * inline-progression direction (Width).
+     * 
+     * @param width the width
+     */
+    public void setWidth(int width)
+    {
+        this.setNumber(WIDTH, width);
+    }
+
+    /**
+     * Gets the height of the element’s content rectangle in the
+     * block-progression direction (Height). The default value is
+     * {@link #HEIGHT_AUTO}.
+     * 
+     * @return the height (a Float or a String)
+     */
+    public Object getHeight()
+    {
+        return this.getNumberOrName(HEIGHT, HEIGHT_AUTO);
+    }
+
+    /**
+     * Sets the height of the element’s content rectangle in the
+     * block-progression direction (Height) to {@link #HEIGHT_AUTO}.
+     */
+    public void setHeightAuto()
+    {
+        this.setName(HEIGHT, HEIGHT_AUTO);
+    }
+
+    /**
+     * Sets the height of the element’s content rectangle in the
+     * block-progression direction (Height).
+     * 
+     * @param height the height
+     */
+    public void setHeight(float height)
+    {
+        this.setNumber(HEIGHT, height);
+    }
+
+    /**
+     * Sets the height of the element’s content rectangle in the
+     * block-progression direction (Height).
+     * 
+     * @param height the height
+     */
+    public void setHeight(int height)
+    {
+        this.setNumber(HEIGHT, height);
+    }
+
+    /**
+     * Gets the alignment, in the block-progression direction, of content within
+     * the table cell (BlockAlign). The default value is
+     * {@link #BLOCK_ALIGN_BEFORE}.
+     * 
+     * @return the block alignment
+     */
+    public String getBlockAlign()
+    {
+        return this.getName(BLOCK_ALIGN, BLOCK_ALIGN_BEFORE);
+    }
+
+    /**
+     * Sets the alignment, in the block-progression direction, of content within
+     * the table cell (BlockAlign). The value should be one of:
+     * <ul>
+     *   <li>{@link #BLOCK_ALIGN_BEFORE},</li>
+     *   <li>{@link #BLOCK_ALIGN_MIDDLE},</li>
+     *   <li>{@link #BLOCK_ALIGN_AFTER},</li>
+     *   <li>{@link #BLOCK_ALIGN_JUSTIFY}.</li>
+     * </ul>
+     * 
+     * @param blockAlign the block alignment
+     */
+    public void setBlockAlign(String blockAlign)
+    {
+        this.setName(BLOCK_ALIGN, blockAlign);
+    }
+
+    /**
+     * Gets the alignment, in the inline-progression direction, of content
+     * within the table cell (InlineAlign). The default value is
+     * {@link #INLINE_ALIGN_START}.
+     * 
+     * @return the inline alignment
+     */
+    public String getInlineAlign()
+    {
+        return this.getName(INLINE_ALIGN, INLINE_ALIGN_START);
+    }
+
+    /**
+     * Sets the alignment, in the inline-progression direction, of content
+     * within the table cell (InlineAlign). The value should be one of
+     * <ul>
+     *   <li>{@link #INLINE_ALIGN_START},</li>
+     *   <li>{@link #INLINE_ALIGN_CENTER},</li>
+     *   <li>{@link #INLINE_ALIGN_END}.</li>
+     * </ul>
+     * 
+     * @param inlineAlign the inline alignment
+     */
+    public void setInlineAlign(String inlineAlign)
+    {
+        this.setName(INLINE_ALIGN, inlineAlign);
+    }
+
+    /**
+     * Gets the style of the border drawn on each edge of a table cell
+     * (TBorderStyle).
+     * 
+     * @return
+     */
+    public Object getTBorderStyle()
+    {
+        return this.getNameOrArrayOfName(T_BORDER_STYLE, BORDER_STYLE_NONE);
+    }
+
+    /**
+     * Sets the same table border style for all four sides (TBorderStyle). The
+     * value should be one of:
+     * <ul>
+     *   <li>{@link #BORDER_STYLE_NONE},</li>
+     *   <li>{@link #BORDER_STYLE_HIDDEN},</li>
+     *   <li>{@link #BORDER_STYLE_DOTTED},</li>
+     *   <li>{@link #BORDER_STYLE_DASHED},</li>
+     *   <li>{@link #BORDER_STYLE_SOLID},</li>
+     *   <li>{@link #BORDER_STYLE_DOUBLE},</li>
+     *   <li>{@link #BORDER_STYLE_GROOVE},</li>
+     *   <li>{@link #BORDER_STYLE_RIDGE},</li>
+     *   <li>{@link #BORDER_STYLE_INSET},</li>
+     *   <li>{@link #BORDER_STYLE_OUTSET}.</li>
+     * </ul>
+     * 
+     * @param tBorderStyle the table border style
+     */
+    public void setAllTBorderStyles(String tBorderStyle)
+    {
+        this.setName(T_BORDER_STYLE, tBorderStyle);
+    }
+
+    /**
+     * Sets the style of the border drawn on each edge of a table cell
+     * (TBorderStyle). The values should be of:
+     * <ul>
+     *   <li>{@link #BORDER_STYLE_NONE},</li>
+     *   <li>{@link #BORDER_STYLE_HIDDEN},</li>
+     *   <li>{@link #BORDER_STYLE_DOTTED},</li>
+     *   <li>{@link #BORDER_STYLE_DASHED},</li>
+     *   <li>{@link #BORDER_STYLE_SOLID},</li>
+     *   <li>{@link #BORDER_STYLE_DOUBLE},</li>
+     *   <li>{@link #BORDER_STYLE_GROOVE},</li>
+     *   <li>{@link #BORDER_STYLE_RIDGE},</li>
+     *   <li>{@link #BORDER_STYLE_INSET},</li>
+     *   <li>{@link #BORDER_STYLE_OUTSET}.</li>
+     * </ul>
+     * 
+     * @param tBorderStyles
+     */
+    public void setTBorderStyles(String[] tBorderStyles)
+    {
+        this.setArrayOfName(T_BORDER_STYLE, tBorderStyles);
+    }
+
+    /**
+     * Gets the offset to account for the separation between the table cell’s
+     * content rectangle and the surrounding border (TPadding). The default
+     * value is 0.
+     * 
+     * @return the table padding (a Float or an array of float)
+     */
+    public Object getTPadding()
+    {
+        return this.getNumberOrArrayOfNumber(T_PADDING, 0.f);
+    }
+
+    /**
+     * Sets the same table padding for all four sides (TPadding).
+     * 
+     * @param tPadding the table padding
+     */
+    public void setAllTPaddings(float tPadding)
+    {
+        this.setNumber(T_PADDING, tPadding);
+    }
+
+    /**
+     * Sets the same table padding for all four sides (TPadding).
+     * 
+     * @param tPadding the table padding
+     */
+    public void setAllTPaddings(int tPadding)
+    {
+        this.setNumber(T_PADDING, tPadding);
+    }
+
+    /**
+     * Sets the table paddings for four sides separately (TPadding).
+     * 
+     * @param tPaddings the table paddings (an array of four floats)
+     */
+    public void setTPaddings(float[] tPaddings)
+    {
+        this.setArrayOfNumber(T_PADDING, tPaddings);
+    }
+
+    /**
+     * Gets the distance by which the element’s baseline shall be shifted
+     * relative to that of its parent element (BaselineShift). The default value
+     * is 0.
+     * 
+     * @return the baseline shift
+     */
+    public float getBaselineShift()
+    {
+        return this.getNumber(BASELINE_SHIFT, 0.f);
+    }
+
+    /**
+     * Sets the distance by which the element’s baseline shall be shifted
+     * relative to that of its parent element (BaselineShift).
+     * 
+     * @param baselineShift the baseline shift
+     */
+    public void setBaselineShift(float baselineShift)
+    {
+        this.setNumber(BASELINE_SHIFT, baselineShift);
+    }
+
+    /**
+     * Sets the distance by which the element’s baseline shall be shifted
+     * relative to that of its parent element (BaselineShift).
+     * 
+     * @param baselineShift the baseline shift
+     */
+    public void setBaselineShift(int baselineShift)
+    {
+        this.setNumber(BASELINE_SHIFT, baselineShift);
+    }
+
+    /**
+     * Gets the element’s preferred height in the block-progression direction
+     * (LineHeight). The default value is {@link #LINE_HEIGHT_NORMAL}.
+     * 
+     * @return the line height (a Float or a String)
+     */
+    public Object getLineHeight()
+    {
+        return this.getNumberOrName(LINE_HEIGHT, LINE_HEIGHT_NORMAL);
+    }
+
+    /**
+     * Sets the element’s preferred height in the block-progression direction
+     * (LineHeight) to {@link #LINE_HEIGHT_NORMAL}.
+     */
+    public void setLineHeightNormal()
+    {
+        this.setName(LINE_HEIGHT, LINE_HEIGHT_NORMAL);
+    }
+
+    /**
+     * Sets the element’s preferred height in the block-progression direction
+     * (LineHeight) to {@link #LINE_HEIGHT_AUTO}.
+     */
+    public void setLineHeightAuto()
+    {
+        this.setName(LINE_HEIGHT, LINE_HEIGHT_AUTO);
+    }
+
+    /**
+     * Sets the element’s preferred height in the block-progression direction
+     * (LineHeight).
+     * 
+     * @param lineHeight the line height
+     */
+    public void setLineHeight(float lineHeight)
+    {
+        this.setNumber(LINE_HEIGHT, lineHeight);
+    }
+
+    /**
+     * Sets the element’s preferred height in the block-progression direction
+     * (LineHeight).
+     * 
+     * @param lineHeight the line height
+     */
+    public void setLineHeight(int lineHeight)
+    {
+        this.setNumber(LINE_HEIGHT, lineHeight);
+    }
+
+    /**
+     * Gets the colour to be used for drawing text decorations
+     * (TextDecorationColor).
+     * 
+     * @return the text decoration colour
+     */
+    public PDGamma getTextDecorationColor()
+    {
+        return this.getColor(TEXT_DECORATION_COLOR);
+    }
+
+    /**
+     * Sets the colour to be used for drawing text decorations
+     * (TextDecorationColor).
+     * 
+     * @param textDecorationColor the text decoration colour
+     */
+    public void setTextDecorationColor(PDGamma textDecorationColor)
+    {
+        this.setColor(TEXT_DECORATION_COLOR, textDecorationColor);
+    }
+
+    /**
+     * Gets the thickness of each line drawn as part of the text decoration
+     * (TextDecorationThickness).
+     * 
+     * @return the text decoration thickness
+     */
+    public float getTextDecorationThickness()
+    {
+        return this.getNumber(TEXT_DECORATION_THICKNESS);
+    }
+
+    /**
+     * Sets the thickness of each line drawn as part of the text decoration
+     * (TextDecorationThickness).
+     * 
+     * @param textDecorationThickness the text decoration thickness
+     */
+    public void setTextDecorationThickness(float textDecorationThickness)
+    {
+        this.setNumber(TEXT_DECORATION_THICKNESS, textDecorationThickness);
+    }
+
+    /**
+     * Sets the thickness of each line drawn as part of the text decoration
+     * (TextDecorationThickness).
+     * 
+     * @param textDecorationThickness the text decoration thickness
+     */
+    public void setTextDecorationThickness(int textDecorationThickness)
+    {
+        this.setNumber(TEXT_DECORATION_THICKNESS, textDecorationThickness);
+    }
+
+    /**
+     * Gets the type of text decoration (TextDecorationType). The default value
+     * is {@link #TEXT_DECORATION_TYPE_NONE}.
+     * 
+     * @return the type of text decoration
+     */
+    public String getTextDecorationType()
+    {
+        return this.getName(TEXT_DECORATION_TYPE, TEXT_DECORATION_TYPE_NONE);
+    }
+
+    /**
+     * Sets the type of text decoration (TextDecorationType). The value should
+     * be one of:
+     * <ul>
+     *   <li>{@link #TEXT_DECORATION_TYPE_NONE},</li>
+     *   <li>{@link #TEXT_DECORATION_TYPE_UNDERLINE},</li>
+     *   <li>{@link #TEXT_DECORATION_TYPE_OVERLINE},</li>
+     *   <li>{@link #TEXT_DECORATION_TYPE_LINE_THROUGH}.</li>
+     * </ul>
+     * 
+     * @param textDecorationType the type of text decoration
+     */
+    public void setTextDecorationType(String textDecorationType)
+    {
+        this.setName(TEXT_DECORATION_TYPE, textDecorationType);
+    }
+
+    /**
+     * Gets the justification of the lines within a ruby assembly (RubyAlign).
+     * The default value is {@link #RUBY_ALIGN_DISTRIBUTE}.
+     * 
+     * @return the ruby alignment
+     */
+    public String getRubyAlign()
+    {
+        return this.getName(RUBY_ALIGN, RUBY_ALIGN_DISTRIBUTE);
+    }
+
+    /**
+     * Sets the justification of the lines within a ruby assembly (RubyAlign).
+     * The value should be one of:
+     * <ul>
+     *   <li>{@link #RUBY_ALIGN_START},</li>
+     *   <li>{@link #RUBY_ALIGN_CENTER},</li>
+     *   <li>{@link #RUBY_ALIGN_END},</li>
+     *   <li>{@link #RUBY_ALIGN_JUSTIFY},</li>
+     *   <li>{@link #RUBY_ALIGN_DISTRIBUTE},</li>
+     * </ul>
+     * 
+     * @param rubyAlign the ruby alignment
+     */
+    public void setRubyAlign(String rubyAlign)
+    {
+        this.setName(RUBY_ALIGN, rubyAlign);
+    }
+
+    /**
+     * Gets the placement of the RT structure element relative to the RB element
+     * in a ruby assembly (RubyPosition). The default value is
+     * {@link #RUBY_POSITION_BEFORE}.
+     * 
+     * @return the ruby position
+     */
+    public String getRubyPosition()
+    {
+        return this.getName(RUBY_POSITION, RUBY_POSITION_BEFORE);
+    }
+
+    /**
+     * Sets the placement of the RT structure element relative to the RB element
+     * in a ruby assembly (RubyPosition). The value should be one of:
+     * <ul>
+     *   <li>{@link #RUBY_POSITION_BEFORE},</li>
+     *   <li>{@link #RUBY_POSITION_AFTER},</li>
+     *   <li>{@link #RUBY_POSITION_WARICHU},</li>
+     *   <li>{@link #RUBY_POSITION_INLINE}.</li>
+     * </ul>
+     * 
+     * @param rubyPosition the ruby position
+     */
+    public void setRubyPosition(String rubyPosition)
+    {
+        this.setName(RUBY_POSITION, rubyPosition);
+    }
+
+    /**
+     * Gets the orientation of glyphs when the inline-progression direction is
+     * top to bottom or bottom to top (GlyphOrientationVertical). The default
+     * value is {@link #GLYPH_ORIENTATION_VERTICAL_AUTO}.
+     * 
+     * @return the vertical glyph orientation
+     */
+    public String getGlyphOrientationVertical()
+    {
+        return this.getName(GLYPH_ORIENTATION_VERTICAL,
+            GLYPH_ORIENTATION_VERTICAL_AUTO);
+    }
+
+    /**
+     * Sets the orientation of glyphs when the inline-progression direction is
+     * top to bottom or bottom to top (GlyphOrientationVertical). The value
+     * should be one of:
+     * <ul>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_AUTO},</li>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_MINUS_180_DEGREES},</li>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_MINUS_90_DEGREES},</li>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_ZERO_DEGREES},</li>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_90_DEGREES},</li>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_180_DEGREES},</li>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_270_DEGREES},</li>
+     *   <li>{@link #GLYPH_ORIENTATION_VERTICAL_360_DEGREES}.</li>
+     * </ul>
+     * 
+     * @param glyphOrientationVertical the vertical glyph orientation
+     */
+    public void setGlyphOrientationVertical(String glyphOrientationVertical)
+    {
+        this.setName(GLYPH_ORIENTATION_VERTICAL, glyphOrientationVertical);
+    }
+
+    /**
+     * Gets the number of columns in the content of the grouping element
+     * (ColumnCount). The default value is 1.
+     * 
+     * @return the column count
+     */
+    public int getColumnCount()
+    {
+        return this.getInteger(COLUMN_COUNT, 1);
+    }
+
+    /**
+     * Sets the number of columns in the content of the grouping element
+     * (ColumnCount).
+     * 
+     * @param columnCount the column count
+     */
+    public void setColumnCount(int columnCount)
+    {
+        this.setInteger(COLUMN_COUNT, columnCount);
+    }
+
+    /**
+     * Gets the desired space between adjacent columns in the inline-progression
+     * direction (ColumnGap).
+     * 
+     * @return the column gap (FLoat or array of floats)
+     */
+    public Object getColumnGap()
+    {
+        return this.getNumberOrArrayOfNumber(COLUMN_GAP, UNSPECIFIED);
+    }
+
+    /**
+     * Sets the desired space between all columns in the inline-progression
+     * direction (ColumnGap).
+     * 
+     * @param columnGap the column gap
+     */
+    public void setColumnGap(float columnGap)
+    {
+        this.setNumber(COLUMN_GAP, columnGap);
+    }
+
+    /**
+     * Sets the desired space between all columns in the inline-progression
+     * direction (ColumnGap).
+     * 
+     * @param columnGap the column gap
+     */
+    public void setColumnGap(int columnGap)
+    {
+        this.setNumber(COLUMN_GAP, columnGap);
+    }
+
+    /**
+     * Sets the desired space between adjacent columns in the inline-progression
+     * direction (ColumnGap), the first element specifying the space between the
+     * first and second columns, the second specifying the space between the
+     * second and third columns, and so on.
+     * 
+     * @param columnGaps the column gaps
+     */
+    public void setColumnGaps(float[] columnGaps)
+    {
+        this.setArrayOfNumber(COLUMN_GAP, columnGaps);
+    }
+
+    /**
+     * Gets the desired width of the columns, measured in default user space
+     * units in the inline-progression direction (ColumnWidths).
+     * 
+     * @return the column widths (Float or array of floats)
+     */
+    public Object getColumnWidths()
+    {
+        return this.getNumberOrArrayOfNumber(COLUMN_WIDTHS, UNSPECIFIED);
+    }
+
+    /**
+     * Sets the same column width for all columns (ColumnWidths).
+     * 
+     * @param columnWidth the column width
+     */
+    public void setAllColumnWidths(float columnWidth)
+    {
+        this.setNumber(COLUMN_WIDTHS, columnWidth);
+    }
+
+    /**
+     * Sets the same column width for all columns (ColumnWidths).
+     * 
+     * @param columnWidth the column width
+     */
+    public void setAllColumnWidths(int columnWidth)
+    {
+        this.setNumber(COLUMN_WIDTHS, columnWidth);
+    }
+
+    /**
+     * Sets the column widths for the columns separately (ColumnWidths).
+     * 
+     * @param columnWidths the column widths
+     */
+    public void setColumnWidths(float[] columnWidths)
+    {
+        this.setArrayOfNumber(COLUMN_WIDTHS, columnWidths);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder().append(super.toString());
+        if (this.isSpecified(PLACEMENT))
+        {
+            sb.append(", Placement=").append(this.getPlacement());
+        }
+        if (this.isSpecified(WRITING_MODE))
+        {
+            sb.append(", WritingMode=").append(this.getWritingMode());
+        }
+        if (this.isSpecified(BACKGROUND_COLOR))
+        {
+            sb.append(", BackgroundColor=").append(this.getBackgroundColor());
+        }
+        if (this.isSpecified(BORDER_COLOR))
+        {
+            sb.append(", BorderColor=").append(this.getBorderColors());
+        }
+        if (this.isSpecified(BORDER_STYLE))
+        {
+            Object borderStyle = this.getBorderStyle();
+            sb.append(", BorderStyle=");
+            if (borderStyle instanceof String[])
+            {
+                sb.append(arrayToString((String[]) borderStyle));
+            }
+            else
+            {
+                sb.append(borderStyle);
+            }
+        }
+        if (this.isSpecified(BORDER_THICKNESS))
+        {
+            Object borderThickness = this.getBorderThickness();
+            sb.append(", BorderThickness=");
+            if (borderThickness instanceof float[])
+            {
+                sb.append(arrayToString((float[]) borderThickness));
+            }
+            else
+            {
+                sb.append(String.valueOf((Float) borderThickness));
+            }
+        }
+        if (this.isSpecified(PADDING))
+        {
+            Object padding = this.getPadding();
+            sb.append(", Padding=");
+            if (padding instanceof float[])
+            {
+                sb.append(arrayToString((float[]) padding));
+            }
+            else
+            {
+                sb.append(String.valueOf((Float) padding));
+            }
+        }
+        if (this.isSpecified(COLOR))
+        {
+            sb.append(", Color=").append(this.getColor());
+        }
+        if (this.isSpecified(SPACE_BEFORE))
+        {
+            sb.append(", SpaceBefore=")
+                .append(String.valueOf(this.getSpaceBefore()));
+        }
+        if (this.isSpecified(SPACE_AFTER))
+        {
+            sb.append(", SpaceAfter=")
+                .append(String.valueOf(this.getSpaceAfter()));
+        }
+        if (this.isSpecified(START_INDENT))
+        {
+            sb.append(", StartIndent=")
+                .append(String.valueOf(this.getStartIndent()));
+        }
+        if (this.isSpecified(END_INDENT))
+        {
+            sb.append(", EndIndent=")
+                .append(String.valueOf(this.getEndIndent()));
+        }
+        if (this.isSpecified(TEXT_INDENT))
+        {
+            sb.append(", TextIndent=")
+                .append(String.valueOf(this.getTextIndent()));
+        }
+        if (this.isSpecified(TEXT_ALIGN))
+        {
+            sb.append(", TextAlign=").append(this.getTextAlign());
+        }
+        if (this.isSpecified(BBOX))
+        {
+            sb.append(", BBox=").append(this.getBBox());
+        }
+        if (this.isSpecified(WIDTH))
+        {
+            Object width = this.getWidth();
+            sb.append(", Width=");
+            if (width instanceof Float)
+            {
+                sb.append(String.valueOf((Float) width));
+            }
+            else
+            {
+                sb.append(width);
+            }
+        }
+        if (this.isSpecified(HEIGHT))
+        {
+            Object height = this.getHeight();
+            sb.append(", Height=");
+            if (height instanceof Float)
+            {
+                sb.append(String.valueOf((Float) height));
+            }
+            else
+            {
+                sb.append(height);
+            }
+        }
+        if (this.isSpecified(BLOCK_ALIGN))
+        {
+            sb.append(", BlockAlign=").append(this.getBlockAlign());
+        }
+        if (this.isSpecified(INLINE_ALIGN))
+        {
+            sb.append(", InlineAlign=").append(this.getInlineAlign());
+        }
+        if (this.isSpecified(T_BORDER_STYLE))
+        {
+            Object tBorderStyle = this.getTBorderStyle();
+            sb.append(", TBorderStyle=");
+            if (tBorderStyle instanceof String[])
+            {
+                sb.append(arrayToString((String[]) tBorderStyle));
+            }
+            else
+            {
+                sb.append(tBorderStyle);
+            }
+        }
+        if (this.isSpecified(T_PADDING))
+        {
+            Object tPadding = this.getTPadding();
+            sb.append(", TPadding=");
+            if (tPadding instanceof float[])
+            {
+                sb.append(arrayToString((float[]) tPadding));
+            }
+            else
+            {
+                sb.append(String.valueOf((Float) tPadding));
+            }
+        }
+        if (this.isSpecified(BASELINE_SHIFT))
+        {
+            sb.append(", BaselineShift=")
+                .append(String.valueOf(this.getBaselineShift()));
+        }
+        if (this.isSpecified(LINE_HEIGHT))
+        {
+            Object lineHeight = this.getLineHeight();
+            sb.append(", LineHeight=");
+            if (lineHeight instanceof Float)
+            {
+                sb.append(String.valueOf((Float) lineHeight));
+            }
+            else
+            {
+                sb.append(lineHeight);
+            }
+        }
+        if (this.isSpecified(TEXT_DECORATION_COLOR))
+        {
+            sb.append(", TextDecorationColor=")
+                .append(this.getTextDecorationColor());
+        }
+        if (this.isSpecified(TEXT_DECORATION_THICKNESS))
+        {
+            sb.append(", TextDecorationThickness=")
+                .append(String.valueOf(this.getTextDecorationThickness()));
+        }
+        if (this.isSpecified(TEXT_DECORATION_TYPE))
+        {
+            sb.append(", TextDecorationType=")
+                .append(this.getTextDecorationType());
+        }
+        if (this.isSpecified(RUBY_ALIGN))
+        {
+            sb.append(", RubyAlign=").append(this.getRubyAlign());
+        }
+        if (this.isSpecified(RUBY_POSITION))
+        {
+            sb.append(", RubyPosition=").append(this.getRubyPosition());
+        }
+        if (this.isSpecified(GLYPH_ORIENTATION_VERTICAL))
+        {
+            sb.append(", GlyphOrientationVertical=")
+                .append(this.getGlyphOrientationVertical());
+        }
+        if (this.isSpecified(COLUMN_COUNT))
+        {
+            sb.append(", ColumnCount=")
+                .append(String.valueOf(this.getColumnCount()));
+        }
+        if (this.isSpecified(COLUMN_GAP))
+        {
+            Object columnGap = this.getColumnGap();
+            sb.append(", ColumnGap=");
+            if (columnGap instanceof float[])
+            {
+                sb.append(arrayToString((float[]) columnGap));
+            }
+            else
+            {
+                sb.append(String.valueOf((Float) columnGap));
+            }
+        }
+        if (this.isSpecified(COLUMN_WIDTHS))
+        {
+            Object columnWidth = this.getColumnWidths();
+            sb.append(", ColumnWidths=");
+            if (columnWidth instanceof float[])
+            {
+                sb.append(arrayToString((float[]) columnWidth));
+            }
+            else
+            {
+                sb.append(String.valueOf((Float) columnWidth));
+            }
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDListAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDListAttributeObject.java
new file mode 100644 (file)
index 0000000..420e9e4
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A List attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDListAttributeObject extends PDStandardAttributeObject
+{
+
+    /**
+     * standard attribute owner: List
+     */
+    public static final String OWNER_LIST = "List";
+
+    protected static final String LIST_NUMBERING = "ListNumbering";
+
+    /**
+     * ListNumbering: Circle: Open circular bullet
+     */
+    public static final String LIST_NUMBERING_CIRCLE = "Circle";
+    /**
+     * ListNumbering: Decimal: Decimal arabic numerals (1–9, 10–99, …)
+     */
+    public static final String LIST_NUMBERING_DECIMAL = "Decimal";
+    /**
+     * ListNumbering: Disc: Solid circular bullet
+     */
+    public static final String LIST_NUMBERING_DISC = "Disc";
+    /**
+     * ListNumbering: LowerAlpha: Lowercase letters (a, b, c, …)
+     */
+    public static final String LIST_NUMBERING_LOWER_ALPHA = "LowerAlpha";
+    /**
+     * ListNumbering: LowerRoman: Lowercase roman numerals (i, ii, iii, iv, …)
+     */
+    public static final String LIST_NUMBERING_LOWER_ROMAN = "LowerRoman";
+    /**
+     * ListNumbering: None: No autonumbering; Lbl elements (if present) contain arbitrary text
+     * not subject to any numbering scheme
+     */
+    public static final String LIST_NUMBERING_NONE = "None";
+    /**
+     * ListNumbering: Square: Solid square bullet
+     */
+    public static final String LIST_NUMBERING_SQUARE = "Square";
+    /**
+     * ListNumbering: UpperAlpha: Uppercase letters (A, B, C, …)
+     */
+    public static final String LIST_NUMBERING_UPPER_ALPHA = "UpperAlpha";
+    /**
+     * ListNumbering: UpperRoman: Uppercase roman numerals (I, II, III, IV, …)
+     */
+    public static final String LIST_NUMBERING_UPPER_ROMAN = "UpperRoman";
+
+
+    /**
+     * Default constructor.
+     */
+    public PDListAttributeObject()
+    {
+        this.setOwner(OWNER_LIST);
+    }
+
+    /**
+     * Creates a new List attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDListAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Gets the list numbering (ListNumbering). The default value is
+     * {@link #LIST_NUMBERING_NONE}.
+     * 
+     * @return the list numbering
+     */
+    public String getListNumbering()
+    {
+        return this.getName(LIST_NUMBERING, LIST_NUMBERING_NONE);
+    }
+
+    /**
+     * Sets the list numbering (ListNumbering). The value shall be one of the
+     * following:
+     * <ul>
+     *   <li>{@link #LIST_NUMBERING_NONE},</li>
+     *   <li>{@link #LIST_NUMBERING_DISC},</li>
+     *   <li>{@link #LIST_NUMBERING_CIRCLE},</li>
+     *   <li>{@link #LIST_NUMBERING_SQUARE},</li>
+     *   <li>{@link #LIST_NUMBERING_DECIMAL},</li>
+     *   <li>{@link #LIST_NUMBERING_UPPER_ROMAN},</li>
+     *   <li>{@link #LIST_NUMBERING_LOWER_ROMAN},</li>
+     *   <li>{@link #LIST_NUMBERING_UPPER_ALPHA},</li>
+     *   <li>{@link #LIST_NUMBERING_LOWER_ALPHA}.</li>
+     * </ul>
+     * 
+     * @param listNumbering the list numbering
+     */
+    public void setListNumbering(String listNumbering)
+    {
+        this.setName(LIST_NUMBERING, listNumbering);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder().append(super.toString());
+        if (this.isSpecified(LIST_NUMBERING))
+        {
+            sb.append(", ListNumbering=").append(this.getListNumbering());
+        }
+        return sb.toString();
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDPrintFieldAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDPrintFieldAttributeObject.java
new file mode 100644 (file)
index 0000000..624261a
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A PrintField attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDPrintFieldAttributeObject extends PDStandardAttributeObject
+{
+
+    /**
+     * standard attribute owner: PrintField
+     */
+    public static final String OWNER_PRINT_FIELD = "PrintField";
+
+    private static final String ROLE = "Role";
+    private static final String CHECKED = "checked";
+    private static final String DESC = "Desc";
+
+    /**
+     * role: rb: Radio button
+     */
+    public static final String ROLE_RB = "rb";
+    /**
+     * role: cb: Check box
+     */
+    public static final String ROLE_CB = "cb";
+    /**
+     * role: pb: Push button
+     */
+    public static final String ROLE_PB = "pb";
+    /**
+     * role: tv: Text-value field
+     */
+    public static final String ROLE_TV = "tv";
+    /**
+     * checked state: on
+     */
+    public static final String CHECKED_STATE_ON = "on";
+    /**
+     * checked state: off
+     */
+    public static final String CHECKED_STATE_OFF = "off";
+    /**
+     * checked state: neutral
+     */
+    public static final String CHECKED_STATE_NEUTRAL = "neutral";
+
+
+    /**
+     * Default constructor.
+     */
+    public PDPrintFieldAttributeObject()
+    {
+        this.setOwner(OWNER_PRINT_FIELD);
+    }
+
+    /**
+     * Creates a new PrintField attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDPrintFieldAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Gets the role.
+     * 
+     * @return the role
+     */
+    public String getRole()
+    {
+        return this.getName(ROLE);
+    }
+
+    /**
+     * Sets the role. The value of Role shall be one of the following:
+     * <ul>
+     *   <li>{@link #ROLE_RB},</li>
+     *   <li>{@link #ROLE_CB},</li>
+     *   <li>{@link #ROLE_PB},</li>
+     *   <li>{@link #ROLE_TV}.</li>
+     * </ul>
+     * 
+     * @param role the role
+     */
+    public void setRole(String role)
+    {
+        this.setName(ROLE, role);
+    }
+
+    /**
+     * Gets the checked state. The default value is {@link #CHECKED_STATE_OFF}.
+     * 
+     * @return the checked state
+     */
+    public String getCheckedState()
+    {
+        return this.getName(CHECKED, CHECKED_STATE_OFF);
+    }
+
+    /**
+     * Sets the checked state. The value shall be one of:
+     * <ul>
+     *   <li>{@link #CHECKED_STATE_ON},</li>
+     *   <li>{@link #CHECKED_STATE_OFF} (default), or</li>
+     *   <li>{@link #CHECKED_STATE_NEUTRAL}.</li>
+     * </ul>
+     * 
+     * @param checkedState the checked state
+     */
+    public void setCheckedState(String checkedState)
+    {
+        this.setName(CHECKED, checkedState);
+    }
+
+    /**
+     * Gets the alternate name of the field (Desc).
+     * 
+     * @return the alternate name of the field
+     */
+    public String getAlternateName()
+    {
+        return this.getString(DESC);
+    }
+
+    /**
+     * Sets the alternate name of the field (Desc).
+     * 
+     * @param alternateName the alternate name of the field
+     */
+    public void setAlternateName(String alternateName)
+    {
+        this.setString(DESC, alternateName);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder().append(super.toString());
+        if (this.isSpecified(ROLE))
+        {
+            sb.append(", Role=").append(this.getRole());
+        }
+        if (this.isSpecified(CHECKED))
+        {
+            sb.append(", Checked=").append(this.getCheckedState());
+        }
+        if (this.isSpecified(DESC))
+        {
+            sb.append(", Desc=").append(this.getAlternateName());
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDStandardAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDStandardAttributeObject.java
new file mode 100644 (file)
index 0000000..fbdfd20
--- /dev/null
@@ -0,0 +1,449 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDAttributeObject;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+
+/**
+ * A standard attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public abstract class PDStandardAttributeObject extends PDAttributeObject
+{
+
+    /**
+     * Default constructor.
+     */
+    public PDStandardAttributeObject()
+    {
+    }
+
+    /**
+     * Creates a new standard attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDStandardAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Is the attribute with the given name specified in this attribute object?
+     * 
+     * @param name the attribute name
+     * @return <code>true</code> if the attribute is specified,
+     * <code>false</code> otherwise
+     */
+    public boolean isSpecified(String name)
+    {
+        return this.getCOSDictionary().getDictionaryObject(name) != null;
+    }
+
+
+    /**
+     * Gets a string attribute value.
+     * 
+     * @param name the attribute name
+     * @return the string attribute value
+     */
+    protected String getString(String name)
+    {
+        return this.getCOSDictionary().getString(name);
+    }
+
+    /**
+     * Sets a string attribute value.
+     * 
+     * @param name the attribute name
+     * @param value the string attribute value
+     */
+    protected void setString(String name, String value)
+    {
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setString(name, value);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Gets an array of strings.
+     * 
+     * @param name the attribute name
+     * @return the array of strings
+     */
+    protected String[] getArrayOfString(String name)
+    {
+        COSBase v = this.getCOSDictionary().getDictionaryObject(name);
+        if (v instanceof COSArray)
+        {
+            COSArray array = (COSArray) v;
+            String[] strings = new String[array.size()];
+            for (int i = 0; i < array.size(); i++)
+            {
+                strings[i] = ((COSName) array.getObject(i)).getName();
+            }
+            return strings;
+        }
+        return null;
+    }
+
+    /**
+     * Sets an array of strings.
+     * 
+     * @param name the attribute name
+     * @param values the array of strings
+     */
+    protected void setArrayOfString(String name, String[] values)
+    {
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        COSArray array = new COSArray();
+        for (int i = 0; i < values.length; i++)
+        {
+            array.add(new COSString(values[i]));
+        }
+        this.getCOSDictionary().setItem(name, array);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Gets a name value.
+     * 
+     * @param name the attribute name
+     * @return the name value
+     */
+    protected String getName(String name)
+    {
+        return this.getCOSDictionary().getNameAsString(name);
+    }
+
+    /**
+     * Gets a name value.
+     * 
+     * @param name the attribute name
+     * @param defaultValue the default value
+     * @return the name value
+     */
+    protected String getName(String name, String defaultValue)
+    {
+        return this.getCOSDictionary().getNameAsString(name, defaultValue);
+    }
+
+    /**
+     * Gets a name value or array of name values.
+     * 
+     * @param name the attribute name
+     * @param defaultValue the default value
+     * @return a String or array of Strings
+     */
+    protected Object getNameOrArrayOfName(String name, String defaultValue)
+    {
+        COSBase v = this.getCOSDictionary().getDictionaryObject(name);
+        if (v instanceof COSArray)
+        {
+            COSArray array = (COSArray) v;
+            String[] names = new String[array.size()];
+            for (int i = 0; i < array.size(); i++)
+            {
+                COSBase item = array.getObject(i);
+                if (item instanceof COSName)
+                {
+                    names[i] = ((COSName) item).getName();
+                }
+            }
+            return names;
+        }
+        if (v instanceof COSName)
+        {
+            return ((COSName) v).getName();
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Sets a name value.
+     * 
+     * @param name the attribute name
+     * @param value the name value
+     */
+    protected void setName(String name, String value)
+    {
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setName(name, value);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Sets an array of name values.
+     * 
+     * @param name the attribute name
+     * @param values the array of name values
+     */
+    protected void setArrayOfName(String name, String[] values)
+    {
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        COSArray array = new COSArray();
+        for (int i = 0; i < values.length; i++)
+        {
+            array.add(COSName.getPDFName(values[i]));
+        }
+        this.getCOSDictionary().setItem(name, array);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Gets a number or a name value.
+     * 
+     * @param name the attribute name
+     * @param defaultValue the default name
+     * @return a Float or a String
+     */
+    protected Object getNumberOrName(String name, String defaultValue)
+    {
+        COSBase value = this.getCOSDictionary().getDictionaryObject(name);
+        if (value instanceof COSNumber)
+        {
+            return ((COSNumber) value).floatValue();
+        }
+        if (value instanceof COSName)
+        {
+            return ((COSName) value).getName();
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Gets an integer.
+     * 
+     * @param name the attribute name
+     * @param defaultValue the default value
+     * @return the integer
+     */
+    protected int getInteger(String name, int defaultValue)
+    {
+        return this.getCOSDictionary().getInt(name, defaultValue);
+    }
+
+    /**
+     * Sets an integer.
+     * 
+     * @param name the attribute name
+     * @param value the integer
+     */
+    protected void setInteger(String name, int value)
+    {
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setInt(name, value);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Gets a number value.
+     * 
+     * @param name the attribute name
+     * @param defaultValue the default value
+     * @return the number value
+     */
+    protected float getNumber(String name, float defaultValue)
+    {
+        return this.getCOSDictionary().getFloat(name, defaultValue);
+    }
+
+    /**
+     * Gets a number value.
+     * 
+     * @param name the attribute name
+     * @return the number value
+     */
+    protected float getNumber(String name)
+    {
+        return this.getCOSDictionary().getFloat(name);
+    }
+
+    /**
+     * An "unspecified" default float value.
+     */
+    protected static final float UNSPECIFIED = -1.f;
+
+    /**
+     * Gets a number or an array of numbers.
+     * 
+     * @param name the attribute name
+     * @param defaultValue the default value
+     * @return a Float or an array of floats
+     */
+    protected Object getNumberOrArrayOfNumber(String name, float defaultValue)
+    {
+        COSBase v = this.getCOSDictionary().getDictionaryObject(name);
+        if (v instanceof COSArray)
+        {
+            COSArray array = (COSArray) v;
+            float[] values = new float[array.size()];
+            for (int i = 0; i < array.size(); i++)
+            {
+                COSBase item = array.getObject(i);
+                if (item instanceof COSNumber)
+                {
+                    values[i] = ((COSNumber) item).floatValue();
+                }
+            }
+            return values;
+        }
+        if (v instanceof COSNumber)
+        {
+            return ((COSNumber) v).floatValue();
+        }
+        if (defaultValue == UNSPECIFIED)
+        {
+            return null;
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Sets a float number.
+     * 
+     * @param name the attribute name
+     * @param value the float number
+     */
+    protected void setNumber(String name, float value)
+    {
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setFloat(name, value);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Sets an integer number.
+     * 
+     * @param name the attribute name
+     * @param value the integer number
+     */
+    protected void setNumber(String name, int value)
+    {
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setInt(name, value);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Sets an array of float numbers.
+     * 
+     * @param name the attribute name
+     * @param values the float numbers
+     */
+    protected void setArrayOfNumber(String name, float[] values)
+    {
+        COSArray array = new COSArray();
+        for (int i = 0; i < values.length; i++)
+        {
+            array.add(new COSFloat(values[i]));
+        }
+        COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setItem(name, array);
+        COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+        this.potentiallyNotifyChanged(oldBase, newBase);
+    }
+
+    /**
+     * Gets a colour.
+     * 
+     * @param name the attribute name
+     * @return the colour
+     */
+    protected PDGamma getColor(String name)
+    {
+        COSArray c = (COSArray) this.getCOSDictionary().getDictionaryObject(name);
+        if (c != null)
+        {
+            return new PDGamma(c);
+        }
+        return null;
+    }
+
+    /**
+     * Gets a single colour or four colours.
+     * 
+     * @param name the attribute name
+     * @return the single ({@link PDGamma}) or a ({@link PDFourColours})
+     */
+    protected Object getColorOrFourColors(String name)
+    {
+        COSArray array =
+            (COSArray) this.getCOSDictionary().getDictionaryObject(name);
+        if (array == null)
+        {
+            return null;
+        }
+        if (array.size() == 3)
+        {
+            // only one colour
+            return new PDGamma(array);
+        }
+        else if (array.size() == 4)
+        {
+            return new PDFourColours(array);
+        }
+        return null;
+    }
+
+    /**
+     * Sets a colour.
+     * 
+     * @param name the attribute name
+     * @param value the colour
+     */
+    protected void setColor(String name, PDGamma value)
+    {
+        COSBase oldValue = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setItem(name, value);
+        COSBase newValue = value == null ? null : value.getCOSObject();
+        this.potentiallyNotifyChanged(oldValue, newValue);
+    }
+
+    /**
+     * Sets four colours.
+     * 
+     * @param name the attribute name
+     * @param value the four colours
+     */
+    protected void setFourColors(String name, PDFourColours value)
+    {
+        COSBase oldValue = this.getCOSDictionary().getDictionaryObject(name);
+        this.getCOSDictionary().setItem(name, value);
+        COSBase newValue = value == null ? null : value.getCOSObject();
+        this.potentiallyNotifyChanged(oldValue, newValue);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDTableAttributeObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/PDTableAttributeObject.java
new file mode 100644 (file)
index 0000000..c787f84
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
+
+/**
+ * A Table attribute object.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDTableAttributeObject extends PDStandardAttributeObject
+{
+
+    /**
+     *  standard attribute owner: Table
+     */
+    public static final String OWNER_TABLE = "Table";
+
+    protected static final String ROW_SPAN = "RowSpan";
+    protected static final String COL_SPAN = "ColSpan";
+    protected static final String HEADERS  = "Headers";
+    protected static final String SCOPE    = "Scope";
+    protected static final String SUMMARY  = "Summary";
+
+    /**
+     * Scope: Both
+     */
+    public static final String SCOPE_BOTH   = "Both";
+    /**
+     * Scope: Column
+     */
+    public static final String SCOPE_COLUMN = "Column";
+    /**
+     * Scope: Row
+     */
+    public static final String SCOPE_ROW    = "Row";
+
+
+    /**
+     * Default constructor.
+     */
+    public PDTableAttributeObject()
+    {
+        this.setOwner(OWNER_TABLE);
+    }
+
+    /**
+     * Creates a new Table attribute object with a given dictionary.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDTableAttributeObject(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+
+    /**
+     * Gets the number of rows in the enclosing table that shall be spanned by
+     * the cell (RowSpan). The default value is 1.
+     * 
+     * @return the row span
+     */
+    public int getRowSpan()
+    {
+        return this.getInteger(ROW_SPAN, 1);
+    }
+
+    /**
+     * Sets the number of rows in the enclosing table that shall be spanned by
+     * the cell (RowSpan).
+     * 
+     * @param rowSpan the row span
+     */
+    public void setRowSpan(int rowSpan)
+    {
+        this.setInteger(ROW_SPAN, rowSpan);
+    }
+
+    /**
+     * Gets the number of columns in the enclosing table that shall be spanned
+     * by the cell (ColSpan). The default value is 1.
+     * 
+     * @return the column span
+     */
+    public int getColSpan()
+    {
+        return this.getInteger(COL_SPAN, 1);
+    }
+
+    /**
+     * Sets the number of columns in the enclosing table that shall be spanned
+     * by the cell (ColSpan).
+     * 
+     * @param colSpan the column span
+     */
+    public void setColSpan(int colSpan)
+    {
+        this.setInteger(COL_SPAN, colSpan);
+    }
+
+    /**
+     * Gets the headers (Headers). An array of byte strings, where each string
+     * shall be the element identifier (see the
+     * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+     * element that shall be used as a header associated with this cell.
+     * 
+     * @return the headers.
+     */
+    public String[] getHeaders()
+    {
+        return this.getArrayOfString(HEADERS);
+    }
+
+    /**
+     * Sets the headers (Headers). An array of byte strings, where each string
+     * shall be the element identifier (see the
+     * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+     * element that shall be used as a header associated with this cell.
+     * 
+     * @param headers the headers
+     */
+    public void setHeaders(String[] headers)
+    {
+        this.setArrayOfString(HEADERS, headers);
+    }
+
+    /**
+     * Gets the scope (Scope). It shall reflect whether the header cell applies
+     * to the rest of the cells in the row that contains it, the column that
+     * contains it, or both the row and the column that contain it.
+     * 
+     * @return the scope
+     */
+    public String getScope()
+    {
+        return this.getName(SCOPE);
+    }
+
+    /**
+     * Sets the scope (Scope). It shall reflect whether the header cell applies
+     * to the rest of the cells in the row that contains it, the column that
+     * contains it, or both the row and the column that contain it. The value
+     * shall be one of the following:
+     * <ul>
+     *   <li>{@link #SCOPE_ROW},</li>
+     *   <li>{@link #SCOPE_COLUMN}, or</li>
+     *   <li>{@link #SCOPE_BOTH}.</li>
+     * </ul>
+     * 
+     * @param scope the scope
+     */
+    public void setScope(String scope)
+    {
+        this.setName(SCOPE, scope);
+    }
+
+    /**
+     * Gets the summary of the table’s purpose and structure.
+     * 
+     * @return the summary
+     */
+    public String getSummary()
+    {
+        return this.getString(SUMMARY);
+    }
+
+    /**
+     * Sets the summary of the table’s purpose and structure.
+     * 
+     * @param summary the summary
+     */
+    public void setSummary(String summary)
+    {
+        this.setString(SUMMARY, summary);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder().append(super.toString());
+        if (this.isSpecified(ROW_SPAN))
+        {
+            sb.append(", RowSpan=").append(String.valueOf(this.getRowSpan()));
+        }
+        if (this.isSpecified(COL_SPAN))
+        {
+            sb.append(", ColSpan=").append(String.valueOf(this.getColSpan()));
+        }
+        if (this.isSpecified(HEADERS))
+        {
+            sb.append(", Headers=").append(arrayToString(this.getHeaders()));
+        }
+        if (this.isSpecified(SCOPE))
+        {
+            sb.append(", Scope=").append(this.getScope());
+        }
+        if (this.isSpecified(SUMMARY))
+        {
+            sb.append(", Summary=").append(this.getSummary());
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/StandardStructureTypes.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/StandardStructureTypes.java
new file mode 100644 (file)
index 0000000..6aadddc
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * 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.pdfbox.pdmodel.documentinterchange.taggedpdf;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The standard structure types.
+ * 
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class StandardStructureTypes
+{
+
+    private StandardStructureTypes()
+    {
+    }
+
+
+    // Grouping Elements
+    /**
+     * Document
+     */
+    public static final String DOCUMENT = "Document";
+
+    /**
+     * Part
+     */
+    public static final String PART = "Part";
+
+    /**
+     * Art
+     */
+    public static final String ART = "Art";
+
+    /**
+     * Sect
+     */
+    public static final String SECT = "Sect";
+
+    /**
+     * Div
+     */
+    public static final String DIV = "Div";
+
+    /**
+     * BlockQuote
+     */
+    public static final String BLOCK_QUOTE = "BlockQuote";
+
+    /**
+     * Caption
+     */
+    public static final String CAPTION = "Caption";
+
+    /**
+     * TOC
+     */
+    public static final String TOC = "TOC";
+
+    /**
+     * TOCI
+     */
+    public static final String TOCI = "TOCI";
+
+    /**
+     * Index
+     */
+    public static final String INDEX = "Index";
+
+    /**
+     * NonStruct
+     */
+    public static final String NON_STRUCT = "NonStruct";
+
+    /**
+     * Private
+     */
+    public static final String PRIVATE = "Private";
+
+
+    // Block-Level Structure Elements
+    /**
+     * P
+     */
+    public static final String P = "P";
+
+    /**
+     * H
+     */
+    public static final String H = "H";
+
+    /**
+     * H1
+     */
+    public static final String H1 = "H1";
+
+    /**
+     * H2
+     */
+    public static final String H2 = "H2";
+
+    /**
+     * H3
+     */
+    public static final String H3 = "H3";
+
+    /**
+     * H4
+     */
+    public static final String H4 = "H4";
+
+    /**
+     * H5
+     */
+    public static final String H5 = "H5";
+
+    /**
+     * H6
+     */
+    public static final String H6 = "H6";
+
+    /**
+     * L
+     */
+    public static final String L = "L";
+
+    /**
+     * LI
+     */
+    public static final String LI = "LI";
+
+    /**
+     * Lbl
+     */
+    public static final String LBL = "Lbl";
+
+    /**
+     * LBody
+     */
+    public static final String L_BODY = "LBody";
+
+    /**
+     * Table
+     */
+    public static final String TABLE = "Table";
+
+    /**
+     * TR
+     */
+    public static final String TR = "TR";
+
+    /**
+     * TH
+     */
+    public static final String TH = "TH";
+
+    /**
+     * TD
+     */
+    public static final String TD = "TD";
+
+    /**
+     * THead
+     */
+    public static final String T_HEAD = "THead";
+
+    /**
+     * TBody
+     */
+    public static final String T_BODY = "TBody";
+
+    /**
+     * TFoot
+     */
+    public static final String T_FOOT = "TFoot";
+
+
+    // Inline-Level Structure Elements
+    /**
+     * Span
+     */
+    public static final String SPAN = "Span";
+
+    /**
+     * Quote
+     */
+    public static final String QUOTE = "Quote";
+
+    /**
+     * Note
+     */
+    public static final String NOTE = "Note";
+
+    /**
+     * Reference
+     */
+    public static final String REFERENCE = "Reference";
+
+    /**
+     * BibEntry
+     */
+    public static final String BIB_ENTRY = "BibEntry";
+
+    /**
+     * Code
+     */
+    public static final String CODE = "Code";
+
+    /**
+     * Link
+     */
+    public static final String LINK = "Link";
+
+    /**
+     * Annot
+     */
+    public static final String ANNOT = "Annot";
+
+    /**
+     * Ruby
+     */
+    public static final String RUBY = "Ruby";
+
+    /**
+     * RB
+     */
+    public static final String RB = "RB";
+
+    /**
+     * RT
+     */
+    public static final String RT = "RT";
+
+    /**
+     * RP
+     */
+    public static final String RP = "RP";
+
+    /**
+     * Warichu
+     */
+    public static final String WARICHU = "Warichu";
+
+    /**
+     * WT
+     */
+    public static final String WT = "WT";
+
+    /**
+     * WP
+     */
+    public static final String WP = "WP";
+
+
+    // Illustration Elements
+    /**
+     * Figure
+     */
+    public static final String Figure = "Figure";
+
+    /**
+     * Formula
+     */
+    public static final String FORMULA = "Formula";
+
+    /**
+     * Form
+     */
+    public static final String FORM = "Form";
+
+    /**
+     * All standard structure types.
+     */
+    public static List<String> types = new ArrayList<String>();
+
+    static
+    {
+        Field[] fields = StandardStructureTypes.class.getFields();
+        for (int i = 0; i < fields.length; i++)
+        {
+            if (Modifier.isFinal(fields[i].getModifiers()))
+            {
+                try
+                {
+                    types.add(fields[i].get(null).toString());
+                }
+                catch (IllegalArgumentException e)
+                {
+                    e.printStackTrace();
+                }
+                catch (IllegalAccessException e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }
+        Collections.sort(types);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/documentinterchange/taggedpdf/package.html
new file mode 100644 (file)
index 0000000..ca5daa9
--- /dev/null
@@ -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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The tagged PDF package provides a mechanism for incorporating "tags" (standard
+structure types and attributes) into a PDF file.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java
new file mode 100644 (file)
index 0000000..132f2a8
--- /dev/null
@@ -0,0 +1,1355 @@
+/*
+ * 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.pdfbox.pdmodel.edit;
+
+import java.awt.Color;
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.COSStreamArray;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.util.MapUtil;
+
+
+/**
+ * This class will is a convenience for creating page content streams.  You MUST
+ * call close() when you are finished with this object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.19 $
+ */
+public class PDPageContentStream
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDPageContentStream.class);
+
+    private PDPage page;
+    private OutputStream output;
+    private boolean inTextMode = false;
+    private Map<PDFont,String> fontMappings;
+    private Map<PDXObject,String> xobjectMappings;
+    private PDResources resources;
+    private Map fonts;
+    private Map xobjects;
+
+    private PDColorSpace currentStrokingColorSpace = new PDDeviceGray();
+    private PDColorSpace currentNonStrokingColorSpace = new PDDeviceGray();
+
+    //cached storage component for getting color values
+    private float[] colorComponents = new float[4];
+
+    private NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US );
+
+    private static final String BEGIN_TEXT = "BT\n";
+    private static final String END_TEXT = "ET\n";
+    private static final String SET_FONT = "Tf\n";
+    private static final String MOVE_TEXT_POSITION = "Td\n";
+    private static final String SET_TEXT_MATRIX = "Tm\n";
+    private static final String SHOW_TEXT = "Tj\n";
+
+    private static final String SAVE_GRAPHICS_STATE = "q\n";
+    private static final String RESTORE_GRAPHICS_STATE = "Q\n";
+    private static final String CONCATENATE_MATRIX = "cm\n";
+    private static final String XOBJECT_DO = "Do\n";
+    private static final String RG_STROKING = "RG\n";
+    private static final String RG_NON_STROKING = "rg\n";
+    private static final String K_STROKING = "K\n";
+    private static final String K_NON_STROKING = "k\n";
+    private static final String G_STROKING = "G\n";
+    private static final String G_NON_STROKING = "g\n";
+    private static final String RECTANGLE = "re\n";
+    private static final String FILL_NON_ZERO = "f\n";
+    private static final String FILL_EVEN_ODD = "f*\n";
+    private static final String LINE_TO = "l\n";
+    private static final String MOVE_TO = "m\n";
+    private static final String CLOSE_STROKE = "s\n";
+    private static final String STROKE = "S\n";
+    private static final String LINE_WIDTH = "w\n";
+    private static final String CLOSE_SUBPATH = "h\n";
+    private static final String CLIP_PATH_NON_ZERO = "W\n";
+    private static final String CLIP_PATH_EVEN_ODD = "W*\n";
+    private static final String NOP = "n\n";
+    private static final String BEZIER_312 = "c\n";
+    private static final String BEZIER_32 = "v\n";
+    private static final String BEZIER_313 = "y\n";
+
+    private static final String MP = "MP\n";
+    private static final String DP = "DP\n";
+    private static final String BMC = "BMC\n";
+    private static final String BDC = "BDC\n";
+    private static final String EMC = "EMC\n";
+
+    private static final String SET_STROKING_COLORSPACE = "CS\n";
+    private static final String SET_NON_STROKING_COLORSPACE = "cs\n";
+
+    private static final String SET_STROKING_COLOR_SIMPLE="SC\n";
+    private static final String SET_STROKING_COLOR_COMPLEX="SCN\n";
+    private static final String SET_NON_STROKING_COLOR_SIMPLE="sc\n";
+    private static final String SET_NON_STROKING_COLOR_COMPLEX="scn\n";
+
+
+
+    private static final int SPACE = 32;
+
+
+    /**
+     * Create a new PDPage content stream.
+     *
+     * @param document The document the page is part of.
+     * @param sourcePage The page to write the contents to.
+     * @throws IOException If there is an error writing to the page contents.
+     */
+    public PDPageContentStream( PDDocument document, PDPage sourcePage ) throws IOException
+    {
+        this(document,sourcePage,false,true);
+    }
+
+    /**
+     * Create a new PDPage content stream.
+     *
+     * @param document The document the page is part of.
+     * @param sourcePage The page to write the contents to.
+     * @param appendContent Indicates whether content will be overwritten. If false all previous content is deleted.
+     * @param compress Tell if the content stream should compress the page contents.
+     * @throws IOException If there is an error writing to the page contents.
+     */
+    public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress )
+        throws IOException
+    {
+        this(document,sourcePage,appendContent,compress,false);
+    }
+    /**
+     * Create a new PDPage content stream.
+     *
+     * @param document The document the page is part of.
+     * @param sourcePage The page to write the contents to.
+     * @param appendContent Indicates whether content will be overwritten. If false all previous content is deleted.
+     * @param compress Tell if the content stream should compress the page contents.
+     * @param resetContext Tell if the graphic context should be reseted.
+     * @throws IOException If there is an error writing to the page contents.
+     */
+    public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress, boolean resetContext )
+            throws IOException
+    {
+        
+        page = sourcePage;
+        resources = page.getResources();
+        if( resources == null )
+        {
+            resources = new PDResources();
+            page.setResources( resources );
+        }
+
+        //Fonts including reverse lookup
+        fonts = resources.getFonts();
+        fontMappings = reverseMap(fonts, PDFont.class);
+
+        //XObjects including reverse lookup
+        xobjects = resources.getXObjects();
+        xobjectMappings = reverseMap(xobjects, PDXObject.class);
+
+        // Get the pdstream from the source page instead of creating a new one
+        PDStream contents = sourcePage.getContents();
+        boolean hasContent = contents != null;
+
+        // If request specifies the need to append to the document
+        if(appendContent && hasContent)
+        {
+            
+            // Create a pdstream to append new content
+            PDStream contentsToAppend = new PDStream( document );
+
+            // This will be the resulting COSStreamArray after existing and new streams are merged
+            COSStreamArray compoundStream = null;
+
+            // If contents is already an array, a new stream is simply appended to it
+            if(contents.getStream() instanceof COSStreamArray)
+            {
+                compoundStream = (COSStreamArray)contents.getStream();
+                compoundStream.appendStream( contentsToAppend.getStream());
+            }
+            else
+            {
+                // Creates the COSStreamArray and adds the current stream plus a new one to it
+                COSArray newArray = new COSArray();
+                newArray.add(contents.getCOSObject());
+                newArray.add(contentsToAppend.getCOSObject());
+                compoundStream = new COSStreamArray(newArray);
+            }
+
+            if( compress )
+            {
+                List<COSName> filters = new ArrayList<COSName>();
+                filters.add( COSName.FLATE_DECODE );
+                contentsToAppend.setFilters( filters );
+            }
+
+            if (resetContext)
+            {
+                // create a new stream to encapsulate the existing stream 
+                PDStream saveGraphics = new PDStream( document );
+                output = saveGraphics.createOutputStream();
+                // save the initial/unmodified graphics context
+                saveGraphicsState();
+                close();
+                if( compress )
+                {
+                    List<COSName> filters = new ArrayList<COSName>();
+                    filters.add( COSName.FLATE_DECODE );
+                    saveGraphics.setFilters( filters );
+                }
+                // insert the new stream at the beginning
+                compoundStream.insertCOSStream(saveGraphics);
+            }
+
+            // Sets the compoundStream as page contents
+            sourcePage.setContents( new PDStream(compoundStream) );
+            output = contentsToAppend.createOutputStream();
+            if (resetContext)
+            {
+                // restore the initial/unmodified graphics context
+                restoreGraphicsState();
+            }
+        }
+        else
+        {
+            if (hasContent)
+            {
+                log.warn("You are overwriting an existing content, you should use the append mode");
+            }
+            contents = new PDStream( document );
+            if( compress )
+            {
+                List<COSName> filters = new ArrayList<COSName>();
+                filters.add( COSName.FLATE_DECODE );
+                contents.setFilters( filters );
+            }
+            sourcePage.setContents( contents );
+            output = contents.createOutputStream();
+        }
+        formatDecimal.setMaximumFractionDigits( 10 );
+        formatDecimal.setGroupingUsed( false );
+    }
+
+    private <T> Map<T, String> reverseMap(Map map, Class<T> keyClass)
+    {
+        Map<T, String> reversed = new java.util.HashMap<T, String>();
+        for (Object o : map.entrySet())
+        {
+            Map.Entry entry = (Map.Entry)o;
+            reversed.put(keyClass.cast(entry.getValue()), (String)entry.getKey());
+        }
+        return reversed;
+    }
+
+    /**
+     * Begin some text operations.
+     *
+     * @throws IOException If there is an error writing to the stream or if you attempt to
+     *         nest beginText calls.
+     */
+    public void beginText() throws IOException
+    {
+        if( inTextMode )
+        {
+            throw new IOException( "Error: Nested beginText() calls are not allowed." );
+        }
+        appendRawCommands( BEGIN_TEXT );
+        inTextMode = true;
+    }
+
+    /**
+     * End some text operations.
+     *
+     * @throws IOException If there is an error writing to the stream or if you attempt to
+     *         nest endText calls.
+     */
+    public void endText() throws IOException
+    {
+        if( !inTextMode )
+        {
+            throw new IOException( "Error: You must call beginText() before calling endText." );
+        }
+        appendRawCommands( END_TEXT );
+        inTextMode = false;
+    }
+
+    /**
+     * Set the font to draw text with.
+     *
+     * @param font The font to use.
+     * @param fontSize The font size to draw the text.
+     * @throws IOException If there is an error writing the font information.
+     */
+    public void setFont( PDFont font, float fontSize ) throws IOException
+    {
+        String fontMapping = fontMappings.get( font );
+        if( fontMapping == null )
+        {
+            fontMapping = MapUtil.getNextUniqueKey( fonts, "F" );
+            fontMappings.put( font, fontMapping );
+            fonts.put( fontMapping, font );
+        }
+        appendRawCommands( "/");
+        appendRawCommands( fontMapping );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( fontSize ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( SET_FONT );
+    }
+
+    /**
+     * Draw an image at the x,y coordinates, with the default size of the image.
+     *
+     * @param image The image to draw.
+     * @param x The x-coordinate to draw the image.
+     * @param y The y-coordinate to draw the image.
+     *
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void drawImage( PDXObjectImage image, float x, float y ) throws IOException
+    {
+        drawXObject( image, x, y, image.getWidth(), image.getHeight() );
+    }
+
+    /**
+     * Draw an xobject(form or image) at the x,y coordinates and a certain width and height.
+     *
+     * @param xobject The xobject to draw.
+     * @param x The x-coordinate to draw the image.
+     * @param y The y-coordinate to draw the image.
+     * @param width The width of the image to draw.
+     * @param height The height of the image to draw.
+     *
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void drawXObject( PDXObject xobject, float x, float y, float width, float height ) throws IOException
+    {
+        AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y);
+        drawXObject(xobject, transform);
+    }
+
+    /**
+     * Draw an xobject(form or image) using the given {@link AffineTransform} to position
+     * the xobject.
+     *
+     * @param xobject The xobject to draw.
+     * @param transform the transformation matrix
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void drawXObject( PDXObject xobject, AffineTransform transform ) throws IOException
+    {
+        String xObjectPrefix = null;
+        if( xobject instanceof PDXObjectImage )
+        {
+            xObjectPrefix = "Im";
+        }
+        else
+        {
+            xObjectPrefix = "Form";
+        }
+
+        String objMapping = xobjectMappings.get( xobject );
+        if( objMapping == null )
+        {
+            objMapping = MapUtil.getNextUniqueKey( xobjects, xObjectPrefix );
+            xobjectMappings.put( xobject, objMapping );
+            xobjects.put( objMapping, xobject );
+        }
+        saveGraphicsState();
+        appendRawCommands( SPACE );
+        concatenate2CTM(transform);
+        appendRawCommands( SPACE );
+        appendRawCommands( "/" );
+        appendRawCommands( objMapping );
+        appendRawCommands( SPACE );
+        appendRawCommands( XOBJECT_DO );
+        restoreGraphicsState();
+    }
+
+
+    /**
+     * The Td operator.
+     * A current text matrix will be replaced with a new one (1 0 0 1 x y).
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void moveTextPositionByAmount( float x, float y ) throws IOException
+    {
+        if( !inTextMode )
+        {
+            throw new IOException( "Error: must call beginText() before moveTextPositionByAmount");
+        }
+        appendRawCommands( formatDecimal.format( x ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( MOVE_TEXT_POSITION );
+    }
+
+    /**
+     * The Tm operator. Sets the text matrix to the given values.
+     * A current text matrix will be replaced with the new one.
+     * @param a The a value of the matrix.
+     * @param b The b value of the matrix.
+     * @param c The c value of the matrix.
+     * @param d The d value of the matrix.
+     * @param e The e value of the matrix.
+     * @param f The f value of the matrix.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void setTextMatrix( double a, double b, double c, double d, double e, double f ) throws IOException
+    {
+        if( !inTextMode )
+        {
+            throw new IOException( "Error: must call beginText() before setTextMatrix");
+        }
+        appendRawCommands( formatDecimal.format( a ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( b ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( c ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( e ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( f ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( SET_TEXT_MATRIX );
+    }
+
+    /**
+    * The Tm operator. Sets the text matrix to the given values.
+    * A current text matrix will be replaced with the new one.
+    * @param matrix the transformation matrix
+    * @throws IOException If there is an error writing to the stream.
+    */
+    public void setTextMatrix(AffineTransform matrix) throws IOException
+    {
+        appendMatrix(matrix);
+        appendRawCommands(SET_TEXT_MATRIX);
+    }
+
+    /**
+     * The Tm operator. Sets the text matrix to the given scaling and translation values.
+     * A current text matrix will be replaced with the new one.
+     * @param sx The scaling factor in x-direction.
+     * @param sy The scaling factor in y-direction.
+     * @param tx The translation value in x-direction.
+     * @param ty The translation value in y-direction.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void setTextScaling( double sx, double sy, double tx, double ty ) throws IOException
+    {
+        setTextMatrix(sx, 0, 0, sy, tx, ty);
+    }
+
+    /**
+     * The Tm operator. Sets the text matrix to the given translation values.
+     * A current text matrix will be replaced with the new one.
+     * @param tx The translation value in x-direction.
+     * @param ty The translation value in y-direction.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void setTextTranslation( double tx, double ty ) throws IOException
+    {
+        setTextMatrix(1, 0, 0, 1, tx, ty);
+    }
+
+    /**
+     * The Tm operator. Sets the text matrix to the given rotation and translation values.
+     * A current text matrix will be replaced with the new one.
+     * @param angle The angle used for the counterclockwise rotation in radians.
+     * @param tx The translation value in x-direction.
+     * @param ty The translation value in y-direction.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void setTextRotation( double angle, double tx, double ty ) throws IOException
+    {
+        double angleCos = Math.cos(angle);
+        double angleSin = Math.sin(angle);
+        setTextMatrix( angleCos, angleSin, -angleSin, angleCos, tx, ty);
+    }
+
+    /**
+     * The Cm operator. Concatenates the current transformation matrix with the given values.
+     * @param a The a value of the matrix.
+     * @param b The b value of the matrix.
+     * @param c The c value of the matrix.
+     * @param d The d value of the matrix.
+     * @param e The e value of the matrix.
+     * @param f The f value of the matrix.
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void concatenate2CTM( double a, double b, double c, double d, double e, double f ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( a ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( b ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( c ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( e ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( f ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( CONCATENATE_MATRIX );
+    }
+
+    /**
+     * The Cm operator. Concatenates the current transformation matrix with the given
+     * {@link AffineTransform}.
+     * @param at the transformation matrix
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void concatenate2CTM(AffineTransform at) throws IOException
+    {
+        appendMatrix(at);
+        appendRawCommands(CONCATENATE_MATRIX);
+    }
+
+    /**
+     * This will draw a string at the current location on the screen.
+     *
+     * @param text The text to draw.
+     * @throws IOException If an io exception occurs.
+     */
+    public void drawString( String text ) throws IOException
+    {
+        if( !inTextMode )
+        {
+            throw new IOException( "Error: must call beginText() before drawString");
+        }
+        COSString string = new COSString( text );
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        string.writePDF( buffer );
+        appendRawCommands( new String( buffer.toByteArray(), "ISO-8859-1"));
+        appendRawCommands( SPACE );
+        appendRawCommands( SHOW_TEXT );
+    }
+
+    /**
+     * Set the stroking color space.  This will add the colorspace to the PDResources
+     * if necessary.
+     *
+     * @param colorSpace The colorspace to write.
+     * @throws IOException If there is an error writing the colorspace.
+     */
+    public void setStrokingColorSpace( PDColorSpace colorSpace ) throws IOException
+    {
+        currentStrokingColorSpace = colorSpace;
+        writeColorSpace( colorSpace );
+        appendRawCommands( SET_STROKING_COLORSPACE );
+    }
+
+    /**
+     * Set the stroking color space.  This will add the colorspace to the PDResources
+     * if necessary.
+     *
+     * @param colorSpace The colorspace to write.
+     * @throws IOException If there is an error writing the colorspace.
+     */
+    public void setNonStrokingColorSpace( PDColorSpace colorSpace ) throws IOException
+    {
+        currentNonStrokingColorSpace = colorSpace;
+        writeColorSpace( colorSpace );
+        appendRawCommands( SET_NON_STROKING_COLORSPACE );
+    }
+
+    private void writeColorSpace( PDColorSpace colorSpace ) throws IOException
+    {
+        COSName key = null;
+        if( colorSpace instanceof PDDeviceGray ||
+            colorSpace instanceof PDDeviceRGB ||
+            colorSpace instanceof PDDeviceCMYK )
+        {
+            key = COSName.getPDFName( colorSpace.getName() );
+        }
+        else
+        {
+            COSDictionary colorSpaces =
+                (COSDictionary)resources.getCOSDictionary().getDictionaryObject(COSName.COLORSPACE);
+            if( colorSpaces == null )
+            {
+                colorSpaces = new COSDictionary();
+                resources.getCOSDictionary().setItem( COSName.COLORSPACE, colorSpaces );
+            }
+            key = colorSpaces.getKeyForValue( colorSpace.getCOSObject() );
+
+            if( key == null )
+            {
+                int counter = 0;
+                String csName = "CS";
+                while( colorSpaces.containsValue( csName + counter ) )
+                {
+                    counter++;
+                }
+                key = COSName.getPDFName( csName + counter );
+                colorSpaces.setItem( key, colorSpace );
+            }
+        }
+        key.writePDF( output );
+        appendRawCommands( SPACE );
+    }
+
+    /**
+     * Set the color components of current stroking colorspace.
+     *
+     * @param components The components to set for the current color.
+     * @throws IOException If there is an error while writing to the stream.
+     */
+    public void setStrokingColor( float[] components ) throws IOException
+    {
+        for( int i=0; i< components.length; i++ )
+        {
+            appendRawCommands( formatDecimal.format( components[i] ) );
+            appendRawCommands( SPACE );
+        }
+        if( currentStrokingColorSpace instanceof PDSeparation ||
+            currentStrokingColorSpace instanceof PDPattern ||
+            currentStrokingColorSpace instanceof PDDeviceN ||
+            currentStrokingColorSpace instanceof PDICCBased )
+        {
+            appendRawCommands( SET_STROKING_COLOR_COMPLEX );
+        }
+        else
+        {
+            appendRawCommands( SET_STROKING_COLOR_SIMPLE );
+        }
+    }
+
+    /**
+     * Set the stroking color, specified as RGB.
+     *
+     * @param color The color to set.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setStrokingColor( Color color ) throws IOException
+    {
+        ColorSpace colorSpace = color.getColorSpace();
+        if( colorSpace.getType() == ColorSpace.TYPE_RGB )
+        {
+            setStrokingColor( color.getRed(), color.getGreen(), color.getBlue() );
+        }
+        else if( colorSpace.getType() == ColorSpace.TYPE_GRAY )
+        {
+            color.getColorComponents( colorComponents );
+            setStrokingColor( colorComponents[0] );
+        }
+        else if( colorSpace.getType() == ColorSpace.TYPE_CMYK )
+        {
+            color.getColorComponents( colorComponents );
+            setStrokingColor( colorComponents[0], colorComponents[2], colorComponents[2], colorComponents[3] );
+        }
+        else
+        {
+            throw new IOException( "Error: unknown colorspace:" + colorSpace );
+        }
+    }
+
+    /**
+     * Set the non stroking color, specified as RGB.
+     *
+     * @param color The color to set.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setNonStrokingColor( Color color ) throws IOException
+    {
+        ColorSpace colorSpace = color.getColorSpace();
+        if( colorSpace.getType() == ColorSpace.TYPE_RGB )
+        {
+            setNonStrokingColor( color.getRed(), color.getGreen(), color.getBlue() );
+        }
+        else if( colorSpace.getType() == ColorSpace.TYPE_GRAY )
+        {
+            color.getColorComponents( colorComponents );
+            setNonStrokingColor( colorComponents[0] );
+        }
+        else if( colorSpace.getType() == ColorSpace.TYPE_CMYK )
+        {
+            color.getColorComponents( colorComponents );
+            setNonStrokingColor( colorComponents[0], colorComponents[2], colorComponents[2], colorComponents[3] );
+        }
+        else
+        {
+            throw new IOException( "Error: unknown colorspace:" + colorSpace );
+        }
+    }
+
+    /**
+     * Set the stroking color, specified as RGB, 0-255.
+     *
+     * @param r The red value.
+     * @param g The green value.
+     * @param b The blue value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setStrokingColor( int r, int g, int b ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( r/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( g/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( b/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( RG_STROKING );
+    }
+
+    /**
+     * Set the stroking color, specified as CMYK, 0-255.
+     *
+     * @param c The cyan value.
+     * @param m The magenta value.
+     * @param y The yellow value.
+     * @param k The black value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setStrokingColor( int c, int m, int y, int k) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( c/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( m/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( k/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( K_STROKING );
+    }
+
+    /**
+     * Set the stroking color, specified as CMYK, 0.0-1.0.
+     *
+     * @param c The cyan value.
+     * @param m The magenta value.
+     * @param y The yellow value.
+     * @param k The black value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setStrokingColor( double c, double m, double y, double k) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( c ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( m ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( k ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( K_STROKING );
+    }
+
+    /**
+     * Set the stroking color, specified as grayscale, 0-255.
+     *
+     * @param g The gray value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setStrokingColor( int g ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( g/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( G_STROKING );
+    }
+
+    /**
+     * Set the stroking color, specified as Grayscale 0.0-1.0.
+     *
+     * @param g The gray value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setStrokingColor( double g ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( g ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( G_STROKING );
+    }
+
+    /**
+     * Set the color components of current non stroking colorspace.
+     *
+     * @param components The components to set for the current color.
+     * @throws IOException If there is an error while writing to the stream.
+     */
+    public void setNonStrokingColor( float[] components ) throws IOException
+    {
+        for( int i=0; i< components.length; i++ )
+        {
+            appendRawCommands( formatDecimal.format( components[i] ) );
+            appendRawCommands( SPACE );
+        }
+        if( currentNonStrokingColorSpace instanceof PDSeparation ||
+            currentNonStrokingColorSpace instanceof PDPattern ||
+            currentNonStrokingColorSpace instanceof PDDeviceN ||
+            currentNonStrokingColorSpace instanceof PDICCBased )
+        {
+            appendRawCommands( SET_NON_STROKING_COLOR_COMPLEX );
+        }
+        else
+        {
+            appendRawCommands( SET_NON_STROKING_COLOR_SIMPLE );
+        }
+    }
+
+    /**
+     * Set the non stroking color, specified as RGB, 0-255.
+     *
+     * @param r The red value.
+     * @param g The green value.
+     * @param b The blue value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setNonStrokingColor( int r, int g, int b ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( r/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( g/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( b/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( RG_NON_STROKING );
+    }
+
+    /**
+     * Set the non stroking color, specified as CMYK, 0-255.
+     *
+     * @param c The cyan value.
+     * @param m The magenta value.
+     * @param y The yellow value.
+     * @param k The black value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setNonStrokingColor( int c, int m, int y, int k) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( c/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( m/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( k/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( K_NON_STROKING );
+    }
+
+    /**
+     * Set the non stroking color, specified as CMYK, 0.0-1.0.
+     *
+     * @param c The cyan value.
+     * @param m The magenta value.
+     * @param y The yellow value.
+     * @param k The black value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setNonStrokingColor( double c, double m, double y, double k) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( c ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( m ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( k ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( K_NON_STROKING );
+    }
+
+    /**
+     * Set the non stroking color, specified as grayscale, 0-255.
+     *
+     * @param g The gray value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setNonStrokingColor( int g ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( g/255d ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( G_NON_STROKING );
+    }
+
+    /**
+     * Set the non stroking color, specified as Grayscale 0.0-1.0.
+     *
+     * @param g The gray value.
+     * @throws IOException If an IO error occurs while writing to the stream.
+     */
+    public void setNonStrokingColor( double g ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( g ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( G_NON_STROKING );
+    }
+
+    /**
+     * Add a rectangle to the current path.
+     *
+     * @param x The lower left x coordinate.
+     * @param y The lower left y coordinate.
+     * @param width The width of the rectangle.
+     * @param height The height of the rectangle.
+     * @throws IOException If there is an error while drawing on the screen.
+     */
+    public void addRect( float x, float y, float width, float height ) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( x ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( width ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( height ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( RECTANGLE );
+    }
+
+    /**
+     * Draw a rectangle on the page using the current non stroking color.
+     *
+     * @param x The lower left x coordinate.
+     * @param y The lower left y coordinate.
+     * @param width The width of the rectangle.
+     * @param height The height of the rectangle.
+     * @throws IOException If there is an error while drawing on the screen.
+     */
+    public void fillRect( float x, float y, float width, float height ) throws IOException
+    {
+        addRect(x, y, width, height);
+        fill(PathIterator.WIND_NON_ZERO);
+    }
+
+    /**
+     * Append a cubic Bézier curve to the current path. The curve extends from the current
+     * point to the point (x3 , y3 ), using (x1 , y1 ) and (x2 , y2 ) as the Bézier control points
+     * @param x1 x coordinate of the point 1
+     * @param y1 y coordinate of the point 1
+     * @param x2 x coordinate of the point 2
+     * @param y2 y coordinate of the point 2
+     * @param x3 x coordinate of the point 3
+     * @param y3 y coordinate of the point 3
+     * @throws IOException If there is an error while adding the .
+     */
+    public void addBezier312(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( x1) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y1) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( x2) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y2) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( x3) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y3) );
+        appendRawCommands( SPACE );
+        appendRawCommands( BEZIER_312 );
+    }
+
+    /**
+     * Append a cubic Bézier curve to the current path. The curve extends from the current
+     * point to the point (x3 , y3 ), using the current point and (x2 , y2 ) as the Bézier control points
+     * @param x2 x coordinate of the point 2
+     * @param y2 y coordinate of the point 2
+     * @param x3 x coordinate of the point 3
+     * @param y3 y coordinate of the point 3
+     * @throws IOException If there is an error while adding the .
+     */
+    public void addBezier32(float x2, float y2, float x3, float y3) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( x2) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y2) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( x3) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y3) );
+        appendRawCommands( SPACE );
+        appendRawCommands( BEZIER_32 );
+    }
+
+    /**
+     * Append a cubic Bézier curve to the current path. The curve extends from the current
+     * point to the point (x3 , y3 ), using (x1 , y1 ) and (x3 , y3 ) as the Bézier control points
+     * @param x1 x coordinate of the point 1
+     * @param y1 y coordinate of the point 1
+     * @param x3 x coordinate of the point 3
+     * @param y3 y coordinate of the point 3
+     * @throws IOException If there is an error while adding the .
+     */
+    public void addBezier31(float x1, float y1, float x3, float y3) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( x1) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y1) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( x3) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y3) );
+        appendRawCommands( SPACE );
+        appendRawCommands( BEZIER_313 );
+    }
+
+
+    /**
+     * Add a line to the given coordinate.
+     *
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     * @throws IOException If there is an error while adding the line.
+     */
+    public void moveTo( float x, float y) throws IOException
+    {
+        // moveTo
+        appendRawCommands( formatDecimal.format( x) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y) );
+        appendRawCommands( SPACE );
+        appendRawCommands( MOVE_TO );
+    }
+
+    /**
+     * Add a move to the given coordinate.
+     *
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     * @throws IOException If there is an error while adding the line.
+     */
+    public void lineTo( float x, float y) throws IOException
+    {
+        // moveTo
+        appendRawCommands( formatDecimal.format( x) );
+        appendRawCommands( SPACE );
+        appendRawCommands( formatDecimal.format( y) );
+        appendRawCommands( SPACE );
+        appendRawCommands( LINE_TO );
+    }
+    /**
+     * add a line to the current path.
+     *
+     * @param xStart The start x coordinate.
+     * @param yStart The start y coordinate.
+     * @param xEnd The end x coordinate.
+     * @param yEnd The end y coordinate.
+     * @throws IOException If there is an error while adding the line.
+     */
+    public void addLine( float xStart, float yStart, float xEnd, float yEnd ) throws IOException
+    {
+        // moveTo
+        moveTo(xStart, yStart);
+        // lineTo
+        lineTo(xEnd, yEnd);
+    }
+
+    /**
+     * Draw a line on the page using the current non stroking color and the current line width.
+     *
+     * @param xStart The start x coordinate.
+     * @param yStart The start y coordinate.
+     * @param xEnd The end x coordinate.
+     * @param yEnd The end y coordinate.
+     * @throws IOException If there is an error while drawing on the screen.
+     */
+    public void drawLine( float xStart, float yStart, float xEnd, float yEnd ) throws IOException
+    {
+        addLine(xStart, yStart, xEnd, yEnd);
+        // stroke
+        stroke();
+    }
+
+    /**
+     * Add a polygon to the current path.
+     * @param x x coordinate of each points
+     * @param y y coordinate of each points
+     * @throws IOException If there is an error while drawing on the screen.
+     */
+    public void addPolygon(float[] x, float[] y) throws IOException
+    {
+        if (x.length != y.length)
+        {
+            throw new IOException( "Error: some points are missing coordinate" );
+        }
+        for (int i = 0; i < x.length; i++)
+        {
+            if (i == 0)
+            {
+                moveTo(x[i], y[i]);
+            }
+            else
+            {
+                lineTo(x[i], y[i]);
+            }
+        }
+        closeSubPath();
+    }
+
+    /**
+     * Draw a polygon on the page using the current non stroking color.
+     * @param x x coordinate of each points
+     * @param y y coordinate of each points
+     * @throws IOException If there is an error while drawing on the screen.
+     */
+    public void drawPolygon(float[] x, float[] y) throws IOException
+    {
+        addPolygon(x, y);
+        stroke();
+    }
+
+    /**
+     * Draw and fill a polygon on the page using the current non stroking color.
+     * @param x x coordinate of each points
+     * @param y y coordinate of each points
+     * @throws IOException If there is an error while drawing on the screen.
+     */
+    public void fillPolygon(float[] x, float[] y) throws IOException
+    {
+        addPolygon(x, y);
+        fill(PathIterator.WIND_NON_ZERO);
+    }
+
+    /**
+     * Stroke the path.
+     */
+    public void stroke() throws IOException
+    {
+        appendRawCommands( STROKE );
+    }
+
+    /**
+     * Close and stroke the path.
+     */
+    public void closeAndStroke() throws IOException
+    {
+        appendRawCommands( CLOSE_STROKE );
+    }
+
+    /**
+     * Fill the path.
+     */
+    public void fill(int windingRule) throws IOException
+    {
+        if (windingRule == PathIterator.WIND_NON_ZERO)
+        {
+            appendRawCommands( FILL_NON_ZERO );
+        }
+        else if (windingRule == PathIterator.WIND_EVEN_ODD)
+        {
+            appendRawCommands( FILL_EVEN_ODD );
+        }
+        else
+        {
+            throw new IOException( "Error: unknown value for winding rule" );
+        }
+
+    }
+
+    /**
+     * Close subpath.
+     */
+    public void closeSubPath() throws IOException
+    {
+        appendRawCommands( CLOSE_SUBPATH );
+    }
+
+    /**
+     * Clip path.
+     */
+    public void clipPath(int windingRule) throws IOException
+    {
+        if (windingRule == PathIterator.WIND_NON_ZERO)
+        {
+            appendRawCommands( CLIP_PATH_NON_ZERO );
+            appendRawCommands( NOP );
+        }
+        else if (windingRule == PathIterator.WIND_EVEN_ODD)
+        {
+            appendRawCommands( CLIP_PATH_EVEN_ODD );
+            appendRawCommands( NOP );
+        }
+        else
+        {
+            throw new IOException( "Error: unknown value for winding rule" );
+        }
+    }
+
+    /**
+     * Set linewidth to the given value.
+     *
+     * @param lineWidth The width which is used for drwaing.
+     * @throws IOException If there is an error while drawing on the screen.
+     */
+    public void setLineWidth(float lineWidth) throws IOException
+    {
+        appendRawCommands( formatDecimal.format( lineWidth ) );
+        appendRawCommands( SPACE );
+        appendRawCommands( LINE_WIDTH );
+    }
+
+    /**
+     * Begin a marked content sequence.
+     * @param tag the tag
+     * @throws IOException if an I/O error occurs
+     */
+    public void beginMarkedContentSequence(COSName tag) throws IOException
+    {
+        appendCOSName(tag);
+        appendRawCommands(SPACE);
+        appendRawCommands(BMC);
+    }
+
+    /**
+     * Begin a marked content sequence with a reference to an entry in the page resources'
+     * Properties dictionary.
+     * @param tag the tag
+     * @param propsName the properties reference
+     * @throws IOException if an I/O error occurs
+     */
+    public void beginMarkedContentSequence(COSName tag, COSName propsName) throws IOException
+    {
+        appendCOSName(tag);
+        appendRawCommands(SPACE);
+        appendCOSName(propsName);
+        appendRawCommands(SPACE);
+        appendRawCommands(BDC);
+    }
+
+    /**
+     * End a marked content sequence.
+     * @throws IOException if an I/O error occurs
+     */
+    public void endMarkedContentSequence() throws IOException
+    {
+        appendRawCommands(EMC);
+    }
+
+    /**
+     * q operator. Saves the current graphics state.
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void saveGraphicsState() throws IOException
+    {
+        appendRawCommands( SAVE_GRAPHICS_STATE);
+    }
+
+    /**
+     * Q operator. Restores the current graphics state.
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void restoreGraphicsState() throws IOException
+    {
+        appendRawCommands( RESTORE_GRAPHICS_STATE );
+    }
+
+    /**
+     * This will append raw commands to the content stream.
+     *
+     * @param commands The commands to append to the stream.
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void appendRawCommands( String commands ) throws IOException
+    {
+        appendRawCommands( commands.getBytes( "ISO-8859-1" ) );
+    }
+
+    /**
+     * This will append raw commands to the content stream.
+     *
+     * @param commands The commands to append to the stream.
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void appendRawCommands( byte[] commands ) throws IOException
+    {
+        output.write( commands );
+    }
+
+    /**
+     * This will append raw commands to the content stream.
+     *
+     * @param data Append a raw byte to the stream.
+     *
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void appendRawCommands( int data ) throws IOException
+    {
+        output.write( data );
+    }
+
+    /**
+     * This will append a {@link COSName} to the content stream.
+     * @param name the name
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void appendCOSName(COSName name) throws IOException
+    {
+        name.writePDF(output);
+    }
+
+    private void appendMatrix(AffineTransform transform) throws IOException
+    {
+        double[] values = new double[6];
+        transform.getMatrix(values);
+        for (double v : values)
+        {
+            appendRawCommands(formatDecimal.format(v));
+            appendRawCommands(SPACE);
+        }
+    }
+
+    /**
+     * Close the content stream.  This must be called when you are done with this
+     * object.
+     * @throws IOException If the underlying stream has a problem being written to.
+     */
+    public void close() throws IOException
+    {
+        output.close();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/edit/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/edit/package.html
new file mode 100644 (file)
index 0000000..677e3eb
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel edit package will be used to store classes for creating page content.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/AccessPermission.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/AccessPermission.java
new file mode 100644 (file)
index 0000000..dc0718e
--- /dev/null
@@ -0,0 +1,422 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+/**
+ * This class represents the access permissions to a document.
+ * These permissions are specified in the PDF format specifications, they include:
+ * <ul>
+ * <li>print the document</li>
+ * <li>modify the content of the document</li>
+ * <li>copy or extract content of the document</li>
+ * <li>add or modify annotations</li>
+ * <li>fill in interactive form fields</li>
+ * <li>extract text and graphics for accessibility to visually impaired people</li>
+ * <li>assemble the document</li>
+ * <li>print in degraded quality</li>
+ * </ul>
+ *
+ * This class can be used to protect a document by assigning access permissions to recipients.
+ * In this case, it must be used with a specific ProtectionPolicy.
+ *
+ *
+ * When a document is decrypted, it has a currentAccessPermission property which is the access permissions
+ * granted to the user who decrypted the document.
+ *
+ * @see ProtectionPolicy
+ * @see org.apache.pdfbox.pdmodel.PDDocument#getCurrentAccessPermission()
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.4 $
+ */
+
+public class AccessPermission
+{
+
+    private static final int DEFAULT_PERMISSIONS = 0xFFFFFFFF ^ 3;//bits 0 & 1 need to be zero
+    private static final int PRINT_BIT = 3;
+    private static final int MODIFICATION_BIT = 4;
+    private static final int EXTRACT_BIT = 5;
+    private static final int MODIFY_ANNOTATIONS_BIT = 6;
+    private static final int FILL_IN_FORM_BIT = 9;
+    private static final int EXTRACT_FOR_ACCESSIBILITY_BIT = 10;
+    private static final int ASSEMBLE_DOCUMENT_BIT = 11;
+    private static final int DEGRADED_PRINT_BIT = 12;
+
+    private int bytes = DEFAULT_PERMISSIONS;
+
+    private boolean readOnly = false;
+
+    /**
+     * Create a new access permission object.
+     * By default, all permissions are granted.
+     */
+    public AccessPermission()
+    {
+        bytes = DEFAULT_PERMISSIONS;
+    }
+
+    /**
+     * Create a new access permission object from a byte array.
+     * Bytes are ordered most significant byte first.
+     *
+     * @param b the bytes as defined in PDF specs
+     */
+
+    public AccessPermission(byte[] b)
+    {
+        bytes = 0;
+        bytes |= b[0] & 0xFF;
+        bytes <<= 8;
+        bytes |= b[1] & 0xFF;
+        bytes <<= 8;
+        bytes |= b[2] & 0xFF;
+        bytes <<= 8;
+        bytes |= b[3] & 0xFF;
+    }
+
+    /**
+     * Creates a new access permission object from a single integer.
+     *
+     * @param permissions The permission bits.
+     */
+    public AccessPermission( int permissions )
+    {
+        bytes = permissions;
+    }
+
+    private boolean isPermissionBitOn( int bit )
+    {
+        return (bytes & (1 << (bit-1))) != 0;
+    }
+
+    private boolean setPermissionBit( int bit, boolean value )
+    {
+        int permissions = bytes;
+        if( value )
+        {
+            permissions = permissions | (1 << (bit-1));
+        }
+        else
+        {
+            permissions = permissions & (0xFFFFFFFF ^ (1 << (bit-1)));
+        }
+        bytes = permissions;
+
+        return (bytes & (1 << (bit-1))) != 0;
+    }
+
+
+
+
+    /**
+     * This will tell if the access permission corresponds to owner
+     * access permission (no restriction).
+     *
+     * @return true if the access permission does not restrict the use of the document
+     */
+    public boolean isOwnerPermission()
+    {
+        return (this.canAssembleDocument()
+                && this.canExtractContent()
+                && this.canExtractForAccessibility()
+                && this.canFillInForm()
+                && this.canModify()
+                && this.canModifyAnnotations()
+                && this.canPrint()
+                && this.canPrintDegraded()
+                );
+    }
+
+    /**
+     * returns an access permission object for a document owner.
+     *
+     * @return A standard owner access permission set.
+     */
+
+    public static AccessPermission getOwnerAccessPermission()
+    {
+        AccessPermission ret = new AccessPermission();
+        ret.setCanAssembleDocument(true);
+        ret.setCanExtractContent(true);
+        ret.setCanExtractForAccessibility(true);
+        ret.setCanFillInForm(true);
+        ret.setCanModify(true);
+        ret.setCanModifyAnnotations(true);
+        ret.setCanPrint(true);
+        ret.setCanPrintDegraded(true);
+        return ret;
+    }
+
+    /**
+     * This returns an integer representing the access permissions.
+     * This integer can be used for public key encryption. This format
+     * is not documented in the PDF specifications but is necessary for compatibility
+     * with Adobe Acrobat and Adobe Reader.
+     *
+     * @return the integer representing access permissions
+     */
+
+    public int getPermissionBytesForPublicKey()
+    {
+        setPermissionBit(1, true);
+        setPermissionBit(7, false);
+        setPermissionBit(8, false);
+        for(int i=13; i<=32; i++)
+        {
+            setPermissionBit(i, false);
+        }
+        return bytes;
+    }
+
+    /**
+     * The returns an integer representing the access permissions.
+     * This integer can be used for standard PDF encryption as specified
+     * in the PDF specifications.
+     *
+     * @return the integer representing the access permissions
+     */
+    public int getPermissionBytes()
+    {
+        return bytes;
+    }
+
+    /**
+     * This will tell if the user can print.
+     *
+     * @return true If supplied with the user password they are allowed to print.
+     */
+    public boolean canPrint()
+    {
+        return isPermissionBitOn( PRINT_BIT );
+    }
+
+    /**
+     * Set if the user can print.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowPrinting A boolean determining if the user can print.
+     */
+    public void setCanPrint( boolean allowPrinting )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( PRINT_BIT, allowPrinting );
+        }
+    }
+
+    /**
+     * This will tell if the user can modify contents of the document.
+     *
+     * @return true If supplied with the user password they are allowed to modify the document
+     */
+    public boolean canModify()
+    {
+        return isPermissionBitOn( MODIFICATION_BIT );
+    }
+
+    /**
+     * Set if the user can modify the document.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowModifications A boolean determining if the user can modify the document.
+     */
+    public void setCanModify( boolean allowModifications )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( MODIFICATION_BIT, allowModifications );
+        }
+    }
+
+    /**
+     * This will tell if the user can extract text and images from the PDF document.
+     *
+     * @return true If supplied with the user password they are allowed to extract content
+     *              from the PDF document
+     */
+    public boolean canExtractContent()
+    {
+        return isPermissionBitOn( EXTRACT_BIT );
+    }
+
+    /**
+     * Set if the user can extract content from the document.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowExtraction A boolean determining if the user can extract content
+     *                        from the document.
+     */
+    public void setCanExtractContent( boolean allowExtraction )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( EXTRACT_BIT, allowExtraction );
+        }
+    }
+
+    /**
+     * This will tell if the user can add/modify text annotations, fill in interactive forms fields.
+     *
+     * @return true If supplied with the user password they are allowed to modify annotations.
+     */
+    public boolean canModifyAnnotations()
+    {
+        return isPermissionBitOn( MODIFY_ANNOTATIONS_BIT );
+    }
+
+    /**
+     * Set if the user can modify annotations.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowAnnotationModification A boolean determining if the user can modify annotations.
+     */
+    public void setCanModifyAnnotations( boolean allowAnnotationModification )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( MODIFY_ANNOTATIONS_BIT, allowAnnotationModification );
+        }
+    }
+
+    /**
+     * This will tell if the user can fill in interactive forms.
+     *
+     * @return true If supplied with the user password they are allowed to fill in form fields.
+     */
+    public boolean canFillInForm()
+    {
+        return isPermissionBitOn( FILL_IN_FORM_BIT );
+    }
+
+    /**
+     * Set if the user can fill in interactive forms.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowFillingInForm A boolean determining if the user can fill in interactive forms.
+     */
+    public void setCanFillInForm( boolean allowFillingInForm )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( FILL_IN_FORM_BIT, allowFillingInForm );
+        }
+    }
+
+    /**
+     * This will tell if the user can extract text and images from the PDF document
+     * for accessibility purposes.
+     *
+     * @return true If supplied with the user password they are allowed to extract content
+     *              from the PDF document
+     */
+    public boolean canExtractForAccessibility()
+    {
+        return isPermissionBitOn( EXTRACT_FOR_ACCESSIBILITY_BIT );
+    }
+
+    /**
+     * Set if the user can extract content from the document for accessibility purposes.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowExtraction A boolean determining if the user can extract content
+     *                        from the document.
+     */
+    public void setCanExtractForAccessibility( boolean allowExtraction )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( EXTRACT_FOR_ACCESSIBILITY_BIT, allowExtraction );
+        }
+    }
+
+    /**
+     * This will tell if the user can insert/rotate/delete pages.
+     *
+     * @return true If supplied with the user password they are allowed to extract content
+     *              from the PDF document
+     */
+    public boolean canAssembleDocument()
+    {
+        return isPermissionBitOn( ASSEMBLE_DOCUMENT_BIT );
+    }
+
+    /**
+     * Set if the user can insert/rotate/delete pages.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowAssembly A boolean determining if the user can assemble the document.
+     */
+    public void setCanAssembleDocument( boolean allowAssembly )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( ASSEMBLE_DOCUMENT_BIT, allowAssembly );
+        }
+    }
+
+    /**
+     * This will tell if the user can print the document in a degraded format.
+     *
+     * @return true If supplied with the user password they are allowed to print the
+     *              document in a degraded format.
+     */
+    public boolean canPrintDegraded()
+    {
+        return isPermissionBitOn( DEGRADED_PRINT_BIT );
+    }
+
+    /**
+     * Set if the user can print the document in a degraded format.
+     * This method will have no effect if the object is in read only mode
+     *
+     * @param allowAssembly A boolean determining if the user can print the
+     *        document in a degraded format.
+     */
+    public void setCanPrintDegraded( boolean allowAssembly )
+    {
+        if(!readOnly)
+        {
+            setPermissionBit( DEGRADED_PRINT_BIT, allowAssembly );
+        }
+    }
+
+    /**
+     * Locks the access permission read only (ie, the setters will have no effects).
+     * After that, the object cannot be unlocked.
+     * This method is used for the currentAccessPermssion of a document to avoid
+     * users to change access permission.
+     */
+    public void setReadOnly()
+    {
+        readOnly = true;
+    }
+
+    /**
+     * This will tell if the object has been set as read only.
+     *
+     * @return true if the object is in read only mode.
+     */
+
+    public boolean isReadOnly()
+    {
+        return readOnly;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/BadSecurityHandlerException.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/BadSecurityHandlerException.java
new file mode 100644 (file)
index 0000000..e93aeba
--- /dev/null
@@ -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.
+ */
+
+package org.apache.pdfbox.pdmodel.encryption;
+
+/**
+ * This exception can be thrown by the SecurityHandlersManager class when
+ * a document required an unimplemented security handler to be opened.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.2 $
+ */
+
+public class BadSecurityHandlerException extends Exception
+{
+    /**
+     * Default Constructor.
+     */
+    public BadSecurityHandlerException()
+    {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param e A sub exception.
+     */
+    public BadSecurityHandlerException(Exception e)
+    {
+        super(e);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param msg Message describing exception.
+     */
+    public BadSecurityHandlerException(String msg)
+    {
+        super(msg);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/DecryptionMaterial.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/DecryptionMaterial.java
new file mode 100644 (file)
index 0000000..bd395c6
--- /dev/null
@@ -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.
+ */
+
+package org.apache.pdfbox.pdmodel.encryption;
+
+/**
+ * This class represents data required to decrypt PDF documents. This can
+ * be a password for standard security or a X509 certificate with a private
+ * key for public key security.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.2 $
+ */
+public abstract class DecryptionMaterial
+{
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java
new file mode 100644 (file)
index 0000000..4054434
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class is a specialized view of the crypt filter dictionary of a PDF document.
+ * It contains a low level dictionary (COSDictionary) and provides the methods to
+ * manage its fields.
+ *
+ *
+ * @version $Revision: 1.0 $
+ */
+public class PDCryptFilterDictionary
+{
+
+    /**
+     * COS crypt filter dictionary.
+     */
+    protected COSDictionary cryptFilterDictionary = null;
+
+    /**
+     * creates a new empty crypt filter dictionary.
+     */
+    public PDCryptFilterDictionary()
+    {
+        cryptFilterDictionary = new COSDictionary();
+    }
+
+    /**
+     * creates a new crypt filter dictionary from the low level dictionary provided.
+     * @param d the low level dictionary that will be managed by the newly created object
+     */
+    public PDCryptFilterDictionary(COSDictionary d)
+    {
+        cryptFilterDictionary = d;
+    }
+
+    /**
+     * This will get the dictionary associated with this crypt filter dictionary.
+     *
+     * @return The COS dictionary that this object wraps.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return cryptFilterDictionary;
+    }
+
+    /**
+     * This will set the number of bits to use for the crypt filter algorithm.
+     *
+     * @param length The new key length.
+     */
+    public void setLength(int length)
+    {
+        cryptFilterDictionary.setInt(COSName.LENGTH, length);
+    }
+
+    /**
+     * This will return the Length entry of the crypt filter dictionary.<br /><br />
+     * The length in <b>bits</b> for the crypt filter algorithm. This will return a multiple of 8.
+     *
+     * @return The length in bits for the encryption algorithm
+     */
+    public int getLength()
+    {
+        return cryptFilterDictionary.getInt( COSName.LENGTH, 40 );
+    }
+
+     /**
+     * This will set the crypt filter method. 
+     * Allowed values are: NONE, V2, AESV2
+     *
+     * @param cfm name of the crypt filter method.
+     *
+     * @throws IOException If there is an error setting the data.
+     */
+    public void setCryptFilterMethod(COSName cfm) throws IOException
+    {
+        cryptFilterDictionary.setItem( COSName.CFM, cfm );
+    }
+
+    /**
+     * This will return the crypt filter method. 
+     * Allowed values are: NONE, V2, AESV2
+     *
+     * @return the name of the crypt filter method.
+     *
+     * @throws IOException If there is an error accessing the data.
+     */
+    public COSName getCryptFilterMethod() throws IOException
+    {
+        return (COSName)cryptFilterDictionary.getDictionaryObject( COSName.CFM );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDEncryptionDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDEncryptionDictionary.java
new file mode 100644 (file)
index 0000000..eaa21d1
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This class is a specialized view of the encryption dictionary of a PDF document.
+ * It contains a low level dictionary (COSDictionary) and provides the methods to
+ * manage its fields.
+ *
+ * The available fields are the ones who are involved by standard security handler
+ * and public key security handler.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.7 $
+ */
+public class PDEncryptionDictionary
+{
+    /**
+     * See PDF Reference 1.4 Table 3.13.
+     */
+    public static final int VERSION0_UNDOCUMENTED_UNSUPPORTED = 0;
+    /**
+     * See PDF Reference 1.4 Table 3.13.
+     */
+    public static final int VERSION1_40_BIT_ALGORITHM = 1;
+    /**
+     * See PDF Reference 1.4 Table 3.13.
+     */
+    public static final int VERSION2_VARIABLE_LENGTH_ALGORITHM = 2;
+    /**
+     * See PDF Reference 1.4 Table 3.13.
+     */
+    public static final int VERSION3_UNPUBLISHED_ALGORITHM = 3;
+    /**
+     * See PDF Reference 1.4 Table 3.13.
+     */
+    public static final int VERSION4_SECURITY_HANDLER = 4;
+
+    /**
+     * The default security handler.
+     */
+    public static final String DEFAULT_NAME = "Standard";
+
+    /**
+     * The default length for the encryption key.
+     */
+    public static final int DEFAULT_LENGTH = 40;
+
+    /**
+     * The default version, according to the PDF Reference.
+     */
+    public static final int DEFAULT_VERSION = VERSION0_UNDOCUMENTED_UNSUPPORTED;
+
+    /**
+     * COS encryption dictionary.
+     */
+    protected COSDictionary encryptionDictionary = null;
+
+    /**
+     * creates a new empty encryption dictionary.
+     */
+    public PDEncryptionDictionary()
+    {
+        encryptionDictionary = new COSDictionary();
+    }
+
+    /**
+     * creates a new encryption dictionary from the low level dictionary provided.
+     * @param d the low level dictionary that will be managed by the newly created object
+     */
+    public PDEncryptionDictionary(COSDictionary d)
+    {
+        encryptionDictionary = d;
+    }
+
+    /**
+     * This will get the dictionary associated with this encryption dictionary.
+     *
+     * @return The COS dictionary that this object wraps.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return encryptionDictionary;
+    }
+
+    /**
+     * Sets the filter entry of the encryption dictionary.
+     *
+     * @param filter The filter name.
+     */
+    public void setFilter(String filter)
+    {
+        encryptionDictionary.setItem( COSName.FILTER, COSName.getPDFName( filter ) );
+    }
+
+    /**
+     * Get the name of the filter.
+     *
+     * @return The filter name contained in this encryption dictionary.
+     */
+    public String getFilter()
+    {
+        return encryptionDictionary.getNameAsString( COSName.FILTER );
+    }
+
+    /**
+     * Get the name of the subfilter.
+     *
+     * @return The subfilter name contained in this encryption dictionary.
+     */
+    public String getSubFilter()
+    {
+        return encryptionDictionary.getNameAsString( COSName.SUB_FILTER );
+    }
+
+    /**
+     * Set the subfilter entry of the encryption dictionary.
+     *
+     * @param subfilter The value of the subfilter field.
+     */
+    public void setSubFilter(String subfilter)
+    {
+        encryptionDictionary.setName( COSName.SUB_FILTER, subfilter );
+    }
+
+    /**
+     * This will set the V entry of the encryption dictionary.<br /><br />
+     * See PDF Reference 1.4 Table 3.13.  <br /><br/>
+     * <b>Note: This value is used to decrypt the pdf document.  If you change this when
+     * the document is encrypted then decryption will fail!.</b>
+     *
+     * @param version The new encryption version.
+     */
+    public void setVersion(int version)
+    {
+        encryptionDictionary.setInt( COSName.V, version );
+    }
+
+    /**
+     * This will return the V entry of the encryption dictionary.<br /><br />
+     * See PDF Reference 1.4 Table 3.13.
+     *
+     * @return The encryption version to use.
+     */
+    public int getVersion()
+    {
+        return encryptionDictionary.getInt( COSName.V, 0 );
+    }
+
+    /**
+     * This will set the number of bits to use for the encryption algorithm.
+     *
+     * @param length The new key length.
+     */
+    public void setLength(int length)
+    {
+        encryptionDictionary.setInt(COSName.LENGTH, length);
+    }
+
+    /**
+     * This will return the Length entry of the encryption dictionary.<br /><br />
+     * The length in <b>bits</b> for the encryption algorithm.  This will return a multiple of 8.
+     *
+     * @return The length in bits for the encryption algorithm
+     */
+    public int getLength()
+    {
+        return encryptionDictionary.getInt( COSName.LENGTH, 40 );
+    }
+
+    /**
+     * This will set the R entry of the encryption dictionary.<br /><br />
+     * See PDF Reference 1.4 Table 3.14.  <br /><br/>
+     *
+     * <b>Note: This value is used to decrypt the pdf document.  If you change this when
+     * the document is encrypted then decryption will fail!.</b>
+     *
+     * @param revision The new encryption version.
+     */
+    public void setRevision(int revision)
+    {
+        encryptionDictionary.setInt( COSName.R, revision );
+    }
+
+    /**
+     * This will return the R entry of the encryption dictionary.<br /><br />
+     * See PDF Reference 1.4 Table 3.14.
+     *
+     * @return The encryption revision to use.
+     */
+    public int getRevision()
+    {
+        return encryptionDictionary.getInt( COSName.R, DEFAULT_VERSION );
+    }
+
+     /**
+     * This will set the O entry in the standard encryption dictionary.
+     *
+     * @param o A 32 byte array or null if there is no owner key.
+     *
+     * @throws IOException If there is an error setting the data.
+     */
+    public void setOwnerKey(byte[] o) throws IOException
+    {
+        COSString owner = new COSString();
+        owner.append( o );
+        encryptionDictionary.setItem( COSName.O, owner );
+    }
+
+    /**
+     * This will get the O entry in the standard encryption dictionary.
+     *
+     * @return A 32 byte array or null if there is no owner key.
+     *
+     * @throws IOException If there is an error accessing the data.
+     */
+    public byte[] getOwnerKey() throws IOException
+    {
+        byte[] o = null;
+        COSString owner = (COSString)encryptionDictionary.getDictionaryObject( COSName.O );
+        if( owner != null )
+        {
+            o = owner.getBytes();
+        }
+        return o;
+    }
+
+    /**
+     * This will set the U entry in the standard encryption dictionary.
+     *
+     * @param u A 32 byte array.
+     *
+     * @throws IOException If there is an error setting the data.
+     */
+    public void setUserKey(byte[] u) throws IOException
+    {
+        COSString user = new COSString();
+        user.append( u );
+        encryptionDictionary.setItem( COSName.U, user );
+    }
+
+    /**
+     * This will get the U entry in the standard encryption dictionary.
+     *
+     * @return A 32 byte array or null if there is no user key.
+     *
+     * @throws IOException If there is an error accessing the data.
+     */
+    public byte[] getUserKey() throws IOException
+    {
+        byte[] u = null;
+        COSString user = (COSString)encryptionDictionary.getDictionaryObject( COSName.U );
+        if( user != null )
+        {
+            u = user.getBytes();
+        }
+        return u;
+    }
+
+    /**
+     * This will set the permissions bit mask.
+     *
+     * @param permissions The new permissions bit mask
+     */
+    public void setPermissions(int permissions)
+    {
+        encryptionDictionary.setInt( COSName.P, permissions );
+    }
+
+    /**
+     * This will get the permissions bit mask.
+     *
+     * @return The permissions bit mask.
+     */
+    public int getPermissions()
+    {
+        return encryptionDictionary.getInt( COSName.P, 0 );
+    }
+
+    /**
+     * Will get the EncryptMetaData dictionary info.
+     * 
+     * @return true if EncryptMetaData is explicitly set to false (the default is true)
+     */
+    public boolean isEncryptMetaData()
+    {
+        // default is true (see 7.6.3.2 Standard Encryption Dictionary PDF 32000-1:2008)
+        boolean encryptMetaData = true;
+        
+        COSBase value = encryptionDictionary.getDictionaryObject(COSName.ENCRYPT_META_DATA);
+        
+        if (value instanceof COSBoolean) {
+            encryptMetaData = ((COSBoolean)value).getValue();
+        }
+        
+        return encryptMetaData;
+    }
+    
+    /**
+     * This will set the Recipients field of the dictionary. This field contains an array
+     * of string.
+     * @param recipients the array of bytes arrays to put in the Recipients field.
+     * @throws IOException If there is an error setting the data.
+     */
+    public void setRecipients(byte[][] recipients) throws IOException
+    {
+        COSArray array = new COSArray();
+        for(int i=0; i<recipients.length; i++)
+        {
+            COSString recip = new COSString();
+            recip.append(recipients[i]);
+            recip.setForceLiteralForm(true);
+            array.add(recip);
+        }
+        encryptionDictionary.setItem(COSName.RECIPIENTS, array);
+    }
+
+    /**
+     * Returns the number of recipients contained in the Recipients field of the dictionary.
+     *
+     * @return the number of recipients contained in the Recipients field.
+     */
+    public int getRecipientsLength()
+    {
+        COSArray array = (COSArray)encryptionDictionary.getItem(COSName.RECIPIENTS);
+        return array.size();
+    }
+
+    /**
+     * returns the COSString contained in the Recipients field at position i.
+     *
+     * @param i the position in the Recipients field array.
+     *
+     * @return a COSString object containing information about the recipient number i.
+     */
+    public COSString getRecipientStringAt(int i)
+    {
+        COSArray array = (COSArray)encryptionDictionary.getItem(COSName.RECIPIENTS);
+        return (COSString)array.get(i);
+    }
+    
+    /**
+     * Returns the standard crypt filter.
+     * 
+     * @return the standard crypt filter if available.
+     */
+    public PDCryptFilterDictionary getStdCryptFilterDictionary() 
+    {
+        return getCryptFilterDictionary(COSName.STD_CF);
+    }
+
+    /**
+     * Returns the crypt filter with the given name.
+     * 
+     * @param cryptFilterName the name of the crypt filter
+     * 
+     * @return the crypt filter with the given name if available
+     */
+    public PDCryptFilterDictionary getCryptFilterDictionary(COSName cryptFilterName) 
+    {
+        COSDictionary cryptFilterDictionary = (COSDictionary)encryptionDictionary.getDictionaryObject( COSName.CF );
+        if (cryptFilterDictionary != null)
+        {
+            COSDictionary stdCryptFilterDictionary = (COSDictionary)cryptFilterDictionary.getDictionaryObject(cryptFilterName);
+            if (stdCryptFilterDictionary != null)
+            {
+                return new PDCryptFilterDictionary(stdCryptFilterDictionary);
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Returns the name of the filter which is used for de/encrypting streams.
+     * Default value is "Identity".
+     * 
+     * @return the name of the filter
+     */
+    public COSName getStreamFilterName() 
+    {
+        COSName stmF = (COSName)encryptionDictionary.getDictionaryObject( COSName.STM_F );
+        if (stmF == null)
+        {
+            stmF = COSName.IDENTITY;
+        }
+        return stmF;
+    }
+
+    /**
+     * Returns the name of the filter which is used for de/encrypting strings.
+     * Default value is "Identity".
+     * 
+     * @return the name of the filter
+     */
+    public COSName getStringFilterName() 
+    {
+        COSName strF = (COSName)encryptionDictionary.getDictionaryObject( COSName.STR_F );
+        if (strF == null)
+        {
+            strF = COSName.IDENTITY;
+        }
+        return strF;
+    }
+
+}
+
+    
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDEncryptionManager.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDEncryptionManager.java
new file mode 100644 (file)
index 0000000..5f97d2d
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import java.io.IOException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class will handle loading of the different security handlers.
+ *
+ * See PDF Reference 1.4 section "3.5 Encryption"
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ * @deprecated Made deprecated by the new security layer of PDFBox. Use SecurityHandlers instead.
+ */
+
+public class PDEncryptionManager
+{
+    private static Map handlerMap = Collections.synchronizedMap( new HashMap() );
+
+    static
+    {
+        registerSecurityHandler( PDStandardEncryption.FILTER_NAME, PDStandardEncryption.class );
+    }
+
+    private PDEncryptionManager()
+    {
+    }
+
+    /**
+     * This will allow the user to register new security handlers when unencrypting a
+     * document.
+     *
+     * @param filterName As described in the encryption dictionary.
+     * @param handlerClass A subclass of PDEncryptionDictionary that has a constructor that takes
+     *        a COSDictionary.
+     */
+    public static void registerSecurityHandler( String filterName, Class handlerClass )
+    {
+        handlerMap.put( COSName.getPDFName( filterName ), handlerClass );
+    }
+
+    /**
+     * This will get the correct security handler for the encryption dictionary.
+     *
+     * @param dictionary The encryption dictionary.
+     *
+     * @return An implementation of PDEncryptionDictionary(PDStandardEncryption for most cases).
+     *
+     * @throws IOException If a security handler could not be found.
+     */
+    public static PDEncryptionDictionary getEncryptionDictionary( COSDictionary dictionary )
+        throws IOException
+    {
+        Object retval = null;
+        if( dictionary != null )
+        {
+            COSName filter = (COSName)dictionary.getDictionaryObject( COSName.FILTER );
+            Class handlerClass = (Class)handlerMap.get( filter );
+            if( handlerClass == null )
+            {
+                throw new IOException( "No handler for security handler '" + filter.getName() + "'" );
+            }
+            else
+            {
+                try
+                {
+                    Constructor ctor = handlerClass.getConstructor( new Class[] {
+                        COSDictionary.class
+                    } );
+                    retval = ctor.newInstance( new Object[] {
+                        dictionary
+                    } );
+                }
+                catch( NoSuchMethodException e )
+                {
+                    throw new IOException( e.getMessage() );
+                }
+                catch( InstantiationException e )
+                {
+                    throw new IOException( e.getMessage() );
+                }
+                catch( IllegalAccessException e )
+                {
+                    throw new IOException( e.getMessage() );
+                }
+                catch( InvocationTargetException e )
+                {
+                    throw new IOException( e.getMessage() );
+                }
+            }
+        }
+        return (PDEncryptionDictionary)retval;
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDStandardEncryption.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PDStandardEncryption.java
new file mode 100644 (file)
index 0000000..4f0c466
--- /dev/null
@@ -0,0 +1,403 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSString;
+
+import java.io.IOException;
+
+/**
+ * This class holds information that is related to the standard PDF encryption.
+ *
+ * See PDF Reference 1.4 section "3.5 Encryption"
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ * @deprecated Made deprecated by the new security layer of PDFBox. Use SecurityHandlers instead.
+ */
+public class PDStandardEncryption extends PDEncryptionDictionary
+{
+    /**
+     * The 'Filter' name for this security handler.
+     */
+    public static final String FILTER_NAME = "Standard";
+
+    /**
+     * The default revision of one is not specified.
+     */
+    public static final int DEFAULT_REVISION = 3;
+
+    /**
+     * Encryption revision 2.
+     */
+    public static final int REVISION2 = 2;
+    /**
+     * Encryption revision 3.
+     */
+    public static final int REVISION3 = 3;
+    /**
+     * Encryption revision 4.
+     */
+    public static final int REVISION4 = 4;
+
+    /**
+     * The default set of permissions which is to allow all.
+     */
+    public static final int DEFAULT_PERMISSIONS = 0xFFFFFFFF ^ 3;//bits 0 & 1 need to be zero
+
+    private static final int PRINT_BIT = 3;
+    private static final int MODIFICATION_BIT = 4;
+    private static final int EXTRACT_BIT = 5;
+    private static final int MODIFY_ANNOTATIONS_BIT = 6;
+    private static final int FILL_IN_FORM_BIT = 9;
+    private static final int EXTRACT_FOR_ACCESSIBILITY_BIT = 10;
+    private static final int ASSEMBLE_DOCUMENT_BIT = 11;
+    private static final int DEGRADED_PRINT_BIT = 12;
+
+    /**
+     * Default constructor that uses Version 2, Revision 3, 40 bit encryption,
+     * all permissions allowed.
+     */
+    public PDStandardEncryption()
+    {
+        super();
+        encryptionDictionary.setItem( COSName.FILTER, COSName.getPDFName( FILTER_NAME ) );
+        setVersion( PDEncryptionDictionary.VERSION1_40_BIT_ALGORITHM  );
+        setRevision( PDStandardEncryption.REVISION2 );
+        setPermissions( DEFAULT_PERMISSIONS );
+    }
+
+    /**
+     * Constructor from existing dictionary.
+     *
+     * @param dict The existing encryption dictionary.
+     */
+    public PDStandardEncryption( COSDictionary dict )
+    {
+        super( dict );
+    }
+
+    /**
+     * This will return the R entry of the encryption dictionary.<br /><br />
+     * See PDF Reference 1.4 Table 3.14.
+     *
+     * @return The encryption revision to use.
+     */
+    public int getRevision()
+    {
+        int revision = DEFAULT_VERSION;
+        COSNumber cosRevision = (COSNumber)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "R" ) );
+        if( cosRevision != null )
+        {
+            revision = cosRevision.intValue();
+        }
+        return revision;
+    }
+
+    /**
+     * This will set the R entry of the encryption dictionary.<br /><br />
+     * See PDF Reference 1.4 Table 3.14.  <br /><br/>
+     *
+     * <b>Note: This value is used to decrypt the pdf document.  If you change this when
+     * the document is encrypted then decryption will fail!.</b>
+     *
+     * @param revision The new encryption version.
+     */
+    public void setRevision( int revision )
+    {
+        encryptionDictionary.setInt( COSName.getPDFName( "R" ), revision );
+    }
+
+    /**
+     * This will get the O entry in the standard encryption dictionary.
+     *
+     * @return A 32 byte array or null if there is no owner key.
+     */
+    public byte[] getOwnerKey()
+    {
+       byte[] o = null;
+       COSString owner = (COSString)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "O" ) );
+       if( owner != null )
+       {
+           o = owner.getBytes();
+       }
+       return o;
+    }
+
+    /**
+     * This will set the O entry in the standard encryption dictionary.
+     *
+     * @param o A 32 byte array or null if there is no owner key.
+     *
+     * @throws IOException If there is an error setting the data.
+     */
+    public void setOwnerKey( byte[] o ) throws IOException
+    {
+       COSString owner = new COSString();
+       owner.append( o );
+       encryptionDictionary.setItem( COSName.getPDFName( "O" ), owner );
+    }
+
+    /**
+     * This will get the U entry in the standard encryption dictionary.
+     *
+     * @return A 32 byte array or null if there is no user key.
+     */
+    public byte[] getUserKey()
+    {
+       byte[] u = null;
+       COSString user = (COSString)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "U" ) );
+       if( user != null )
+       {
+           u = user.getBytes();
+       }
+       return u;
+    }
+
+    /**
+     * This will set the U entry in the standard encryption dictionary.
+     *
+     * @param u A 32 byte array.
+     *
+     * @throws IOException If there is an error setting the data.
+     */
+    public void setUserKey( byte[] u ) throws IOException
+    {
+       COSString user = new COSString();
+       user.append( u );
+       encryptionDictionary.setItem( COSName.getPDFName( "U" ), user );
+    }
+
+    /**
+     * This will get the permissions bit mask.
+     *
+     * @return The permissions bit mask.
+     */
+    public int getPermissions()
+    {
+        int permissions = 0;
+        COSInteger p = (COSInteger)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "P" ) );
+        if( p != null )
+        {
+            permissions = p.intValue();
+        }
+        return permissions;
+    }
+
+    /**
+     * This will set the permissions bit mask.
+     *
+     * @param p The new permissions bit mask
+     */
+    public void setPermissions( int p )
+    {
+        encryptionDictionary.setInt( COSName.getPDFName( "P" ), p );
+    }
+
+    private boolean isPermissionBitOn( int bit )
+    {
+        return (getPermissions() & (1 << (bit-1))) != 0;
+    }
+
+    private boolean setPermissionBit( int bit, boolean value )
+    {
+        int permissions = getPermissions();
+        if( value )
+        {
+            permissions = permissions | (1 << (bit-1));
+        }
+        else
+        {
+            permissions = permissions & (0xFFFFFFFF ^ (1 << (bit-1)));
+        }
+        setPermissions( permissions );
+
+        return (getPermissions() & (1 << (bit-1))) != 0;
+    }
+
+    /**
+     * This will tell if the user can print.
+     *
+     * @return true If supplied with the user password they are allowed to print.
+     */
+    public boolean canPrint()
+    {
+        return isPermissionBitOn( PRINT_BIT );
+    }
+
+    /**
+     * Set if the user can print.
+     *
+     * @param allowPrinting A boolean determining if the user can print.
+     */
+    public void setCanPrint( boolean allowPrinting )
+    {
+        setPermissionBit( PRINT_BIT, allowPrinting );
+    }
+
+    /**
+     * This will tell if the user can modify contents of the document.
+     *
+     * @return true If supplied with the user password they are allowed to modify the document
+     */
+    public boolean canModify()
+    {
+        return isPermissionBitOn( MODIFICATION_BIT );
+    }
+
+    /**
+     * Set if the user can modify the document.
+     *
+     * @param allowModifications A boolean determining if the user can modify the document.
+     */
+    public void setCanModify( boolean allowModifications )
+    {
+        setPermissionBit( MODIFICATION_BIT, allowModifications );
+    }
+
+    /**
+     * This will tell if the user can extract text and images from the PDF document.
+     *
+     * @return true If supplied with the user password they are allowed to extract content
+     *              from the PDF document
+     */
+    public boolean canExtractContent()
+    {
+        return isPermissionBitOn( EXTRACT_BIT );
+    }
+
+    /**
+     * Set if the user can extract content from the document.
+     *
+     * @param allowExtraction A boolean determining if the user can extract content
+     *                        from the document.
+     */
+    public void setCanExtractContent( boolean allowExtraction )
+    {
+        setPermissionBit( EXTRACT_BIT, allowExtraction );
+    }
+
+    /**
+     * This will tell if the user can add/modify text annotations, fill in interactive forms fields.
+     *
+     * @return true If supplied with the user password they are allowed to modify annotations.
+     */
+    public boolean canModifyAnnotations()
+    {
+        return isPermissionBitOn( MODIFY_ANNOTATIONS_BIT );
+    }
+
+    /**
+     * Set if the user can modify annotations.
+     *
+     * @param allowAnnotationModification A boolean determining if the user can modify annotations.
+     */
+    public void setCanModifyAnnotations( boolean allowAnnotationModification )
+    {
+        setPermissionBit( MODIFY_ANNOTATIONS_BIT, allowAnnotationModification );
+    }
+
+    /**
+     * This will tell if the user can fill in interactive forms.
+     *
+     * @return true If supplied with the user password they are allowed to fill in form fields.
+     */
+    public boolean canFillInForm()
+    {
+        return isPermissionBitOn( FILL_IN_FORM_BIT );
+    }
+
+    /**
+     * Set if the user can fill in interactive forms.
+     *
+     * @param allowFillingInForm A boolean determining if the user can fill in interactive forms.
+     */
+    public void setCanFillInForm( boolean allowFillingInForm )
+    {
+        setPermissionBit( FILL_IN_FORM_BIT, allowFillingInForm );
+    }
+
+    /**
+     * This will tell if the user can extract text and images from the PDF document
+     * for accessibility purposes.
+     *
+     * @return true If supplied with the user password they are allowed to extract content
+     *              from the PDF document
+     */
+    public boolean canExtractForAccessibility()
+    {
+        return isPermissionBitOn( EXTRACT_FOR_ACCESSIBILITY_BIT );
+    }
+
+    /**
+     * Set if the user can extract content from the document for accessibility purposes.
+     *
+     * @param allowExtraction A boolean determining if the user can extract content
+     *                        from the document.
+     */
+    public void setCanExtractForAccessibility( boolean allowExtraction )
+    {
+        setPermissionBit( EXTRACT_FOR_ACCESSIBILITY_BIT, allowExtraction );
+    }
+
+    /**
+     * This will tell if the user can insert/rotate/delete pages.
+     *
+     * @return true If supplied with the user password they are allowed to extract content
+     *              from the PDF document
+     */
+    public boolean canAssembleDocument()
+    {
+        return isPermissionBitOn( ASSEMBLE_DOCUMENT_BIT );
+    }
+
+    /**
+     * Set if the user can insert/rotate/delete pages.
+     *
+     * @param allowAssembly A boolean determining if the user can assemble the document.
+     */
+    public void setCanAssembleDocument( boolean allowAssembly )
+    {
+        setPermissionBit( ASSEMBLE_DOCUMENT_BIT, allowAssembly );
+    }
+
+    /**
+     * This will tell if the user can print the document in a degraded format.
+     *
+     * @return true If supplied with the user password they are allowed to print the
+     *              document in a degraded format.
+     */
+    public boolean canPrintDegraded()
+    {
+        return isPermissionBitOn( DEGRADED_PRINT_BIT );
+    }
+
+    /**
+     * Set if the user can print the document in a degraded format.
+     *
+     * @param allowAssembly A boolean determining if the user can print the
+     *        document in a degraded format.
+     */
+    public void setCanPrintDegraded( boolean allowAssembly )
+    {
+        setPermissionBit( DEGRADED_PRINT_BIT, allowAssembly );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/ProtectionPolicy.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/ProtectionPolicy.java
new file mode 100644 (file)
index 0000000..4f1337d
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.pdmodel.encryption;
+
+/**
+ * This class represents the protection policy to apply to a document.
+ *
+ * Objects implementing this abstract class can be passed to the protect method of PDDocument
+ * to protect a document.
+ *
+ * @see org.apache.pdfbox.pdmodel.PDDocument#protect(ProtectionPolicy)
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.3 $
+ */
+public abstract class ProtectionPolicy
+{
+
+    private static final int DEFAULT_KEY_LENGTH = 40;
+
+    private int encryptionKeyLength = DEFAULT_KEY_LENGTH;
+
+    /**
+     * set the length in (bits) of the secret key that will be
+     * used to encrypt document data.
+     * The default value is 40 bits, which provides a low security level
+     * but is compatible with old versions of Acrobat Reader.
+     *
+     * @param l the length in bits (must be 40 or 128)
+     */
+    public void setEncryptionKeyLength(int l)
+    {
+        if(l!=40 && l!=128)
+        {
+            throw new RuntimeException("Invalid key length '" + l + "' value must be 40 or 128!");
+        }
+        encryptionKeyLength = l;
+    }
+
+    /**
+     * Get the length of the secrete key that will be used to encrypt
+     * document data.
+     *
+     * @return The length (in bits) of the encryption key.
+     */
+    public int getEncryptionKeyLength()
+    {
+        return encryptionKeyLength;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyDecryptionMaterial.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyDecryptionMaterial.java
new file mode 100644 (file)
index 0000000..8b4a2c5
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
+/**
+ * This class holds necessary information to decrypt a PDF document
+ * protected by the public key security handler.
+ *
+ * To decrypt such a document, we need:
+ * <ul>
+ * <li>a valid X509 certificate which correspond to one of the recipient of the document</li>
+ * <li>the private key corresponding to this certificate
+ * <li>the password to decrypt the private key if necessary</li>
+ * </ul>
+ *
+ * Objects of this class can be used with the <code>openProtection</code> method of <code>PDDocument</code>.
+ *
+ * The following example shows how to decrypt a document using a PKCS#12 certificate
+ * (typically files with a pfx extension).
+ *
+ * <pre>
+ * PDDocument doc = PDDocument.load(document_path);
+ * KeyStore ks = KeyStore.getInstance("PKCS12");
+ * ks.load(new FileInputStream(certificate_path), password.toCharArray());
+ * PublicKeyDecryptionMaterial dm = new PublicKeyDecryptionMaterial(ks, null, password);
+ * doc.openProtection(dm);
+ * </pre>
+ *
+ * In this code sample certificate_path contains the path to the PKCS#12 certificate.
+ *
+ * @see org.apache.pdfbox.pdmodel.PDDocument#openProtection(DecryptionMaterial)
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.2 $
+ */
+
+public class PublicKeyDecryptionMaterial extends DecryptionMaterial
+{
+    private String password = null;
+    private KeyStore keyStore = null;
+    private String alias = null;
+
+    /**
+     * Create a new public key decryption material.
+     *
+     * @param keystore The keystore were the private key and the certificate are
+     * @param a The alias of the private key and the certificate.
+     *   If the keystore contains only 1 entry, this parameter can be left null.
+     * @param pwd The password to extract the private key from the keystore.
+     */
+
+    public PublicKeyDecryptionMaterial(KeyStore keystore, String a, String pwd)
+    {
+        keyStore = keystore;
+        alias = a;
+        password = pwd;
+    }
+
+
+    /**
+     * Returns the certificate contained in the keystore.
+     *
+     * @return The certificate that will be used to try to open the document.
+     *
+     * @throws KeyStoreException If there is an error accessing the certificate.
+     */
+
+    public X509Certificate getCertificate() throws KeyStoreException
+    {
+        if(keyStore.size() == 1)
+        {
+            Enumeration aliases = keyStore.aliases();
+            String keyStoreAlias = (String)aliases.nextElement();
+            return (X509Certificate)keyStore.getCertificate(keyStoreAlias);
+        }
+        else
+        {
+            if(keyStore.containsAlias(alias))
+            {
+                return (X509Certificate)keyStore.getCertificate(alias);
+            }
+            throw new KeyStoreException("the keystore does not contain the given alias");
+        }
+    }
+
+    /**
+     * Returns the password given by the user and that will be used
+     * to open the private key.
+     *
+     * @return The password.
+     */
+    public String getPassword()
+    {
+        return password;
+    }
+
+    /**
+     * returns The private key that will be used to open the document protection.
+     * @return The private key.
+     * @throws KeyStoreException If there is an error accessing the key.
+     */
+    public Key getPrivateKey() throws KeyStoreException
+    {
+        try
+        {
+            if(keyStore.size() == 1)
+            {
+                Enumeration aliases = keyStore.aliases();
+                String keyStoreAlias = (String)aliases.nextElement();
+                return keyStore.getKey(keyStoreAlias, password.toCharArray());
+            }
+            else
+            {
+                if(keyStore.containsAlias(alias))
+                {
+                    return keyStore.getKey(alias, password.toCharArray());
+                }
+                throw new KeyStoreException("the keystore does not contain the given alias");
+            }
+        }
+        catch(UnrecoverableKeyException ex)
+        {
+            throw new KeyStoreException("the private key is not recoverable");
+        }
+        catch(NoSuchAlgorithmException ex)
+        {
+            throw new KeyStoreException("the algorithm necessary to recover the key is not available");
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyProtectionPolicy.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyProtectionPolicy.java
new file mode 100644 (file)
index 0000000..6837202
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * This class represents the protection policy to use to protect
+ * a document with the public key security handler as described
+ * in the PDF specification 1.6 p104.
+ *
+ * PDF documents are encrypted so that they can be decrypted by
+ * one or more recipients. Each recipient have its own access permission.
+ *
+ * The following code sample shows how to protect a document using
+ * the public key security handler. In this code sample, <code>doc</code> is
+ * a <code>PDDocument</code> object.
+ *
+ * <pre>
+ * PublicKeyProtectionPolicy policy = new PublicKeyProtectionPolicy();
+ * PublicKeyRecipient recip = new PublicKeyRecipient();
+ * AccessPermission ap = new AccessPermission();
+ * ap.setCanModify(false);
+ * recip.setPermission(ap);
+ *
+ * // load the recipient's certificate
+ * InputStream inStream = new FileInputStream(certificate_path);
+ * CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ * X509Certificate certificate = (X509Certificate)cf.generateCertificate(inStream);
+ * inStream.close();
+ *
+ * recip.setX509(certificate); // set the recipient's certificate
+ * policy.addRecipient(recip);
+ * policy.setEncryptionKeyLength(128); // the document will be encrypted with 128 bits secret key
+ * doc.protect(policy);
+ * doc.save(out);
+ * </pre>
+ *
+ *
+ * @see org.apache.pdfbox.pdmodel.PDDocument#protect(ProtectionPolicy)
+ * @see AccessPermission
+ * @see PublicKeyRecipient
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.2 $
+ */
+public class PublicKeyProtectionPolicy extends ProtectionPolicy
+{
+
+    /**
+     * The list of recipients.
+     */
+    private ArrayList recipients = null;
+
+    /**
+     * The X509 certificate used to decrypt the current document.
+     */
+    private X509Certificate decryptionCertificate;
+
+    /**
+     * Constructor for encryption. Just creates an empty recipients list.
+     */
+    public PublicKeyProtectionPolicy()
+    {
+        recipients = new ArrayList();
+    }
+
+    /**
+     * Adds a new recipient to the recipients list.
+     *
+     * @param r A new recipient.
+     */
+    public void addRecipient(PublicKeyRecipient r)
+    {
+        recipients.add(r);
+    }
+
+    /**
+     * Removes a recipient from the recipients list.
+     *
+     * @param r The recipient to remove.
+     *
+     * @return true If a recipient was found and removed.
+     */
+    public boolean removeRecipient(PublicKeyRecipient r)
+    {
+        return recipients.remove(r);
+    }
+
+    /**
+     * Returns an iterator to browse the list of recipients. Object
+     * found in this iterator are <code>PublicKeyRecipient</code>.
+     *
+     * @return The recipients list iterator.
+     */
+    public Iterator getRecipientsIterator()
+    {
+        return recipients.iterator();
+    }
+
+    /**
+     * Getter of the property <tt>decryptionCertificate</tt>.
+     *
+     * @return  Returns the decryptionCertificate.
+     */
+    public X509Certificate getDecryptionCertificate()
+    {
+        return decryptionCertificate;
+    }
+
+    /**
+     * Setter of the property <tt>decryptionCertificate</tt>.
+     *
+     * @param aDecryptionCertificate The decryption certificate to set.
+     */
+    public void setDecryptionCertificate(X509Certificate aDecryptionCertificate)
+    {
+        this.decryptionCertificate = aDecryptionCertificate;
+    }
+
+    /**
+     * Returns the number of recipients.
+     *
+     * @return The number of recipients.
+     */
+    public int getRecipientsNumber()
+    {
+        return recipients.size();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyRecipient.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeyRecipient.java
new file mode 100644 (file)
index 0000000..cdbc981
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * Represents a recipient in the public key protection policy.
+ *
+ * @see PublicKeyProtectionPolicy
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.2 $
+ */
+public class PublicKeyRecipient
+{
+    private X509Certificate x509;
+
+    private AccessPermission permission;
+
+    /**
+     * Returns the X509 certificate of the recipient.
+     *
+     * @return The X509 certificate
+     */
+    public X509Certificate getX509()
+    {
+        return x509;
+    }
+
+    /**
+     * Set the X509 certificate of the recipient.
+     *
+     * @param aX509 The X509 certificate
+     */
+    public void setX509(X509Certificate aX509)
+    {
+        this.x509 = aX509;
+    }
+
+    /**
+     * Returns the access permission granted to the recipient.
+     *
+     * @return The access permission object.
+     */
+    public AccessPermission getPermission()
+    {
+        return permission;
+    }
+
+    /**
+     * Set the access permission granted to the recipient.
+     *
+     * @param permissions The permission to set.
+     */
+    public void setPermission(AccessPermission permissions)
+    {
+        this.permission = permissions;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java
new file mode 100644 (file)
index 0000000..9c1bdb2
--- /dev/null
@@ -0,0 +1,407 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientIdentifier;
+import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * This class implements the public key security handler
+ * described in the PDF specification.
+ *
+ * @see PDF Spec 1.6 p104
+ *
+ * @see PublicKeyProtectionPolicy to see how to protect document with this security handler.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.3 $
+ */
+public class PublicKeySecurityHandler extends SecurityHandler
+{
+
+    /**
+     * The filter name.
+     */
+    public static final String FILTER = "Adobe.PubSec";
+
+    private static final String SUBFILTER = "adbe.pkcs7.s4";
+
+    private PublicKeyProtectionPolicy policy = null;
+
+    /**
+     * Constructor.
+     */
+    public PublicKeySecurityHandler()
+    {
+    }
+
+    /**
+     * Constructor used for encryption.
+     *
+     * @param p The protection policy.
+     */
+    public PublicKeySecurityHandler(PublicKeyProtectionPolicy p)
+    {
+        policy = p;
+        this.keyLength = policy.getEncryptionKeyLength();
+    }
+
+    /**
+     * Decrypt the document.
+     *
+     * @param doc The document to decrypt.
+     * @param decryptionMaterial The data used to decrypt the document.
+     *
+     * @throws CryptographyException If there is an error during decryption.
+     * @throws IOException If there is an error accessing data.
+     */
+    public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMaterial)
+        throws CryptographyException, IOException
+    {
+        this.document = doc;
+
+        PDEncryptionDictionary dictionary = doc.getEncryptionDictionary();
+
+        if(dictionary.getLength() != 0)
+        {
+            this.keyLength = dictionary.getLength();
+        }
+
+        if(!(decryptionMaterial instanceof PublicKeyDecryptionMaterial))
+        {
+            throw new CryptographyException(
+                "Provided decryption material is not compatible with the document");
+        }
+
+        PublicKeyDecryptionMaterial material = (PublicKeyDecryptionMaterial)decryptionMaterial;
+
+        try
+        {
+            boolean foundRecipient = false;
+
+            // the decrypted content of the enveloped data that match
+            // the certificate in the decryption material provided
+            byte[] envelopedData = null;
+
+            // the bytes of each recipient in the recipients array
+            byte[][] recipientFieldsBytes = new byte[dictionary.getRecipientsLength()][];
+
+            int recipientFieldsLength = 0;
+
+            for(int i=0; i<dictionary.getRecipientsLength(); i++)
+            {
+                COSString recipientFieldString = dictionary.getRecipientStringAt(i);
+                byte[] recipientBytes = recipientFieldString.getBytes();
+                CMSEnvelopedData data = new CMSEnvelopedData(recipientBytes);
+                Iterator recipCertificatesIt = data.getRecipientInfos().getRecipients().iterator();
+                while(recipCertificatesIt.hasNext())
+                {
+                    RecipientInformation ri =
+                        (RecipientInformation)recipCertificatesIt.next();
+                    // Impl: if a matching certificate was previously found it is an error,
+                    // here we just don't care about it
+                    if(ri.getRID().match(material.getCertificate()) && !foundRecipient)
+                    {
+                        foundRecipient = true;
+                        envelopedData = ri.getContent(material.getPrivateKey(), "BC");
+                    }
+                }
+                recipientFieldsBytes[i] = recipientBytes;
+                recipientFieldsLength += recipientBytes.length;
+            }
+            if(!foundRecipient || envelopedData == null)
+            {
+                throw new CryptographyException("The certificate matches no recipient entry");
+            }
+            if(envelopedData.length != 24)
+            {
+                throw new CryptographyException("The enveloped data does not contain 24 bytes");
+            }
+            // now envelopedData contains:
+            // - the 20 bytes seed
+            // - the 4 bytes of permission for the current user
+
+            byte[] accessBytes = new byte[4];
+            System.arraycopy(envelopedData, 20, accessBytes, 0, 4);
+
+            currentAccessPermission = new AccessPermission(accessBytes);
+            currentAccessPermission.setReadOnly();
+
+             // what we will put in the SHA1 = the seed + each byte contained in the recipients array
+            byte[] sha1Input = new byte[recipientFieldsLength + 20];
+
+            // put the seed in the sha1 input
+            System.arraycopy(envelopedData, 0, sha1Input, 0, 20);
+
+            // put each bytes of the recipients array in the sha1 input
+            int sha1InputOffset = 20;
+            for(int i=0; i<recipientFieldsBytes.length; i++)
+            {
+                System.arraycopy(
+                    recipientFieldsBytes[i], 0,
+                    sha1Input, sha1InputOffset, recipientFieldsBytes[i].length);
+                sha1InputOffset += recipientFieldsBytes[i].length;
+            }
+
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            byte[] mdResult = md.digest(sha1Input);
+
+            // we have the encryption key ...
+            encryptionKey = new byte[this.keyLength/8];
+            System.arraycopy(mdResult, 0, encryptionKey, 0, this.keyLength/8);
+
+            proceedDecryption();
+
+
+        }
+        catch(CMSException e)
+        {
+            throw new CryptographyException(e);
+        }
+        catch(KeyStoreException e)
+        {
+            throw new CryptographyException(e);
+        }
+        catch(NoSuchProviderException e)
+        {
+            throw new CryptographyException(e);
+        }
+        catch(NoSuchAlgorithmException e)
+        {
+            throw new CryptographyException(e);
+        }
+
+    }
+
+    /**
+     * Prepare the document for encryption.
+     *
+     * @param doc The document that will be encrypted.
+     *
+     * @throws CryptographyException If there is an error while encrypting.
+     */
+    public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException
+    {
+
+        try
+        {
+            Security.addProvider(new BouncyCastleProvider());
+
+            PDEncryptionDictionary dictionary = doc.getEncryptionDictionary();
+            if (dictionary == null) 
+            {
+                dictionary = new PDEncryptionDictionary();
+            }
+
+            dictionary.setFilter(FILTER);
+            dictionary.setLength(this.keyLength);
+            dictionary.setVersion(2);
+            dictionary.setSubFilter(SUBFILTER);
+
+            byte[][] recipientsField = new byte[policy.getRecipientsNumber()][];
+
+            // create the 20 bytes seed
+
+            byte[] seed = new byte[20];
+
+            KeyGenerator key = KeyGenerator.getInstance("AES");
+            key.init(192, new SecureRandom());
+            SecretKey sk = key.generateKey();
+            System.arraycopy(sk.getEncoded(), 0, seed, 0, 20); // create the 20 bytes seed
+
+
+            Iterator it = policy.getRecipientsIterator();
+            int i = 0;
+
+
+            while(it.hasNext())
+            {
+                PublicKeyRecipient recipient = (PublicKeyRecipient)it.next();
+                X509Certificate certificate = recipient.getX509();
+                int permission = recipient.getPermission().getPermissionBytesForPublicKey();
+
+                byte[] pkcs7input = new byte[24];
+                byte one = (byte)(permission);
+                byte two = (byte)(permission >>> 8);
+                byte three = (byte)(permission >>> 16);
+                byte four = (byte)(permission >>> 24);
+
+                System.arraycopy(seed, 0, pkcs7input, 0, 20); // put this seed in the pkcs7 input
+
+                pkcs7input[20] = four;
+                pkcs7input[21] = three;
+                pkcs7input[22] = two;
+                pkcs7input[23] = one;
+
+                DERObject obj = createDERForRecipient(pkcs7input, certificate);
+
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+                DEROutputStream k = new DEROutputStream(baos);
+
+                k.writeObject(obj);
+
+                recipientsField[i] = baos.toByteArray();
+
+                i++;
+            }
+
+            dictionary.setRecipients(recipientsField);
+
+            int sha1InputLength = seed.length;
+
+            for(int j=0; j<dictionary.getRecipientsLength(); j++)
+            {
+                COSString string = dictionary.getRecipientStringAt(j);
+                sha1InputLength += string.getBytes().length;
+            }
+
+
+            byte[] sha1Input = new byte[sha1InputLength];
+
+            System.arraycopy(seed, 0, sha1Input, 0, 20);
+
+            int sha1InputOffset = 20;
+
+
+            for(int j=0; j<dictionary.getRecipientsLength(); j++)
+            {
+                COSString string = dictionary.getRecipientStringAt(j);
+                System.arraycopy(
+                    string.getBytes(), 0,
+                    sha1Input, sha1InputOffset, string.getBytes().length);
+                sha1InputOffset += string.getBytes().length;
+            }
+
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+
+            byte[] mdResult = md.digest(sha1Input);
+
+            this.encryptionKey = new byte[this.keyLength/8];
+            System.arraycopy(mdResult, 0, this.encryptionKey, 0, this.keyLength/8);
+
+            doc.setEncryptionDictionary(dictionary);
+            doc.getDocument().setEncryptionDictionary(dictionary.encryptionDictionary);
+
+        }
+        catch(NoSuchAlgorithmException ex)
+        {
+            throw new CryptographyException(ex);
+        }
+        catch(NoSuchProviderException ex)
+        {
+            throw new CryptographyException(ex);
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+            throw new CryptographyException(e);
+        }
+
+    }
+
+    private DERObject createDERForRecipient(byte[] in, X509Certificate cert)
+        throws IOException,
+               GeneralSecurityException
+    {
+
+        String s = "1.2.840.113549.3.2";
+
+        AlgorithmParameterGenerator algorithmparametergenerator = AlgorithmParameterGenerator.getInstance(s);
+        AlgorithmParameters algorithmparameters = algorithmparametergenerator.generateParameters();
+        ByteArrayInputStream bytearrayinputstream = new ByteArrayInputStream(algorithmparameters.getEncoded("ASN.1"));
+        ASN1InputStream asn1inputstream = new ASN1InputStream(bytearrayinputstream);
+        DERObject derobject = asn1inputstream.readObject();
+        KeyGenerator keygenerator = KeyGenerator.getInstance(s);
+        keygenerator.init(128);
+        SecretKey secretkey = keygenerator.generateKey();
+        Cipher cipher = Cipher.getInstance(s);
+        cipher.init(1, secretkey, algorithmparameters);
+        byte[] abyte1 = cipher.doFinal(in);
+        DEROctetString deroctetstring = new DEROctetString(abyte1);
+        KeyTransRecipientInfo keytransrecipientinfo = computeRecipientInfo(cert, secretkey.getEncoded());
+        DERSet derset = new DERSet(new RecipientInfo(keytransrecipientinfo));
+        AlgorithmIdentifier algorithmidentifier = new AlgorithmIdentifier(new DERObjectIdentifier(s), derobject);
+        EncryptedContentInfo encryptedcontentinfo =
+            new EncryptedContentInfo(PKCSObjectIdentifiers.data, algorithmidentifier, deroctetstring);
+        EnvelopedData env = new EnvelopedData(null, derset, encryptedcontentinfo, null);
+        ContentInfo contentinfo =
+            new ContentInfo(PKCSObjectIdentifiers.envelopedData, env);
+        return contentinfo.getDERObject();
+    }
+
+    private KeyTransRecipientInfo computeRecipientInfo(X509Certificate x509certificate, byte[] abyte0)
+        throws GeneralSecurityException, IOException
+    {
+        ASN1InputStream asn1inputstream =
+            new ASN1InputStream(new ByteArrayInputStream(x509certificate.getTBSCertificate()));
+        TBSCertificateStructure tbscertificatestructure =
+            TBSCertificateStructure.getInstance(asn1inputstream.readObject());
+        AlgorithmIdentifier algorithmidentifier = tbscertificatestructure.getSubjectPublicKeyInfo().getAlgorithmId();
+        IssuerAndSerialNumber issuerandserialnumber =
+            new IssuerAndSerialNumber(
+                tbscertificatestructure.getIssuer(),
+                tbscertificatestructure.getSerialNumber().getValue());
+        Cipher cipher = Cipher.getInstance(algorithmidentifier.getObjectId().getId());
+        cipher.init(1, x509certificate.getPublicKey());
+        DEROctetString deroctetstring = new DEROctetString(cipher.doFinal(abyte0));
+        RecipientIdentifier recipId = new RecipientIdentifier(issuerandserialnumber);
+        return new KeyTransRecipientInfo( recipId, algorithmidentifier, deroctetstring);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java
new file mode 100644 (file)
index 0000000..1be793e
--- /dev/null
@@ -0,0 +1,530 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.encryption.ARCFour;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * This class represents a security handler as described in the PDF specifications.
+ * A security handler is responsible of documents protection.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.4 $
+ */
+
+public abstract class SecurityHandler
+{
+
+    /* ------------------------------------------------
+     * CONSTANTS
+     -------------------------------------------------- */
+
+    private static final int DEFAULT_KEY_LENGTH = 40;
+
+    /*
+     * See 7.6.2, page 58, PDF 32000-1:2008
+     */
+    private final static byte[] AES_SALT = {(byte) 0x73, (byte) 0x41, (byte) 0x6c, (byte) 0x54};
+    
+    /**
+     * The value of V field of the Encryption dictionary.
+     */
+    protected int version;
+
+    /**
+     * The length of the secret key used to encrypt the document.
+     */
+    protected int keyLength = DEFAULT_KEY_LENGTH;
+
+    /**
+     * The encryption key that will used to encrypt / decrypt.
+     */
+    protected byte[] encryptionKey;
+
+    /**
+     * The document whose security is handled by this security handler.
+     */
+
+    protected PDDocument document;
+
+    /**
+     * The RC4 implementation used for cryptographic functions.
+     */
+    protected ARCFour rc4 = new ARCFour();
+
+    private Set<COSBase> objects = new HashSet<COSBase>();
+
+    private Set<COSDictionary> potentialSignatures = new HashSet<COSDictionary>();
+
+    /*
+     * If true, AES will be used
+     */
+    private boolean aes;
+    
+    /**
+     * The access permission granted to the current user for the document. These
+     * permissions are computed during decryption and are in read only mode.
+     */
+
+    protected AccessPermission currentAccessPermission = null;
+
+    /**
+     * Prepare the document for encryption.
+     *
+     * @param doc The document that will be encrypted.
+     *
+     * @throws CryptographyException If there is an error while preparing.
+     * @throws IOException If there is an error with the document.
+     */
+    public abstract void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException, IOException;
+
+    /**
+     * Prepare the document for decryption.
+     *
+     * @param doc The document to decrypt.
+     * @param mat Information required to decrypt the document.
+     * @throws CryptographyException If there is an error while preparing.
+     * @throws IOException If there is an error with the document.
+     */
+    public abstract void decryptDocument(PDDocument doc, DecryptionMaterial mat)
+        throws CryptographyException, IOException;
+
+
+    /**
+     * This method must be called by an implementation of this class to really proceed
+     * to decryption.
+     *
+     * @throws IOException If there is an error in the decryption.
+     * @throws CryptographyException If there is an error in the decryption.
+     */
+    protected void proceedDecryption() throws IOException, CryptographyException
+    {
+
+        COSDictionary trailer = document.getDocument().getTrailer();
+        COSArray fields = (COSArray)trailer.getObjectFromPath( "Root/AcroForm/Fields" );
+
+        //We need to collect all the signature dictionaries, for some
+        //reason the 'Contents' entry of signatures is not really encrypted
+        if( fields != null )
+        {
+            for( int i=0; i<fields.size(); i++ )
+            {
+                COSDictionary field = (COSDictionary)fields.getObject( i );
+                if (field!= null)
+                {
+                    addDictionaryAndSubDictionary( potentialSignatures, field );
+                }
+                else
+                {
+                    throw new IOException("Could not decypt document, object not found.");
+                }
+            }
+        }
+
+        List<COSObject> allObjects = document.getDocument().getObjects();
+        Iterator<COSObject> objectIter = allObjects.iterator();
+        while( objectIter.hasNext() )
+        {
+            decryptObject( objectIter.next() );
+        }
+        document.setEncryptionDictionary( null );
+    }
+
+    private void addDictionaryAndSubDictionary( Set<COSDictionary> set, COSDictionary dic )
+    {
+        set.add( dic );
+        COSArray kids = (COSArray)dic.getDictionaryObject( COSName.KIDS );
+        for( int i=0; kids != null && i<kids.size(); i++ )
+        {
+            addDictionaryAndSubDictionary( set, (COSDictionary)kids.getObject( i ) );
+        }
+        COSBase value = dic.getDictionaryObject( COSName.V );
+        if( value instanceof COSDictionary )
+        {
+            addDictionaryAndSubDictionary( set, (COSDictionary)value );
+        }
+    }
+
+    /**
+     * Encrypt a set of data.
+     *
+     * @param objectNumber The data object number.
+     * @param genNumber The data generation number.
+     * @param data The data to encrypt.
+     * @param output The output to write the encrypted data to.
+     * @throws CryptographyException If there is an error during the encryption.
+     * @throws IOException If there is an error reading the data.
+     * @deprecated While this works fine for RC4 encryption, it will never decrypt AES data
+     *             You should use encryptData(objectNumber, genNumber, data, output, decrypt)
+     *             which can do everything.  This function is just here for compatibility
+     *             reasons and will be removed in the future.
+     */
+    public void encryptData(long objectNumber, long genNumber, InputStream data, OutputStream output)
+    throws CryptographyException, IOException
+    {
+        // default to encrypting since the function is named "encryptData"
+        encryptData(objectNumber, genNumber, data, output, false);
+    }
+
+    /**
+     * Encrypt a set of data.
+     *
+     * @param objectNumber The data object number.
+     * @param genNumber The data generation number.
+     * @param data The data to encrypt.
+     * @param output The output to write the encrypted data to.
+     * @param decrypt true to decrypt the data, false to encrypt it
+     *
+     * @throws CryptographyException If there is an error during the encryption.
+     * @throws IOException If there is an error reading the data.
+     */
+    public void encryptData(long objectNumber, long genNumber, InputStream data, OutputStream output, boolean decrypt) 
+    throws CryptographyException, IOException
+    {
+        if (aes && !decrypt) {
+            throw new IllegalArgumentException("AES encryption is not yet implemented.");
+        }
+        
+        byte[] newKey = new byte[ encryptionKey.length + 5 ];
+        System.arraycopy( encryptionKey, 0, newKey, 0, encryptionKey.length );
+        //PDF 1.4 reference pg 73
+        //step 1
+        //we have the reference
+
+        //step 2
+        newKey[newKey.length -5] = (byte)(objectNumber & 0xff);
+        newKey[newKey.length -4] = (byte)((objectNumber >> 8) & 0xff);
+        newKey[newKey.length -3] = (byte)((objectNumber >> 16) & 0xff);
+        newKey[newKey.length -2] = (byte)(genNumber & 0xff);
+        newKey[newKey.length -1] = (byte)((genNumber >> 8) & 0xff);
+
+
+        //step 3
+        byte[] digestedKey = null;
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance( "MD5" );
+            md.update(newKey);
+            if (aes) {
+                md.update(AES_SALT);
+            }
+            digestedKey = md.digest();
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            throw new CryptographyException( e );
+        }
+
+        //step 4
+        int length = Math.min( newKey.length, 16 );
+        byte[] finalKey = new byte[ length ];
+        System.arraycopy( digestedKey, 0, finalKey, 0, length );
+
+        if (aes)
+        {
+            byte[] iv = new byte[16];
+    
+            data.read(iv);
+    
+            try {
+                Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+    
+                SecretKey aesKey = new SecretKeySpec(finalKey, "AES");
+    
+                IvParameterSpec ips = new IvParameterSpec(iv);
+    
+                decryptCipher.init(decrypt ? Cipher.DECRYPT_MODE : Cipher.ENCRYPT_MODE, aesKey, ips);
+    
+                CipherInputStream cipherStream = new CipherInputStream(data, decryptCipher);
+                
+                try {
+                    byte buffer[] = new byte[4096];
+                    long count = 0L;
+                    for(int n = 0; -1 != (n = cipherStream.read(buffer));)
+                    {
+                        output.write(buffer, 0, n);
+                        count += n;
+                    }
+                }
+                finally {
+                    cipherStream.close();
+                }
+            }
+            catch (InvalidKeyException e) {
+                throw new WrappedIOException(e);
+            }
+            catch (InvalidAlgorithmParameterException e) {
+                throw new WrappedIOException(e);
+            }
+            catch (NoSuchAlgorithmException e) {
+                throw new WrappedIOException(e);
+            }
+            catch (NoSuchPaddingException e) {
+                throw new WrappedIOException(e);
+            }
+        }
+        else {
+            rc4.setKey( finalKey );
+            rc4.write( data, output );
+        }
+        
+        output.flush();
+    }
+    
+    /**
+     * This will decrypt an object in the document.
+     *
+     * @param object The object to decrypt.
+     *
+     * @throws CryptographyException If there is an error decrypting the stream.
+     * @throws IOException If there is an error getting the stream data.
+     */
+    private void decryptObject( COSObject object )
+        throws CryptographyException, IOException
+    {
+        long objNum = object.getObjectNumber().intValue();
+        long genNum = object.getGenerationNumber().intValue();
+        COSBase base = object.getObject();
+        decrypt( base, objNum, genNum );
+    }
+
+    /**
+     * This will dispatch to the correct method.
+     *
+     * @param obj The object to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation Number.
+     *
+     * @throws CryptographyException If there is an error decrypting the stream.
+     * @throws IOException If there is an error getting the stream data.
+     */
+    private void decrypt( COSBase obj, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        if( !objects.contains( obj ) )
+        {
+            objects.add( obj );
+
+            if( obj instanceof COSString )
+            {
+                decryptString( (COSString)obj, objNum, genNum );
+            }
+            else if( obj instanceof COSStream )
+            {
+                decryptStream( (COSStream)obj, objNum, genNum );
+            }
+            else if( obj instanceof COSDictionary )
+            {
+                decryptDictionary( (COSDictionary)obj, objNum, genNum );
+            }
+            else if( obj instanceof COSArray )
+            {
+                decryptArray( (COSArray)obj, objNum, genNum );
+            }
+        }
+    }
+
+    /**
+     * This will decrypt a stream.
+     *
+     * @param stream The stream to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If there is an error getting the stream.
+     * @throws IOException If there is an error getting the stream data.
+     */
+    public void decryptStream( COSStream stream, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        decryptDictionary( stream, objNum, genNum );
+        InputStream encryptedStream = stream.getFilteredStream();
+        encryptData( objNum,
+                                genNum,
+                                encryptedStream,
+                                stream.createFilteredStream(),
+                                true /* decrypt */);
+    }
+    
+    /**
+     * This will encrypt a stream, but not the dictionary as the dictionary is
+     * encrypted by visitFromString() in COSWriter and we don't want to encrypt
+     * it twice.
+     *
+     * @param stream The stream to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If there is an error getting the stream.
+     * @throws IOException If there is an error getting the stream data.
+     */
+    public void encryptStream( COSStream stream, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        InputStream encryptedStream = stream.getFilteredStream();
+        encryptData( objNum,
+                                genNum,
+                                encryptedStream,
+                                stream.createFilteredStream(),
+                                false /* encrypt */);
+    }
+
+    /**
+     * This will decrypt a dictionary.
+     *
+     * @param dictionary The dictionary to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If there is an error decrypting the document.
+     * @throws IOException If there is an error creating a new string.
+     */
+    private void decryptDictionary( COSDictionary dictionary, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        for( Map.Entry<COSName, COSBase> entry : dictionary.entrySet() )
+        {
+            //if we are a signature dictionary and contain a Contents entry then
+            //we don't decrypt it.
+            if( !(entry.getKey().getName().equals( "Contents" ) &&
+                  entry.getValue() instanceof COSString &&
+                  potentialSignatures.contains( dictionary )))
+            {
+                decrypt( entry.getValue(), objNum, genNum );
+            }
+        }
+    }
+
+    /**
+     * This will decrypt a string.
+     *
+     * @param string the string to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If an error occurs during decryption.
+     * @throws IOException If an error occurs writing the new string.
+     */
+    public void decryptString( COSString string, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        ByteArrayInputStream data = new ByteArrayInputStream( string.getBytes() );
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        encryptData( objNum, genNum, data, buffer, true /* decrypt */ );
+        string.reset();
+        string.append( buffer.toByteArray() );
+    }
+
+    /**
+     * This will decrypt an array.
+     *
+     * @param array The array to decrypt.
+     * @param objNum The object number.
+     * @param genNum The object generation number.
+     *
+     * @throws CryptographyException If an error occurs during decryption.
+     * @throws IOException If there is an error accessing the data.
+     */
+    private void decryptArray( COSArray array, long objNum, long genNum )
+        throws CryptographyException, IOException
+    {
+        for( int i=0; i<array.size(); i++ )
+        {
+            decrypt( array.get( i ), objNum, genNum );
+        }
+    }
+
+    /**
+     * Getter of the property <tt>keyLength</tt>.
+     * @return  Returns the keyLength.
+     * @uml.property  name="keyLength"
+     */
+    public int getKeyLength()
+    {
+        return keyLength;
+    }
+
+    /**
+     * Setter of the property <tt>keyLength</tt>.
+     *
+     * @param keyLen  The keyLength to set.
+     */
+    public void setKeyLength(int keyLen)
+    {
+        this.keyLength = keyLen;
+    }
+
+    /**
+     * Returns the access permissions that were computed during document decryption.
+     * The returned object is in read only mode.
+     *
+     * @return the access permissions or null if the document was not decrypted.
+     */
+    public AccessPermission getCurrentAccessPermission()
+    {
+        return currentAccessPermission;
+    }
+
+    /*
+     * True if AES is used for encryption and decryption.
+     */
+    public boolean isAES() {
+        return aes;
+    }
+
+    /*
+     * Set to true if AES for encryption and decryption should be used.
+     */
+    public void setAES(boolean aes) {
+        this.aes = aes;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/SecurityHandlersManager.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/SecurityHandlersManager.java
new file mode 100644 (file)
index 0000000..d0b359f
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.lang.reflect.Constructor;
+import java.security.Security;
+import java.util.Hashtable;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * This class manages security handlers for the application. It follows the singleton pattern.
+ * To be usable, security managers must be registered in it. Security managers are retrieved by
+ * the application when necessary.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.3 $
+ *
+ */
+public class SecurityHandlersManager
+{
+
+    /**
+     * The unique instance of this manager.
+     */
+    private static SecurityHandlersManager instance;
+
+    /**
+     * hashtable used to index handlers regarding their name.
+     * Basically this will be used when opening an encrypted
+     * document to find the appropriate security handler to handle
+     * security features of the document.
+     */
+    private Hashtable handlerNames = null;
+
+    /**
+     * Hashtable used to index handlers regarding the class of
+     * protection policy they use.  Basically this will be used when
+     * encrypting a document.
+     */
+    private Hashtable handlerPolicyClasses = null;
+
+    /**
+     * private constructor.
+     */
+    private SecurityHandlersManager()
+    {
+        handlerNames = new Hashtable();
+        handlerPolicyClasses = new Hashtable();
+        try
+        {
+            this.registerHandler(
+                StandardSecurityHandler.FILTER,
+                StandardSecurityHandler.class,
+                StandardProtectionPolicy.class);
+            this.registerHandler(
+                PublicKeySecurityHandler.FILTER,
+                PublicKeySecurityHandler.class,
+                PublicKeyProtectionPolicy.class);
+        }
+        catch(Exception e)
+        {
+            System.err.println("SecurityHandlersManager strange error with builtin handlers: " + e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    /**
+     * register a security handler.
+     *
+     * If the security handler was already registered an exception is thrown.
+     * If another handler was previously registered for the same filter name or
+     * for the same policy name, an exception is thrown
+     *
+     * @param filterName The name of the filter.
+     * @param securityHandlerClass Security Handler class to register.
+     * @param protectionPolicyClass Protection Policy class to register.
+     *
+     * @throws BadSecurityHandlerException If there is an error registering the security handler.
+     */
+    public void registerHandler(String filterName, Class securityHandlerClass, Class protectionPolicyClass)
+        throws BadSecurityHandlerException
+    {
+        if(handlerNames.contains(securityHandlerClass) || handlerPolicyClasses.contains(securityHandlerClass))
+        {
+            throw new BadSecurityHandlerException("the following security handler was already registered: " +
+                securityHandlerClass.getName());
+        }
+
+        if(SecurityHandler.class.isAssignableFrom(securityHandlerClass))
+        {
+            try
+            {
+                if(handlerNames.containsKey(filterName))
+                {
+                    throw new BadSecurityHandlerException("a security handler was already registered " +
+                        "for the filter name " + filterName);
+                }
+                if(handlerPolicyClasses.containsKey(protectionPolicyClass))
+                {
+                    throw new BadSecurityHandlerException("a security handler was already registered " +
+                        "for the policy class " + protectionPolicyClass.getName());
+                }
+
+                handlerNames.put(filterName, securityHandlerClass);
+                handlerPolicyClasses.put(protectionPolicyClass, securityHandlerClass);
+            }
+            catch(Exception e)
+            {
+                throw new BadSecurityHandlerException(e);
+            }
+        }
+        else
+        {
+            throw new BadSecurityHandlerException("The class is not a super class of SecurityHandler");
+        }
+    }
+
+
+    /**
+     * Get the singleton instance.
+     *
+     * @return The SecurityHandlersManager.
+     */
+    public static SecurityHandlersManager getInstance()
+    {
+        if(instance == null)
+        {
+            instance = new SecurityHandlersManager();
+        }
+        Security.addProvider(new BouncyCastleProvider());
+
+        return instance;
+    }
+
+    /**
+     * Get the security handler for the protection policy.
+     *
+     * @param policy The policy to get the security handler for.
+     *
+     * @return The appropriate security handler.
+     *
+     * @throws BadSecurityHandlerException If it is unable to create a SecurityHandler.
+     */
+    public SecurityHandler getSecurityHandler(ProtectionPolicy policy) throws BadSecurityHandlerException
+    {
+
+        Object found = handlerPolicyClasses.get(policy.getClass());
+        if(found == null)
+        {
+            throw new BadSecurityHandlerException(
+                "Cannot find an appropriate security handler for " + policy.getClass().getName());
+        }
+        Class handlerclass = (Class) found;
+        Class[] argsClasses = {policy.getClass()};
+        Object[] args = {policy};
+        try
+        {
+            Constructor c = handlerclass.getDeclaredConstructor(argsClasses);
+            SecurityHandler handler = (SecurityHandler)c.newInstance(args);
+            return handler;
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+            throw new BadSecurityHandlerException(
+                "problem while trying to instanciate the security handler "+
+                handlerclass.getName() + ": " + e.getMessage());
+        }
+    }
+
+
+
+    /**
+     * Retrieve the appropriate SecurityHandler for a the given filter name.
+     * The filter name is an entry of the encryption dictionary of an encrypted document.
+     *
+     * @param filterName The filter name.
+     *
+     * @return The appropriate SecurityHandler if it exists.
+     *
+     * @throws BadSecurityHandlerException If the security handler does not exist.
+     */
+    public SecurityHandler getSecurityHandler(String filterName) throws BadSecurityHandlerException
+    {
+        Object found = handlerNames.get(filterName);
+        if(found == null)
+        {
+            throw new BadSecurityHandlerException("Cannot find an appropriate security handler for " + filterName);
+        }
+        Class handlerclass = (Class) found;
+        Class[] argsClasses = {};
+        Object[] args = {};
+        try
+        {
+            Constructor c = handlerclass.getDeclaredConstructor(argsClasses);
+            SecurityHandler handler = (SecurityHandler)c.newInstance(args);
+            return handler;
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+            throw new BadSecurityHandlerException(
+                "problem while trying to instanciate the security handler "+
+                handlerclass.getName() + ": " + e.getMessage());
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardDecryptionMaterial.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardDecryptionMaterial.java
new file mode 100644 (file)
index 0000000..eeb8a66
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+
+/**
+ *
+ * Represents the necessary information to decrypt a document protected by
+ * the standard security handler (password protection).
+ *
+ * This is only composed of a password.
+ *
+ * The following example shows how to decrypt a document protected with
+ * the standard security handler:
+ *
+ *  <pre>
+ *  PDDocument doc = PDDocument.load(in);
+ *  StandardDecryptionMaterial dm = new StandardDecryptionMaterial("password");
+ *  doc.openProtection(dm);
+ *  </pre>
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.2 $
+ */
+
+public class StandardDecryptionMaterial extends DecryptionMaterial
+{
+
+    private String password = null;
+
+    /**
+     * Create a new standard decryption material with the given password.
+     *
+     * @param pwd The password.
+     */
+    public StandardDecryptionMaterial(String pwd)
+    {
+        password = pwd;
+    }
+
+    /**
+     * Returns the password.
+     *
+     * @return The password used to decrypt the document.
+     */
+    public String getPassword()
+    {
+        return password;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardProtectionPolicy.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardProtectionPolicy.java
new file mode 100644 (file)
index 0000000..cfc7c01
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+/**
+ * This class represents the protection policy to add to a document
+ * for password-based protection.
+ *
+ * The following example shows how to protect a PDF document with password.
+ * In this example, the document will be protected so that someone opening
+ * the document with the user password <code>user_pwd</code> will not be
+ * able to modify the document.
+ *
+ * <pre>
+ * AccessPermission ap = new AccessPermission();
+ * ap.setCanModify(false);
+ * StandardProtectionPolicy policy = new StandardProtectionPolicy(owner_pwd, user_pwd, ap);
+ * doc.protect(policy);
+ * </pre>
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.3 $
+ */
+public class StandardProtectionPolicy extends ProtectionPolicy
+{
+
+    private AccessPermission permissions;
+
+    private String ownerPassword = "";
+
+    private String userPassword = "";
+
+
+    /**
+     * Creates an new instance of the standard protection policy
+     * in order to protect a PDF document with passwords.
+     *
+     * @param ownerPass The owner's password.
+     * @param userPass The users's password.
+     * @param perms The access permissions given to the user.
+     */
+    public StandardProtectionPolicy(String ownerPass, String userPass, AccessPermission perms)
+    {
+        this.permissions = perms;
+        this.userPassword = userPass;
+        this.ownerPassword = ownerPass;
+    }
+
+    /**
+     * Getter of the property <tt>permissions</tt>.
+     *
+     * @return Returns the permissions.
+     */
+    public AccessPermission getPermissions()
+    {
+        return permissions;
+    }
+
+    /**
+     * Setter of the property <tt>permissions</tt>.
+     *
+     * @param perms The permissions to set.
+     */
+    public void setPermissions(AccessPermission perms)
+    {
+        this.permissions = perms;
+    }
+
+    /**
+     * Getter of the property <tt>ownerPassword</tt>.
+     *
+     * @return Returns the ownerPassword.
+     */
+    public String getOwnerPassword()
+    {
+        return ownerPassword;
+    }
+
+    /**
+     * Setter of the property <tt>ownerPassword</tt>.
+     *
+     * @param ownerPass The ownerPassword to set.
+     */
+    public void setOwnerPassword(String ownerPass)
+    {
+        this.ownerPassword = ownerPass;
+    }
+
+    /**
+     * Getter of the property <tt>userPassword</tt>.
+     *
+     * @return Returns the userPassword.
+     */
+    public String getUserPassword()
+    {
+        return userPassword;
+    }
+
+    /**
+     * Setter of the property <tt>userPassword</tt>.
+     *
+     * @param userPass The userPassword to set.
+     */
+    public void setUserPassword(String userPass)
+    {
+        this.userPassword = userPass;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java
new file mode 100644 (file)
index 0000000..c314c94
--- /dev/null
@@ -0,0 +1,900 @@
+/*
+ * 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.pdfbox.pdmodel.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.encryption.ARCFour;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ *
+ * The class implements the standard security handler as decribed
+ * in the PDF specifications. This security handler protects document
+ * with password.
+ *
+ * @see StandardProtectionPolicy to see how to protect document with this security handler.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.5 $
+ */
+
+public class StandardSecurityHandler extends SecurityHandler
+{
+    /**
+     * Type of security handler.
+     */
+    public static final String FILTER = "Standard";
+
+    private static final int DEFAULT_VERSION = 1;
+
+    private static final int DEFAULT_REVISION = 3;
+
+    private int revision = DEFAULT_REVISION;
+
+    private StandardProtectionPolicy policy;
+
+    private ARCFour rc4 = new ARCFour();
+
+    /**
+     * Protection policy class for this handler.
+     */
+    public static final Class PROTECTION_POLICY_CLASS = StandardProtectionPolicy.class;
+
+    /**
+     * Standard padding for encryption.
+     */
+    public static final byte[] ENCRYPT_PADDING =
+    {
+        (byte)0x28, (byte)0xBF, (byte)0x4E, (byte)0x5E, (byte)0x4E,
+        (byte)0x75, (byte)0x8A, (byte)0x41, (byte)0x64, (byte)0x00,
+        (byte)0x4E, (byte)0x56, (byte)0xFF, (byte)0xFA, (byte)0x01,
+        (byte)0x08, (byte)0x2E, (byte)0x2E, (byte)0x00, (byte)0xB6,
+        (byte)0xD0, (byte)0x68, (byte)0x3E, (byte)0x80, (byte)0x2F,
+        (byte)0x0C, (byte)0xA9, (byte)0xFE, (byte)0x64, (byte)0x53,
+        (byte)0x69, (byte)0x7A
+    };
+
+    /**
+     * Constructor.
+     */
+    public StandardSecurityHandler()
+    {
+    }
+
+    /**
+     * Constructor used for encryption.
+     *
+     * @param p The protection policy.
+     */
+    public StandardSecurityHandler(StandardProtectionPolicy p)
+    {
+        policy = p;
+        keyLength = policy.getEncryptionKeyLength();
+    }
+
+
+    /**
+     * Computes the version number of the StandardSecurityHandler
+     * regarding the encryption key length.
+     * See PDF Spec 1.6 p 93
+     *
+     * @return The computed cersion number.
+     */
+    private int computeVersionNumber()
+    {
+        if(keyLength == 40)
+        {
+            return DEFAULT_VERSION;
+        }
+        return 2;
+    }
+
+    /**
+     * Computes the revision version of the StandardSecurityHandler to
+     * use regarding the version number and the permissions bits set.
+     * See PDF Spec 1.6 p98
+     *
+     * @return The computed revision number.
+     */
+    private int computeRevisionNumber()
+    {
+        if(version == 2
+            && !policy.getPermissions().canFillInForm()
+            && !policy.getPermissions().canExtractForAccessibility()
+            && !policy.getPermissions().canPrintDegraded() )
+        {
+            return 2;
+        }
+        return 3;
+    }
+
+    /**
+     * Decrypt the document.
+     *
+     * @param doc The document to be decrypted.
+     * @param decryptionMaterial Information used to decrypt the document.
+     *
+     * @throws IOException If there is an error accessing data.
+     * @throws CryptographyException If there is an error with decryption.
+     */
+    public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMaterial)
+        throws CryptographyException, IOException
+    {
+        document = doc;
+
+        PDEncryptionDictionary dictionary = document.getEncryptionDictionary();
+        if(!(decryptionMaterial instanceof StandardDecryptionMaterial))
+        {
+            throw new CryptographyException("Provided decryption material is not compatible with the document");
+        }
+
+        StandardDecryptionMaterial material = (StandardDecryptionMaterial)decryptionMaterial;
+
+        String password = material.getPassword();
+        if(password == null)
+        {
+            password = "";
+        }
+
+        int dicPermissions = dictionary.getPermissions();
+        int dicRevision = dictionary.getRevision();
+        int dicLength = dictionary.getLength()/8;
+
+        //some documents may have not document id, see
+        //test\encryption\encrypted_doc_no_id.pdf
+        COSArray documentIDArray = document.getDocument().getDocumentID();
+        byte[] documentIDBytes = null;
+        if( documentIDArray != null && documentIDArray.size() >= 1 )
+        {
+            COSString id = (COSString)documentIDArray.getObject( 0 );
+            documentIDBytes = id.getBytes();
+        }
+        else
+        {
+            documentIDBytes = new byte[0];
+        }
+
+        // we need to know whether the meta data was encrypted for password calculation
+        boolean encryptMetadata = dictionary.isEncryptMetaData();
+        
+        byte[] u = dictionary.getUserKey();
+        byte[] o = dictionary.getOwnerKey();
+
+        boolean isUserPassword =
+            isUserPassword(
+                password.getBytes("ISO-8859-1"),
+                u,
+                o,
+                dicPermissions,
+                documentIDBytes,
+                dicRevision,
+                dicLength,
+                encryptMetadata);
+        boolean isOwnerPassword =
+            isOwnerPassword(
+                password.getBytes("ISO-8859-1"),
+                u,
+                o,
+                dicPermissions,
+                documentIDBytes,
+                dicRevision,
+                dicLength,
+                encryptMetadata);
+
+        if( isUserPassword )
+        {
+            currentAccessPermission = new AccessPermission( dicPermissions );
+            encryptionKey =
+                computeEncryptedKey(
+                    password.getBytes("ISO-8859-1"),
+                    o,
+                    dicPermissions,
+                    documentIDBytes,
+                    dicRevision,
+                    dicLength,
+                    encryptMetadata );
+        }
+        else if( isOwnerPassword )
+        {
+            currentAccessPermission = AccessPermission.getOwnerAccessPermission();
+            byte[] computedUserPassword = getUserPassword(password.getBytes("ISO-8859-1"),o,dicRevision,dicLength );
+            encryptionKey =
+                computeEncryptedKey(
+                    computedUserPassword,
+                    o,
+                    dicPermissions,
+                    documentIDBytes,
+                    dicRevision,
+                    dicLength,
+                    encryptMetadata);
+        }
+        else
+        {
+            throw new CryptographyException(
+                "Error: The supplied password does not match either the owner or user password in the document." );
+        }
+
+        // detect whether AES encryption is used. This assumes that the encryption algo is 
+        // stored in the PDCryptFilterDictionary
+        PDCryptFilterDictionary stdCryptFilterDictionary =  doc.getEncryptionDictionary().
+                getStdCryptFilterDictionary();
+
+        if (stdCryptFilterDictionary != null)
+        {
+            COSName cryptFilterMethod = stdCryptFilterDictionary.getCryptFilterMethod();
+
+            if (cryptFilterMethod != null) {
+                setAES("AESV2".equalsIgnoreCase(cryptFilterMethod.getName()));
+            }
+        }
+        
+        this.proceedDecryption();
+    }
+
+    /**
+     * Prepare document for encryption.
+     *
+     * @param doc The documeent to encrypt.
+     *
+     * @throws IOException If there is an error accessing data.
+     * @throws CryptographyException If there is an error with decryption.
+     */
+    public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException, IOException
+    {
+        document = doc;
+        PDEncryptionDictionary encryptionDictionary = document.getEncryptionDictionary();
+        if(encryptionDictionary == null)
+        {
+            encryptionDictionary = new PDEncryptionDictionary();
+        }
+        version = computeVersionNumber();
+        revision = computeRevisionNumber();
+        encryptionDictionary.setFilter(FILTER);
+        encryptionDictionary.setVersion(version);
+        encryptionDictionary.setRevision(revision);
+        encryptionDictionary.setLength(keyLength);
+
+        String ownerPassword = policy.getOwnerPassword();
+        String userPassword = policy.getUserPassword();
+        if( ownerPassword == null )
+        {
+            ownerPassword = "";
+        }
+        if( userPassword == null )
+        {
+            userPassword = "";
+        }
+
+        int permissionInt = policy.getPermissions().getPermissionBytes();
+
+        encryptionDictionary.setPermissions(permissionInt);
+
+        int length = keyLength/8;
+
+        COSArray idArray = document.getDocument().getDocumentID();
+
+        //check if the document has an id yet.  If it does not then
+        //generate one
+        if( idArray == null || idArray.size() < 2 )
+        {
+            idArray = new COSArray();
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance( "MD5" );
+                BigInteger time = BigInteger.valueOf( System.currentTimeMillis() );
+                md.update( time.toByteArray() );
+                md.update( ownerPassword.getBytes("ISO-8859-1") );
+                md.update( userPassword.getBytes("ISO-8859-1") );
+                md.update( document.getDocument().toString().getBytes() );
+                byte[] id = md.digest( this.toString().getBytes("ISO-8859-1") );
+                COSString idString = new COSString();
+                idString.append( id );
+                idArray.add( idString );
+                idArray.add( idString );
+                document.getDocument().setDocumentID( idArray );
+            }
+            catch( NoSuchAlgorithmException e )
+            {
+                throw new CryptographyException( e );
+            }
+            catch(IOException e )
+            {
+                throw new CryptographyException( e );
+            }
+        }
+
+        COSString id = (COSString)idArray.getObject( 0 );
+
+        byte[] o = computeOwnerPassword(
+            ownerPassword.getBytes("ISO-8859-1"),
+            userPassword.getBytes("ISO-8859-1"), revision, length);
+
+        byte[] u = computeUserPassword(
+            userPassword.getBytes("ISO-8859-1"),
+            o, permissionInt, id.getBytes(), revision, length, true);
+
+        encryptionKey = computeEncryptedKey(
+            userPassword.getBytes("ISO-8859-1"), o, permissionInt, id.getBytes(), revision, length, true);
+
+        encryptionDictionary.setOwnerKey(o);
+        encryptionDictionary.setUserKey(u);
+
+        document.setEncryptionDictionary( encryptionDictionary );
+        document.getDocument().setEncryptionDictionary(encryptionDictionary.getCOSDictionary());
+
+    }
+
+    /**
+     * Check for owner password.
+     *
+     * @param ownerPassword The owner password.
+     * @param u The u entry of the encryption dictionary.
+     * @param o The o entry of the encryption dictionary.
+     * @param permissions The set of permissions on the document.
+     * @param id The document id.
+     * @param encRevision The encryption algorithm revision.
+     * @param length The encryption key length.
+     *
+     * @return True If the ownerPassword param is the owner password.
+     *
+     * @throws CryptographyException If there is an error during encryption.
+     * @throws IOException If there is an error accessing data.
+     */
+    public final boolean isOwnerPassword(
+            byte[] ownerPassword,
+            byte[] u,
+            byte[] o,
+            int permissions,
+            byte[] id,
+            int encRevision,
+            int length,
+            boolean encryptMetadata)
+            throws CryptographyException, IOException
+    {
+        byte[] userPassword = getUserPassword( ownerPassword, o, encRevision, length );
+        return isUserPassword( userPassword, u, o, permissions, id, encRevision, length, encryptMetadata );
+    }
+
+    /**
+     * Get the user password based on the owner password.
+     *
+     * @param ownerPassword The plaintext owner password.
+     * @param o The o entry of the encryption dictionary.
+     * @param encRevision The encryption revision number.
+     * @param length The key length.
+     *
+     * @return The u entry of the encryption dictionary.
+     *
+     * @throws CryptographyException If there is an error generating the user password.
+     * @throws IOException If there is an error accessing data while generating the user password.
+     */
+    public final byte[] getUserPassword(
+            byte[] ownerPassword,
+            byte[] o,
+            int encRevision,
+            long length )
+            throws CryptographyException, IOException
+    {
+        try
+        {
+            ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+            //3.3 STEP 1
+            byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+            //3.3 STEP 2
+            MessageDigest md = MessageDigest.getInstance( "MD5" );
+            md.update( ownerPadded );
+            byte[] digest = md.digest();
+
+            //3.3 STEP 3
+            if( encRevision == 3 || encRevision == 4 )
+            {
+                for( int i=0; i<50; i++ )
+                {
+                    md.reset();
+                    md.update( digest );
+                    digest = md.digest();
+                }
+            }
+            if( encRevision == 2 && length != 5 )
+            {
+                throw new CryptographyException(
+                    "Error: Expected length=5 actual=" + length );
+            }
+
+            //3.3 STEP 4
+            byte[] rc4Key = new byte[ (int)length ];
+            System.arraycopy( digest, 0, rc4Key, 0, (int)length );
+
+            //3.7 step 2
+            if( encRevision == 2 )
+            {
+                rc4.setKey( rc4Key );
+                rc4.write( o, result );
+            }
+            else if( encRevision == 3 || encRevision == 4)
+            {
+                /**
+                byte[] iterationKey = new byte[ rc4Key.length ];
+                byte[] dataToEncrypt = o;
+                for( int i=19; i>=0; i-- )
+                {
+                    System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+                    for( int j=0; j< iterationKey.length; j++ )
+                    {
+                        iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+                    }
+                    rc4.setKey( iterationKey );
+                    rc4.write( dataToEncrypt, result );
+                    dataToEncrypt = result.toByteArray();
+                    result.reset();
+                }
+                result.write( dataToEncrypt, 0, dataToEncrypt.length );
+                */
+                byte[] iterationKey = new byte[ rc4Key.length ];
+
+
+                byte[] otemp = new byte[ o.length ]; //sm
+                System.arraycopy( o, 0, otemp, 0, o.length ); //sm
+                rc4.write( o, result);//sm
+
+                for( int i=19; i>=0; i-- )
+                {
+                    System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+                    for( int j=0; j< iterationKey.length; j++ )
+                    {
+                        iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+                    }
+                    rc4.setKey( iterationKey );
+                    result.reset();  //sm
+                    rc4.write( otemp, result ); //sm
+                    otemp = result.toByteArray(); //sm
+                }
+            }
+
+
+            return result.toByteArray();
+
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            throw new CryptographyException( e );
+        }
+    }
+
+    /**
+     * Compute the encryption key.
+     *
+     * @param password The password to compute the encrypted key.
+     * @param o The o entry of the encryption dictionary.
+     * @param permissions The permissions for the document.
+     * @param id The document id.
+     * @param encRevision The revision of the encryption algorithm.
+     * @param length The length of the encryption key.
+     *
+     * @return The encrypted key bytes.
+     *
+     * @throws CryptographyException If there is an error with encryption.
+     */
+    public final byte[] computeEncryptedKey(
+            byte[] password,
+            byte[] o,
+            int permissions,
+            byte[] id,
+            int encRevision,
+            int length,
+            boolean encryptMetadata)
+            throws CryptographyException
+        {
+            byte[] result = new byte[ length ];
+            try
+            {
+                //PDFReference 1.4 pg 78
+                //step1
+                byte[] padded = truncateOrPad( password );
+
+                //step 2
+                MessageDigest md = MessageDigest.getInstance("MD5");
+                md.update( padded );
+
+                //step 3
+                md.update( o );
+
+                //step 4
+                byte zero = (byte)(permissions >>> 0);
+                byte one = (byte)(permissions >>> 8);
+                byte two = (byte)(permissions >>> 16);
+                byte three = (byte)(permissions >>> 24);
+
+                md.update( zero );
+                md.update( one );
+                md.update( two );
+                md.update( three );
+
+                //step 5
+                md.update( id );
+                
+                //(Security handlers of revision 4 or greater) If document metadata is not being encrypted, 
+                //pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
+                //see 7.6.3.3 Algorithm 2 Step f of PDF 32000-1:2008
+                if( encRevision == 4 && !encryptMetadata)
+                {
+                    md.update(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff});
+                }
+                
+                byte[] digest = md.digest();
+
+                //step 6
+                if( encRevision == 3 || encRevision == 4)
+                {
+                    for( int i=0; i<50; i++ )
+                    {
+                        md.reset();
+                        md.update( digest, 0, length );
+                        digest = md.digest();
+                    }
+                }
+
+                //step 7
+                if( encRevision == 2 && length != 5 )
+                {
+                    throw new CryptographyException(
+                        "Error: length should be 5 when revision is two actual=" + length );
+                }
+                System.arraycopy( digest, 0, result, 0, length );
+            }
+            catch( NoSuchAlgorithmException e )
+            {
+                throw new CryptographyException( e );
+            }
+            return result;
+        }
+
+    /**
+     * This will compute the user password hash.
+     *
+     * @param password The plain text password.
+     * @param o The owner password hash.
+     * @param permissions The document permissions.
+     * @param id The document id.
+     * @param encRevision The revision of the encryption.
+     * @param length The length of the encryption key.
+     *
+     * @return The user password.
+     *
+     * @throws CryptographyException If there is an error computing the user password.
+     * @throws IOException If there is an IO error.
+     */
+
+    public final byte[] computeUserPassword(
+            byte[] password,
+            byte[] o,
+            int permissions,
+            byte[] id,
+            int encRevision,
+            int length,
+            boolean encryptMetadata)
+            throws CryptographyException, IOException
+        {
+            ByteArrayOutputStream result = new ByteArrayOutputStream();
+            //STEP 1
+            byte[] encryptionKey = computeEncryptedKey( password, o, permissions, id, encRevision, length, encryptMetadata );
+
+            if( encRevision == 2 )
+            {
+                //STEP 2
+                rc4.setKey( encryptionKey );
+                rc4.write( ENCRYPT_PADDING, result );
+            }
+            else if( encRevision == 3 || encRevision == 4 )
+            {
+                try
+                {
+                    //STEP 2
+                    MessageDigest md = MessageDigest.getInstance("MD5");
+                    //md.update( truncateOrPad( password ) );
+                    md.update( ENCRYPT_PADDING );
+
+                    //STEP 3
+                    md.update( id );
+                    result.write( md.digest() );
+
+                    //STEP 4 and 5
+                    byte[] iterationKey = new byte[ encryptionKey.length ];
+                    for( int i=0; i<20; i++ )
+                    {
+                        System.arraycopy( encryptionKey, 0, iterationKey, 0, iterationKey.length );
+                        for( int j=0; j< iterationKey.length; j++ )
+                        {
+                            iterationKey[j] = (byte)(iterationKey[j] ^ i);
+                        }
+                        rc4.setKey( iterationKey );
+                        ByteArrayInputStream input = new ByteArrayInputStream( result.toByteArray() );
+                        result.reset();
+                        rc4.write( input, result );
+                    }
+
+                    //step 6
+                    byte[] finalResult = new byte[32];
+                    System.arraycopy( result.toByteArray(), 0, finalResult, 0, 16 );
+                    System.arraycopy( ENCRYPT_PADDING, 0, finalResult, 16, 16 );
+                    result.reset();
+                    result.write( finalResult );
+                }
+                catch( NoSuchAlgorithmException e )
+                {
+                    throw new CryptographyException( e );
+                }
+            }
+            return result.toByteArray();
+        }
+
+    /**
+     * Compute the owner entry in the encryption dictionary.
+     *
+     * @param ownerPassword The plaintext owner password.
+     * @param userPassword The plaintext user password.
+     * @param encRevision The revision number of the encryption algorithm.
+     * @param length The length of the encryption key.
+     *
+     * @return The o entry of the encryption dictionary.
+     *
+     * @throws CryptographyException If there is an error with encryption.
+     * @throws IOException If there is an error accessing data.
+     */
+    public final byte[] computeOwnerPassword(
+            byte[] ownerPassword,
+            byte[] userPassword,
+            int encRevision,
+            int length )
+            throws CryptographyException, IOException
+        {
+            try
+            {
+                //STEP 1
+                byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+                //STEP 2
+                MessageDigest md = MessageDigest.getInstance( "MD5" );
+                md.update( ownerPadded );
+                byte[] digest = md.digest();
+
+                //STEP 3
+                if( encRevision == 3 || encRevision == 4)
+                {
+                    for( int i=0; i<50; i++ )
+                    {
+                        md.reset();
+                        md.update( digest, 0, length );
+                        digest = md.digest();
+                    }
+                }
+                if( encRevision == 2 && length != 5 )
+                {
+                    throw new CryptographyException(
+                        "Error: Expected length=5 actual=" + length );
+                }
+
+                //STEP 4
+                byte[] rc4Key = new byte[ length ];
+                System.arraycopy( digest, 0, rc4Key, 0, length );
+
+                //STEP 5
+                byte[] paddedUser = truncateOrPad( userPassword );
+
+
+                //STEP 6
+                rc4.setKey( rc4Key );
+                ByteArrayOutputStream crypted = new ByteArrayOutputStream();
+                rc4.write( new ByteArrayInputStream( paddedUser ), crypted );
+
+
+                //STEP 7
+                if( encRevision == 3 || encRevision == 4 )
+                {
+                    byte[] iterationKey = new byte[ rc4Key.length ];
+                    for( int i=1; i<20; i++ )
+                    {
+                        System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+                        for( int j=0; j< iterationKey.length; j++ )
+                        {
+                            iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+                        }
+                        rc4.setKey( iterationKey );
+                        ByteArrayInputStream input = new ByteArrayInputStream( crypted.toByteArray() );
+                        crypted.reset();
+                        rc4.write( input, crypted );
+                    }
+                }
+
+                //STEP 8
+                return crypted.toByteArray();
+            }
+            catch( NoSuchAlgorithmException e )
+            {
+                throw new CryptographyException( e.getMessage() );
+            }
+        }
+
+
+    /**
+     * This will take the password and truncate or pad it as necessary.
+     *
+     * @param password The password to pad or truncate.
+     *
+     * @return The padded or truncated password.
+     */
+    private final byte[] truncateOrPad( byte[] password )
+    {
+        byte[] padded = new byte[ ENCRYPT_PADDING.length ];
+        int bytesBeforePad = Math.min( password.length, padded.length );
+        System.arraycopy( password, 0, padded, 0, bytesBeforePad );
+        System.arraycopy( ENCRYPT_PADDING, 0, padded, bytesBeforePad, ENCRYPT_PADDING.length-bytesBeforePad );
+        return padded;
+    }
+
+    /**
+     * Check if a plaintext password is the user password.
+     *
+     * @param password The plaintext password.
+     * @param u The u entry of the encryption dictionary.
+     * @param o The o entry of the encryption dictionary.
+     * @param permissions The permissions set in the the PDF.
+     * @param id The document id used for encryption.
+     * @param encRevision The revision of the encryption algorithm.
+     * @param length The length of the encryption key.
+     *
+     * @return true If the plaintext password is the user password.
+     *
+     * @throws CryptographyException If there is an error during encryption.
+     * @throws IOException If there is an error accessing data.
+     */
+    public final boolean isUserPassword(
+            byte[] password,
+            byte[] u,
+            byte[] o,
+            int permissions,
+            byte[] id,
+            int encRevision,
+            int length,
+            boolean encryptMetadata)
+            throws CryptographyException, IOException
+        {
+            boolean matches = false;
+            //STEP 1
+            byte[] computedValue = computeUserPassword( password, o, permissions, id, encRevision, length, 
+                    encryptMetadata );
+            if( encRevision == 2 )
+            {
+                //STEP 2
+                matches = arraysEqual( u, computedValue );
+            }
+            else if( encRevision == 3 || encRevision == 4 )
+            {
+                //STEP 2
+                matches = arraysEqual( u, computedValue, 16 );
+            }
+            else
+            {
+                throw new IOException( "Unknown Encryption Revision " + encRevision );
+            }
+            return matches;
+        }
+    
+    /**
+     * Check if a plaintext password is the user password.
+     *
+     * @param password The plaintext password.
+     * @param u The u entry of the encryption dictionary.
+     * @param o The o entry of the encryption dictionary.
+     * @param permissions The permissions set in the the PDF.
+     * @param id The document id used for encryption.
+     * @param encRevision The revision of the encryption algorithm.
+     * @param length The length of the encryption key.
+     *
+     * @return true If the plaintext password is the user password.
+     *
+     * @throws CryptographyException If there is an error during encryption.
+     * @throws IOException If there is an error accessing data.
+     */
+    public final boolean isUserPassword(
+            String password,
+            byte[] u,
+            byte[] o,
+            int permissions,
+            byte[] id,
+            int encRevision,
+            int length,
+            boolean encryptMetadata )
+            throws CryptographyException, IOException
+            {
+                return isUserPassword(password.getBytes("ISO-8859-1"),
+                        u,o,permissions, id, encRevision, length, encryptMetadata);
+            }
+
+    /**
+     * Check for owner password.
+     *
+     * @param password The owner password.
+     * @param u The u entry of the encryption dictionary.
+     * @param o The o entry of the encryption dictionary.
+     * @param permissions The set of permissions on the document.
+     * @param id The document id.
+     * @param encRevision The encryption algorithm revision.
+     * @param length The encryption key length.
+     *
+     * @return True If the ownerPassword param is the owner password.
+     *
+     * @throws CryptographyException If there is an error during encryption.
+     * @throws IOException If there is an error accessing data.
+     */
+    public final boolean isOwnerPassword(
+            String password,
+            byte[] u,
+            byte[] o,
+            int permissions,
+            byte[] id,
+            int encRevision,
+            int length,
+            boolean encryptMetadata)
+            throws CryptographyException, IOException
+            {
+                return isOwnerPassword(password.getBytes("ISO-8859-1"),
+                        u,o,permissions, id, encRevision, length, encryptMetadata);
+            }
+
+    private static final boolean arraysEqual( byte[] first, byte[] second, int count )
+    {
+        boolean equal = first.length >= count && second.length >= count;
+        for( int i=0; i<count && equal; i++ )
+        {
+            equal = first[i] == second[i];
+        }
+        return equal;
+    }
+
+    /**
+     * This will compare two byte[] for equality.
+     *
+     * @param first The first byte array.
+     * @param second The second byte array.
+     *
+     * @return true If the arrays contain the exact same data.
+     */
+    private static final boolean arraysEqual( byte[] first, byte[] second )
+    {
+        boolean equal = first.length == second.length;
+        for( int i=0; i<first.length && equal; i++ )
+        {
+            equal = first[i] == second[i];
+        }
+        return equal;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/encryption/package.html
new file mode 100644 (file)
index 0000000..5f0862e
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The encryption package will handle the PDF document security handlers and the functionality of pluggable security handlers.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotation.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotation.java
new file mode 100644 (file)
index 0000000..018f69e
--- /dev/null
@@ -0,0 +1,608 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.util.Calendar;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.util.BitFlagHelper;
+import org.apache.pdfbox.util.DateConverter;
+import org.w3c.dom.Element;
+
+/**
+ * This represents an FDF annotation that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public abstract class FDFAnnotation implements COSObjectable
+{
+    /**
+     * Annotation dictionary.
+     */
+    protected COSDictionary annot;
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotation()
+    {
+        annot = new COSDictionary();
+        annot.setName( "Type", "Annot" );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The FDF annotation.
+     */
+    public FDFAnnotation( COSDictionary a )
+    {
+        annot = a;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param element An XFDF element.
+     *
+     * @throws IOException If there is an error extracting data from the element.
+     */
+    public FDFAnnotation( Element element ) throws IOException
+    {
+        this();
+
+        String page = element.getAttribute( "page" );
+        if( page != null )
+        {
+            setPage( Integer.parseInt( page ) );
+        }
+
+        String color = element.getAttribute( "color" );
+        if( color != null )
+        {
+            if( color.length() == 7 && color.charAt( 0 ) == '#' )
+            {
+                int colorValue = Integer.parseInt(color.substring(1,7), 16);
+                setColor( new Color(colorValue) );
+            }
+        }
+
+        setDate( element.getAttribute( "date" ) );
+
+        String flags = element.getAttribute( "flags" );
+        if( flags != null )
+        {
+            String[] flagTokens = flags.split( "," );
+            for( int i=0; i< flagTokens.length; i++ )
+            {
+                if( flagTokens[i].equals( "invisible" ) )
+                {
+                    setInvisible( true );
+                }
+                else if( flagTokens[i].equals( "hidden" ) )
+                {
+                    setHidden( true );
+                }
+                else if( flagTokens[i].equals( "print" ) )
+                {
+                    setPrinted( true );
+                }
+                else if( flagTokens[i].equals( "nozoom" ) )
+                {
+                    setNoZoom( true );
+                }
+                else if( flagTokens[i].equals( "norotate" ) )
+                {
+                    setNoRotate( true );
+                }
+                else if( flagTokens[i].equals( "noview" ) )
+                {
+                    setNoView( true );
+                }
+                else if( flagTokens[i].equals( "readonly" ) )
+                {
+                    setReadOnly( true );
+                }
+                else if( flagTokens[i].equals( "locked" ) )
+                {
+                    setLocked( true );
+                }
+                else if( flagTokens[i].equals( "togglenoview" ) )
+                {
+                    setToggleNoView( true );
+                }
+            }
+
+        }
+
+        setName( element.getAttribute( "name" ) );
+
+        String rect = element.getAttribute( "rect" );
+        if( rect != null )
+        {
+            String[] rectValues = rect.split( "," );
+            float[] values = new float[ rectValues.length ];
+            for( int i=0; i<rectValues.length; i++ )
+            {
+                values[i] = Float.parseFloat( rectValues[i] );
+            }
+            COSArray array = new COSArray();
+            array.setFloatArray( values );
+            setRectangle( new PDRectangle( array ) );
+        }
+
+        setName( element.getAttribute( "title" ) );
+        setCreationDate( DateConverter.toCalendar( element.getAttribute( "creationdate" ) ) );
+        String opac = element.getAttribute( "opacity" );
+        if( opac != null )
+        {
+            setOpacity( Float.parseFloat( opac ) );
+        }
+        setSubject( element.getAttribute( "subject" ) );
+    }
+
+    /**
+     * Create the correct FDFAnnotation.
+     *
+     * @param fdfDic The FDF dictionary.
+     *
+     * @return A newly created FDFAnnotation
+     *
+     * @throws IOException If there is an error accessing the FDF information.
+     */
+    public static FDFAnnotation create( COSDictionary fdfDic ) throws IOException
+    {
+        FDFAnnotation retval = null;
+        if( fdfDic == null )
+        {
+            //do nothing and return null
+        }
+        else if( FDFAnnotationText.SUBTYPE.equals( fdfDic.getNameAsString( COSName.SUBTYPE ) ) )
+        {
+            retval = new FDFAnnotationText( fdfDic );
+        }
+        else
+        {
+            throw new IOException( "Unknown annotation type '" + fdfDic.getNameAsString( COSName.SUBTYPE ) + "'" );
+        }
+        return retval;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return annot;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return annot;
+    }
+
+    /**
+     * This will get the page number or null if it does not exist.
+     *
+     * @return The page number.
+     */
+    public Integer getPage()
+    {
+        Integer retval = null;
+        COSNumber page = (COSNumber)annot.getDictionaryObject( "Page" );
+        if( page != null )
+        {
+            retval = new Integer( page.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the page.
+     *
+     * @param page The page number.
+     */
+    public void setPage( int page )
+    {
+        annot.setInt( "Page", page );
+    }
+
+    /**
+     * Get the annotation color.
+     *
+     * @return The annotation color, or null if there is none.
+     */
+    public Color getColor()
+    {
+        Color retval = null;
+        COSArray array = (COSArray)annot.getDictionaryObject( "color" );
+        if( array != null )
+        {
+            float[] rgb = array.toFloatArray();
+            if( rgb.length >=3 )
+            {
+                retval = new Color( rgb[0], rgb[1], rgb[2] );
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Set the annotation color.
+     *
+     * @param c The annotation color.
+     */
+    public void setColor( Color c )
+    {
+        COSArray color = null;
+        if( c != null )
+        {
+            float[] colors = c.getRGBColorComponents( null );
+            color = new COSArray();
+            color.setFloatArray( colors );
+        }
+        annot.setItem( "color", color );
+    }
+
+    /**
+     * Modification date.
+     *
+     * @return The date as a string.
+     */
+    public String getDate()
+    {
+        return annot.getString( "date" );
+    }
+
+    /**
+     * The annotation modification date.
+     *
+     * @param date The date to store in the FDF annotation.
+     */
+    public void setDate( String date )
+    {
+        annot.setString( "date", date );
+    }
+
+    /**
+     * Get the invisible flag.
+     *
+     * @return The invisible flag.
+     */
+    public boolean isInvisible()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_INVISIBLE );
+    }
+
+    /**
+     * Set the invisible flag.
+     *
+     * @param invisible The new invisible flag.
+     */
+    public void setInvisible( boolean invisible )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_INVISIBLE, invisible );
+    }
+
+    /**
+     * Get the hidden flag.
+     *
+     * @return The hidden flag.
+     */
+    public boolean isHidden()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_HIDDEN );
+    }
+
+    /**
+     * Set the hidden flag.
+     *
+     * @param hidden The new hidden flag.
+     */
+    public void setHidden( boolean hidden )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_HIDDEN, hidden );
+    }
+
+    /**
+     * Get the printed flag.
+     *
+     * @return The printed flag.
+     */
+    public boolean isPrinted()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_PRINTED );
+    }
+
+    /**
+     * Set the printed flag.
+     *
+     * @param printed The new printed flag.
+     */
+    public void setPrinted( boolean printed )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_PRINTED, printed );
+    }
+
+    /**
+     * Get the noZoom flag.
+     *
+     * @return The noZoom flag.
+     */
+    public boolean isNoZoom()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ZOOM );
+    }
+
+    /**
+     * Set the noZoom flag.
+     *
+     * @param noZoom The new noZoom flag.
+     */
+    public void setNoZoom( boolean noZoom )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ZOOM, noZoom );
+    }
+
+    /**
+     * Get the noRotate flag.
+     *
+     * @return The noRotate flag.
+     */
+    public boolean isNoRotate()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ROTATE );
+    }
+
+    /**
+     * Set the noRotate flag.
+     *
+     * @param noRotate The new noRotate flag.
+     */
+    public void setNoRotate( boolean noRotate )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ROTATE, noRotate );
+    }
+
+    /**
+     * Get the noView flag.
+     *
+     * @return The noView flag.
+     */
+    public boolean isNoView()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_NO_VIEW );
+    }
+
+    /**
+     * Set the noView flag.
+     *
+     * @param noView The new noView flag.
+     */
+    public void setNoView( boolean noView )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_NO_VIEW, noView );
+    }
+
+    /**
+     * Get the readOnly flag.
+     *
+     * @return The readOnly flag.
+     */
+    public boolean isReadOnly()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_READ_ONLY );
+    }
+
+    /**
+     * Set the readOnly flag.
+     *
+     * @param readOnly The new readOnly flag.
+     */
+    public void setReadOnly( boolean readOnly )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_READ_ONLY, readOnly );
+    }
+
+    /**
+     * Get the locked flag.
+     *
+     * @return The locked flag.
+     */
+    public boolean isLocked()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_LOCKED );
+    }
+
+    /**
+     * Set the locked flag.
+     *
+     * @param locked The new locked flag.
+     */
+    public void setLocked( boolean locked )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_LOCKED, locked );
+    }
+
+    /**
+     * Get the toggleNoView flag.
+     *
+     * @return The toggleNoView flag.
+     */
+    public boolean isToggleNoView()
+    {
+        return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_TOGGLE_NO_VIEW );
+    }
+
+    /**
+     * Set the toggleNoView flag.
+     *
+     * @param toggleNoView The new toggleNoView flag.
+     */
+    public void setToggleNoView( boolean toggleNoView )
+    {
+        BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_TOGGLE_NO_VIEW, toggleNoView );
+    }
+
+    /**
+     * Set a unique name for an annotation.
+     *
+     * @param name The unique annotation name.
+     */
+    public void setName( String name )
+    {
+        annot.setString( COSName.NM, name );
+    }
+
+    /**
+     * Get the annotation name.
+     *
+     * @return The unique name of the annotation.
+     */
+    public String getName()
+    {
+        return annot.getString( COSName.NM );
+    }
+
+    /**
+     * Set the rectangle associated with this annotation.
+     *
+     * @param rectangle The annotation rectangle.
+     */
+    public void setRectangle( PDRectangle rectangle )
+    {
+        annot.setItem( COSName.RECT, rectangle );
+    }
+
+    /**
+     * The rectangle associated with this annotation.
+     *
+     * @return The annotation rectangle.
+     */
+    public PDRectangle getRectangle()
+    {
+        PDRectangle retval = null;
+        COSArray rectArray = (COSArray)annot.getDictionaryObject( COSName.RECT );
+        if( rectArray != null )
+        {
+            retval = new PDRectangle( rectArray );
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set a unique title for an annotation.
+     *
+     * @param title The annotation title.
+     */
+    public void setTitle( String title )
+    {
+        annot.setString( COSName.T, title );
+    }
+
+    /**
+     * Get the annotation title.
+     *
+     * @return The title of the annotation.
+     */
+    public String getTitle()
+    {
+        return annot.getString( COSName.T );
+    }
+
+    /**
+     * The annotation create date.
+     *
+     * @return The date of the creation of the annotation date
+     *
+     * @throws IOException If there is an error converting the string to a Calendar object.
+     */
+    public Calendar getCreationDate() throws IOException
+    {
+        return annot.getDate( COSName.CREATION_DATE );
+    }
+
+    /**
+     * Set the creation date.
+     *
+     * @param date The date the annotation was created.
+     */
+    public void setCreationDate( Calendar date )
+    {
+        annot.setDate( COSName.CREATION_DATE, date );
+    }
+
+    /**
+     * Set the annotation opacity.
+     *
+     * @param opacity The new opacity value.
+     */
+    public void setOpacity( float opacity )
+    {
+        annot.setFloat( COSName.CA, opacity );
+    }
+
+    /**
+     * Get the opacity value.
+     *
+     * @return The opacity of the annotation.
+     */
+    public float getOpacity()
+    {
+        return annot.getFloat( COSName.CA, 1f );
+
+    }
+
+    /**
+     * A short description of the annotation.
+     *
+     * @param subject The annotation subject.
+     */
+    public void setSubject( String subject )
+    {
+        annot.setString( COSName.SUBJ, subject );
+    }
+
+    /**
+     * Get the description of the annotation.
+     *
+     * @return The subject of the annotation.
+     */
+    public String getSubject()
+    {
+        return annot.getString( COSName.SUBJ );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationCaret.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationCaret.java
new file mode 100644 (file)
index 0000000..19199af
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Caret FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationCaret extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Caret";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationCaret()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationCaret( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationCaret( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationCircle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationCircle.java
new file mode 100644 (file)
index 0000000..905aed3
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Circle FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationCircle extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Circle";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationCircle()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationCircle( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationCircle( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationFileAttachment.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationFileAttachment.java
new file mode 100644 (file)
index 0000000..d848e9e
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a FileAttachment FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationFileAttachment extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="FileAttachment";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationFileAttachment()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationFileAttachment( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationFileAttachment( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationFreeText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationFreeText.java
new file mode 100644 (file)
index 0000000..ea4f9a5
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a FreeText FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationFreeText extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="FreeText";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationFreeText()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationFreeText( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationFreeText( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationHighlight.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationHighlight.java
new file mode 100644 (file)
index 0000000..d9fbcda
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Highlight FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationHighlight extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Highlight";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationHighlight()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationHighlight( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationHighlight( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationInk.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationInk.java
new file mode 100644 (file)
index 0000000..9878a89
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Ink FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationInk extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Ink";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationInk()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationInk( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationInk( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationLine.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationLine.java
new file mode 100644 (file)
index 0000000..870591d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Line FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationLine extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Line";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationLine()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationLine( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationLine( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationPolygon.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationPolygon.java
new file mode 100644 (file)
index 0000000..67371f5
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Polygon FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationPolygon extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Polygon";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationPolygon()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationPolygon( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationPolygon( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationPolyline.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationPolyline.java
new file mode 100644 (file)
index 0000000..316a879
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Polyline FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationPolyline extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Polyline";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationPolyline()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationPolyline( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationPolyline( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSound.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSound.java
new file mode 100644 (file)
index 0000000..048b317
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Sound FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationSound extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Sound";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationSound()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationSound( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationSound( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSquare.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSquare.java
new file mode 100644 (file)
index 0000000..aeb7533
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Square FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationSquare extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Square";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationSquare()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationSquare( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationSquare( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSquiggly.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationSquiggly.java
new file mode 100644 (file)
index 0000000..de2758d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Squiggly FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationSquiggly extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Squiggly";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationSquiggly()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationSquiggly( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationSquiggly( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java
new file mode 100644 (file)
index 0000000..170e449
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Stamp FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationStamp extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Stamp";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationStamp()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationStamp( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationStamp( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStrikeOut.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationStrikeOut.java
new file mode 100644 (file)
index 0000000..a8b8fd7
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a StrikeOut FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationStrikeOut extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="StrikeOut";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationStrikeOut()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationStrikeOut( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationStrikeOut( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationText.java
new file mode 100644 (file)
index 0000000..76ce511
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Text FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationText extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Text";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationText()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationText( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationText( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationUnderline.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFAnnotationUnderline.java
new file mode 100644 (file)
index 0000000..5eafc7c
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Underline FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationUnderline extends FDFAnnotation
+{
+    /**
+     * COS Model value for SubType entry.
+     */
+    public static final String SUBTYPE ="Underline";
+
+    /**
+     * Default constructor.
+     */
+    public FDFAnnotationUnderline()
+    {
+        super();
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a An existing FDF Annotation.
+     */
+    public FDFAnnotationUnderline( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Constructor.
+     *
+     *  @param element An XFDF element.
+     *
+     *  @throws IOException If there is an error extracting information from the element.
+     */
+    public FDFAnnotationUnderline( Element element ) throws IOException
+    {
+        super( element );
+        annot.setName( COSName.SUBTYPE, SUBTYPE );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFCatalog.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFCatalog.java
new file mode 100644 (file)
index 0000000..4af3b6b
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+
+import org.w3c.dom.Element;
+
+/**
+ * This represents an FDF catalog that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFCatalog implements COSObjectable
+{
+    private COSDictionary catalog;
+
+    /**
+     * Default constructor.
+     */
+    public FDFCatalog()
+    {
+        catalog = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param cat The FDF documents catalog.
+     */
+    public FDFCatalog( COSDictionary cat )
+    {
+        catalog = cat;
+    }
+
+    /**
+     * This will create an FDF catalog from an XFDF XML document.
+     *
+     * @param element The XML document that contains the XFDF data.
+     * @throws IOException If there is an error reading from the dom.
+     */
+    public FDFCatalog( Element element ) throws IOException
+    {
+        this();
+        FDFDictionary fdfDict = new FDFDictionary( element );
+        setFDF( fdfDict );
+    }
+
+    /**
+     * This will write this element as an XML document.
+     *
+     * @param output The stream to write the xml to.
+     *
+     * @throws IOException If there is an error writing the XML.
+     */
+    public void writeXML( Writer output ) throws IOException
+    {
+        FDFDictionary fdf = getFDF();
+        fdf.writeXML( output );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return catalog;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return catalog;
+    }
+
+    /**
+     * This will get the version that was specified in the catalog dictionary.
+     *
+     * @return The FDF version.
+     */
+    public String getVersion()
+    {
+        return catalog.getNameAsString( "Version" );
+    }
+
+    /**
+     * This will set the version of the FDF document.
+     *
+     * @param version The new version for the FDF document.
+     */
+    public void setVersion( String version )
+    {
+        catalog.setName( "Version", version );
+    }
+
+    /**
+     * This will get the FDF dictionary.
+     *
+     * @return The FDF dictionary.
+     */
+    public FDFDictionary getFDF()
+    {
+        COSDictionary fdf = (COSDictionary)catalog.getDictionaryObject( "FDF" );
+        FDFDictionary retval = null;
+        if( fdf != null )
+        {
+            retval = new FDFDictionary( fdf );
+        }
+        else
+        {
+            retval = new FDFDictionary();
+            setFDF( retval );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the FDF document.
+     *
+     * @param fdf The new FDF dictionary.
+     */
+    public void setFDF( FDFDictionary fdf )
+    {
+        catalog.setItem( "FDF", fdf );
+    }
+
+    /**
+     * This will get the signature or null if there is none.
+     *
+     * @return The signature.
+     */
+    public PDSignature getSignature()
+    {
+        PDSignature signature = null;
+        COSDictionary sig = (COSDictionary)catalog.getDictionaryObject( "Sig" );
+        if( sig != null )
+        {
+            signature = new PDSignature( sig );
+        }
+        return signature;
+    }
+
+    /**
+     * This will set the signature that is associated with this catalog.
+     *
+     * @param sig The new signature.
+     */
+    public void setSignature( PDSignature sig )
+    {
+        catalog.setItem( "Sig", sig );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFDictionary.java
new file mode 100644 (file)
index 0000000..1b5c899
--- /dev/null
@@ -0,0 +1,480 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDSimpleFileSpecification;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This represents an FDF dictionary that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class FDFDictionary implements COSObjectable
+{
+    private COSDictionary fdf;
+
+    /**
+     * Default constructor.
+     */
+    public FDFDictionary()
+    {
+        fdf = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fdfDictionary The FDF documents catalog.
+     */
+    public FDFDictionary( COSDictionary fdfDictionary )
+    {
+        fdf = fdfDictionary;
+    }
+
+    /**
+     * This will create an FDF dictionary from an XFDF XML document.
+     *
+     * @param fdfXML The XML document that contains the XFDF data.
+     * @throws IOException If there is an error reading from the dom.
+     */
+    public FDFDictionary( Element fdfXML ) throws IOException
+    {
+        this();
+        NodeList nodeList = fdfXML.getChildNodes();
+        for( int i=0; i<nodeList.getLength(); i++ )
+        {
+            Node node = nodeList.item( i );
+            if( node instanceof Element )
+            {
+                Element child = (Element)node;
+                if( child.getTagName().equals( "f" ) )
+                {
+                    PDSimpleFileSpecification fs = new PDSimpleFileSpecification();
+                    fs.setFile( child.getAttribute( "href" ) );
+                    setFile(fs);
+
+                }
+                else if( child.getTagName().equals( "ids" ) )
+                {
+                    COSArray ids = new COSArray();
+                    String original = child.getAttribute( "original" );
+                    String modified = child.getAttribute( "modified" );
+                    ids.add( COSString.createFromHexString( original ) );
+                    ids.add( COSString.createFromHexString( modified ) );
+                    setID( ids );
+                }
+                else if( child.getTagName().equals( "fields" ) )
+                {
+                    NodeList fields = child.getElementsByTagName( "field" );
+                    List fieldList = new ArrayList();
+                    for( int f=0; f<fields.getLength(); f++ )
+                    {
+                        fieldList.add( new FDFField( (Element)fields.item( f ) ) );
+                    }
+                    setFields( fieldList );
+                }
+                else if( child.getTagName().equals( "annots" ) )
+                {
+                    NodeList annots = child.getChildNodes();
+                    List annotList = new ArrayList();
+                    for( int j=0; j<annots.getLength(); j++ )
+                    {
+                        Node annotNode = annots.item( i );
+                        if( annotNode instanceof Element )
+                        {
+                            Element annot = (Element)annotNode;
+                            if( annot.getNodeName().equals( "text" ) )
+                            {
+                                annotList.add( new FDFAnnotationText( annot ) );
+                            }
+                            else
+                            {
+                                throw new IOException( "Error: Unknown annotation type '" + annot.getNodeName() );
+                            }
+                        }
+                    }
+                    setAnnotations(annotList);
+                }
+            }
+        }
+    }
+
+    /**
+     * This will write this element as an XML document.
+     *
+     * @param output The stream to write the xml to.
+     *
+     * @throws IOException If there is an error writing the XML.
+     */
+    public void writeXML( Writer output ) throws IOException
+    {
+        PDFileSpecification fs = this.getFile();
+        if( fs != null )
+        {
+            output.write( "<f href=\"" + fs.getFile() + "\" />\n" );
+        }
+        COSArray ids = this.getID();
+        if( ids != null )
+        {
+            COSString original = (COSString)ids.getObject( 0 );
+            COSString modified = (COSString)ids.getObject( 1 );
+            output.write( "<ids original=\"" + original.getHexString() + "\" " );
+            output.write( "modified=\"" + modified.getHexString() + "\" />\n");
+        }
+        List fields = getFields();
+        if( fields != null && fields.size() > 0 )
+        {
+            output.write( "<fields>\n" );
+            for( int i=0; i<fields.size(); i++ )
+            {
+                ((FDFField)fields.get( i )).writeXML( output );
+            }
+            output.write( "</fields>\n" );
+        }
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return fdf;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return fdf;
+    }
+
+    /**
+     * The source file or target file: the PDF document file that
+     * this FDF file was exported from or is intended to be imported into.
+     *
+     * @return The F entry of the FDF dictionary.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public PDFileSpecification getFile() throws IOException
+    {
+        return PDFileSpecification.createFS( fdf.getDictionaryObject( "F" ) );
+    }
+
+    /**
+     * This will set the file specification.
+     *
+     * @param fs The file specification.
+     */
+    public void setFile( PDFileSpecification fs )
+    {
+        fdf.setItem( "F", fs );
+    }
+
+    /**
+     * This is the FDF id.
+     *
+     * @return The FDF ID.
+     */
+    public COSArray getID()
+    {
+        return (COSArray)fdf.getDictionaryObject( "ID" );
+    }
+
+    /**
+     * This will set the FDF id.
+     *
+     * @param id The new id for the FDF.
+     */
+    public void setID( COSArray id )
+    {
+        fdf.setItem( "ID", id );
+    }
+
+    /**
+     * This will get the list of FDF Fields.  This will return a list of FDFField
+     * objects.
+     *
+     * @return A list of FDF fields.
+     */
+    public List getFields()
+    {
+        List retval = null;
+        COSArray fieldArray = (COSArray)fdf.getDictionaryObject( "Fields" );
+        if( fieldArray != null )
+        {
+            List fields = new ArrayList();
+            for( int i=0; i<fieldArray.size(); i++ )
+            {
+                fields.add( new FDFField( (COSDictionary)fieldArray.getObject( i ) ) );
+            }
+            retval = new COSArrayList( fields, fieldArray );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the list of fields.  This should be a list of FDFField objects.
+     *
+     * @param fields The list of fields.
+     */
+    public void setFields( List fields )
+    {
+        fdf.setItem( "Fields", COSArrayList.converterToCOSArray( fields ) );
+    }
+
+    /**
+     * This will get the status string to be displayed as the result of an
+     * action.
+     *
+     * @return The status.
+     */
+    public String getStatus()
+    {
+        return fdf.getString( "Status" );
+    }
+
+    /**
+     * This will set the status string.
+     *
+     * @param status The new status string.
+     */
+    public void setStatus( String status )
+    {
+        fdf.setString( "Status", status );
+    }
+
+    /**
+     * This will get the list of FDF Pages.  This will return a list of FDFPage objects.
+     *
+     * @return A list of FDF pages.
+     */
+    public List getPages()
+    {
+        List retval = null;
+        COSArray pageArray = (COSArray)fdf.getDictionaryObject( "Pages" );
+        if( pageArray != null )
+        {
+            List pages = new ArrayList();
+            for( int i=0; i<pageArray.size(); i++ )
+            {
+                pages.add( new FDFPage( (COSDictionary)pageArray.get( i ) ) );
+            }
+            retval = new COSArrayList( pages, pageArray );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the list of pages.  This should be a list of FDFPage objects.
+     *
+     *
+     * @param pages The list of pages.
+     */
+    public void setPages( List pages )
+    {
+        fdf.setItem( "Pages", COSArrayList.converterToCOSArray( pages ) );
+    }
+
+    /**
+     * The encoding to be used for a FDF field.  The default is PDFDocEncoding
+     * and this method will never return null.
+     *
+     * @return The encoding value.
+     */
+    public String getEncoding()
+    {
+        String encoding = fdf.getNameAsString( "Encoding" );
+        if( encoding == null )
+        {
+            encoding = "PDFDocEncoding";
+        }
+        return encoding;
+
+    }
+
+    /**
+     * This will set the encoding.
+     *
+     * @param encoding The new encoding.
+     */
+    public void setEncoding( String encoding )
+    {
+        fdf.setName( "Encoding", encoding );
+    }
+
+    /**
+     * This will get the list of FDF Annotations.  This will return a list of FDFAnnotation objects
+     * or null if the entry is not set.
+     *
+     * @return A list of FDF annotations.
+     *
+     * @throws IOException If there is an error creating the annotation list.
+     */
+    public List getAnnotations() throws IOException
+    {
+        List retval = null;
+        COSArray annotArray = (COSArray)fdf.getDictionaryObject( "Annots" );
+        if( annotArray != null )
+        {
+            List annots = new ArrayList();
+            for( int i=0; i<annotArray.size(); i++ )
+            {
+                annots.add( FDFAnnotation.create( (COSDictionary)annotArray.getObject( i ) ) );
+            }
+            retval = new COSArrayList( annots, annotArray );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the list of annotations.  This should be a list of FDFAnnotation objects.
+     *
+     *
+     * @param annots The list of annotations.
+     */
+    public void setAnnotations( List annots )
+    {
+        fdf.setItem( "Annots", COSArrayList.converterToCOSArray( annots ) );
+    }
+
+    /**
+     * This will get the incremental updates since the PDF was last opened.
+     *
+     * @return The differences entry of the FDF dictionary.
+     */
+    public COSStream getDifferences()
+    {
+        return (COSStream)fdf.getDictionaryObject( "Differences" );
+    }
+
+    /**
+     * This will set the differences stream.
+     *
+     * @param diff The new differences stream.
+     */
+    public void setDifferences( COSStream diff )
+    {
+        fdf.setItem( "Differences", diff );
+    }
+
+    /**
+     * This will get the target frame in the browser to open this document.
+     *
+     * @return The target frame.
+     */
+    public String getTarget()
+    {
+        return fdf.getString( "Target" );
+    }
+
+    /**
+     * This will set the target frame in the browser to open this document.
+     *
+     * @param target The new target frame.
+     */
+    public void setTarget( String target )
+    {
+        fdf.setString( "Target", target );
+    }
+
+    /**
+     * This will get the list of embedded FDF entries, or null if the entry is null.
+     * This will return a list of PDFileSpecification objects.
+     *
+     * @return A list of embedded FDF files.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public List getEmbeddedFDFs() throws IOException
+    {
+        List retval = null;
+        COSArray embeddedArray = (COSArray)fdf.getDictionaryObject( "EmbeddedFDFs" );
+        if( embeddedArray != null )
+        {
+            List embedded = new ArrayList();
+            for( int i=0; i<embeddedArray.size(); i++ )
+            {
+                embedded.add( PDFileSpecification.createFS( embeddedArray.get( i ) ) );
+            }
+            retval = new COSArrayList( embedded, embeddedArray );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the list of embedded FDFs.  This should be a list of
+     * PDFileSpecification objects.
+     *
+     *
+     * @param embedded The list of embedded FDFs.
+     */
+    public void setEmbeddedFDFs( List embedded )
+    {
+        fdf.setItem( "EmbeddedFDFs", COSArrayList.converterToCOSArray( embedded ) );
+    }
+
+    /**
+     * This will get the java script entry.
+     *
+     * @return The java script entry describing javascript commands.
+     */
+    public FDFJavaScript getJavaScript()
+    {
+        FDFJavaScript fs = null;
+        COSDictionary dic = (COSDictionary)fdf.getDictionaryObject( "JavaScript" );
+        if( dic != null )
+        {
+            fs = new FDFJavaScript( dic );
+        }
+        return fs;
+    }
+
+    /**
+     * This will set the JavaScript entry.
+     *
+     * @param js The javascript entries.
+     */
+    public void setJavaScript( FDFJavaScript js )
+    {
+        fdf.setItem( "JavaScript", js );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java
new file mode 100644 (file)
index 0000000..90846d5
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+import org.apache.pdfbox.util.XMLUtil;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * This is the in-memory representation of the FDF document.  You need to call
+ * close() on this object when you are done using it!!
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class FDFDocument
+{
+    private COSDocument document;
+
+    /**
+     * Constructor, creates a new FDF document.
+     *
+     * @throws IOException If there is an error creating this document.
+     */
+    public FDFDocument() throws IOException
+    {
+        document = new COSDocument();
+        document.setHeaderString( "%FDF-1.2" );
+
+        //First we need a trailer
+        document.setTrailer( new COSDictionary() );
+
+        //Next we need the root dictionary.
+        FDFCatalog catalog = new FDFCatalog();
+        setCatalog( catalog );
+    }
+
+    /**
+     * Constructor that uses an existing document.  The COSDocument that
+     * is passed in must be valid.
+     *
+     * @param doc The COSDocument that this document wraps.
+     */
+    public FDFDocument( COSDocument doc )
+    {
+        document = doc;
+    }
+
+    /**
+     * This will create an FDF document from an XFDF XML document.
+     *
+     * @param doc The XML document that contains the XFDF data.
+     * @throws IOException If there is an error reading from the dom.
+     */
+    public FDFDocument( Document doc ) throws IOException
+    {
+        this();
+        Element xfdf = doc.getDocumentElement();
+        if( !xfdf.getNodeName().equals( "xfdf" ) )
+        {
+            throw new IOException( "Error while importing xfdf document, " +
+                "root should be 'xfdf' and not '" + xfdf.getNodeName() + "'" );
+        }
+        FDFCatalog cat = new FDFCatalog( xfdf );
+        setCatalog( cat );
+    }
+
+    /**
+     * This will write this element as an XML document.
+     *
+     * @param output The stream to write the xml to.
+     *
+     * @throws IOException If there is an error writing the XML.
+     */
+    public void writeXML( Writer output ) throws IOException
+    {
+        output.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
+        output.write( "<xfdf xmlns=\"http://ns.adobe.com/xfdf/\" xml:space=\"preserve\">\n" );
+
+        getCatalog().writeXML( output );
+
+        output.write( "</xfdf>\n" );
+    }
+
+
+
+    /**
+     * This will get the low level document.
+     *
+     * @return The document that this layer sits on top of.
+     */
+    public COSDocument getDocument()
+    {
+        return document;
+    }
+
+    /**
+     * This will get the FDF Catalog.  This is guaranteed to not return null.
+     *
+     * @return The documents /Root dictionary
+     */
+    public FDFCatalog getCatalog()
+    {
+        FDFCatalog retval = null;
+        COSDictionary trailer = document.getTrailer();
+        COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT );
+        if( root == null )
+        {
+            retval = new FDFCatalog();
+            setCatalog( retval );
+        }
+        else
+        {
+            retval = new FDFCatalog( root );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the FDF catalog for this FDF document.
+     *
+     * @param cat The FDF catalog.
+     */
+    public void setCatalog( FDFCatalog cat )
+    {
+        COSDictionary trailer = document.getTrailer();
+        trailer.setItem( COSName.ROOT, cat );
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param filename The name of the file to load.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static FDFDocument load( String filename ) throws IOException
+    {
+        return load( new BufferedInputStream( new FileInputStream( filename ) ) );
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param file The name of the file to load.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static FDFDocument load( File file ) throws IOException
+    {
+        return load( new BufferedInputStream( new FileInputStream( file ) ) );
+    }
+
+    /**
+     * This will load a document from an input stream.
+     *
+     * @param input The stream that contains the document.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static FDFDocument load( InputStream input ) throws IOException
+    {
+        PDFParser parser = new PDFParser( input );
+        parser.parse();
+        return parser.getFDFDocument();
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param filename The name of the file to load.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static FDFDocument loadXFDF( String filename ) throws IOException
+    {
+        return loadXFDF( new BufferedInputStream( new FileInputStream( filename ) ) );
+    }
+
+    /**
+     * This will load a document from a file.
+     *
+     * @param file The name of the file to load.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static FDFDocument loadXFDF( File file ) throws IOException
+    {
+        return loadXFDF( new BufferedInputStream( new FileInputStream( file ) ) );
+    }
+
+    /**
+     * This will load a document from an input stream.
+     *
+     * @param input The stream that contains the document.
+     *
+     * @return The document that was loaded.
+     *
+     * @throws IOException If there is an error reading from the stream.
+     */
+    public static FDFDocument loadXFDF( InputStream input ) throws IOException
+    {
+        Document doc = XMLUtil.parse( input );
+        return new FDFDocument( doc );
+    }
+
+    /**
+     * This will save this document to the filesystem.
+     *
+     * @param fileName The file to save as.
+     *
+     * @throws IOException If there is an error saving the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void save( File fileName ) throws IOException, COSVisitorException
+    {
+        save( new FileOutputStream( fileName ) );
+    }
+
+    /**
+     * This will save this document to the filesystem.
+     *
+     * @param fileName The file to save as.
+     *
+     * @throws IOException If there is an error saving the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void save( String fileName ) throws IOException, COSVisitorException
+    {
+        save( new FileOutputStream( fileName ) );
+    }
+
+    /**
+     * This will save the document to an output stream.
+     *
+     * @param output The stream to write to.
+     *
+     * @throws IOException If there is an error writing the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void save( OutputStream output ) throws IOException, COSVisitorException
+    {
+        COSWriter writer = null;
+        try
+        {
+            writer = new COSWriter( output );
+            writer.write( document );
+            writer.close();
+        }
+        finally
+        {
+            if( writer != null )
+            {
+                writer.close();
+            }
+        }
+    }
+
+    /**
+     * This will save this document to the filesystem.
+     *
+     * @param fileName The file to save as.
+     *
+     * @throws IOException If there is an error saving the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void saveXFDF( File fileName ) throws IOException, COSVisitorException
+    {
+        saveXFDF( new BufferedWriter( new FileWriter( fileName ) ) );
+    }
+
+    /**
+     * This will save this document to the filesystem.
+     *
+     * @param fileName The file to save as.
+     *
+     * @throws IOException If there is an error saving the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void saveXFDF( String fileName ) throws IOException, COSVisitorException
+    {
+        saveXFDF( new BufferedWriter( new FileWriter( fileName ) ) );
+    }
+
+    /**
+     * This will save the document to an output stream and close the stream.
+     *
+     * @param output The stream to write to.
+     *
+     * @throws IOException If there is an error writing the document.
+     * @throws COSVisitorException If an error occurs while generating the data.
+     */
+    public void saveXFDF( Writer output ) throws IOException, COSVisitorException
+    {
+        try
+        {
+            writeXML( output );
+        }
+        finally
+        {
+            if( output != null )
+            {
+                output.close();
+            }
+        }
+    }
+
+    /**
+     * This will close the underlying COSDocument object.
+     *
+     * @throws IOException If there is an error releasing resources.
+     */
+    public void close() throws IOException
+    {
+        document.close();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFField.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFField.java
new file mode 100644 (file)
index 0000000..2fdf058
--- /dev/null
@@ -0,0 +1,749 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.PDAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+
+import org.apache.pdfbox.util.XMLUtil;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This represents an FDF field that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class FDFField implements COSObjectable
+{
+    private COSDictionary field;
+
+    /**
+     * Default constructor.
+     */
+    public FDFField()
+    {
+        field = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param f The FDF field.
+     */
+    public FDFField( COSDictionary f )
+    {
+        field = f;
+    }
+
+    /**
+     * This will create an FDF field from an XFDF XML document.
+     *
+     * @param fieldXML The XML document that contains the XFDF data.
+     * @throws IOException If there is an error reading from the dom.
+     */
+    public FDFField( Element fieldXML ) throws IOException
+    {
+        this();
+        this.setPartialFieldName( fieldXML.getAttribute( "name" ) );
+        NodeList nodeList = fieldXML.getChildNodes();
+        List<FDFField> kids = new ArrayList<FDFField>();
+        for( int i=0; i<nodeList.getLength(); i++ )
+        {
+            Node node = nodeList.item( i );
+            if( node instanceof Element )
+            {
+                Element child = (Element)node;
+                if( child.getTagName().equals( "value" ) )
+                {
+                    setValue( XMLUtil.getNodeValue( child ) );
+                }
+                else if( child.getTagName().equals( "value-richtext" ) )
+                {
+                    setRichText( new PDTextStream( XMLUtil.getNodeValue( child ) ) );
+                }
+                else if( child.getTagName().equals( "field" ) )
+                {
+                    kids.add( new FDFField( child ) );
+                }
+            }
+        }
+        if( kids.size() > 0 )
+        {
+            setKids( kids );
+        }
+
+    }
+
+    /**
+     * This will write this element as an XML document.
+     *
+     * @param output The stream to write the xml to.
+     *
+     * @throws IOException If there is an error writing the XML.
+     */
+    public void writeXML( Writer output ) throws IOException
+    {
+        output.write( "<field name=\"" + getPartialFieldName() + "\">\n");
+        Object value = getValue();
+        if( value != null )
+        {
+            output.write( "<value>" + value + "</value>\n" );
+        }
+        PDTextStream rt = getRichText();
+        if( rt != null )
+        {
+            output.write( "<value-richtext>" + rt.getAsString() + "</value-richtext>\n" );
+        }
+        List<FDFField> kids = getKids();
+        if( kids != null )
+        {
+            for( int i=0; i<kids.size(); i++ )
+            {
+                ((FDFField)kids.get( i ) ).writeXML( output );
+            }
+        }
+        output.write( "</field>\n");
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return field;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return field;
+    }
+
+    /**
+     * This will get the list of kids.  This will return a list of FDFField objects.
+     * This will return null if the underlying list is null.
+     *
+     * @return The list of kids.
+     */
+    public List<FDFField> getKids()
+    {
+        COSArray kids = (COSArray)field.getDictionaryObject( COSName.KIDS );
+        List<FDFField> retval = null;
+        if( kids != null )
+        {
+            List<FDFField> actuals = new ArrayList<FDFField>();
+            for( int i=0; i<kids.size(); i++ )
+            {
+                actuals.add( new FDFField( (COSDictionary)kids.getObject( i ) ) );
+            }
+            retval = new COSArrayList( actuals, kids );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the list of kids.
+     *
+     * @param kids A list of FDFField objects.
+     */
+    public void setKids( List<FDFField> kids )
+    {
+        field.setItem( COSName.KIDS, COSArrayList.converterToCOSArray( kids ) );
+    }
+
+    /**
+     * This will get the "T" entry in the field dictionary.  A partial field
+     * name.  Where the fully qualified field name is a concatenation of
+     * the parent's fully qualified field name and "." as a separator.  For example<br/>
+     * Address.State<br />
+     * Address.City<br />
+     *
+     * @return The partial field name.
+     */
+    public String getPartialFieldName()
+    {
+        return field.getString( COSName.T );
+    }
+
+    /**
+     * This will set the partial field name.
+     *
+     * @param partial The partial field name.
+     */
+    public void setPartialFieldName( String partial )
+    {
+        field.setString( COSName.T, partial );
+    }
+
+    /**
+     * This will set the value for the field.  This will return type will either be <br />
+     * String : Checkboxes, Radio Button <br />
+     * java.util.List of strings: Choice Field
+     * PDTextStream: Textfields
+     *
+     * @return The value of the field.
+     *
+     * @throws IOException If there is an error getting the value.
+     */
+    public Object getValue() throws IOException
+    {
+        Object retval = null;
+        COSBase value = field.getDictionaryObject( COSName.V );
+        if( value instanceof COSName )
+        {
+            retval = ((COSName)value).getName();
+        }
+        else if( value instanceof COSArray )
+        {
+            retval = COSArrayList.convertCOSStringCOSArrayToList( (COSArray)value );
+        }
+        else if( value instanceof COSString || value instanceof COSStream )
+        {
+            retval = PDTextStream.createTextStream( value );
+        }
+        else if( value == null )
+        {
+            //Ok, value is null so do nothing
+        }
+        else
+        {
+            throw new IOException( "Error:Unknown type for field import" + value );
+        }
+        return retval;
+    }
+
+    /**
+     * You should pass in a string, or a java.util.List of strings to set the
+     * value.
+     *
+     * @param value The value that should populate when imported.
+     *
+     * @throws IOException If there is an error setting the value.
+     */
+    public void setValue( Object value ) throws IOException
+    {
+        COSBase cos = null;
+        if( value instanceof List )
+        {
+            cos = COSArrayList.convertStringListToCOSStringCOSArray( (List)value );
+        }
+        else if( value instanceof String )
+        {
+            cos = COSName.getPDFName( (String)value );
+        }
+        else if( value instanceof COSObjectable )
+        {
+            cos = ((COSObjectable)value).getCOSObject();
+        }
+        else if( value == null )
+        {
+            //do nothing and let cos remain null as well.
+        }
+        else
+        {
+            throw new IOException( "Error:Unknown type for field import" + value );
+        }
+        field.setItem( COSName.V, cos );
+    }
+
+    /**
+     * This will get the Ff entry of the cos dictionary.  If it it not present then
+     * this method will return null.
+     *
+     * @return The field flags.
+     */
+    public Integer getFieldFlags()
+    {
+        Integer retval = null;
+        COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.FF );
+        if( ff != null )
+        {
+            retval = new Integer( ff.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The Ff entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the field flags.
+     */
+    public void setFieldFlags( Integer ff )
+    {
+        COSInteger value = null;
+        if( ff != null )
+        {
+            value = COSInteger.get( ff );
+        }
+        field.setItem( COSName.FF, value );
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The Ff entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the field flags.
+     */
+    public void setFieldFlags( int ff )
+    {
+        field.setInt( COSName.FF, ff );
+    }
+
+    /**
+     * This will get the SetFf entry of the cos dictionary.  If it it not present then
+     * this method will return null.
+     *
+     * @return The field flags.
+     */
+    public Integer getSetFieldFlags()
+    {
+        Integer retval = null;
+        COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.SET_FF );
+        if( ff != null )
+        {
+            retval = new Integer( ff.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The SetFf entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "set field flags".
+     */
+    public void setSetFieldFlags( Integer ff )
+    {
+        COSInteger value = null;
+        if( ff != null )
+        {
+            value = COSInteger.get( ff );
+        }
+        field.setItem( COSName.SET_FF, value );
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The SetFf entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "set field flags".
+     */
+    public void setSetFieldFlags( int ff )
+    {
+        field.setInt( COSName.SET_FF, ff );
+    }
+
+    /**
+     * This will get the ClrFf entry of the cos dictionary.  If it it not present then
+     * this method will return null.
+     *
+     * @return The field flags.
+     */
+    public Integer getClearFieldFlags()
+    {
+        Integer retval = null;
+        COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.CLR_FF );
+        if( ff != null )
+        {
+            retval = new Integer( ff.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The ClrFf entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "clear field flags".
+     */
+    public void setClearFieldFlags( Integer ff )
+    {
+        COSInteger value = null;
+        if( ff != null )
+        {
+            value = COSInteger.get( ff );
+        }
+        field.setItem( COSName.CLR_FF, value );
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The ClrFf entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "clear field flags".
+     */
+    public void setClearFieldFlags( int ff )
+    {
+        field.setInt( COSName.CLR_FF, ff );
+    }
+
+    /**
+     * This will get the F entry of the cos dictionary.  If it it not present then
+     * this method will return null.
+     *
+     * @return The widget field flags.
+     */
+    public Integer getWidgetFieldFlags()
+    {
+        Integer retval = null;
+        COSNumber f = (COSNumber)field.getDictionaryObject( "F" );
+        if( f != null )
+        {
+            retval = new Integer( f.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the widget field flags that are associated with this field.  The F entry
+     * in the FDF field dictionary.
+     *
+     * @param f The new value for the field flags.
+     */
+    public void setWidgetFieldFlags( Integer f )
+    {
+        COSInteger value = null;
+        if( f != null )
+        {
+            value = COSInteger.get( f );
+        }
+        field.setItem( COSName.F, value );
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The F entry
+     * in the FDF field dictionary.
+     *
+     * @param f The new value for the field flags.
+     */
+    public void setWidgetFieldFlags( int f )
+    {
+        field.setInt( COSName.F, f );
+    }
+
+    /**
+     * This will get the SetF entry of the cos dictionary.  If it it not present then
+     * this method will return null.
+     *
+     * @return The field flags.
+     */
+    public Integer getSetWidgetFieldFlags()
+    {
+        Integer retval = null;
+        COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.SET_F );
+        if( ff != null )
+        {
+            retval = new Integer( ff.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the widget field flags that are associated with this field.  The SetF entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "set widget field flags".
+     */
+    public void setSetWidgetFieldFlags( Integer ff )
+    {
+        COSInteger value = null;
+        if( ff != null )
+        {
+            value = COSInteger.get( ff );
+        }
+        field.setItem( COSName.SET_F, value );
+    }
+
+    /**
+     * This will get the widget field flags that are associated with this field.  The SetF entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "set widget field flags".
+     */
+    public void setSetWidgetFieldFlags( int ff )
+    {
+        field.setInt( COSName.SET_F, ff );
+    }
+
+    /**
+     * This will get the ClrF entry of the cos dictionary.  If it it not present then
+     * this method will return null.
+     *
+     * @return The widget field flags.
+     */
+    public Integer getClearWidgetFieldFlags()
+    {
+        Integer retval = null;
+        COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.CLR_F );
+        if( ff != null )
+        {
+            retval = new Integer( ff.intValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The ClrF entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "clear widget field flags".
+     */
+    public void setClearWidgetFieldFlags( Integer ff )
+    {
+        COSInteger value = null;
+        if( ff != null )
+        {
+            value = COSInteger.get( ff );
+        }
+        field.setItem( COSName.CLR_F, value );
+    }
+
+    /**
+     * This will get the field flags that are associated with this field.  The ClrF entry
+     * in the FDF field dictionary.
+     *
+     * @param ff The new value for the "clear field flags".
+     */
+    public void setClearWidgetFieldFlags( int ff )
+    {
+        field.setInt( COSName.CLR_F, ff );
+    }
+
+    /**
+     * This will get the appearance dictionary that specifies the appearance of
+     * a pushbutton field.
+     *
+     * @return The AP entry of this dictionary.
+     */
+    public PDAppearanceDictionary getAppearanceDictionary()
+    {
+        PDAppearanceDictionary retval = null;
+        COSDictionary dict = (COSDictionary)field.getDictionaryObject( COSName.AP );
+        if( dict != null )
+        {
+            retval = new PDAppearanceDictionary( dict );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the appearance dictionary.
+     *
+     * @param ap The apperance dictionary.
+     */
+    public void setAppearanceDictionary( PDAppearanceDictionary ap )
+    {
+        field.setItem( COSName.AP, ap );
+    }
+
+    /**
+     * This will get named page references..
+     *
+     * @return The named page references.
+     */
+    public FDFNamedPageReference getAppearanceStreamReference()
+    {
+        FDFNamedPageReference retval = null;
+        COSDictionary ref = (COSDictionary)field.getDictionaryObject( COSName.AP_REF );
+        if( ref != null )
+        {
+            retval = new FDFNamedPageReference( ref );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the named page references.
+     *
+     * @param ref The named page references.
+     */
+    public void setAppearanceStreamReference( FDFNamedPageReference ref )
+    {
+        field.setItem( COSName.AP_REF, ref );
+    }
+
+    /**
+     * This will get the icon fit that is associated with this field.
+     *
+     * @return The IF entry.
+     */
+    public FDFIconFit getIconFit()
+    {
+        FDFIconFit retval = null;
+        COSDictionary dic = (COSDictionary)field.getDictionaryObject( "IF" );
+        if( dic != null )
+        {
+            retval = new FDFIconFit( dic );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the icon fit entry.
+     *
+     * @param fit The icon fit object.
+     */
+    public void setIconFit( FDFIconFit fit )
+    {
+        field.setItem( "IF", fit );
+    }
+
+    /**
+     * This will return a list of options for a choice field.  The value in the
+     * list will be 1 of 2 types.  java.lang.String or FDFOptionElement.
+     *
+     * @return A list of all options.
+     */
+    public List getOptions()
+    {
+        List retval = null;
+        COSArray array = (COSArray)field.getDictionaryObject( COSName.OPT );
+        if( array != null )
+        {
+            List objects = new ArrayList();
+            for( int i=0; i<array.size(); i++ )
+            {
+                COSBase next = array.getObject( i );
+                if( next instanceof COSString )
+                {
+                    objects.add( ((COSString)next).getString() );
+                }
+                else
+                {
+                    COSArray value = (COSArray)next;
+                    objects.add( new FDFOptionElement( value ) );
+                }
+            }
+            retval = new COSArrayList( objects, array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the options for the choice field.  The objects in the list
+     * should either be java.lang.String or FDFOptionElement.
+     *
+     * @param options The options to set.
+     */
+    public void setOptions( List options )
+    {
+        COSArray value = COSArrayList.converterToCOSArray( options );
+        field.setItem( COSName.OPT, value );
+    }
+
+    /**
+     * This will get the action that is associated with this field.
+     *
+     * @return The A entry in the field dictionary.
+     */
+    public PDAction getAction()
+    {
+        return PDActionFactory.createAction( (COSDictionary)field.getDictionaryObject( COSName.A ) );
+    }
+
+    /**
+     * This will set the action that is associated with this field.
+     *
+     * @param a The new action.
+     */
+    public void setAction( PDAction a )
+    {
+        field.setItem( COSName.A, a );
+    }
+
+    /**
+     * This will get a list of additional actions that will get executed based
+     * on events.
+     *
+     * @return The AA entry in this field dictionary.
+     */
+    public PDAdditionalActions getAdditionalActions()
+    {
+        PDAdditionalActions retval = null;
+        COSDictionary dict = (COSDictionary)field.getDictionaryObject( COSName.AA );
+        if( dict != null )
+        {
+            retval = new PDAdditionalActions( dict );
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the additional actions that are associated with this field.
+     *
+     * @param aa The additional actions.
+     */
+    public void setAdditionalActions( PDAdditionalActions aa )
+    {
+        field.setItem( COSName.AA, aa );
+    }
+
+    /**
+     * This will set the rich text that is associated with this field.
+     *
+     * @return The rich text XHTML stream.
+     */
+    public PDTextStream getRichText()
+    {
+        COSBase rv = field.getDictionaryObject( COSName.RV );
+        return PDTextStream.createTextStream( rv );
+    }
+
+    /**
+     * This will set the rich text value.
+     *
+     * @param rv The rich text value for the stream.
+     */
+    public void setRichText( PDTextStream rv )
+    {
+        field.setItem( COSName.RV, rv );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFIconFit.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFIconFit.java
new file mode 100644 (file)
index 0000000..a971f10
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+/**
+ * This represents an Icon fit dictionary for an FDF field.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFIconFit implements COSObjectable
+{
+    private COSDictionary fit;
+
+    /**
+     * A scale option.
+     */
+    public static final String SCALE_OPTION_ALWAYS = "A";
+    /**
+     * A scale option.
+     */
+    public static final String SCALE_OPTION_ONLY_WHEN_ICON_IS_BIGGER = "B";
+    /**
+     * A scale option.
+     */
+    public static final String SCALE_OPTION_ONLY_WHEN_ICON_IS_SMALLER = "S";
+    /**
+     * A scale option.
+     */
+    public static final String SCALE_OPTION_NEVER = "N";
+
+    /**
+     * Scale to fill with of annotation, disregarding aspect ratio.
+     */
+    public static final String SCALE_TYPE_ANAMORPHIC = "A";
+    /**
+     * Scale to fit width or height, smaller of two, while retaining aspect ration.
+     */
+    public static final String SCALE_TYPE_PROPORTIONAL = "P";
+
+
+
+    /**
+     * Default constructor.
+     */
+    public FDFIconFit()
+    {
+        fit = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param f The icon fit dictionary.
+     */
+    public FDFIconFit( COSDictionary f )
+    {
+        fit = f;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return fit;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return fit;
+    }
+
+    /**
+     * This will get the scale option.  See the SCALE_OPTION_XXX constants.  This
+     * is guaranteed to never return null.  Default: Always
+     *
+     * @return The scale option.
+     */
+    public String getScaleOption()
+    {
+        String retval =  fit.getNameAsString( "SW" );
+        if( retval == null )
+        {
+            retval = SCALE_OPTION_ALWAYS;
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the scale option for the icon.  Set the SCALE_OPTION_XXX constants.
+     *
+     * @param option The scale option.
+     */
+    public void setScaleOption( String option )
+    {
+        fit.setName( "SW", option );
+    }
+
+    /**
+     * This will get the scale type.  See the SCALE_TYPE_XXX constants.  This is
+     * guaranteed to never return null.  Default: Proportional
+     *
+     * @return The scale type.
+     */
+    public String getScaleType()
+    {
+        String retval =  fit.getNameAsString( "S" );
+        if( retval == null )
+        {
+            retval = SCALE_TYPE_PROPORTIONAL;
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the scale type.  See the SCALE_TYPE_XXX constants.
+     *
+     * @param scale The scale type.
+     */
+    public void setScaleType( String scale )
+    {
+        fit.setName( "S", scale );
+    }
+
+    /**
+     * This is guaranteed to never return null.<br />
+     *
+     * To quote the PDF Spec
+     * "An array of two numbers between 0.0 and 1.0 indicating the fraction of leftover
+     * space to allocate at the left and bottom of the icon. A value of [0.0 0.0] positions the
+     * icon at the bottom-left corner of the annotation rectangle; a value of [0.5 0.5] centers it
+     * within the rectangle. This entry is used only if the icon is scaled proportionally. Default
+     * value: [0.5 0.5]."
+     *
+     * @return The fractional space to allocate.
+     */
+    public PDRange getFractionalSpaceToAllocate()
+    {
+        PDRange retval = null;
+        COSArray array = (COSArray)fit.getDictionaryObject( "A" );
+        if( array == null )
+        {
+            retval = new PDRange();
+            retval.setMin( .5f );
+            retval.setMax( .5f );
+            setFractionalSpaceToAllocate( retval );
+        }
+        else
+        {
+            retval = new PDRange( array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set frational space to allocate.
+     *
+     * @param space The space to allocate.
+     */
+    public void setFractionalSpaceToAllocate( PDRange space )
+    {
+        fit.setItem( "A", space );
+    }
+
+    /**
+     * This will tell if the icon should scale to fit the annotation bounds.  Default: false
+     *
+     * @return A flag telling if the icon should scale.
+     */
+    public boolean shouldScaleToFitAnnotation()
+    {
+        return fit.getBoolean( "FB", false );
+    }
+
+    /**
+     * This will tell the icon to scale.
+     *
+     * @param value The flag value.
+     */
+    public void setScaleToFitAnnotation( boolean value )
+    {
+        fit.setBoolean( "FB", value );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFJavaScript.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFJavaScript.java
new file mode 100644 (file)
index 0000000..b882aee
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+import org.apache.pdfbox.pdmodel.common.PDNamedTextStream;
+
+/**
+ * This represents an FDF JavaScript dictionary that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class FDFJavaScript implements COSObjectable
+{
+    private COSDictionary js;
+
+    /**
+     * Default constructor.
+     */
+    public FDFJavaScript()
+    {
+        js = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param javaScript The FDF java script.
+     */
+    public FDFJavaScript( COSDictionary javaScript )
+    {
+        js = javaScript;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return js;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return js;
+    }
+
+    /**
+     * This will get the javascript that is executed before the import.
+     *
+     * @return Some javascript code.
+     */
+    public PDTextStream getBefore()
+    {
+        return PDTextStream.createTextStream( js.getDictionaryObject( "Before" ) );
+    }
+
+    /**
+     * This will set the javascript code the will get execute before the import.
+     *
+     * @param before A reference to some javascript code.
+     */
+    public void setBefore( PDTextStream before )
+    {
+        js.setItem( "Before", before );
+    }
+
+    /**
+     * This will get the javascript that is executed after the import.
+     *
+     * @return Some javascript code.
+     */
+    public PDTextStream getAfter()
+    {
+        return PDTextStream.createTextStream( js.getDictionaryObject( "After" ) );
+    }
+
+    /**
+     * This will set the javascript code the will get execute after the import.
+     *
+     * @param after A reference to some javascript code.
+     */
+    public void setAfter( PDTextStream after )
+    {
+        js.setItem( "After", after );
+    }
+
+    /**
+     * This will return a list of PDNamedTextStream objects.  This is the "Doc"
+     * entry of the pdf document.  These will be added to the PDF documents
+     * javascript name tree.  This will not return null.
+     *
+     * @return A list of all named javascript entries.
+     */
+    public List getNamedJavaScripts()
+    {
+        COSArray array = (COSArray)js.getDictionaryObject( "Doc" );
+        List namedStreams = new ArrayList();
+        if( array == null )
+        {
+            array = new COSArray();
+            js.setItem( "Doc", array );
+        }
+        for( int i=0; i<array.size(); i++ )
+        {
+            COSName name = (COSName)array.get( i );
+            i++;
+            COSBase stream = array.get( i );
+            PDNamedTextStream namedStream = new PDNamedTextStream( name, stream );
+            namedStreams.add( namedStream );
+        }
+        return new COSArrayList( namedStreams, array );
+    }
+
+    /**
+     * This should be a list of PDNamedTextStream objects.
+     *
+     * @param namedStreams The named streams.
+     */
+    public void setNamedJavaScripts( List namedStreams )
+    {
+        COSArray array = COSArrayList.converterToCOSArray( namedStreams );
+        js.setItem( "Doc", array );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFNamedPageReference.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFNamedPageReference.java
new file mode 100644 (file)
index 0000000..a8817ef
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This represents an FDF named page reference that is part of the FDF field.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFNamedPageReference implements COSObjectable
+{
+    private COSDictionary ref;
+
+    /**
+     * Default constructor.
+     */
+    public FDFNamedPageReference()
+    {
+        ref = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param r The FDF named page reference dictionary.
+     */
+    public FDFNamedPageReference( COSDictionary r )
+    {
+        ref = r;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return ref;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return ref;
+    }
+
+    /**
+     * This will get the name of the referenced page.  A required parameter.
+     *
+     * @return The name of the referenced page.
+     */
+    public String getName()
+    {
+        return ref.getString( "Name" );
+    }
+
+    /**
+     * This will set the name of the referenced page.
+     *
+     * @param name The referenced page name.
+     */
+    public void setName( String name )
+    {
+        ref.setString( "Name", name );
+    }
+
+    /**
+     * This will get the file specification of this reference.  An optional parameter.
+     *
+     * @return The F entry for this dictionary.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public PDFileSpecification getFileSpecification() throws IOException
+    {
+        return PDFileSpecification.createFS( ref.getDictionaryObject( "F" ) );
+    }
+
+    /**
+     * This will set the file specification for this named page reference.
+     *
+     * @param fs The file specification to set.
+     */
+    public void setFileSpecification( PDFileSpecification fs )
+    {
+        ref.setItem( "F", fs );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFOptionElement.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFOptionElement.java
new file mode 100644 (file)
index 0000000..ec191cc
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an object that can be used in a Field's Opt entry to represent
+ * an available option and a default appearance string.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class FDFOptionElement implements COSObjectable
+{
+    private COSArray option;
+
+    /**
+     * Default constructor.
+     */
+    public FDFOptionElement()
+    {
+        option = new COSArray();
+        option.add( new COSString( "" ) );
+        option.add( new COSString( "" ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param o The option element.
+     */
+    public FDFOptionElement( COSArray o )
+    {
+        option = o;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return option;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSArray getCOSArray()
+    {
+        return option;
+    }
+
+    /**
+     * This will get the string of one of the available options.  A required element.
+     *
+     * @return An available option.
+     */
+    public String getOption()
+    {
+        return ((COSString)option.getObject( 0 ) ).getString();
+    }
+
+    /**
+     * This will set the string for an available option.
+     *
+     * @param opt One of the available options.
+     */
+    public void setOption( String opt )
+    {
+        option.set( 0, new COSString( opt ) );
+    }
+
+    /**
+     * This will get the string of default appearance string.  A required element.
+     *
+     * @return A default appearance string.
+     */
+    public String getDefaultAppearanceString()
+    {
+        return ((COSString)option.getObject( 1 ) ).getString();
+    }
+
+    /**
+     * This will set the default appearance string.
+     *
+     * @param da The default appearance string.
+     */
+    public void setDefaultAppearanceString( String da )
+    {
+        option.set( 1, new COSString( da ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFPage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFPage.java
new file mode 100644 (file)
index 0000000..e7027c1
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an FDF page that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFPage implements COSObjectable
+{
+    private COSDictionary page;
+
+    /**
+     * Default constructor.
+     */
+    public FDFPage()
+    {
+        page = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param p The FDF page.
+     */
+    public FDFPage( COSDictionary p )
+    {
+        page = p;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return page;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return page;
+    }
+
+    /**
+     * This will get a list of FDFTemplage objects that describe the named pages
+     * that serve as templates.
+     *
+     * @return A list of templates.
+     */
+    public List getTemplates()
+    {
+        List retval = null;
+        COSArray array = (COSArray)page.getDictionaryObject( "Templates" );
+        if( array != null )
+        {
+            List objects = new ArrayList();
+            for( int i=0; i<array.size(); i++ )
+            {
+                objects.add( new FDFTemplate( (COSDictionary)array.getObject( i ) ) );
+            }
+            retval = new COSArrayList( objects, array );
+        }
+        return retval;
+    }
+
+    /**
+     * A list of FDFTemplate objects.
+     *
+     * @param templates A list of templates for this Page.
+     */
+    public void setTemplates( List templates )
+    {
+        page.setItem( "Templates", COSArrayList.converterToCOSArray( templates ) );
+    }
+
+    /**
+     * This will get the FDF page info object.
+     *
+     * @return The Page info.
+     */
+    public FDFPageInfo getPageInfo()
+    {
+        FDFPageInfo retval = null;
+        COSDictionary dict = (COSDictionary)page.getDictionaryObject( "Info" );
+        if( dict != null )
+        {
+            retval = new FDFPageInfo( dict );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the page info.
+     *
+     * @param info The new page info dictionary.
+     */
+    public void setPageInfo( FDFPageInfo info )
+    {
+        page.setItem( "Info", info );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFPageInfo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFPageInfo.java
new file mode 100644 (file)
index 0000000..d458ede
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an FDF page info that is part of the FDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class FDFPageInfo implements COSObjectable
+{
+    private COSDictionary pageInfo;
+
+    /**
+     * Default constructor.
+     */
+    public FDFPageInfo()
+    {
+        pageInfo = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param p The FDF page.
+     */
+    public FDFPageInfo( COSDictionary p )
+    {
+        pageInfo = p;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return pageInfo;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return pageInfo;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFTemplate.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/FDFTemplate.java
new file mode 100644 (file)
index 0000000..9c794dc
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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.pdfbox.pdmodel.fdf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an FDF template that is part of the FDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFTemplate implements COSObjectable
+{
+    private COSDictionary template;
+
+    /**
+     * Default constructor.
+     */
+    public FDFTemplate()
+    {
+        template = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param t The FDF page template.
+     */
+    public FDFTemplate( COSDictionary t )
+    {
+        template = t;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return template;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return template;
+    }
+
+    /**
+     * This is the template reference.
+     *
+     * @return The template reference.
+     */
+    public FDFNamedPageReference getTemplateReference()
+    {
+        FDFNamedPageReference retval = null;
+        COSDictionary dict = (COSDictionary)template.getDictionaryObject( "TRef" );
+        if( dict != null )
+        {
+            retval = new FDFNamedPageReference( dict );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the template reference.
+     *
+     * @param tRef The template reference.
+     */
+    public void setTemplateReference( FDFNamedPageReference tRef )
+    {
+        template.setItem( "TRef", tRef );
+    }
+
+    /**
+     * This will get a list of fields that are part of this template.
+     *
+     * @return A list of fields.
+     */
+    public List getFields()
+    {
+        List retval = null;
+        COSArray array = (COSArray)template.getDictionaryObject( "Fields" );
+        if( array != null )
+        {
+            List fields = new ArrayList();
+            for( int i=0; i<array.size(); i++ )
+            {
+                fields.add( new FDFField( (COSDictionary)array.getObject( i ) ) );
+            }
+            retval = new COSArrayList( fields, array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a list of fields for this template.
+     *
+     * @param fields The list of fields to set for this template.
+     */
+    public void setFields( List fields )
+    {
+        template.setItem( "Fields", COSArrayList.converterToCOSArray( fields ) );
+    }
+
+    /**
+     * A flag telling if the fields imported from the template may be renamed if there are conflicts.
+     *
+     * @return A flag telling if the fields can be renamed.
+     */
+    public boolean shouldRename()
+    {
+        return template.getBoolean( "Rename", false );
+    }
+
+    /**
+     * This will set if the fields can be renamed.
+     *
+     * @param value The flag value.
+     */
+    public void setRename( boolean value )
+    {
+        template.setBoolean( "Rename", value );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/fdf/package.html
new file mode 100644 (file)
index 0000000..ac8a6ec
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The fdf package will handle all of the logic used for FDF objects inside of the PDF/FDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/FontManager.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/FontManager.java
new file mode 100644 (file)
index 0000000..9c2d4cd
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Properties;
+
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ *  This class is used as font manager.
+ *  @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ *  @version $Revision: 1.0 $
+ */
+
+public class FontManager 
+{
+
+    // HashMap with all known fonts
+    private static HashMap envFonts = new HashMap();
+    // the standard font
+    private static String standardFont = null;
+    private static Properties fontMapping = new Properties(); 
+    
+    static {
+        try
+        {
+            ResourceLoader.loadProperties(
+                    "org/apache/pdfbox/resources/FontMapping.properties",
+                    fontMapping );
+        }
+        catch( IOException io )
+        {
+            io.printStackTrace();
+            throw new RuntimeException( "Error loading font mapping" );
+        }
+        loadFonts();
+        loadFontMapping();
+        loadBasefontMapping();
+        setStandardFont();
+    }
+    
+    private FontManager() 
+    {
+    }
+    /**
+     * Get the standard font from the environment, usually Arial or Times New Roman. 
+     *
+     * @return The standard font 
+     * 
+     */
+    public static java.awt.Font getStandardFont() 
+    {
+        if (standardFont != null)
+        {
+            return getAwtFont(standardFont);
+        }
+        return null;
+    }
+    
+    /**
+     * Get the font for the given fontname.
+     *
+     * @param font The name of the font.
+     *
+     * @return The font we are looking for or a similar font or null if nothing is found.
+     * 
+     */
+    public static java.awt.Font getAwtFont(String font) 
+    {
+        String fontname = normalizeFontname(font);
+        if (envFonts.containsKey(fontname))
+        {
+            return (java.awt.Font)envFonts.get(fontname);
+        }
+        return null;
+    }
+
+    /**
+     * Load all available fonts from the environment.
+     */
+    private static void loadFonts() 
+    {
+        java.awt.Font[] allFonts = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
+        int numberOfFonts = allFonts.length;
+        for (int i=0;i<numberOfFonts;i++) 
+        {
+            java.awt.Font font = allFonts[i];
+            String family = normalizeFontname(font.getFamily());
+            String psname = normalizeFontname(font.getPSName());
+            if (isBoldItalic(font)) 
+            {
+                envFonts.put(family+"bolditalic", font);
+            }
+            else if (isBold(font)) 
+            {
+                envFonts.put(family+"bold", font);
+            }
+            else if (isItalic(font)) 
+            {
+                envFonts.put(family+"italic", font);
+            }
+            else 
+            { 
+                envFonts.put(family, font);
+            }
+            if (!family.equals(psname)) 
+            {
+                envFonts.put(normalizeFontname(font.getPSName()),font);
+            }
+        }
+    }
+
+    private static void setStandardFont() 
+    {
+        // One of the following fonts will be the standard-font 
+        if (envFonts.containsKey("arial"))
+        {
+            standardFont = "arial";
+        }
+        else if (envFonts.containsKey("timesnewroman"))
+        {
+            standardFont = "timesnewroman";
+        }
+    }
+    
+    /**
+     * Normalize the fontname.
+     *
+     * @param fontname The name of the font.
+     *
+     * @return The normalized name of the font.
+     * 
+     */
+    private static String normalizeFontname(String fontname) 
+    {
+        // Terminate all whitespaces, commas and hyphens
+        String normalizedFontname = fontname.toLowerCase().replaceAll(" ","").replaceAll(",","").replaceAll("-","");
+        // Terminate trailing characters up to the "+".
+        // As far as I know, these characters are used in names of embedded fonts
+        // If the embedded font can't be read, we'll try to find it here
+        if (normalizedFontname.indexOf("+") > -1)
+        {
+            normalizedFontname = normalizedFontname.substring(normalizedFontname.indexOf("+")+1);
+        }
+        // normalize all kinds of fonttypes. There are several possible version which have to be normalized
+        // e.g. Arial,Bold Arial-BoldMT Helevtica-oblique ...
+        boolean isBold = normalizedFontname.indexOf("bold") > -1;
+        boolean isItalic = normalizedFontname.indexOf("italic") > -1 || normalizedFontname.indexOf("oblique") > -1;
+        normalizedFontname = normalizedFontname.toLowerCase().replaceAll("bold" , "")
+            .replaceAll("italic" , "").replaceAll("oblique" , "");
+        if (isBold)
+        {
+            normalizedFontname += "bold";
+        }
+        if (isItalic)
+        {
+            normalizedFontname += "italic";
+        }
+        return normalizedFontname;
+    }
+    
+    
+    /**
+     * Add a font-mapping.
+     *
+     * @param font The name of the font.
+     *
+     * @param mappedName The name of the mapped font.
+     * 
+     */
+    private static boolean addFontMapping(String font, String mappedName) 
+    {
+        String fontname = normalizeFontname(font);
+        // is there already a font mapping ?
+        if (envFonts.containsKey(fontname))
+        {
+            return false;
+        }
+        String mappedFontname = normalizeFontname(mappedName);
+        // is the mapped font available ?
+        if (!envFonts.containsKey(mappedFontname))
+        {
+            return false;
+        }
+        envFonts.put(fontname, envFonts.get(mappedFontname));
+        return true;
+    }
+    
+    /**
+     * Load the mapping for the well knwon font-substitutions.
+     *
+     */
+    private static void loadFontMapping() 
+    {
+        boolean addedMapping = true;
+        // There could be some recursive mappings in the fontmapping, so that we have to 
+        // read the list until no more additional mapping is added to it 
+        while (addedMapping) 
+        {
+            int counter = 0;
+            Enumeration keys = fontMapping.keys();
+            while (keys.hasMoreElements()) 
+            {
+                String key = (String)keys.nextElement();
+                if (addFontMapping(key,(String)fontMapping.get(key)))
+                {
+                    counter++;
+                }
+            }
+            if (counter == 0)
+            {
+                addedMapping = false;
+            }
+        }
+    }
+
+    /**
+     * Mapping for the basefonts.
+     */
+    private static void loadBasefontMapping() 
+    {
+        addFontMapping("Times-Roman","TimesNewRoman");
+        addFontMapping("Times-Bold","TimesNewRoman,Bold");
+        addFontMapping("Times-Italic","TimesNewRoman,Italic");
+        addFontMapping("Times-BoldItalic","TimesNewRoman,Bold,Italic");
+        addFontMapping("Helvetica-Oblique","Helvetica,Italic");
+        addFontMapping("Helvetica-BoldOblique","Helvetica,Bold,Italic");
+        addFontMapping("Courier-Oblique","Courier,Italic");
+        addFontMapping("Courier-BoldOblique","Courier,Bold,Italic");
+    }
+
+    /**
+     * Try to determine if the font has both a BOLD and an ITALIC-type.
+     *
+     * @param name The font.
+     *
+     * @return font has BOLD and ITALIC-type or not
+     */
+    private static boolean isBoldItalic(java.awt.Font font) 
+    {
+        return isBold(font) && isItalic(font);
+    }
+    
+    /**
+     * Try to determine if the font has a BOLD-type.
+     *
+     * @param name The font.
+     *
+     * @return font has BOLD-type or not
+     */
+    private static boolean isBold(java.awt.Font font) 
+    {
+        String name = font.getName().toLowerCase();
+        if (name.indexOf("bold") > -1)
+        {
+            return true;
+        }
+        String psname = font.getPSName().toLowerCase();
+        if (psname.indexOf("bold") > -1)
+        {
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Try to determine if the font has an ITALIC-type.
+     *
+     * @param name The font.
+     *
+     * @return font has ITALIC-type or not
+     */
+    private static boolean isItalic(java.awt.Font font) 
+    {
+        String name = font.getName().toLowerCase();
+        // oblique is the same as italic
+        if (name.indexOf("italic") > -1 || name.indexOf("oblique") > -1)
+        {
+            return true;
+        }
+        String psname = font.getPSName().toLowerCase();
+        if (psname.indexOf("italic") > -1 || psname.indexOf("oblique") > -1)
+        {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFont.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFont.java
new file mode 100644 (file)
index 0000000..47fdf2b
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.encoding.conversion.CMapSubstitution;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ * This is implementation for the CIDFontType0/CIDFontType2 Fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public abstract class PDCIDFont extends PDSimpleFont
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDCIDFont.class);
+
+    private Map<Integer,Float> widthCache = null;
+    private long defaultWidth = 0;
+    
+    /**
+     * Constructor.
+     */
+    public PDCIDFont()
+    {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDCIDFont( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+        extractWidths();
+    }
+
+    /**
+     * This will get the fonts bouding box.
+     *
+     * @return The fonts bouding box.
+     *
+     * @throws IOException If there is an error getting the font bounding box.
+     */
+    public PDRectangle getFontBoundingBox() throws IOException
+    {
+        throw new RuntimeException( "getFontBoundingBox(): Not yet implemented" );
+    }
+
+    /**
+     * This will get the default width.  The default value for the default width is 1000.
+     *
+     * @return The default width for the glyphs in this font.
+     */
+    public long getDefaultWidth()
+    {
+        if (defaultWidth == 0) 
+        {
+            COSNumber number = (COSNumber)font.getDictionaryObject( COSName.DW);
+            if( number != null )
+            {
+                defaultWidth = number.intValue();
+            }
+            else
+            {
+                defaultWidth = 1000;
+            }
+        }
+        return defaultWidth;
+    }
+
+    /**
+     * This will set the default width for the glyphs of this font.
+     *
+     * @param dw The default width.
+     */
+    public void setDefaultWidth( long dw )
+    {
+        defaultWidth = dw;
+        font.setLong( COSName.DW, dw );
+    }
+
+    /**
+     * This will get the font width for a character.
+     *
+     * @param c The character code to get the width for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getFontWidth( byte[] c, int offset, int length ) throws IOException
+    {
+
+        float retval = getDefaultWidth();
+        int code = getCodeFromArray( c, offset, length );
+
+        Float widthFloat = widthCache.get( code );
+        if( widthFloat != null )
+        {
+            retval = widthFloat.floatValue();
+        }
+        return retval;
+    }
+
+    private void extractWidths() 
+    {
+        if (widthCache == null) 
+        {
+            widthCache = new HashMap<Integer,Float>();
+            COSArray widths = (COSArray)font.getDictionaryObject( COSName.W );
+            if( widths != null )
+            {
+                int size = widths.size();
+                int counter = 0;
+                while (counter < size) 
+                {
+                    COSNumber firstCode = (COSNumber)widths.getObject( counter++ );
+                    COSBase next = widths.getObject( counter++ );
+                    if( next instanceof COSArray )
+                    {
+                        COSArray array = (COSArray)next;
+                        int startRange = firstCode.intValue();
+                        int arraySize = array.size();
+                        for (int i=0; i<arraySize; i++) 
+                        {
+                            COSNumber width = (COSNumber)array.get(i);
+                            widthCache.put(startRange+i, width.floatValue());
+                        }
+                    }
+                    else
+                    {
+                        COSNumber secondCode = (COSNumber)next;
+                        COSNumber rangeWidth = (COSNumber)widths.getObject( counter++ );
+                        int startRange = firstCode.intValue();
+                        int endRange = secondCode.intValue();
+                        float width = rangeWidth.floatValue();
+                        for (int i=startRange; i<=endRange; i++) {
+                            widthCache.put(i,width);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    /**
+     * This will get the font height for a character.
+     *
+     * @param c The character code to get the height for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getFontHeight( byte[] c, int offset, int length ) throws IOException
+    {
+        float retval = 0;
+        PDFontDescriptor desc = getFontDescriptor();
+        float xHeight = desc.getXHeight();
+        float capHeight = desc.getCapHeight();
+        if( xHeight != 0f && capHeight != 0 )
+        {
+            //do an average of these two.  Can we do better???
+            retval = (xHeight + capHeight)/2f;
+        }
+        else if( xHeight != 0 )
+        {
+            retval = xHeight;
+        }
+        else if( capHeight != 0 )
+        {
+            retval = capHeight;
+        }
+        else
+        {
+            retval = 0;
+        }
+        if( retval == 0 )
+        {
+            retval = desc.getAscent();
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the average font width for all characters.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getAverageFontWidth() throws IOException
+    {
+        float totalWidths = 0.0f;
+        float characterCount = 0.0f;
+        float defaultWidth = getDefaultWidth();
+        COSArray widths = (COSArray)font.getDictionaryObject( COSName.W );
+
+        if( widths != null )
+        {
+            for( int i=0; i<widths.size(); i++ )
+            {
+                COSNumber firstCode = (COSNumber)widths.getObject( i++ );
+                COSBase next = widths.getObject( i );
+                if( next instanceof COSArray )
+                {
+                    COSArray array = (COSArray)next;
+                    for( int j=0; j<array.size(); j++ )
+                    {
+                        COSNumber width = (COSNumber)array.get( j );
+                        totalWidths+=width.floatValue();
+                        characterCount += 1;
+                    }
+                }
+                else
+                {
+                    i++;
+                    COSNumber rangeWidth = (COSNumber)widths.getObject( i );
+                    if( rangeWidth.floatValue() > 0 )
+                    {
+                        totalWidths += rangeWidth.floatValue();
+                        characterCount += 1;
+                    }
+                }
+            }
+        }
+        float average = totalWidths / characterCount;
+        if( average <= 0 )
+        {
+            average = defaultWidth;
+        }
+        return average;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public float getFontWidth( int charCode ) 
+    {
+        float width = -1;
+        if (widthCache.containsKey(charCode)) 
+        {
+            width = widthCache.get(charCode);
+        }
+        return width;
+    }
+
+    /**
+     * Extract the CIDSystemInfo.
+     * @return the CIDSystemInfo as String
+     */
+    private String getCIDSystemInfo()
+    {
+        String cidSystemInfo = null; 
+        COSDictionary cidsysteminfo = (COSDictionary)font.getDictionaryObject(COSName.CIDSYSTEMINFO);
+        if (cidsysteminfo != null) 
+        {
+            String ordering = cidsysteminfo.getString(COSName.ORDERING);
+            String registry = cidsysteminfo.getString(COSName.REGISTRY);
+            int supplement = cidsysteminfo.getInt(COSName.SUPPLEMENT);
+            cidSystemInfo = registry + "-" + ordering+ "-" + supplement;
+        }
+        return cidSystemInfo;
+    }
+    
+    @Override
+    protected void determineEncoding()
+    {
+        String cidSystemInfo = getCIDSystemInfo();
+        if (cidSystemInfo != null) 
+        {
+            if (cidSystemInfo.contains("Identity"))
+            {
+                cidSystemInfo = "Identity-H";
+            }
+            else if (cidSystemInfo.startsWith("Adobe-UCS-"))
+            {
+                cidSystemInfo = "Adobe-Identity-UCS";
+            }
+            else
+            {
+                cidSystemInfo = cidSystemInfo.substring(0,cidSystemInfo.lastIndexOf("-"))+"-UCS2";
+            }
+            cmap = cmapObjects.get( cidSystemInfo );
+            if (cmap == null)
+            {
+                String resourceName = resourceRootCMAP + cidSystemInfo;
+                try {
+                    parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ) );
+                    if( cmap == null)
+                    {
+                        log.error("Error: Could not parse predefined CMAP file for '" + cidSystemInfo + "'" );
+                    }
+                }
+                catch(IOException exception) 
+                {
+                    log.error("Error: Could not find predefined CMAP file for '" + cidSystemInfo + "'" );
+                }
+            }
+        }
+        else
+        {
+            super.determineEncoding();
+        }
+    }
+    
+    @Override
+    public String encode(byte[] c, int offset, int length) throws IOException
+    {
+        String result = null;
+        if (cmap != null)
+        {
+            result = cmapEncoding(getCodeFromArray( c, offset, length ), length, true);
+        }
+        else
+        {
+            result = super.encode(c, offset, length);
+        }
+        return result;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFontType0Font.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFontType0Font.java
new file mode 100644 (file)
index 0000000..6707617
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.awt.Font;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is implementation of the CIDFontType0 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDCIDFontType0Font extends PDCIDFont
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDCIDFontType0Font.class);
+
+    /**
+     * Constructor.
+     */
+    public PDCIDFontType0Font()
+    {
+        super();
+        font.setItem( COSName.SUBTYPE, COSName.CID_FONT_TYPE0 );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDCIDFontType0Font( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Font getawtFont() throws IOException
+    {
+        Font awtFont = null;
+        PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary)getFontDescriptor();
+        if( fd.getFontFile3() != null )
+        {
+            // create a font with the embedded data
+            PDType1CFont type1CFont = new PDType1CFont( super.font );
+            awtFont = type1CFont.getawtFont();
+            if (awtFont == null)
+            {
+                awtFont = FontManager.getAwtFont(fd.getFontName());
+                if (awtFont != null)
+                {
+                    log.info("Using font "+awtFont.getName()+ " instead");
+                }
+            }
+        }
+        return awtFont;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Font.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Font.java
new file mode 100644 (file)
index 0000000..595433e
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This is implementation of the CIDFontType2 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDCIDFontType2Font extends PDCIDFont
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDCIDFontType2Font.class);
+
+    /**
+     * Constructor.
+     */
+    public PDCIDFontType2Font()
+    {
+        super();
+        font.setItem( COSName.SUBTYPE, COSName.CID_FONT_TYPE2 );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDCIDFontType2Font( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Font getawtFont() throws IOException
+    {
+        Font awtFont = null;
+        PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary)getFontDescriptor();
+        PDStream ff2Stream = fd.getFontFile2();
+        if( ff2Stream != null )
+        {
+            try
+            {
+                // create a font with the embedded data
+                awtFont = Font.createFont( Font.TRUETYPE_FONT, ff2Stream.createInputStream() );
+            }
+            catch( FontFormatException f )
+            {
+                log.info("Can't read the embedded font " + fd.getFontName() );
+            }
+            if (awtFont == null)
+            {
+                awtFont = FontManager.getAwtFont(fd.getFontName());
+                if (awtFont != null)
+                {
+                    log.info("Using font "+awtFont.getName()+ " instead");
+                }
+            }
+        }
+        // TODO FontFile3
+        return awtFont;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFont.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFont.java
new file mode 100644 (file)
index 0000000..fae3a93
--- /dev/null
@@ -0,0 +1,828 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import org.apache.fontbox.afm.AFMParser;
+import org.apache.fontbox.afm.FontMetric;
+import org.apache.fontbox.cmap.CMapParser;
+import org.apache.fontbox.cmap.CMap;
+
+import org.apache.pdfbox.encoding.Encoding;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import org.apache.pdfbox.util.ResourceLoader;
+
+import java.awt.Graphics;
+import java.awt.geom.AffineTransform;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is the base class for all PDF fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.46 $
+ */
+public abstract class PDFont implements COSObjectable
+{
+
+    /**
+     * The cos dictionary for this font.
+     */
+    protected COSDictionary font;
+
+    /**
+     * This is only used if this is a font object and it has an encoding.
+     */
+    private Encoding fontEncoding = null;
+
+    /**
+     *  The descriptor of the font
+     */
+    private PDFontDescriptor fontDescriptor = null;
+
+    /**
+     *  The font matrix
+     */
+    protected PDMatrix fontMatrix = null;
+    
+    /**
+     * This is only used if this is a font object and it has an encoding and it is
+     * a type0 font with a cmap.
+     */
+    protected CMap cmap = null;
+    
+    private boolean hasToUnicode = false;
+
+    protected static Map<String, CMap> cmapObjects =
+        Collections.synchronizedMap( new HashMap<String, CMap>() );
+    
+    /**
+     *  A list a floats representing the widths
+     */
+    private List<Float> widths = null;
+
+    /**
+     * The static map of the default Adobe font metrics.
+     */
+    private static final Map<String, FontMetric> afmObjects =
+        Collections.unmodifiableMap( getAdobeFontMetrics() );
+
+    // TODO move the Map to PDType1Font as these are the 14 Standard fonts
+    // which are definitely Type 1 fonts
+    private static Map<String, FontMetric> getAdobeFontMetrics() {
+        Map<String, FontMetric> metrics = new HashMap<String, FontMetric>();
+        addAdobeFontMetric( metrics, "Courier-Bold" );
+        addAdobeFontMetric( metrics, "Courier-BoldOblique" );
+        addAdobeFontMetric( metrics, "Courier" );
+        addAdobeFontMetric( metrics, "Courier-Oblique" );
+        addAdobeFontMetric( metrics, "Helvetica" );
+        addAdobeFontMetric( metrics, "Helvetica-Bold" );
+        addAdobeFontMetric( metrics, "Helvetica-BoldOblique" );
+        addAdobeFontMetric( metrics, "Helvetica-Oblique" );
+        addAdobeFontMetric( metrics, "Symbol" );
+        addAdobeFontMetric( metrics, "Times-Bold" );
+        addAdobeFontMetric( metrics, "Times-BoldItalic" );
+        addAdobeFontMetric( metrics, "Times-Italic" );
+        addAdobeFontMetric( metrics, "Times-Roman" );
+        addAdobeFontMetric( metrics, "ZapfDingbats" );
+        return metrics;
+    }
+
+    protected final static String resourceRootCMAP = "org/apache/pdfbox/resources/cmap/";
+    private final static String resourceRootAFM = "org/apache/pdfbox/resources/afm/";
+
+    private static void addAdobeFontMetric(
+            Map<String, FontMetric> metrics, String name ) {
+        try {
+            String resource = resourceRootAFM + name + ".afm";
+            InputStream afmStream = ResourceLoader.loadResource( resource );
+            if( afmStream != null )
+            {
+                try {
+                    AFMParser parser = new AFMParser( afmStream );
+                    parser.parse();
+                    metrics.put( name, parser.getResult() );
+                } finally {
+                    afmStream.close();
+                }
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+    }
+
+    /**
+     * This will clear AFM resources that are stored statically.
+     * This is usually not a problem unless you want to reclaim
+     * resources for a long running process.
+     *
+     * SPECIAL NOTE: The font calculations are currently in COSObject, which
+     * is where they will reside until PDFont is mature enough to take them over.
+     * PDFont is the appropriate place for them and not in COSObject but we need font
+     * calculations for text extraction.  THIS METHOD WILL BE MOVED OR REMOVED
+     * TO ANOTHER LOCATION IN A FUTURE VERSION OF PDFBOX.
+     */
+    public static void clearResources()
+    {
+        cmapObjects.clear();
+    }
+
+    /**
+     * Constructor.
+     */
+    public PDFont()
+    {
+        font = new COSDictionary();
+        font.setItem( COSName.TYPE, COSName.FONT );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDFont( COSDictionary fontDictionary )
+    {
+        font = fontDictionary;
+        determineEncoding();
+    }
+
+    /**
+     * This will get the font descriptor for this font.
+     *
+     * @return The font descriptor for this font.
+     *
+     */
+    public PDFontDescriptor getFontDescriptor()
+    {
+        if(fontDescriptor == null)
+        {
+            COSDictionary fd = (COSDictionary)font.getDictionaryObject( COSName.FONT_DESC );
+            if (fd != null) 
+            {
+                fontDescriptor = new PDFontDescriptorDictionary( fd );
+            }
+            else
+            {
+                FontMetric afm = getAFM();
+                if( afm != null )
+                {
+                    fontDescriptor = new PDFontDescriptorAFM( afm );
+                }
+            }
+        }
+        return fontDescriptor;
+    }
+    
+    /**
+     * This will set the font descriptor.
+     *
+     * @param fontDescriptor The font descriptor.
+     */
+    public void setFontDescriptor( PDFontDescriptorDictionary fontDescriptor )
+    {
+        COSDictionary dic = null;
+        if( fontDescriptor != null )
+        {
+            dic = fontDescriptor.getCOSDictionary();
+        }
+        font.setItem( COSName.FONT_DESC, dic );
+        this.fontDescriptor = fontDescriptor;
+    }
+    
+    /**
+     * Determines the encoding for the font.
+     * This method as to be overwritten, as there are different 
+     * possibilities to define a mapping.
+     */
+       protected abstract void determineEncoding();
+    
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return font;
+    }
+
+    /**
+     * This will get the font width for a character.
+     *
+     * @param c The character code to get the width for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public abstract float getFontWidth( byte[] c, int offset, int length ) throws IOException;
+
+    /**
+     * This will get the font width for a character.
+     *
+     * @param c The character code to get the width for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public abstract float getFontHeight( byte[] c, int offset, int length ) throws IOException;
+
+    /**
+     * This will get the width of this string for this font.
+     *
+     * @param string The string to get the width of.
+     *
+     * @return The width of the string in 1000 units of text space, ie 333 567...
+     *
+     * @throws IOException If there is an error getting the width information.
+     */
+    public float getStringWidth( String string ) throws IOException
+    {
+        byte[] data = string.getBytes("ISO-8859-1");
+        float totalWidth = 0;
+        for( int i=0; i<data.length; i++ )
+        {
+            totalWidth+=getFontWidth( data, i, 1 );
+        }
+        return totalWidth;
+    }
+
+    /**
+     * This will get the average font width for all characters.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public abstract float getAverageFontWidth() throws IOException;
+
+    /**
+     * This will draw a string on a canvas using the font.
+     *
+     * @param string The string to draw.
+     * @param g The graphics to draw onto.
+     * @param fontSize The size of the font to draw.
+     * @param at The transformation matrix with all infos for scaling and shearing of the font.
+     * @param x The x coordinate to draw at.
+     * @param y The y coordinate to draw at.
+     *
+     * @throws IOException If there is an error drawing the specific string.
+     */
+    public abstract void drawString( String string, Graphics g, float fontSize,
+        AffineTransform at, float x, float y ) throws IOException;
+
+    /**
+     * Used for multibyte encodings.
+     *
+     * @param data The array of data.
+     * @param offset The offset into the array.
+     * @param length The number of bytes to use.
+     *
+     * @return The int value of data from the array.
+     */
+    protected int getCodeFromArray( byte[] data, int offset, int length )
+    {
+        int code = 0;
+        for( int i=0; i<length; i++ )
+        {
+            code <<= 8;
+            code |= (data[offset+i]+256)%256;
+        }
+        return code;
+    }
+
+    /**
+     * This will attempt to get the font width from an AFM file.
+     *
+     * @param code The character code we are trying to get.
+     *
+     * @return The font width from the AFM file.
+     *
+     * @throws IOException if we cannot find the width.
+     */
+    protected float getFontWidthFromAFMFile( int code ) throws IOException
+    {
+        float retval = 0;
+        FontMetric metric = getAFM();
+        if( metric != null )
+        {
+            Encoding encoding = getFontEncoding();
+            String characterName = encoding.getName( code );
+            retval = metric.getCharacterWidth( characterName );
+        }
+        return retval;
+    }
+
+    /**
+     * This will attempt to get the average font width from an AFM file.
+     *
+     * @return The average font width from the AFM file.
+     *
+     * @throws IOException if we cannot find the width.
+     */
+    protected float getAverageFontWidthFromAFMFile() throws IOException
+    {
+        float retval = 0;
+        FontMetric metric = getAFM();
+        if( metric != null )
+        {
+            retval = metric.getAverageCharacterWidth();
+        }
+        return retval;
+    }
+
+    /**
+     * This will get an AFM object if one exists.
+     *
+     * @return The afm object from the name.
+     *
+     */
+    protected FontMetric getAFM()
+    {
+        if(isType1Font() && afm==null){
+            COSBase baseFont = font.getDictionaryObject( COSName.BASE_FONT );
+            String name = null;
+            if( baseFont instanceof COSName )
+            {
+                name = ((COSName)baseFont).getName();
+                if (name.indexOf("+") > -1)
+                {
+                    name = name.substring(name.indexOf("+")+1);
+                }
+
+            }
+            else if( baseFont instanceof COSString )
+            {
+                COSString string = (COSString)baseFont;
+                name = string.getString();
+            }
+            if( name != null )
+            {
+                afm = afmObjects.get( name );
+            }
+        }
+        return afm;
+    }
+
+    private FontMetric afm = null;
+    
+    private COSBase encoding = null;
+    /**
+     * cache the {@link COSName#ENCODING} object from
+     * the font's dictionary since it is called so often.
+     * <p>
+     * Use this method instead of
+     * <pre>
+     *   font.getDictionaryObject(COSName.ENCODING);
+     * </pre>
+     * @return
+     */
+    protected COSBase getEncoding(){
+       if(encoding==null)
+       {
+               encoding = font.getDictionaryObject( COSName.ENCODING );
+       }
+       return encoding;
+    }
+
+    /**
+     * Set the encoding object from the fonts dictionary.
+     * @param encoding the given encoding.
+     */
+    protected void setEncoding(COSBase encoding){
+        font.setItem( COSName.ENCODING, encoding );
+        this.encoding = encoding;
+    }
+    
+    /**
+     * Encode the given value using the CMap of the font.
+     * 
+     * @param code the code to encode.
+     * @param length the byte length of the given code.
+     * @param isCIDFont indicates that the used font is a CID font.
+     * 
+     * @return The value of the encoded character.
+     */
+    protected String cmapEncoding( int code, int length, boolean isCIDFont ) throws IOException
+    {
+        String retval = null;
+        if (cmap != null)
+        {
+            retval = cmap.lookup(code, length);
+            if (retval == null && isCIDFont) 
+            {
+                retval = cmap.lookupCID(code);
+            }
+        }
+        return retval;
+    }
+    /**
+     * This will perform the encoding of a character if needed.
+     *
+     * @param c The character to encode.
+     * @param offset The offset into the array to get the data
+     * @param length The number of bytes to read.
+     *
+     * @return The value of the encoded character.
+     *
+     * @throws IOException If there is an error during the encoding.
+     */
+    public String encode( byte[] c, int offset, int length ) throws IOException
+    {
+        String retval = null;
+        int code = getCodeFromArray( c, offset, length );
+        if( cmap != null )
+        {
+            retval = cmapEncoding(code, length, false);
+        }
+        
+        // there is no cmap but probably an encoding with a suitable mapping
+        if( retval == null )
+        {
+            Encoding encoding = getFontEncoding();
+            if( encoding != null )
+            {
+                retval = encoding.getCharacter( code );
+            }
+            if( retval == null && (cmap == null || length == 2))
+            {
+                retval = getStringFromArray( c, offset, length );
+            }
+        }
+        return retval;
+    }
+
+    private static final String[] SINGLE_CHAR_STRING = new String[256];
+    private static final String[][] DOUBLE_CHAR_STRING = new String[256][256];
+    static
+    {
+        for( int i=0; i<256; i++ )
+        {
+            try
+            {
+                SINGLE_CHAR_STRING[i] = new String( new byte[] {(byte)i}, "ISO-8859-1" );
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                // Nothing should happen here
+                e.printStackTrace();
+            }
+            for( int j=0; j<256; j++ )
+            {
+                try
+                {
+                    DOUBLE_CHAR_STRING[i][j] = new String( new byte[] {(byte)i, (byte)j}, "UTF-16BE" );
+                }
+                catch (UnsupportedEncodingException e)
+                {
+                    // Nothing should happen here
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    private static String getStringFromArray( byte[] c, int offset, int length ) throws IOException
+    {
+        String retval = null;
+        if( length == 1 )
+        {
+            retval = SINGLE_CHAR_STRING[(c[offset]+256)%256];
+        }
+        else if( length == 2 )
+        {
+            retval = DOUBLE_CHAR_STRING[(c[offset]+256)%256][(c[offset+1]+256)%256];
+        }
+        else
+        {
+            throw new IOException( "Error:Unknown character length:" + length );
+        }
+        return retval;
+    }
+
+    protected void parseCmap( String cmapRoot, InputStream cmapStream)
+    {
+        if( cmapStream != null )
+        {
+            CMapParser parser = new CMapParser();
+            try 
+            {
+                cmap = parser.parse( cmapRoot, cmapStream );
+                // limit the cache to external CMaps
+                if (cmapRoot != null)
+                {
+                    cmapObjects.put( cmap.getName(), cmap );
+                }
+            }
+            catch (IOException exception) {}
+        }
+    }
+
+    /**
+     * The will set the encoding for this font.
+     *
+     * @param enc The font encoding.
+     */
+    public void setFontEncoding( Encoding enc )
+    {
+        fontEncoding = enc;
+    }
+
+    /**
+     * This will get or create the encoder.
+     *
+     * @return The encoding to use.
+     */
+    public Encoding getFontEncoding()
+    {
+        return fontEncoding;
+    }
+
+    /**
+     * This will always return "Font" for fonts.
+     *
+     * @return The type of object that this is.
+     */
+    public String getType()
+    {
+        return font.getNameAsString( COSName.TYPE );
+    }
+
+    // Memorized values to avoid repeated dictionary lookups
+    private String subtype = null;
+    private boolean type1Font;
+    private boolean trueTypeFont;
+    private boolean typeFont;
+
+    /**
+     * This will get the subtype of font, Type1, Type3, ...
+     *
+     * @return The type of font that this is.
+     */
+    public String getSubType()
+    {
+        if (subtype == null) {
+            subtype = font.getNameAsString( COSName.SUBTYPE );
+            type1Font = "Type1".equals(subtype);
+            trueTypeFont = "TrueType".equals(subtype);
+            typeFont = type1Font || "Type0".equals(subtype) || trueTypeFont;
+        }
+        return subtype;
+    }
+
+    /**
+     * Determines if the font is a type 1 font.
+     * @return returns true if the font is a type 1 font
+     */
+    protected boolean isType1Font() {
+        getSubType();
+        return type1Font;
+    }
+
+    private boolean isTrueTypeFont() {
+        getSubType();
+        return trueTypeFont;
+    }
+
+    private boolean isTypeFont() {
+        getSubType();
+        return typeFont;
+    }
+
+    /**
+     * The PostScript name of the font.
+     *
+     * @return The postscript name of the font.
+     */
+    public String getBaseFont()
+    {
+        return font.getNameAsString( COSName.BASE_FONT );
+    }
+
+    /**
+     * Set the PostScript name of the font.
+     *
+     * @param baseFont The postscript name for the font.
+     */
+    public void setBaseFont( String baseFont )
+    {
+        font.setName( COSName.BASE_FONT, baseFont );
+    }
+
+    /**
+     * The code for the first char or -1 if there is none.
+     *
+     * @return The code for the first character.
+     */
+    public int getFirstChar()
+    {
+        return font.getInt( COSName.FIRST_CHAR, -1 );
+    }
+
+    /**
+     * Set the first character this font supports.
+     *
+     * @param firstChar The first character.
+     */
+    public void setFirstChar( int firstChar )
+    {
+        font.setInt( COSName.FIRST_CHAR, firstChar );
+    }
+
+    /**
+     * The code for the last char or -1 if there is none.
+     *
+     * @return The code for the last character.
+     */
+    public int getLastChar()
+    {
+        return font.getInt( COSName.LAST_CHAR, -1 );
+    }
+
+    /**
+     * Set the last character this font supports.
+     *
+     * @param lastChar The last character.
+     */
+    public void setLastChar( int lastChar )
+    {
+        font.setInt( COSName.LAST_CHAR, lastChar );
+    }
+
+    /**
+     * The widths of the characters.  This will be null for the standard 14 fonts.
+     *
+     * @return The widths of the characters.
+     */
+    public List<Float> getWidths()
+    {
+        if (widths == null)
+        {
+            COSArray array = (COSArray)font.getDictionaryObject( COSName.WIDTHS );
+            if (array != null)
+            {
+                widths = COSArrayList.convertFloatCOSArrayToList( array );
+            }
+        }
+        return widths;
+    }
+
+    /**
+     * Set the widths of the characters code.
+     *
+     * @param widths The widths of the character codes.
+     */
+    public void setWidths( List<Float> widths )
+    {
+        this.widths = widths;
+        font.setItem( COSName.WIDTHS, COSArrayList.converterToCOSArray( this.widths ) );
+    }
+
+    /**
+     * This will get the matrix that is used to transform glyph space to
+     * text space.  By default there are 1000 glyph units to 1 text space
+     * unit, but type3 fonts can use any value.
+     *
+     * Note:If this is a type3 font then it can be modified via the PDType3Font.setFontMatrix, otherwise this
+     * is a read-only property.
+     *
+     * @return The matrix to transform from glyph space to text space.
+     */
+    public PDMatrix getFontMatrix()
+    {
+        if (fontMatrix == null)
+        {
+            COSArray array = (COSArray)font.getDictionaryObject( COSName.FONT_MATRIX );
+            if( array == null )
+            {
+                array = new COSArray();
+                array.add( new COSFloat( 0.001f ) );
+                array.add( COSInteger.ZERO );
+                array.add( COSInteger.ZERO );
+                array.add( new COSFloat( 0.001f ) );
+                array.add( COSInteger.ZERO );
+                array.add( COSInteger.ZERO );
+            }
+            fontMatrix = new PDMatrix(array);
+        }
+        return fontMatrix;
+    }
+
+    /**
+     * This will get the fonts bounding box.
+     *
+     * @return The fonts bounding box.
+     *
+     * @throws IOException If there is an error getting the bounding box.
+     */
+    public abstract PDRectangle getFontBoundingBox() throws IOException;
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals( Object other )
+    {
+        return other instanceof PDFont && ((PDFont)other).getCOSObject() == this.getCOSObject();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return this.getCOSObject().hashCode();
+    }
+
+    /**
+     * Determines the width of the given character.
+     * @param charCode the code of the given character 
+     * @return the width of the character
+     */
+    public float getFontWidth( int charCode )
+    {
+        float width = -1;
+        int firstChar = getFirstChar();
+        int lastChar = getLastChar();
+        if (charCode >= firstChar && charCode <= lastChar)
+        {
+            List<Float> widths = getWidths();
+            // maybe the font doesn't provide any widths
+            if (widths != null)
+            {
+                width = widths.get(charCode-firstChar).floatValue();
+            }
+        }
+        else 
+        {
+            PDFontDescriptor fd = getFontDescriptor();
+            if (fd instanceof PDFontDescriptorDictionary)
+            {
+                width = fd.getMissingWidth();
+            }
+        }
+        return width;
+    }
+
+    /**
+     * Determines if a font as a ToUnicode entry.
+     * @return true if the font has a ToUnicode entry
+     */
+    protected boolean hasToUnicode() 
+    {
+        return hasToUnicode;
+    }
+    
+    /**
+     * Sets hasToUnicode to the given value.
+     * @param hasToUnicode the given value for hasToUnicode
+     */
+    protected void setHasToUnicode(boolean hasToUnicode)
+    {
+        this.hasToUnicode = hasToUnicode;
+    }
+    
+    public COSString createString(String text) throws IOException
+    {
+        return new COSString(text);
+        
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptor.java
new file mode 100644 (file)
index 0000000..399832e
--- /dev/null
@@ -0,0 +1,531 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This class represents an interface to the font description.  This will depend
+ * on the font type for the actual implementation.  If it is a AFM/cmap/or embedded font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public abstract class PDFontDescriptor
+{
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_FIXED_PITCH = 1;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_SERIF = 2;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_SYMBOLIC = 4;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_SCRIPT = 8;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_NON_SYMBOLIC = 32;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_ITALIC = 64;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_ALL_CAP = 65536;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_SMALL_CAP = 131072;
+    /**
+     * A font descriptor flag.  See PDF Reference for description.
+     */
+    private static final int FLAG_FORCE_BOLD = 262144;
+
+
+    /**
+     * Get the font name.
+     *
+     * @return The name of the font.
+     */
+    public abstract String getFontName();
+
+    /**
+     * This will set the font name.
+     *
+     * @param fontName The new name for the font.
+     */
+    public abstract void setFontName( String fontName );
+
+    /**
+     * A string representing the preferred font family.
+     *
+     * @return The font family.
+     */
+    public abstract String getFontFamily();
+
+    /**
+     * This will set the font family.
+     *
+     * @param fontFamily The font family.
+     */
+    public abstract void setFontFamily( String fontFamily );
+
+    /**
+     * A string representing the preferred font stretch.
+     * According to the PDF Spec:
+     * The font stretch value; it must be one of the following (ordered from
+     * narrowest to widest): UltraCondensed, ExtraCondensed, Condensed, SemiCondensed,
+     * Normal, SemiExpanded, Expanded, ExtraExpanded or UltraExpanded.
+     *
+     * @return The font stretch.
+     */
+    public abstract String getFontStretch();
+
+    /**
+     * This will set the font stretch.
+     *
+     * @param fontStretch The font stretch
+     */
+    public abstract void setFontStretch( String fontStretch );
+
+    /**
+     * The weight of the font.  According to the PDF spec "possible values are
+     * 100, 200, 300, 400, 500, 600, 700, 800 or 900"  Where a higher number is
+     * more weight and appears to be more bold.
+     *
+     * @return The font weight.
+     */
+    public abstract float getFontWeight();
+
+    /**
+     * Set the weight of the font.
+     *
+     * @param fontWeight The new weight of the font.
+     */
+    public abstract void setFontWeight( float fontWeight );
+
+    /**
+     * This will get the font flags.
+     *
+     * @return The font flags.
+     */
+    public abstract int getFlags();
+
+    /**
+     * This will set the font flags.
+     *
+     * @param flags The new font flags.
+     */
+    public abstract void setFlags( int flags );
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isFixedPitch()
+    {
+        return isFlagBitOn( FLAG_FIXED_PITCH );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setFixedPitch( boolean flag )
+    {
+        setFlagBit( FLAG_FIXED_PITCH, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isSerif()
+    {
+        return isFlagBitOn( FLAG_SERIF );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setSerif( boolean flag )
+    {
+        setFlagBit( FLAG_SERIF, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isSymbolic()
+    {
+        return isFlagBitOn( FLAG_SYMBOLIC );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setSymbolic( boolean flag )
+    {
+        setFlagBit( FLAG_SYMBOLIC, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isScript()
+    {
+        return isFlagBitOn( FLAG_SCRIPT );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setScript( boolean flag )
+    {
+        setFlagBit( FLAG_SCRIPT, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isNonSymbolic()
+    {
+        return isFlagBitOn( FLAG_NON_SYMBOLIC );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setNonSymbolic( boolean flag )
+    {
+        setFlagBit( FLAG_NON_SYMBOLIC, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isItalic()
+    {
+        return isFlagBitOn( FLAG_ITALIC );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setItalic( boolean flag )
+    {
+        setFlagBit( FLAG_ITALIC, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isAllCap()
+    {
+        return isFlagBitOn( FLAG_ALL_CAP);
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setAllCap( boolean flag )
+    {
+        setFlagBit( FLAG_ALL_CAP, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isSmallCap()
+    {
+        return isFlagBitOn( FLAG_SMALL_CAP );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setSmallCap( boolean flag )
+    {
+        setFlagBit( FLAG_SMALL_CAP, flag );
+    }
+
+    /**
+     * A convenience method that checks the flag bit.
+     *
+     * @return The flag value.
+     */
+    public boolean isForceBold()
+    {
+        return isFlagBitOn( FLAG_FORCE_BOLD );
+    }
+
+    /**
+     * A convenience method that sets the flag bit.
+     *
+     * @param flag The flag value.
+     */
+    public void setForceBold( boolean flag )
+    {
+        setFlagBit( FLAG_FORCE_BOLD, flag );
+    }
+
+    private boolean isFlagBitOn( int bit )
+    {
+        return (getFlags() & bit) != 0;
+    }
+
+    private void setFlagBit( int bit, boolean value )
+    {
+        int flags = getFlags();
+        if( value )
+        {
+            flags = flags | bit;
+        }
+        else
+        {
+            flags = flags & (0xFFFFFFFF ^ bit);
+        }
+        setFlags( flags );
+    }
+
+    /**
+     * This will get the fonts bouding box.
+     *
+     * @return The fonts bouding box.
+     */
+    public abstract PDRectangle getFontBoundingBox();
+
+    /**
+     * Set the fonts bounding box.
+     *
+     * @param rect The new bouding box.
+     */
+    public abstract void setFontBoundingBox( PDRectangle rect );
+
+    /**
+     * This will get the italic angle for the font.
+     *
+     * @return The italic angle.
+     */
+    public abstract float getItalicAngle();
+
+    /**
+     * This will set the italic angle for the font.
+     *
+     * @param angle The new italic angle for the font.
+     */
+    public abstract void setItalicAngle( float angle );
+
+    /**
+     * This will get the ascent for the font.
+     *
+     * @return The ascent.
+     */
+    public abstract float getAscent();
+
+    /**
+     * This will set the ascent for the font.
+     *
+     * @param ascent The new ascent for the font.
+     */
+    public abstract void setAscent( float ascent );
+
+    /**
+     * This will get the descent for the font.
+     *
+     * @return The descent.
+     */
+    public abstract float getDescent();
+
+    /**
+     * This will set the descent for the font.
+     *
+     * @param descent The new descent for the font.
+     */
+    public abstract void setDescent( float descent );
+
+    /**
+     * This will get the leading for the font.
+     *
+     * @return The leading.
+     */
+    public abstract float getLeading();
+
+    /**
+     * This will set the leading for the font.
+     *
+     * @param leading The new leading for the font.
+     */
+    public abstract void setLeading( float leading );
+
+    /**
+     * This will get the CapHeight for the font.
+     *
+     * @return The cap height.
+     */
+    public abstract float getCapHeight();
+
+    /**
+     * This will set the cap height for the font.
+     *
+     * @param capHeight The new cap height for the font.
+     */
+    public abstract void setCapHeight( float capHeight );
+
+    /**
+     * This will get the x height for the font.
+     *
+     * @return The x height.
+     */
+    public abstract float getXHeight();
+
+    /**
+     * This will set the x height for the font.
+     *
+     * @param xHeight The new x height for the font.
+     */
+    public abstract void setXHeight( float xHeight );
+
+    /**
+     * This will get the stemV for the font.
+     *
+     * @return The stem v value.
+     */
+    public abstract float getStemV();
+
+    /**
+     * This will set the stem V for the font.
+     *
+     * @param stemV The new stem v for the font.
+     */
+    public abstract void setStemV( float stemV );
+
+    /**
+     * This will get the stemH for the font.
+     *
+     * @return The stem h value.
+     */
+    public abstract float getStemH();
+
+    /**
+     * This will set the stem H for the font.
+     *
+     * @param stemH The new stem h for the font.
+     */
+    public abstract void setStemH( float stemH );
+
+    /**
+     * This will get the average width for the font.  This is part of the
+     * definition in the font description.  If it is not present then PDFBox
+     * will make an attempt to calculate it.
+     *
+     * @return The average width value.
+     *
+     * @throws IOException If there is an error calculating the average width.
+     */
+    public abstract float getAverageWidth() throws IOException;
+
+    /**
+     * This will set the average width for the font.
+     *
+     * @param averageWidth The new average width for the font.
+     */
+    public abstract void setAverageWidth( float averageWidth );
+
+    /**
+     * This will get the max width for the font.
+     *
+     * @return The max width value.
+     */
+    public abstract float getMaxWidth();
+
+    /**
+     * This will set the max width for the font.
+     *
+     * @param maxWidth The new max width for the font.
+     */
+    public abstract void setMaxWidth( float maxWidth );
+
+    /**
+     * This will get the character set for the font.
+     *
+     * @return The character set value.
+     */
+    public abstract String getCharSet();
+
+    /**
+     * This will set the character set for the font.
+     *
+     * @param charSet The new character set for the font.
+     */
+    public abstract void setCharacterSet( String charSet );
+
+    /**
+     * This will get the missing width for the font.
+     *
+     * @return The missing width value.
+     */
+    public abstract float getMissingWidth();
+
+    /**
+     * This will set the missing width for the font.
+     *
+     * @param charSet The new missing width for the font.
+     */
+    public abstract void setMissingWidth( float missingWidth );
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptorAFM.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptorAFM.java
new file mode 100644 (file)
index 0000000..e5eae5e
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.io.IOException;
+
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import org.apache.fontbox.util.BoundingBox;
+
+/**
+ * This class represents the font descriptor when the font information
+ * is coming from an AFM file.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDFontDescriptorAFM extends PDFontDescriptor
+{
+    private FontMetric afm;
+
+    /**
+     * Constructor.
+     *
+     * @param afmFile The AFM file.
+     */
+    public PDFontDescriptorAFM( FontMetric afmFile )
+    {
+        afm = afmFile;
+    }
+
+    /**
+     * Get the font name.
+     *
+     * @return The name of the font.
+     */
+    public String getFontName()
+    {
+        return afm.getFontName();
+    }
+
+    /**
+     * This will set the font name.
+     *
+     * @param fontName The new name for the font.
+     */
+    public void setFontName( String fontName )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * A string representing the preferred font family.
+     *
+     * @return The font family.
+     */
+    public String getFontFamily()
+    {
+        return afm.getFamilyName();
+    }
+
+    /**
+     * This will set the font family.
+     *
+     * @param fontFamily The font family.
+     */
+    public void setFontFamily( String fontFamily )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * The weight of the font.  According to the PDF spec "possible values are
+     * 100, 200, 300, 400, 500, 600, 700, 800 or 900"  Where a higher number is
+     * more weight and appears to be more bold.
+     *
+     * @return The font weight.
+     */
+    public float getFontWeight()
+    {
+        String weight = afm.getWeight();
+        float retval = 500;
+        if( weight != null && weight.equalsIgnoreCase( "bold" ) )
+        {
+            retval = 900;
+        }
+        else if( weight != null && weight.equalsIgnoreCase( "light" ) )
+        {
+            retval = 100;
+        }
+        return retval;
+    }
+
+    /**
+     * Set the weight of the font.
+     *
+     * @param fontWeight The new weight of the font.
+     */
+    public void setFontWeight( float fontWeight )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * A string representing the preferred font stretch.
+     *
+     * @return The font stretch.
+     */
+    public String getFontStretch()
+    {
+        return null;
+    }
+
+    /**
+     * This will set the font stretch.
+     *
+     * @param fontStretch The font stretch
+     */
+    public void setFontStretch( String fontStretch )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the font flags.
+     *
+     * @return The font flags.
+     */
+    public int getFlags()
+    {
+        //I believe that the only flag that AFM supports is the is fixed pitch
+        return afm.isFixedPitch() ? 1 : 0;
+    }
+
+    /**
+     * This will set the font flags.
+     *
+     * @param flags The new font flags.
+     */
+    public void setFlags( int flags )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the fonts bouding box.
+     *
+     * @return The fonts bouding box.
+     */
+    public PDRectangle getFontBoundingBox()
+    {
+        BoundingBox box = afm.getFontBBox();
+        PDRectangle retval = null;
+        if( box != null )
+        {
+            retval = new PDRectangle( box );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the fonts bounding box.
+     *
+     * @param rect The new bouding box.
+     */
+    public void setFontBoundingBox( PDRectangle rect )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the italic angle for the font.
+     *
+     * @return The italic angle.
+     */
+    public float getItalicAngle()
+    {
+        return afm.getItalicAngle();
+    }
+
+    /**
+     * This will set the italic angle for the font.
+     *
+     * @param angle The new italic angle for the font.
+     */
+    public void setItalicAngle( float angle )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the ascent for the font.
+     *
+     * @return The ascent.
+     */
+    public float getAscent()
+    {
+        return afm.getAscender();
+    }
+
+    /**
+     * This will set the ascent for the font.
+     *
+     * @param ascent The new ascent for the font.
+     */
+    public void setAscent( float ascent )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the descent for the font.
+     *
+     * @return The descent.
+     */
+    public float getDescent()
+    {
+        return afm.getDescender();
+    }
+
+    /**
+     * This will set the descent for the font.
+     *
+     * @param descent The new descent for the font.
+     */
+    public void setDescent( float descent )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the leading for the font.
+     *
+     * @return The leading.
+     */
+    public float getLeading()
+    {
+        //AFM does not support setting the leading so we will just ignore it.
+        return 0f;
+    }
+
+    /**
+     * This will set the leading for the font.
+     *
+     * @param leading The new leading for the font.
+     */
+    public void setLeading( float leading )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the CapHeight for the font.
+     *
+     * @return The cap height.
+     */
+    public float getCapHeight()
+    {
+        return afm.getCapHeight();
+    }
+
+    /**
+     * This will set the cap height for the font.
+     *
+     * @param capHeight The new cap height for the font.
+     */
+    public void setCapHeight( float capHeight )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the x height for the font.
+     *
+     * @return The x height.
+     */
+    public float getXHeight()
+    {
+        return afm.getXHeight();
+    }
+
+    /**
+     * This will set the x height for the font.
+     *
+     * @param xHeight The new x height for the font.
+     */
+    public void setXHeight( float xHeight )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the stemV for the font.
+     *
+     * @return The stem v value.
+     */
+    public float getStemV()
+    {
+        //afm does not have a stem v
+        return 0;
+    }
+
+    /**
+     * This will set the stem V for the font.
+     *
+     * @param stemV The new stem v for the font.
+     */
+    public void setStemV( float stemV )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the stemH for the font.
+     *
+     * @return The stem h value.
+     */
+    public float getStemH()
+    {
+        //afm does not have a stem h
+        return 0;
+    }
+
+    /**
+     * This will set the stem H for the font.
+     *
+     * @param stemH The new stem h for the font.
+     */
+    public void setStemH( float stemH )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the average width for the font.
+     *
+     * @return The average width value.
+     *
+     * @throws IOException If there is an error calculating the average width.
+     */
+    public float getAverageWidth() throws IOException
+    {
+        return afm.getAverageCharacterWidth();
+    }
+
+    /**
+     * This will set the average width for the font.
+     *
+     * @param averageWidth The new average width for the font.
+     */
+    public void setAverageWidth( float averageWidth )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the max width for the font.
+     *
+     * @return The max width value.
+     */
+    public float getMaxWidth()
+    {
+        //afm does not support max width;
+        return 0;
+    }
+
+    /**
+     * This will set the max width for the font.
+     *
+     * @param maxWidth The new max width for the font.
+     */
+    public void setMaxWidth( float maxWidth )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the missing width for the font.
+     *
+     * @return The missing width value.
+     */
+    public float getMissingWidth()
+    {
+        return 0;
+    }
+
+    /**
+     * This will set the missing width for the font.
+     *
+     * @param missingWidth The new missing width for the font.
+     */
+    public void setMissingWidth( float missingWidth )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+
+    /**
+     * This will get the character set for the font.
+     *
+     * @return The character set value.
+     */
+    public String getCharSet()
+    {
+        return afm.getCharacterSet();
+    }
+
+    /**
+     * This will set the character set for the font.
+     *
+     * @param charSet The new character set for the font.
+     */
+    public void setCharacterSet( String charSet )
+    {
+        throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptorDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontDescriptorDictionary.java
new file mode 100644 (file)
index 0000000..c75ad03
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This class represents an implementation to the font descriptor that gets its
+ * information from a COS Dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFontDescriptorDictionary extends PDFontDescriptor implements COSObjectable
+{
+    private COSDictionary dic;
+    private float xHeight = Float.NEGATIVE_INFINITY;
+    private float capHeight = Float.NEGATIVE_INFINITY;
+    private int flags = -1;
+
+    /**
+     * Constructor.
+     */
+    public PDFontDescriptorDictionary()
+    {
+        dic = new COSDictionary();
+        dic.setItem( COSName.TYPE, COSName.FONT_DESC );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param desc The wrapped COS Dictionary.
+     */
+    public PDFontDescriptorDictionary( COSDictionary desc )
+    {
+        dic = desc;
+    }
+
+    /**
+     * This will get the dictionary for this object.
+     *
+     * @return The COS dictionary.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return dic;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return dic;
+    }
+
+    /**
+     * Get the font name.
+     *
+     * @return The name of the font.
+     */
+    public String getFontName()
+    {
+        String retval = null;
+        COSName name = (COSName)dic.getDictionaryObject( COSName.FONT_NAME );
+        if( name != null )
+        {
+            retval = name.getName();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the font name.
+     *
+     * @param fontName The new name for the font.
+     */
+    public void setFontName( String fontName )
+    {
+        COSName name = null;
+        if( fontName != null )
+        {
+            name = COSName.getPDFName( fontName );
+        }
+        dic.setItem( COSName.FONT_NAME, name );
+    }
+
+    /**
+     * A string representing the preferred font family.
+     *
+     * @return The font family.
+     */
+    public String getFontFamily()
+    {
+        String retval = null;
+        COSString name = (COSString)dic.getDictionaryObject( COSName.FONT_FAMILY );
+        if( name != null )
+        {
+            retval = name.getString();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the font family.
+     *
+     * @param fontFamily The font family.
+     */
+    public void setFontFamily( String fontFamily )
+    {
+        COSString name = null;
+        if( fontFamily != null )
+        {
+            name = new COSString( fontFamily );
+        }
+        dic.setItem( COSName.FONT_FAMILY, name );
+    }
+
+    /**
+     * The weight of the font.  According to the PDF spec "possible values are
+     * 100, 200, 300, 400, 500, 600, 700, 800 or 900"  Where a higher number is
+     * more weight and appears to be more bold.
+     *
+     * @return The font weight.
+     */
+    public float getFontWeight()
+    {
+        return dic.getFloat( COSName.FONT_WEIGHT,0 );
+    }
+
+    /**
+     * Set the weight of the font.
+     *
+     * @param fontWeight The new weight of the font.
+     */
+    public void setFontWeight( float fontWeight )
+    {
+        dic.setFloat( COSName.FONT_WEIGHT, fontWeight );
+    }
+
+    /**
+     * A string representing the preferred font stretch.
+     * According to the PDF Spec:
+     * The font stretch value; it must be one of the following (ordered from
+     * narrowest to widest): UltraCondensed, ExtraCondensed, Condensed, SemiCondensed,
+     * Normal, SemiExpanded, Expanded, ExtraExpanded or UltraExpanded.
+     *
+     * @return The stretch of the font.
+     */
+    public String getFontStretch()
+    {
+        String retval = null;
+        COSName name = (COSName)dic.getDictionaryObject( COSName.FONT_STRETCH );
+        if( name != null )
+        {
+            retval = name.getName();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the font stretch.
+     *
+     * @param fontStretch The new stretch for the font.
+     */
+    public void setFontStretch( String fontStretch )
+    {
+        COSName name = null;
+        if( fontStretch != null )
+        {
+            name = COSName.getPDFName( fontStretch );
+        }
+        dic.setItem( COSName.FONT_STRETCH, name );
+    }
+
+    /**
+     * This will get the font flags.
+     *
+     * @return The font flags.
+     */
+    public int getFlags()
+    {
+        if (flags == -1)
+        {
+            flags = dic.getInt( COSName.FLAGS, 0 );
+        }
+        return flags;
+    }
+
+    /**
+     * This will set the font flags.
+     *
+     * @param flags The new font flags.
+     */
+    public void setFlags( int flags )
+    {
+        dic.setInt( COSName.FLAGS, flags );
+        this.flags = flags;
+    }
+
+    /**
+     * This will get the fonts bounding box.
+     *
+     * @return The fonts bounding box.
+     */
+    public PDRectangle getFontBoundingBox()
+    {
+        COSArray rect = (COSArray)dic.getDictionaryObject( COSName.FONT_BBOX );
+        PDRectangle retval = null;
+        if( rect != null )
+        {
+            retval = new PDRectangle( rect );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the fonts bounding box.
+     *
+     * @param rect The new bouding box.
+     */
+    public void setFontBoundingBox( PDRectangle rect )
+    {
+        COSArray array = null;
+        if( rect != null )
+        {
+            array = rect.getCOSArray();
+        }
+        dic.setItem( COSName.FONT_BBOX, array );
+    }
+
+    /**
+     * This will get the italic angle for the font.
+     *
+     * @return The italic angle.
+     */
+    public float getItalicAngle()
+    {
+        return dic.getFloat( COSName.ITALIC_ANGLE, 0 );
+    }
+
+    /**
+     * This will set the italic angle for the font.
+     *
+     * @param angle The new italic angle for the font.
+     */
+    public void setItalicAngle( float angle )
+    {
+        dic.setFloat( COSName.ITALIC_ANGLE, angle );
+    }
+
+    /**
+     * This will get the ascent for the font.
+     *
+     * @return The ascent.
+     */
+    public float getAscent()
+    {
+        return dic.getFloat( COSName.ASCENT, 0 );
+    }
+
+    /**
+     * This will set the ascent for the font.
+     *
+     * @param ascent The new ascent for the font.
+     */
+    public void setAscent( float ascent )
+    {
+        dic.setFloat( COSName.ASCENT, ascent );
+    }
+
+    /**
+     * This will get the descent for the font.
+     *
+     * @return The descent.
+     */
+    public float getDescent()
+    {
+        return dic.getFloat( COSName.DESCENT, 0 );
+    }
+
+    /**
+     * This will set the descent for the font.
+     *
+     * @param descent The new descent for the font.
+     */
+    public void setDescent( float descent )
+    {
+        dic.setFloat( COSName.DESCENT, descent );
+    }
+
+    /**
+     * This will get the leading for the font.
+     *
+     * @return The leading.
+     */
+    public float getLeading()
+    {
+        return dic.getFloat( COSName.LEADING, 0 );
+    }
+
+    /**
+     * This will set the leading for the font.
+     *
+     * @param leading The new leading for the font.
+     */
+    public void setLeading( float leading )
+    {
+        dic.setFloat( COSName.LEADING, leading );
+    }
+
+    /**
+     * This will get the CapHeight for the font.
+     *
+     * @return The cap height.
+     */
+    public float getCapHeight()
+    {
+        if(capHeight==Float.NEGATIVE_INFINITY){
+               /* We observed a negative value being returned with
+             * the Scheherazade font. PDFBOX-429 was logged for this. 
+             * We are not sure if returning the absolute value
+             * is the correct fix, but it seems to work.  */
+            capHeight = java.lang.Math.abs(dic.getFloat( COSName.CAP_HEIGHT, 0 ));
+        }
+        return capHeight;
+    }
+
+    
+    /**
+     * This will set the cap height for the font.
+     *
+     * @param capHeight The new cap height for the font.
+     */
+    public void setCapHeight( float capHeight )
+    {
+        dic.setFloat( COSName.CAP_HEIGHT, capHeight );
+        this.capHeight = capHeight;
+    }
+
+    /**
+     * This will get the x height for the font.
+     *
+     * @return The x height.
+     */
+    public float getXHeight()
+    {
+        if(xHeight==Float.NEGATIVE_INFINITY){
+               /* We observed a negative value being returned with
+             * the Scheherazade font. PDFBOX-429 was logged for this. 
+             * We are not sure if returning the absolute value
+             * is the correct fix, but it seems to work.  */
+            xHeight = java.lang.Math.abs(dic.getFloat( COSName.XHEIGHT, 0 ));
+        }
+        return xHeight;
+    }
+
+    /**
+     * This will set the x height for the font.
+     *
+     * @param xHeight The new x height for the font.
+     */
+    public void setXHeight( float xHeight )
+    {
+        dic.setFloat( COSName.XHEIGHT, xHeight );
+        this.xHeight = xHeight;
+    }
+
+    /**
+     * This will get the stemV for the font.
+     *
+     * @return The stem v value.
+     */
+    public float getStemV()
+    {
+        return dic.getFloat( COSName.STEM_V, 0 );
+    }
+
+    /**
+     * This will set the stem V for the font.
+     *
+     * @param stemV The new stem v for the font.
+     */
+    public void setStemV( float stemV )
+    {
+        dic.setFloat( COSName.STEM_V, stemV );
+    }
+
+    /**
+     * This will get the stemH for the font.
+     *
+     * @return The stem h value.
+     */
+    public float getStemH()
+    {
+        return dic.getFloat( COSName.STEM_H, 0 );
+    }
+
+    /**
+     * This will set the stem H for the font.
+     *
+     * @param stemH The new stem h for the font.
+     */
+    public void setStemH( float stemH )
+    {
+        dic.setFloat( COSName.STEM_H, stemH );
+    }
+
+    /**
+     * This will get the average width for the font.
+     *
+     * @return The average width value.
+     */
+    public float getAverageWidth()
+    {
+        return dic.getFloat( COSName.AVG_WIDTH, 0 );
+    }
+
+    /**
+     * This will set the average width for the font.
+     *
+     * @param averageWidth The new average width for the font.
+     */
+    public void setAverageWidth( float averageWidth )
+    {
+        dic.setFloat( COSName.AVG_WIDTH, averageWidth );
+    }
+
+    /**
+     * This will get the max width for the font.
+     *
+     * @return The max width value.
+     */
+    public float getMaxWidth()
+    {
+        return dic.getFloat( COSName.MAX_WIDTH, 0 );
+    }
+
+    /**
+     * This will set the max width for the font.
+     *
+     * @param maxWidth The new max width for the font.
+     */
+    public void setMaxWidth( float maxWidth )
+    {
+        dic.setFloat( COSName.MAX_WIDTH, maxWidth );
+    }
+
+    /**
+     * This will get the missing width for the font.
+     *
+     * @return The missing width value.
+     */
+    public float getMissingWidth()
+    {
+        return dic.getFloat( COSName.MISSING_WIDTH, 0 );
+    }
+
+    /**
+     * This will set the missing width for the font.
+     *
+     * @param missingWidth The new missing width for the font.
+     */
+    public void setMissingWidth( float missingWidth )
+    {
+        dic.setFloat( COSName.MISSING_WIDTH, missingWidth );
+    }
+
+    /**
+     * This will get the character set for the font.
+     *
+     * @return The character set value.
+     */
+    public String getCharSet()
+    {
+        String retval = null;
+        COSString name = (COSString)dic.getDictionaryObject( COSName.CHAR_SET );
+        if( name != null )
+        {
+            retval = name.getString();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the character set for the font.
+     *
+     * @param charSet The new character set for the font.
+     */
+    public void setCharacterSet( String charSet )
+    {
+        COSString name = null;
+        if( charSet != null )
+        {
+            name = new COSString( charSet );
+        }
+        dic.setItem( COSName.CHAR_SET, name );
+    }
+
+    /**
+     * A stream containing a Type 1 font program.
+     *
+     * @return A stream containing a Type 1 font program.
+     */
+    public PDStream getFontFile()
+    {
+        PDStream retval = null;
+        COSStream stream = (COSStream)dic.getDictionaryObject( COSName.FONT_FILE );
+        if( stream != null )
+        {
+            retval = new PDStream( stream );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the type 1 font program.
+     *
+     * @param type1Stream The type 1 stream.
+     */
+    public void setFontFile( PDStream type1Stream )
+    {
+        dic.setItem( COSName.FONT_FILE, type1Stream );
+    }
+
+    /**
+     * A stream containing a true type font program.
+     *
+     * @return A stream containing a true type font program.
+     */
+    public PDStream getFontFile2()
+    {
+        PDStream retval = null;
+        COSStream stream = (COSStream)dic.getDictionaryObject( COSName.FONT_FILE2 );
+        if( stream != null )
+        {
+            retval = new PDStream( stream );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the true type font program.
+     *
+     * @param ttfStream The true type stream.
+     */
+    public void setFontFile2( PDStream ttfStream )
+    {
+        dic.setItem( COSName.FONT_FILE2, ttfStream );
+    }
+
+    /**
+     * A stream containing a font program that is not true type or type 1.
+     *
+     * @return A stream containing a font program.
+     */
+    public PDStream getFontFile3()
+    {
+        PDStream retval = null;
+        COSStream stream = (COSStream)dic.getDictionaryObject( COSName.FONT_FILE3 );
+        if( stream != null )
+        {
+            retval = new PDStream( stream );
+        }
+        return retval;
+    }
+
+    /**
+     * Set a stream containing a font program that is not true type or type 1.
+     *
+     * @param stream The font program stream.
+     */
+    public void setFontFile3( PDStream stream )
+    {
+        dic.setItem( COSName.FONT_FILE3, stream );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontFactory.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDFontFactory.java
new file mode 100644 (file)
index 0000000..f1c6e5c
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This will create the correct type of font based on information in the dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFontFactory
+{
+    /**
+     * private constructor, should only use static methods in this class.
+     */
+    private PDFontFactory()
+    {
+    }
+
+    /**
+     * Logger instance.
+     */
+    private static final Log log = LogFactory.getLog(PDFontFactory.class);
+    
+    /**
+     * This will create the correct font based on information in the dictionary.
+     *
+     * @param dic The populated dictionary.
+     *
+     * @param fontCache A Map to cache already created fonts
+     *
+     * @return The corrent implementation for the font.
+     *
+     * @throws IOException If the dictionary is not valid.
+     */
+    public static PDFont createFont(COSDictionary dic, Map fontCache) throws IOException
+    {
+        PDFont retval = null;
+        if (fontCache != null) 
+        {
+            String fontKey = dic.getNameAsString(COSName.BASE_FONT) + dic.getNameAsString(COSName.NAME)
+                                + dic.getNameAsString(COSName.SUBTYPE);
+            if (dic.getItem(COSName.ENCODING) != null)
+            {
+                fontKey += dic.getItem(COSName.ENCODING).toString();
+            }
+            if (fontCache.containsKey(fontKey))
+            {
+                retval = (PDFont)fontCache.get(fontKey);
+            }
+            else 
+            {
+                retval = PDFontFactory.createFont( dic );
+                fontCache.put(fontKey, retval);
+            }
+        }
+        else
+        {
+            retval = PDFontFactory.createFont( dic );
+        }
+        return retval;
+    }
+
+    /**
+     * This will create the correct font based on information in the dictionary.
+     *
+     * @param dic The populated dictionary.
+     *
+     * @return The corrent implementation for the font.
+     *
+     * @throws IOException If the dictionary is not valid.
+     */
+    public static PDFont createFont( COSDictionary dic ) throws IOException
+    {
+        PDFont retval = null;
+
+        COSName type = (COSName)dic.getDictionaryObject( COSName.TYPE );
+        if( !type.equals( COSName.FONT ) )
+        {
+            throw new IOException( "Cannot create font if /Type is not /Font.  Actual=" +type );
+        }
+
+        COSName subType = (COSName)dic.getDictionaryObject( COSName.SUBTYPE );
+        if( subType.equals( COSName.TYPE1) )
+        {
+            retval = new PDType1Font( dic );
+        }
+        else if( subType.equals( COSName.MM_TYPE1 ) )
+        {
+            retval = new PDMMType1Font( dic );
+        }
+        else if( subType.equals( COSName.TRUE_TYPE ) )
+        {
+            retval = new PDTrueTypeFont( dic );
+        }
+        else if( subType.equals( COSName.TYPE3 ) )
+        {
+            retval = new PDType3Font( dic );
+        }
+        else if( subType.equals( COSName.TYPE0 ) )
+        {
+            retval = new PDType0Font( dic );
+        }
+        else if( subType.equals( COSName.CID_FONT_TYPE0 ) )
+        {
+            retval = new PDCIDFontType0Font( dic );
+        }
+        else if( subType.equals( COSName.CID_FONT_TYPE2 ) )
+        {
+            retval = new PDCIDFontType2Font( dic );
+        }
+        else
+        {
+            log.warn("Substituting TrueType for unknown font subtype=" + dic.getDictionaryObject( COSName.SUBTYPE ).toString());
+            //throw new IOException( "Unknown font subtype=" + subType );
+            retval = new PDTrueTypeFont( dic );
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDMMType1Font.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDMMType1Font.java
new file mode 100644 (file)
index 0000000..8b9b67e
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is implementation of the Multiple Master Type1 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDMMType1Font extends PDSimpleFont
+{
+    /**
+     * Constructor.
+     */
+    public PDMMType1Font()
+    {
+        super();
+        font.setItem( COSName.SUBTYPE, COSName.MM_TYPE1 );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDMMType1Font( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java
new file mode 100644 (file)
index 0000000..575bca6
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.NoninvertibleTransformException;
+import java.io.IOException;
+
+import java.util.HashMap;
+
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.encoding.DictionaryEncoding;
+import org.apache.pdfbox.encoding.Encoding;
+import org.apache.pdfbox.encoding.EncodingManager;
+import org.apache.pdfbox.encoding.conversion.CMapSubstitution;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ * This class contains implementation details of the simple pdf fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.18 $
+ */
+public abstract class PDSimpleFont extends PDFont
+{
+    private final HashMap<Integer, Float> mFontSizes =
+        new HashMap<Integer, Float>(128);
+
+    private float avgFontWidth = 0.0f;
+
+    
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDSimpleFont.class);
+    
+    /**
+     * Constructor.
+     */
+    public PDSimpleFont()
+    {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDSimpleFont( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+    }
+
+    /**
+    * Looks up, creates, returns  the AWT Font.
+    */
+    public Font getawtFont() throws IOException
+    {
+        log.error("Not yet implemented:" + getClass().getName() );
+        return null;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void drawString( String string, Graphics g, float fontSize, 
+            AffineTransform at, float x, float y ) throws IOException
+    {
+        Font _awtFont = getawtFont();
+
+        // mdavis - fix fontmanager.so/dll on sun.font.FileFont.getGlyphImage
+        // for font with bad cmaps?
+        // Type1 fonts are not affected as they don't have cmaps
+        if (!isType1Font() && _awtFont.canDisplayUpTo(string) != -1) { 
+            log.warn("Changing font on <" + string + "> from <"
+                    + _awtFont.getName() + "> to the default font");
+            _awtFont = Font.decode(null); 
+        }
+
+        Graphics2D g2d = (Graphics2D)g;
+        g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
+        writeFont(g2d, at, _awtFont, x, y, string);
+    }
+
+    /**
+     * This will get the font height for a character.
+     *
+     * @param c The character code to get the width for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getFontHeight( byte[] c, int offset, int length ) throws IOException
+    {
+        float retval = 0;
+        int code = getCodeFromArray( c, offset, length );
+        FontMetric metric = getAFM();
+        if( metric != null )
+        {
+            Encoding encoding = getFontEncoding();
+            String characterName = encoding.getName( code );
+            retval = metric.getCharacterHeight( characterName );
+        }
+        else
+        {
+            PDFontDescriptor desc = getFontDescriptor();
+            if( desc != null )
+            {
+                float xHeight = desc.getXHeight();
+                float capHeight = desc.getCapHeight();
+                if( xHeight != 0f && capHeight != 0 )
+                {
+                    //do an average of these two.  Can we do better???
+                    retval = (xHeight + capHeight)/2f;
+                }
+                else if( xHeight != 0 )
+                {
+                    retval = xHeight;
+                }
+                else if( capHeight != 0 )
+                {
+                    retval = capHeight;
+                }
+                else
+                {
+                    retval = 0;
+                }
+                //hmm, not sure if this is 100% correct
+                //but gives a height, Should we add Descent as well??
+                if( retval == 0 )
+                {
+                    retval = desc.getAscent();
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the font width for a character.
+     *
+     * @param c The character code to get the width for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getFontWidth( byte[] c, int offset, int length ) throws IOException
+    {
+        int code = getCodeFromArray( c, offset, length );
+        Float fontWidth = mFontSizes.get(code);
+        if (fontWidth == null)
+        {
+            fontWidth = getFontWidth(code);
+            if (fontWidth == -1)
+            {
+                //hmm should this be in PDType1Font??
+                fontWidth = getFontWidthFromAFMFile( code );
+            }
+            mFontSizes.put(code, fontWidth);
+        }
+        return fontWidth;
+    }
+
+    /**
+     * This will get the average font width for all characters.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getAverageFontWidth() throws IOException
+    {
+        float average = 0.0f;
+
+        //AJW
+        if (avgFontWidth != 0.0f)
+        {
+            average = avgFontWidth;
+        }
+        else
+        {
+            float totalWidth = 0.0f;
+            float characterCount = 0.0f;
+            COSArray widths = (COSArray)font.getDictionaryObject( COSName.WIDTHS );
+            if( widths != null )
+            {
+                for( int i=0; i<widths.size(); i++ )
+                {
+                    COSNumber fontWidth = (COSNumber)widths.getObject( i );
+                    if( fontWidth.floatValue() > 0 )
+                    {
+                        totalWidth += fontWidth.floatValue();
+                        characterCount += 1;
+                    }
+                }
+            }
+
+            if( totalWidth > 0 )
+            {
+                average = totalWidth / characterCount;
+            }
+            else
+            {
+                average = getAverageFontWidthFromAFMFile();
+            }
+            avgFontWidth = average;
+        }
+        return average;
+    }
+
+    
+    /**
+     * This will get the ToUnicode object.
+     *
+     * @return The ToUnicode object.
+     */
+    public COSBase getToUnicode()
+    {
+        return font.getDictionaryObject( COSName.TO_UNICODE );
+    }
+
+    /**
+     * This will set the ToUnicode object.
+     *
+     * @param unicode The unicode object.
+     */
+    public void setToUnicode( COSBase unicode )
+    {
+        font.setItem( COSName.TO_UNICODE, unicode );
+    }
+
+    /**
+     * This will get the fonts bounding box.
+     *
+     * @return The fonts bouding box.
+     *
+     * @throws IOException If there is an error getting the bounding box.
+     */
+    public PDRectangle getFontBoundingBox() throws IOException
+    {
+        return getFontDescriptor().getFontBoundingBox();
+    }
+
+    /**
+     * This will draw a string on a canvas using the font.
+     *
+     * @param g2d The graphics to draw onto.
+     * @param at The transformation matrix with all infos for scaling and shearing of the font.
+     * @param awtFont The font to draw.
+     * @param x The x coordinate to draw at.
+     * @param y The y coordinate to draw at.
+     * @param string The string to draw.
+     *
+     */
+    protected void writeFont(final Graphics2D g2d, final AffineTransform at, final Font awtFont,
+                             final float x, final float y, final String string) 
+    {
+        // check if we have a rotation
+        if (!at.isIdentity()) 
+        {
+            try 
+            {
+                AffineTransform atInv = at.createInverse();
+                // do only apply the size of the transform, rotation will be realized by rotating the graphics,
+                // otherwise the hp printers will not render the font
+                g2d.setFont(awtFont.deriveFont(1f));
+                // apply the inverse transformation to the graphics, which should be the same as applying the
+                // transformation itself to the text
+                g2d.transform(at);
+                // translate the coordinates
+                Point2D.Float newXy = new  Point2D.Float(x,y);
+                atInv.transform(new Point2D.Float( x, y), newXy);
+                g2d.drawString( string, (float)newXy.getX(), (float)newXy.getY() );
+                // restore the original transformation
+                g2d.transform(atInv);
+            }
+            catch (NoninvertibleTransformException e) 
+            {
+                log.error("Error in "+getClass().getName()+".writeFont",e);
+            }
+        }
+        else 
+        {
+            g2d.setFont( awtFont.deriveFont( at ) );
+            g2d.drawString( string, x, y );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void determineEncoding()
+    {
+        String cmapName = null;
+        COSName encodingName = null;
+        COSBase encoding = getEncoding(); 
+        Encoding fontEncoding = null;
+        if (encoding != null) 
+        {
+            if (encoding instanceof COSName) 
+            {
+                if (cmap == null)
+                {
+                    encodingName = (COSName)encoding;
+                    cmap = cmapObjects.get( encodingName.getName() );
+                    if (cmap == null) 
+                    {
+                        cmapName = encodingName.getName();
+                    }
+                }
+                if (cmap == null && cmapName != null)
+                {
+                    try 
+                    {
+                        fontEncoding =
+                            EncodingManager.INSTANCE.getEncoding(encodingName);
+                    }
+                    catch(IOException exception) 
+                    {
+                        log.debug("Debug: Could not find encoding for " + encodingName );
+                    }
+                }
+            }
+            else if (encoding instanceof COSDictionary) 
+            {
+                try 
+                {
+                    fontEncoding = new DictionaryEncoding((COSDictionary)encoding);
+                }
+                catch(IOException exception) 
+                {
+                    log.error("Error: Could not create the DictionaryEncoding" );
+                }
+            }
+            else if(encoding instanceof COSStream )
+            {
+                if (cmap == null)
+                {
+                    COSStream encodingStream = (COSStream)encoding;
+                    try 
+                    {
+                        parseCmap( null, encodingStream.getUnfilteredStream() );
+                    }
+                    catch(IOException exception) 
+                    {
+                        log.error("Error: Could not parse the embedded CMAP" );
+                    }
+                }
+            }
+        }
+        setFontEncoding(fontEncoding);
+        extractToUnicodeEncoding();
+
+        if (cmap == null && cmapName != null) 
+        {
+            String resourceName = resourceRootCMAP + cmapName;
+            try {
+                parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ) );
+                if( cmap == null && encodingName == null)
+                {
+                    log.error("Error: Could not parse predefined CMAP file for '" + cmapName + "'" );
+                }
+            }
+            catch(IOException exception) 
+            {
+                log.error("Error: Could not find predefined CMAP file for '" + cmapName + "'" );
+            }
+        }
+    }
+
+    private void extractToUnicodeEncoding()
+    {
+        COSName encodingName = null;
+        String cmapName = null;
+        COSBase toUnicode = getToUnicode();
+        if( toUnicode != null )
+        {
+            setHasToUnicode(true);
+            if ( toUnicode instanceof COSStream )
+            {
+                try {
+                    parseCmap(null, ((COSStream)toUnicode).getUnfilteredStream());
+                }
+                catch(IOException exception) 
+                {
+                    log.error("Error: Could not load embedded CMAP" );
+                }
+            }
+            else if ( toUnicode instanceof COSName)
+            {
+                encodingName = (COSName)toUnicode;
+                cmap = cmapObjects.get( encodingName.getName() );
+                if (cmap == null) 
+                {
+                    cmapName = encodingName.getName();
+                    String resourceName = resourceRootCMAP + cmapName;
+                    try {
+                        parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ));
+                    }
+                    catch(IOException exception) 
+                    {
+                        log.error("Error: Could not find predefined CMAP file for '" + cmapName + "'" );
+                    }
+                    if( cmap == null)
+                    {
+                        log.error("Error: Could not parse predefined CMAP file for '" + cmapName + "'" );
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java
new file mode 100644 (file)
index 0000000..f4e422c
--- /dev/null
@@ -0,0 +1,485 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.ttf.CMAPEncodingEntry;
+import org.apache.fontbox.ttf.CMAPTable;
+import org.apache.fontbox.ttf.GlyphData;
+import org.apache.fontbox.ttf.GlyphTable;
+import org.apache.fontbox.ttf.HeaderTable;
+import org.apache.fontbox.ttf.HorizontalHeaderTable;
+import org.apache.fontbox.ttf.HorizontalMetricsTable;
+import org.apache.fontbox.ttf.NamingTable;
+import org.apache.fontbox.ttf.NameRecord;
+import org.apache.fontbox.ttf.OS2WindowsMetricsTable;
+import org.apache.fontbox.ttf.PostScriptTable;
+import org.apache.fontbox.ttf.TTFParser;
+import org.apache.fontbox.ttf.TrueTypeFont;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.encoding.WinAnsiEncoding;
+import org.apache.pdfbox.util.ResourceLoader;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This is the TrueType implementation of fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.17 $
+ */
+public class PDTrueTypeFont extends PDSimpleFont
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDTrueTypeFont.class);
+
+    /**
+     * This is the key to a property in the PDFBox_External_Fonts.properties
+     * file to load a Font when a mapping does not exist for the current font.
+     */
+    public static final String UNKNOWN_FONT = "UNKNOWN_FONT";
+
+    private Font awtFont = null;
+
+    private static Properties externalFonts = new Properties();
+    private static Map<String,TrueTypeFont> loadedExternalFonts = new HashMap<String,TrueTypeFont>();
+
+    static
+    {
+        try
+        {
+            ResourceLoader.loadProperties(
+                    "org/apache/pdfbox/resources/PDFBox_External_Fonts.properties",
+                    externalFonts );
+        }
+        catch( IOException io )
+        {
+            throw new RuntimeException( "Error loading font resources", io );
+        }
+    }
+
+
+    /**
+     * Constructor.
+     */
+    public PDTrueTypeFont()
+    {
+        super();
+        font.setItem( COSName.SUBTYPE, COSName.TRUE_TYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDTrueTypeFont( COSDictionary fontDictionary ) throws IOException
+    {
+        super( fontDictionary );
+        ensureFontDescriptor();
+    }
+
+    /**
+     * This will load a TTF font from a font file.
+     *
+     * @param doc The PDF document that will hold the embedded font.
+     * @param file The file on the filesystem that holds the font file.
+     * @return A true type font.
+     * @throws IOException If there is an error loading the file data.
+     */
+    public static PDTrueTypeFont loadTTF( PDDocument doc, String file ) throws IOException
+    {
+        return loadTTF( doc, new File( file ) );
+    }
+
+    /**
+     * This will load a TTF to be embedded into a document.
+     *
+     * @param doc The PDF document that will hold the embedded font.
+     * @param file a ttf file.
+     * @return a PDTrueTypeFont instance.
+     * @throws IOException If there is an error loading the data.
+     */
+    public static PDTrueTypeFont loadTTF( PDDocument doc, File file ) throws IOException
+    {
+        return loadTTF( doc, new FileInputStream( file ) );
+    } 
+    
+    /**
+     * This will load a TTF to be embedded into a document.
+     *
+     * @param doc The PDF document that will hold the embedded font.
+     * @param stream a ttf input stream.
+     * @return a PDTrueTypeFont instance.
+     * @throws IOException If there is an error loading the data.
+     */
+    public static PDTrueTypeFont loadTTF( PDDocument doc, InputStream stream ) throws IOException
+    { 
+        PDTrueTypeFont retval = new PDTrueTypeFont();
+        PDFontDescriptorDictionary fd = new PDFontDescriptorDictionary();
+        retval.setFontDescriptor( fd );
+        PDStream fontStream = new PDStream(doc, stream, false );
+        fontStream.getStream().setInt( COSName.LENGTH1, fontStream.getByteArray().length );
+        fontStream.addCompression();
+        fd.setFontFile2( fontStream );
+        // As the stream was close within the PDStream constructor, we have to recreate it
+        stream = fontStream.createInputStream();
+        try
+        {
+            retval.loadDescriptorDictionary(fd, stream); 
+        }
+        finally
+        {
+            stream.close();
+        }
+        //only support winansi encoding right now, should really
+        //just use Identity-H with unicode mapping
+        retval.setFontEncoding( new WinAnsiEncoding() );
+        retval.setEncoding(COSName.WIN_ANSI_ENCODING);
+        return retval;
+    }
+
+    private void ensureFontDescriptor() throws IOException
+    {
+        if( getFontDescriptor() == null )
+        {
+            PDFontDescriptorDictionary fdd = new PDFontDescriptorDictionary(); 
+            setFontDescriptor(fdd);
+            InputStream ttfData = getExternalTTFData();
+            if( ttfData != null )
+            {
+                try
+                {
+                    loadDescriptorDictionary(fdd, ttfData);
+                }
+                finally
+                {
+                    ttfData.close();
+                }
+            }
+        }
+    }
+
+    private void loadDescriptorDictionary(PDFontDescriptorDictionary fd, InputStream ttfData) throws IOException
+    {
+        TrueTypeFont ttf = null;
+        try
+        {
+            TTFParser parser = new TTFParser();
+            ttf = parser.parseTTF( ttfData );
+            NamingTable naming = ttf.getNaming();
+            List<NameRecord> records = naming.getNameRecords();
+            for( int i=0; i<records.size(); i++ )
+            {
+                NameRecord nr = records.get( i );
+                if( nr.getNameId() == NameRecord.NAME_POSTSCRIPT_NAME )
+                {
+                    setBaseFont( nr.getString() );
+                    fd.setFontName( nr.getString() );
+                }
+                else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME )
+                {
+                    fd.setFontFamily( nr.getString() );
+                }
+            }
+
+            OS2WindowsMetricsTable os2 = ttf.getOS2Windows();
+            fd.setNonSymbolic( true );
+            switch( os2.getFamilyClass() )
+            {
+                case OS2WindowsMetricsTable.FAMILY_CLASS_SYMBOLIC:
+                    fd.setSymbolic( true );
+                    fd.setNonSymbolic( false );
+                    break;
+                case OS2WindowsMetricsTable.FAMILY_CLASS_SCRIPTS:
+                    fd.setScript( true );
+                    break;
+                case OS2WindowsMetricsTable.FAMILY_CLASS_CLAREDON_SERIFS:
+                case OS2WindowsMetricsTable.FAMILY_CLASS_FREEFORM_SERIFS:
+                case OS2WindowsMetricsTable.FAMILY_CLASS_MODERN_SERIFS:
+                case OS2WindowsMetricsTable.FAMILY_CLASS_OLDSTYLE_SERIFS:
+                case OS2WindowsMetricsTable.FAMILY_CLASS_SLAB_SERIFS:
+                    fd.setSerif( true );
+                    break;
+                default:
+                    //do nothing
+            }
+            switch( os2.getWidthClass() )
+            {
+                case OS2WindowsMetricsTable.WIDTH_CLASS_ULTRA_CONDENSED:
+                    fd.setFontStretch( "UltraCondensed" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_EXTRA_CONDENSED:
+                    fd.setFontStretch( "ExtraCondensed" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_CONDENSED:
+                    fd.setFontStretch( "Condensed" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_SEMI_CONDENSED:
+                    fd.setFontStretch( "SemiCondensed" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_MEDIUM:
+                    fd.setFontStretch( "Normal" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_SEMI_EXPANDED:
+                    fd.setFontStretch( "SemiExpanded" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_EXPANDED:
+                    fd.setFontStretch( "Expanded" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_EXTRA_EXPANDED:
+                    fd.setFontStretch( "ExtraExpanded" );
+                    break;
+                case OS2WindowsMetricsTable.WIDTH_CLASS_ULTRA_EXPANDED:
+                    fd.setFontStretch( "UltraExpanded" );
+                    break;
+                default:
+                    //do nothing
+            }
+            fd.setFontWeight( os2.getWeightClass() );
+
+            //todo retval.setFixedPitch
+            //todo retval.setNonSymbolic
+            //todo retval.setItalic
+            //todo retval.setAllCap
+            //todo retval.setSmallCap
+            //todo retval.setForceBold
+
+            HeaderTable header = ttf.getHeader();
+            PDRectangle rect = new PDRectangle();
+            float scaling = 1000f/header.getUnitsPerEm();
+            rect.setLowerLeftX( header.getXMin() * 1000f/header.getUnitsPerEm() );
+            rect.setLowerLeftY( header.getYMin() * 1000f/header.getUnitsPerEm() );
+            rect.setUpperRightX( header.getXMax() * 1000f/header.getUnitsPerEm() );
+            rect.setUpperRightY( header.getYMax() * 1000f/header.getUnitsPerEm() );
+            fd.setFontBoundingBox( rect );
+
+            HorizontalHeaderTable hHeader = ttf.getHorizontalHeader();
+            fd.setAscent( hHeader.getAscender() * 1000f/header.getUnitsPerEm() );
+            fd.setDescent( hHeader.getDescender() * 1000f/header.getUnitsPerEm() );
+
+            GlyphTable glyphTable = ttf.getGlyph();
+            GlyphData[] glyphs = glyphTable.getGlyphs();
+
+            PostScriptTable ps = ttf.getPostScript();
+            fd.setFixedPitch( ps.getIsFixedPitch() > 0 );
+            fd.setItalicAngle( ps.getItalicAngle() );
+
+            String[] names = ps.getGlyphNames();
+            if( names != null )
+            {
+                for( int i=0; i<names.length; i++ )
+                {
+                    //if we have a capital H then use that, otherwise use the
+                    //tallest letter
+                    if( names[i].equals( "H" ) )
+                    {
+                        fd.setCapHeight( (glyphs[i].getBoundingBox().getUpperRightY()* 1000f)/
+                                         header.getUnitsPerEm() );
+                    }
+                    if( names[i].equals( "x" ) )
+                    {
+                        fd.setXHeight( (glyphs[i].getBoundingBox().getUpperRightY()* 1000f)/header.getUnitsPerEm() );
+                    }
+                }
+            }
+
+            //hmm there does not seem to be a clear definition for StemV,
+            //this is close enough and I am told it doesn't usually get used.
+            fd.setStemV( (fd.getFontBoundingBox().getWidth() * .13f) );
+
+
+            CMAPTable cmapTable = ttf.getCMAP();
+            CMAPEncodingEntry[] cmaps = cmapTable.getCmaps();
+            int[] glyphToCCode = null;
+            for( int i=0; i<cmaps.length; i++ )
+            {
+                if( cmaps[i].getPlatformId() == CMAPTable.PLATFORM_WINDOWS &&
+                    cmaps[i].getPlatformEncodingId() == CMAPTable.ENCODING_UNICODE )
+                {
+                    glyphToCCode = cmaps[i].getGlyphIdToCharacterCode();
+                }
+            }
+            int firstChar = 0;
+            int maxWidths = glyphToCCode.length;
+            HorizontalMetricsTable hMet = ttf.getHorizontalMetrics();
+            int[] widthValues = hMet.getAdvanceWidth();
+            List<Float> widths = new ArrayList<Float>(maxWidths);
+            float zero = 250;
+            for( int i=0; i<maxWidths; i++ )
+            {
+                widths.add( zero );
+            }
+            for( int i=0; i<widthValues.length; i++ )
+            {
+                if(glyphToCCode[i]-firstChar < widths.size() && glyphToCCode[i]-firstChar >= 0 
+                        && widths.get( glyphToCCode[i]-firstChar) == zero )
+                {
+                    widths.set( glyphToCCode[i]-firstChar, widthValues[i]*scaling );
+                }
+            }
+            setWidths( widths );
+            setFirstChar( firstChar );
+            setLastChar( firstChar + widths.size()-1 );
+
+        }
+        finally
+        {
+            if( ttf != null )
+            {
+                ttf.close();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Font getawtFont() throws IOException
+    {
+         PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary)getFontDescriptor();
+        if( awtFont == null )
+        {
+            PDStream ff2Stream = fd.getFontFile2();
+            if( ff2Stream != null )
+            {
+                try
+                {
+                    // create a font with the embedded data
+                    awtFont = Font.createFont( Font.TRUETYPE_FONT, ff2Stream.createInputStream() );
+                }
+                catch( FontFormatException f )
+                {
+                    log.info("Can't read the embedded font " + fd.getFontName() );
+                }
+                if (awtFont == null)
+                {
+                    awtFont = FontManager.getAwtFont(fd.getFontName());
+                    if (awtFont != null)
+                    {
+                        log.info("Using font "+awtFont.getName()+ " instead");
+                    }
+                }
+            }
+            else
+            {
+                // check if the font is part of our environment
+                awtFont = FontManager.getAwtFont(fd.getFontName());
+                if (awtFont == null)
+                {
+                    log.info("Can't find the specified font " + fd.getFontName() );
+                    // check if there is a font mapping for an external font file
+                    TrueTypeFont ttf = getExternalFontFile2( fd );
+                    if( ttf != null )
+                    {
+                        try
+                        {
+                            awtFont = Font.createFont( Font.TRUETYPE_FONT, ttf.getOriginalData() );
+                        }
+                        catch( FontFormatException f )
+                        {
+                            log.info("Can't read the external fontfile " + fd.getFontName() );
+                        }
+                    }
+                }
+            }
+            if (awtFont == null)
+            {
+                // we can't find anything, so we have to use the standard font
+                awtFont = FontManager.getStandardFont();
+                log.info("Using font "+awtFont.getName()+ " instead");
+            }
+        }
+        return awtFont;
+    }
+
+    private InputStream getExternalTTFData() throws IOException
+    {
+        String ttfResource = externalFonts.getProperty( UNKNOWN_FONT );
+        String baseFont = getBaseFont();
+        if( baseFont != null && externalFonts.containsKey(baseFont) )
+        {
+            ttfResource = externalFonts.getProperty( baseFont );
+        }
+        return (ttfResource != null ? ResourceLoader.loadResource(ttfResource) : null);
+    }
+
+    /**
+     * Permit to load an external TTF Font program file
+     *
+     * Created by Pascal Allain
+     * Vertical7 Inc.
+     *
+     * @param fd The font descriptor currently used
+     * @return A PDStream with the Font File program, null if fd is null
+     * @throws IOException If the font is not found
+     */
+    private TrueTypeFont getExternalFontFile2(PDFontDescriptorDictionary fd)
+        throws IOException
+    {
+        TrueTypeFont retval = null;
+
+        if ( fd != null )
+        {
+            String baseFont = getBaseFont();
+            String fontResource = externalFonts.getProperty( UNKNOWN_FONT );
+            if( (baseFont != null) &&
+                 (externalFonts.containsKey(baseFont)) )
+            {
+                fontResource = externalFonts.getProperty(baseFont);
+            }
+            if( fontResource != null )
+            {
+                retval = (TrueTypeFont)loadedExternalFonts.get( baseFont );
+                if( retval == null )
+                {
+                    TTFParser ttfParser = new TTFParser();
+                    InputStream fontStream = ResourceLoader.loadResource( fontResource );
+                    if( fontStream == null )
+                    {
+                        throw new IOException( "Error missing font resource '" + externalFonts.get(baseFont) + "'" );
+                    }
+                    retval = ttfParser.parseTTF( fontStream );
+                    loadedExternalFonts.put( baseFont, retval );
+                }
+            }
+        }
+
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType0Font.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType0Font.java
new file mode 100644 (file)
index 0000000..fcce352
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.awt.Font;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This is implementation of the Type0 Font. 
+ * See <a href="https://issues.apache.org/jira/browse/PDFBOX-605">PDFBOX-605</a>
+ * for the related improvement issue.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class PDType0Font extends PDSimpleFont
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDType0Font.class);
+
+    private COSArray descendantFontArray;
+    private PDFont descendentFont;
+    private COSDictionary descendantFontDictionary;
+    private Font awtFont;
+    /**
+     * Constructor.
+     */
+    public PDType0Font()
+    {
+        super();
+        font.setItem( COSName.SUBTYPE, COSName.TYPE0 );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDType0Font( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+        descendantFontDictionary = (COSDictionary)getDescendantFonts().getObject( 0 );
+        if (descendantFontDictionary != null)
+        {
+            try 
+            {
+                descendentFont = PDFontFactory.createFont( descendantFontDictionary );
+            }
+            catch (IOException exception)
+            {
+                log.error("Error while creating the descendant font!");
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Font getawtFont() throws IOException
+    {
+        if (awtFont == null)
+        {
+            if (descendentFont != null) 
+            {
+                awtFont = ((PDSimpleFont)descendentFont).getawtFont();
+            }
+            if (awtFont == null)
+            {
+                awtFont = FontManager.getStandardFont();
+                log.info("Using font "+awtFont.getName()+ " instead of "+descendentFont.getFontDescriptor().getFontName());
+            }
+        }
+        return awtFont;
+    }
+    
+    /**
+     * This will get the fonts bounding box.
+     *
+     * @return The fonts bounding box.
+     *
+     * @throws IOException If there is an error getting the bounding box.
+     */
+    public PDRectangle getFontBoundingBox() throws IOException
+    {
+        throw new RuntimeException( "Not yet implemented" );
+    }
+
+    /**
+     * This will get the font width for a character.
+     *
+     * @param c The character code to get the width for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getFontWidth( byte[] c, int offset, int length ) throws IOException
+    {
+        return descendentFont.getFontWidth( c, offset, length );
+    }
+
+    /**
+     * This will get the font height for a character.
+     *
+     * @param c The character code to get the height for.
+     * @param offset The offset into the array.
+     * @param length The length of the data.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getFontHeight( byte[] c, int offset, int length ) throws IOException
+    {
+        return descendentFont.getFontHeight( c, offset, length );
+    }
+
+    /**
+     * This will get the average font width for all characters.
+     *
+     * @return The width is in 1000 unit of text space, ie 333 or 777
+     *
+     * @throws IOException If an error occurs while parsing.
+     */
+    public float getAverageFontWidth() throws IOException
+    {
+        return descendentFont.getAverageFontWidth();
+    }
+    private COSArray getDescendantFonts()
+    {
+        if (descendantFontArray == null)
+        {
+            descendantFontArray = (COSArray)font.getDictionaryObject( COSName.DESCENDANT_FONTS );
+        }
+        return descendantFontArray;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public float getFontWidth( int charCode ) 
+    {
+        return descendentFont.getFontWidth(charCode);
+    }
+
+    @Override
+    public String encode(byte[] c, int offset, int length) throws IOException
+    {
+        if (hasToUnicode())
+        {
+            return super.encode(c, offset, length);
+        }
+        else
+        {
+            int result = cmap.lookupCID(c, offset, length);
+            if (result != -1) 
+            {
+                return descendentFont.cmapEncoding(result, 2, true);
+            }
+            return null;
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java
new file mode 100644 (file)
index 0000000..18e72fe
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.fontbox.afm.AFMParser;
+import org.apache.fontbox.afm.CharMetric;
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.fontbox.pfb.PfbParser;
+
+import org.apache.pdfbox.encoding.AFMEncoding;
+import org.apache.pdfbox.encoding.DictionaryEncoding;
+import org.apache.pdfbox.encoding.Encoding;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is implementation of the Type1 Font
+ * with a afm and a pfb file.
+ *
+ * @author <a href="mailto:m.g.n@gmx.de">Michael Niedermair</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDType1AfmPfbFont extends PDType1Font
+{
+    /**
+     * the buffersize.
+     */
+    private static final int BUFFERSIZE = 0xffff;
+
+    /**
+     * The font metric.
+     */
+    private FontMetric metric;
+
+    /**
+     * Create a new object.
+     * @param doc       The PDF document that will hold the embedded font.
+     * @param afmname   The font filename.
+     * @throws IOException If there is an error loading the data.
+     */
+    public PDType1AfmPfbFont(final PDDocument doc, final String afmname)
+        throws IOException
+    {
+
+        super();
+        InputStream afmin = new BufferedInputStream(new FileInputStream(afmname), BUFFERSIZE);
+        String pfbname = afmname.replaceAll(".AFM", "").replaceAll(".afm", "") + ".pfb";
+        InputStream pfbin = new BufferedInputStream(new FileInputStream(pfbname), BUFFERSIZE);
+        load(doc, afmin, pfbin);
+    }
+
+    /**
+     * Create a new object.
+     * @param doc       The PDF document that will hold the embedded font.
+     * @param afm       The afm input.
+     * @param pfb       The pfb input.
+     * @throws IOException If there is an error loading the data.
+     */
+    public PDType1AfmPfbFont(final PDDocument doc, final InputStream afm, final InputStream pfb)
+        throws IOException
+    {
+        super();
+        load(doc, afm, pfb);
+    }
+
+    /**
+     * This will load a afm and pfb to be embedding into a document.
+     *
+     * @param doc   The PDF document that will hold the embedded font.
+     * @param afm   The afm input.
+     * @param pfb   The pfb input.
+     * @throws IOException If there is an error loading the data.
+     */
+    private void load(final PDDocument doc, final InputStream afm,
+            final InputStream pfb) throws IOException
+    {
+
+        PDFontDescriptorDictionary fd = new PDFontDescriptorDictionary();
+        setFontDescriptor(fd);
+
+        // read the pfb
+        PfbParser pfbparser = new PfbParser(pfb);
+        pfb.close();
+
+        PDStream fontStream = new PDStream(doc, pfbparser.getInputStream(),
+                false);
+        fontStream.getStream().setInt("Length", pfbparser.size());
+        for (int i = 0; i < pfbparser.getLengths().length; i++)
+        {
+            fontStream.getStream().setInt("Length" + (i + 1),
+                    pfbparser.getLengths()[i]);
+        }
+        fontStream.addCompression();
+        fd.setFontFile(fontStream);
+
+        // read the afm
+        AFMParser parser = new AFMParser(afm);
+        parser.parse();
+        metric = parser.getResult();
+        setFontEncoding(afmToDictionary(new AFMEncoding(metric)));
+
+        // set the values
+        setBaseFont(metric.getFontName());
+        fd.setFontName(metric.getFontName());
+        fd.setFontFamily(metric.getFamilyName());
+        fd.setNonSymbolic(true);
+        fd.setFontBoundingBox(new PDRectangle(metric.getFontBBox()));
+        fd.setItalicAngle(metric.getItalicAngle());
+        fd.setAscent(metric.getAscender());
+        fd.setDescent(metric.getDescender());
+        fd.setCapHeight(metric.getCapHeight());
+        fd.setXHeight(metric.getXHeight());
+        fd.setAverageWidth(metric.getAverageCharacterWidth());
+        fd.setCharacterSet(metric.getCharacterSet());
+
+        // get firstchar, lastchar
+        int firstchar = 255;
+        int lastchar = 0;
+
+        // widths
+        List<CharMetric> listmetric = metric.getCharMetrics();
+        Encoding encoding = getFontEncoding();
+        int maxWidths = 256;
+        List<Float> widths = new ArrayList<Float>(maxWidths);
+        float zero = 250;
+        Iterator<CharMetric> iter = listmetric.iterator();
+        for( int i=0; i<maxWidths; i++ )
+        {
+            widths.add(zero);
+        }
+        while (iter.hasNext())
+        {
+            CharMetric m = iter.next();
+            int n = m.getCharacterCode();
+            if (n > 0)
+            {
+                firstchar = Math.min(firstchar, n);
+                lastchar = Math.max(lastchar, n);
+                if (m.getWx() > 0)
+                {
+                    float width = m.getWx();
+                    widths.set(n,new Float(width));
+                    // germandbls has 2 character codes !! Don't ask me why .....
+                    // StandardEncoding = 0373 = 251
+                    // WinANSIEncoding = 0337 = 223
+                    if (m.getName().equals("germandbls") && n != 223)
+                    {
+                        widths.set(0337,new Float(width));
+                    }
+                }
+            }
+            else 
+            {
+                // my AFMPFB-Fonts has no character-codes for german umlauts
+                // so that I've to add them here by hand
+                if (m.getName().equals("adieresis"))
+                {
+                    widths.set(0344,(Float)widths.get(encoding.getCode("a")));
+                }
+                else if (m.getName().equals("odieresis"))
+                {
+                    widths.set(0366,(Float)widths.get(encoding.getCode("o")));
+                }
+                else if (m.getName().equals("udieresis"))
+                {
+                    widths.set(0374,(Float)widths.get(encoding.getCode("u")));
+                }
+                else if (m.getName().equals("Adieresis"))
+                {
+                    widths.set(0304,(Float)widths.get(encoding.getCode("A")));
+                }
+                else if (m.getName().equals("Odieresis"))
+                {
+                    widths.set(0326,(Float)widths.get(encoding.getCode("O")));
+                }
+                else if (m.getName().equals("Udieresis"))
+                {
+                    widths.set(0334,(Float)widths.get(encoding.getCode("U")));
+                }
+            }
+        }
+        setFirstChar(0);
+        setLastChar(255);
+        setWidths(widths);
+    }
+
+    /*
+     *  This will generate a Encoding from the AFM-Encoding, because the AFM-Enconding isn't exported to the pdf
+     *  and consequently the StandardEncoding is used so that any special character is missing
+     *  I've copied the code from the pdfbox-forum posted by V0JT4 and made some additions concerning german umlauts
+     *  see also https://sourceforge.net/forum/message.php?msg_id=4705274
+     */
+    private DictionaryEncoding afmToDictionary(AFMEncoding encoding) throws java.io.IOException 
+    {
+        COSArray array = new COSArray();
+        array.add(COSInteger.ZERO);
+        for (int i = 0; i < 256; i++) 
+        {
+            array.add(COSName.getPDFName(encoding.getName(i)));
+        }
+        // my AFMPFB-Fonts has no character-codes for german umlauts
+        // so that I've to add them here by hand
+        array.set( 0337+1, COSName.getPDFName("germandbls"));
+        array.set( 0344+1, COSName.getPDFName("adieresis"));
+        array.set( 0366+1, COSName.getPDFName("odieresis"));
+        array.set( 0374+1, COSName.getPDFName("udieresis"));
+        array.set( 0304+1, COSName.getPDFName("Adieresis"));
+        array.set( 0326+1, COSName.getPDFName("Odieresis"));
+        array.set( 0334+1, COSName.getPDFName("Udieresis"));
+
+        COSDictionary dictionary = new COSDictionary();
+        dictionary.setItem(COSName.NAME, COSName.ENCODING);
+        dictionary.setItem(COSName.DIFFERENCES, array);
+        dictionary.setItem(COSName.BASE_ENCODING, COSName.STANDARD_ENCODING);
+        return new DictionaryEncoding(dictionary);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1CFont.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1CFont.java
new file mode 100644 (file)
index 0000000..67c8690
--- /dev/null
@@ -0,0 +1,608 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fontbox.afm.AFMParser;
+import org.apache.fontbox.afm.FontMetric;
+import org.apache.fontbox.cff.AFMFormatter;
+import org.apache.fontbox.cff.charset.CFFCharset;
+import org.apache.fontbox.cff.encoding.CFFEncoding;
+import org.apache.fontbox.cff.CFFFont;
+import org.apache.fontbox.cff.CFFParser;
+import org.apache.fontbox.cff.Type1FontFormatter;
+import org.apache.fontbox.util.BoundingBox;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.encoding.Encoding;
+import org.apache.pdfbox.encoding.EncodingManager;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This class represents a CFF/Type2 Font (aka Type1C Font).
+ * @author Villu Ruusmann
+ * @version $Revision: 10.0$
+ */
+public class PDType1CFont extends PDSimpleFont
+{
+    private CFFFont cffFont = null;
+
+    private Map<Integer, String> codeToName = new HashMap<Integer, String>();
+
+    private Map<Integer, String> codeToCharacter = new HashMap<Integer, String>();
+
+    private Map<String, Integer> characterToCode = new HashMap<String, Integer>();
+
+    private FontMetric fontMetric = null;
+
+    private Font awtFont = null;
+
+    private Map<String, Float> glyphWidths = new HashMap<String, Float>();
+
+    private Map<String, Float> glyphHeights = new HashMap<String, Float>();
+
+    private Float avgWidth = null;
+
+    private PDRectangle fontBBox = null;
+
+    private static final Log log = LogFactory.getLog(PDType1CFont.class);
+
+    private static final byte[] SPACE_BYTES = {(byte)32};
+
+    private COSDictionary fontDict = null;
+    
+    /**
+     * Constructor.
+     * @param fontDictionary the corresponding dictionary
+     */
+    public PDType1CFont( COSDictionary fontDictionary ) throws IOException
+    {
+        super( fontDictionary );
+        fontDict = fontDictionary;
+        load();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String encode( byte[] bytes, int offset, int length ) throws IOException
+    {
+        String character = getCharacter(bytes, offset, length);
+        if( character == null )
+        {
+            log.debug("No character for code " + (bytes[offset] & 0xff) + " in " + this.cffFont.getName());
+            return null;
+        }
+
+        return character;
+    }
+
+    private String getCharacter( byte[] bytes, int offset, int length )
+    {
+        if (length > 2)
+        {
+            return null;
+        }
+        int code = bytes[offset] & 0xff;
+        if (length == 2)
+        {
+            code = code * 256 + bytes[offset+1] & 0xff;
+        }
+        return (String)this.codeToCharacter.get(code);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float getFontWidth( byte[] bytes, int offset, int length ) throws IOException
+    {
+        String name = getName(bytes, offset, length);
+        if( name == null && !Arrays.equals(SPACE_BYTES, bytes) )
+        {
+            log.debug("No name for code " + (bytes[offset] & 0xff) + " in " + this.cffFont.getName());
+
+            return 0;
+        }
+
+        Float width = (Float)this.glyphWidths.get(name);
+        if( width == null )
+        {
+            width = Float.valueOf(getFontMetric().getCharacterWidth(name));
+            this.glyphWidths.put(name, width);
+        }
+
+        return width.floatValue();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float getFontHeight( byte[] bytes, int offset, int length ) throws IOException
+    {
+        String name = getName(bytes, offset, length);
+        if( name == null )
+        {
+            log.debug("No name for code " + (bytes[offset] & 0xff) + " in " + this.cffFont.getName());
+
+            return 0;
+        }
+
+        Float height = (Float)this.glyphHeights.get(name);
+        if( height == null )
+        {
+            height = Float.valueOf(getFontMetric().getCharacterHeight(name));
+            this.glyphHeights.put(name, height);
+        }
+
+        return height.floatValue();
+    }
+
+    private String getName( byte[] bytes, int offset, int length )
+    {
+        if (length > 2)
+        {
+            return null;
+        }
+        
+        int code = bytes[offset] & 0xff;
+        if (length == 2)
+        {
+            code = code * 256 + bytes[offset+1] & 0xff;
+        }
+
+        return (String)this.codeToName.get(code);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float getStringWidth( String string ) throws IOException
+    {
+        float width = 0;
+
+        for( int i = 0; i < string.length(); i++ )
+        {
+            String character = string.substring(i, i + 1);
+
+            Integer code = getCode(character);
+            if( code == null )
+            {
+                log.debug("No code for character " + character);
+
+                return 0;
+            }
+
+            width += getFontWidth(new byte[]{(byte)code.intValue()}, 0, 1);
+        }
+
+        return width;
+    }
+
+    private Integer getCode( String character )
+    {
+        return (Integer)this.characterToCode.get(character);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public float getAverageFontWidth() throws IOException
+    {
+        if( this.avgWidth == null )
+        {
+            this.avgWidth = Float.valueOf(getFontMetric().getAverageCharacterWidth());
+        }
+
+        return this.avgWidth.floatValue();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PDRectangle getFontBoundingBox() throws IOException
+    {
+        if( this.fontBBox == null )
+        {
+            this.fontBBox = new PDRectangle(getFontMetric().getFontBBox());
+        }
+
+        return this.fontBBox;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PDMatrix getFontMatrix()
+    {
+        if( fontMatrix == null )
+        {
+            List<Number> numbers = (List<Number>)this.cffFont.getProperty("FontMatrix");
+            if( numbers != null && numbers.size() == 6 )
+            {
+                COSArray array = new COSArray();
+                for(Number number : numbers)
+                {
+                    array.add(new COSFloat(number.floatValue()));
+                }
+                fontMatrix = new PDMatrix(array);
+            }
+            else
+            {
+                super.getFontMatrix();
+            }
+        }
+        return fontMatrix;
+    }
+
+    /**
+     * {@inheritDoc}
+     */    
+    public Font getawtFont() throws IOException
+    {
+        if (awtFont == null)
+        {
+            this.awtFont = prepareAwtFont(this.cffFont);
+        }
+        return awtFont;
+    }
+    
+    private FontMetric getFontMetric() 
+    {
+        if (fontMetric == null)
+        {
+            try
+            {
+                fontMetric = prepareFontMetric(cffFont);
+            }
+            catch (IOException exception)
+            {
+                log.error("An error occured while extracting the font metrics!", exception);
+            }
+        }
+        return fontMetric;
+    }
+
+    private void load() throws IOException
+    {
+        byte[] cffBytes = loadBytes();
+
+        CFFParser cffParser = new CFFParser();
+        List<CFFFont> fonts = cffParser.parse(cffBytes);
+
+        this.cffFont = (CFFFont)fonts.get(0);
+
+        CFFEncoding encoding = this.cffFont.getEncoding();
+        PDFEncoding pdfEncoding = new PDFEncoding(encoding);
+
+        CFFCharset charset = this.cffFont.getCharset();
+        PDFCharset pdfCharset = new PDFCharset(charset);
+
+        Map<String,byte[]> charStringsDict = this.cffFont.getCharStringsDict();
+        Map<String,byte[]> pdfCharStringsDict = new LinkedHashMap<String,byte[]>();
+        pdfCharStringsDict.put(".notdef", charStringsDict.get(".notdef"));
+
+        Map<Integer,String> codeToNameMap = new LinkedHashMap<Integer,String>();
+
+        Collection<CFFFont.Mapping> mappings = this.cffFont.getMappings();
+        for( Iterator<CFFFont.Mapping> it = mappings.iterator(); it.hasNext();)
+        {
+            CFFFont.Mapping mapping = it.next();
+            Integer code = Integer.valueOf(mapping.getCode());
+            String name = mapping.getName();
+            codeToNameMap.put(code, name);
+        }
+
+        Set<String> knownNames = new HashSet<String>(codeToNameMap.values());
+
+        Map<Integer,String> codeToNameOverride = loadOverride();
+        for( Iterator<Map.Entry<Integer, String>> it = (codeToNameOverride.entrySet()).iterator(); it.hasNext();)
+        {
+            Map.Entry<Integer, String> entry = it.next();
+            Integer code = (Integer)entry.getKey();
+            String name = (String)entry.getValue();
+            if(knownNames.contains(name))
+            {
+                codeToNameMap.put(code, name);
+            }
+        }
+
+        Map nameToCharacter;
+        try
+        {
+            // TODO remove access by reflection
+            Field nameToCharacterField = Encoding.class.getDeclaredField("NAME_TO_CHARACTER");
+            nameToCharacterField.setAccessible(true);
+            nameToCharacter = (Map)nameToCharacterField.get(null);
+        }
+        catch( Exception e )
+        {
+            throw new RuntimeException(e);
+        }
+
+        for( Iterator<Map.Entry<Integer,String>> it = (codeToNameMap.entrySet()).iterator(); it.hasNext();)
+        {
+            Map.Entry<Integer,String> entry = it.next();
+            Integer code = (Integer)entry.getKey();
+            String name = (String)entry.getValue();
+            String uniName = "uni";
+            String character = (String)nameToCharacter.get(name);
+            if( character != null )
+            {
+                for( int j = 0; j < character.length(); j++ )
+                {
+                    uniName += hexString(character.charAt(j), 4);
+                }
+            }
+            else
+            {
+                uniName += hexString(code.intValue(), 4);
+                character = String.valueOf((char)code.intValue());
+            }
+            pdfEncoding.register(code.intValue(), code.intValue());
+            pdfCharset.register(code.intValue(), uniName);
+            this.codeToName.put(code, uniName);
+            this.codeToCharacter.put(code, character);
+            this.characterToCode.put(character, code);
+            pdfCharStringsDict.put(uniName, charStringsDict.get(name));
+        }
+
+        this.cffFont.setEncoding(pdfEncoding);
+        this.cffFont.setCharset(pdfCharset);
+        charStringsDict.clear();
+        charStringsDict.putAll(pdfCharStringsDict);
+        Number defaultWidthX = (Number)this.cffFont.getProperty("defaultWidthX");
+        this.glyphWidths.put(null, Float.valueOf(defaultWidthX.floatValue()));
+    }
+
+    private byte[] loadBytes() throws IOException
+    {
+        PDFontDescriptor fd = getFontDescriptor();
+        if( fd != null && fd instanceof PDFontDescriptorDictionary)
+        {
+            PDStream ff3Stream = ((PDFontDescriptorDictionary)fd).getFontFile3();
+            if( ff3Stream != null )
+            {
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+                InputStream is = ff3Stream.createInputStream();
+                try
+                {
+                    byte[] buf = new byte[512];
+                    while(true)
+                    {
+                        int count = is.read(buf);
+                        if( count < 0 )
+                        {
+                            break;
+                        }
+                        os.write(buf, 0, count);
+                    }
+                }
+                finally
+                {
+                    is.close();
+                }
+
+                return os.toByteArray();
+            }
+        }
+
+        throw new IOException();
+    }
+
+    private Map<Integer,String> loadOverride() throws IOException
+    {
+        Map<Integer,String> result = new LinkedHashMap<Integer,String>();
+        COSBase encoding = fontDict.getDictionaryObject(COSName.ENCODING);
+        if( encoding instanceof COSName )
+        {
+            COSName name = (COSName)encoding;
+            result.putAll(loadEncoding(name));
+        }
+        else if( encoding instanceof COSDictionary )
+        {
+            COSDictionary encodingDic = (COSDictionary)encoding;
+            COSName baseName = (COSName)encodingDic.getDictionaryObject(COSName.BASE_ENCODING);
+            if( baseName != null )
+            {
+                result.putAll(loadEncoding(baseName));
+            }
+            COSArray differences = (COSArray)encodingDic.getDictionaryObject(COSName.DIFFERENCES);
+            if( differences != null )
+            {
+                result.putAll(loadDifferences(differences));
+            }
+        }
+
+        return result;
+    }
+
+    private Map<Integer,String> loadEncoding(COSName name) throws IOException
+    {
+        Map<Integer,String> result = new LinkedHashMap<Integer,String>();
+        Encoding encoding = EncodingManager.INSTANCE.getEncoding(name);
+        for( Iterator<Map.Entry<Integer,String>> it = (encoding.getCodeToNameMap().entrySet()).iterator();
+                    it.hasNext();)
+        {
+            Map.Entry<Integer,String> entry = it.next();
+            result.put(entry.getKey(), (entry.getValue()));
+        }
+
+        return result;
+    }
+
+    private Map<Integer,String> loadDifferences(COSArray differences)
+    {
+        Map<Integer,String> result = new LinkedHashMap<Integer,String>();
+        Integer code = null;
+        for( int i = 0; i < differences.size(); i++)
+        {
+            COSBase element = differences.get(i);
+            if( element instanceof COSNumber )
+            {
+                COSNumber number = (COSNumber)element;
+                code = Integer.valueOf(number.intValue());
+            } 
+            else 
+            {
+                if( element instanceof COSName )
+                {
+                    COSName name = (COSName)element;
+                    result.put(code, name.getName());
+                    code = Integer.valueOf(code.intValue() + 1);
+                }
+            }
+        }
+        return result;
+    }
+
+    
+    private static String hexString( int code, int length )
+    {
+        String string = Integer.toHexString(code);
+        while(string.length() < length)
+        {
+            string = ("0" + string);
+        }
+
+        return string;
+    }
+
+    private FontMetric prepareFontMetric( CFFFont font ) throws IOException
+    {
+        byte[] afmBytes = AFMFormatter.format(font);
+
+        InputStream is = new ByteArrayInputStream(afmBytes);
+        try
+        {
+            AFMParser afmParser = new AFMParser(is);
+            afmParser.parse();
+
+            FontMetric result = afmParser.getResult();
+
+            // Replace default FontBBox value with a newly computed one
+            BoundingBox bounds = result.getFontBBox();
+            List<Integer> numbers = Arrays.asList(
+                    Integer.valueOf((int)bounds.getLowerLeftX()),
+                    Integer.valueOf((int)bounds.getLowerLeftY()),
+                    Integer.valueOf((int)bounds.getUpperRightX()),
+                    Integer.valueOf((int)bounds.getUpperRightY())
+                );
+            font.addValueToTopDict("FontBBox", numbers);
+
+            return result;
+        }
+        finally
+        {
+            is.close();
+        }
+    }
+
+    private static Font prepareAwtFont( CFFFont font ) throws IOException
+    {
+        byte[] type1Bytes = Type1FontFormatter.format(font);
+
+        InputStream is = new ByteArrayInputStream(type1Bytes);
+        try
+        {
+            return Font.createFont(Font.TYPE1_FONT, is);
+        }
+        catch( FontFormatException ffe )
+        {
+            throw new WrappedIOException(ffe);
+        }
+        finally
+        {
+            is.close();
+        }
+    }
+
+    /**
+     * This class represents a PDFEncoding.
+     *
+     */
+    private static class PDFEncoding extends CFFEncoding
+    {
+
+        private PDFEncoding( CFFEncoding parent )
+        {
+            Iterator<Entry> parentEntries = parent.getEntries().iterator();
+            while(parentEntries.hasNext())
+            {
+                addEntry(parentEntries.next());
+            }
+        }
+
+        public boolean isFontSpecific()
+        {
+            return true;
+        }
+
+    }
+
+    /**
+     * This class represents a PDFCharset.
+     *
+     */
+    private static class PDFCharset extends CFFCharset
+    {
+        private PDFCharset( CFFCharset parent )
+        {
+            Iterator<Entry> parentEntries = parent.getEntries().iterator();
+            while(parentEntries.hasNext())
+            {
+                addEntry(parentEntries.next());
+            }
+        }
+
+        public boolean isFontSpecific()
+        {
+            return true;
+        }
+
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1Font.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType1Font.java
new file mode 100644 (file)
index 0000000..7c30324
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.afm.FontMetric;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.encoding.AFMEncoding;
+import org.apache.pdfbox.encoding.Encoding;
+import org.apache.pdfbox.encoding.EncodingManager;
+import org.apache.pdfbox.encoding.Type1Encoding;
+import org.apache.pdfbox.encoding.WinAnsiEncoding;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This is implementation of the Type1 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public class PDType1Font extends PDSimpleFont
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDType1Font.class);
+
+    private PDType1CFont type1CFont = null;
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font TIMES_ROMAN = new PDType1Font( "Times-Roman" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font TIMES_BOLD = new PDType1Font( "Times-Bold" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font TIMES_ITALIC = new PDType1Font( "Times-Italic" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font TIMES_BOLD_ITALIC = new PDType1Font( "Times-BoldItalic" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font HELVETICA = new PDType1Font( "Helvetica" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font HELVETICA_BOLD = new PDType1Font( "Helvetica-Bold" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font HELVETICA_OBLIQUE = new PDType1Font( "Helvetica-Oblique" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font HELVETICA_BOLD_OBLIQUE = new PDType1Font( "Helvetica-BoldOblique" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font COURIER = new PDType1Font( "Courier" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font COURIER_BOLD = new PDType1Font( "Courier-Bold" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font COURIER_OBLIQUE = new PDType1Font( "Courier-Oblique" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font COURIER_BOLD_OBLIQUE = new PDType1Font( "Courier-BoldOblique" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font SYMBOL = new PDType1Font( "Symbol" );
+    /**
+     * Standard Base 14 Font.
+     */
+    public static final PDType1Font ZAPF_DINGBATS = new PDType1Font( "ZapfDingbats" );
+
+    private static final Map<String, PDType1Font> STANDARD_14 = new HashMap<String, PDType1Font>();
+    static
+    {
+        STANDARD_14.put( TIMES_ROMAN.getBaseFont(), TIMES_ROMAN );
+        STANDARD_14.put( TIMES_BOLD.getBaseFont(), TIMES_BOLD );
+        STANDARD_14.put( TIMES_ITALIC.getBaseFont(), TIMES_ITALIC );
+        STANDARD_14.put( TIMES_BOLD_ITALIC.getBaseFont(), TIMES_BOLD_ITALIC );
+        STANDARD_14.put( HELVETICA.getBaseFont(), HELVETICA );
+        STANDARD_14.put( HELVETICA_BOLD.getBaseFont(), HELVETICA_BOLD );
+        STANDARD_14.put( HELVETICA_OBLIQUE.getBaseFont(), HELVETICA_OBLIQUE );
+        STANDARD_14.put( HELVETICA_BOLD_OBLIQUE.getBaseFont(), HELVETICA_BOLD_OBLIQUE );
+        STANDARD_14.put( COURIER.getBaseFont(), COURIER );
+        STANDARD_14.put( COURIER_BOLD.getBaseFont(), COURIER_BOLD );
+        STANDARD_14.put( COURIER_OBLIQUE.getBaseFont(), COURIER_OBLIQUE );
+        STANDARD_14.put( COURIER_BOLD_OBLIQUE.getBaseFont(), COURIER_BOLD_OBLIQUE );
+        STANDARD_14.put( SYMBOL.getBaseFont(), SYMBOL );
+        STANDARD_14.put( ZAPF_DINGBATS.getBaseFont(), ZAPF_DINGBATS );
+    }
+
+    private Font awtFont = null;
+
+    /**
+     * Constructor.
+     */
+    public PDType1Font()
+    {
+        super();
+        font.setItem( COSName.SUBTYPE, COSName.TYPE1 );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDType1Font( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+        PDFontDescriptor fd = getFontDescriptor();
+        if (fd != null && fd instanceof PDFontDescriptorDictionary)
+        {
+            // a Type1 font may contain a Type1C font
+            PDStream fontFile3 = ((PDFontDescriptorDictionary)fd).getFontFile3();
+            if (fontFile3 != null)
+            {
+                try 
+                {
+                    type1CFont = new PDType1CFont( super.font );
+                }
+                catch (IOException exception) 
+                {
+                    log.info("Can't read the embedded type1C font " + fd.getFontName() );
+                }
+            }
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param baseFont The base font for this font.
+     */
+    public PDType1Font( String baseFont )
+    {
+        this();
+        setBaseFont( baseFont );
+        setFontEncoding(new WinAnsiEncoding());
+        setEncoding(COSName.WIN_ANSI_ENCODING);
+    }
+
+    /**
+     * A convenience method to get one of the standard 14 font from name.
+     *
+     * @param name The name of the font to get.
+     *
+     * @return The font that matches the name or null if it does not exist.
+     */
+    public static PDType1Font getStandardFont( String name )
+    {
+        return (PDType1Font)STANDARD_14.get( name );
+    }
+
+    /**
+     * This will get the names of the standard 14 fonts.
+     *
+     * @return An array of the names of the standard 14 fonts.
+     */
+    public static String[] getStandard14Names()
+    {
+        return (String[])STANDARD_14.keySet().toArray( new String[14] );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Font getawtFont() throws IOException
+    {
+        if( awtFont == null )
+        {
+            if (type1CFont != null)
+            {
+                awtFont = type1CFont.getawtFont();
+            }
+            else
+            {
+                String baseFont = getBaseFont();
+                PDFontDescriptor fd = getFontDescriptor();
+                if (fd != null && fd instanceof PDFontDescriptorDictionary)
+                {
+                    PDFontDescriptorDictionary fdDictionary = (PDFontDescriptorDictionary)fd;
+                    if( fdDictionary.getFontFile() != null )
+                    {
+                        try 
+                        {
+                            // create a type1 font with the embedded data
+                            awtFont = Font.createFont( Font.TYPE1_FONT, fdDictionary.getFontFile().createInputStream() );
+                        } 
+                        catch (FontFormatException e) 
+                        {
+                            log.info("Can't read the embedded type1 font " + fd.getFontName() );
+                        }
+                    }
+                    if (awtFont == null)
+                    {
+                        // check if the font is part of our environment
+                        awtFont = FontManager.getAwtFont(fd.getFontName());
+                        if (awtFont == null)
+                        {
+                            log.info("Can't find the specified font " + fd.getFontName() );
+                        }
+                    }
+                }
+                else
+                {
+                    // check if the font is part of our environment
+                    awtFont = FontManager.getAwtFont(baseFont);
+                    if (awtFont == null) 
+                    {
+                        log.info("Can't find the specified basefont " + baseFont );
+                    }
+                }
+            }
+            if (awtFont == null)
+            {
+                // we can't find anything, so we have to use the standard font
+                awtFont = FontManager.getStandardFont();
+                log.info("Using font "+awtFont.getName()+ " instead");
+            }
+        }
+        return awtFont;
+    }
+
+    protected void determineEncoding()
+    {
+        super.determineEncoding();
+        Encoding fontEncoding = getFontEncoding();
+        if(fontEncoding == null)
+        {
+            FontMetric metric = getAFM();
+            if (metric != null)
+            {
+                fontEncoding = new AFMEncoding( metric );
+            }
+            setFontEncoding(fontEncoding);
+        }
+        getEncodingFromFont(getFontEncoding() == null);
+    }
+    
+    /**
+     * Tries to get the encoding for the type1 font.
+     *
+     */
+    private void getEncodingFromFont(boolean extractEncoding)
+    {
+        // This whole section of code needs to be replaced with an actual type1 font parser!!
+        // Get the font program from the embedded type font.
+        PDFontDescriptor fontDescriptor = getFontDescriptor();
+        if( fontDescriptor != null && fontDescriptor instanceof PDFontDescriptorDictionary)
+        {
+            PDStream fontFile = ((PDFontDescriptorDictionary)fontDescriptor).getFontFile();
+            if( fontFile != null )
+            {
+                BufferedReader in = null;
+                try 
+                {
+                    in = new BufferedReader(new InputStreamReader(fontFile.createInputStream()));
+                    
+                    // this section parses the font program stream searching for a /Encoding entry
+                    // if it contains an array of values a Type1Encoding will be returned
+                    // if it encoding contains an encoding name the corresponding Encoding will be returned
+                    String line = "";
+                    Type1Encoding encoding = null;
+                    while( (line = in.readLine()) != null)
+                    {
+                        if (extractEncoding) 
+                        {
+                            if (line.startsWith("currentdict end")) {
+                                if (encoding != null)
+                                    setFontEncoding(encoding);
+                                break;
+                            }
+                            if (line.startsWith("/Encoding")) 
+                            {
+                                if(line.contains("array")) 
+                                {
+                                    StringTokenizer st = new StringTokenizer(line);
+                                    // ignore the first token
+                                    st.nextElement();
+                                    int arraySize = Integer.parseInt(st.nextToken());
+                                    encoding = new Type1Encoding(arraySize);
+                                }
+                                // if there is already an encoding, we don't need to
+                                // assign another one
+                                else if (getFontEncoding() == null)
+                                {
+                                    StringTokenizer st = new StringTokenizer(line);
+                                    // ignore the first token
+                                    st.nextElement();
+                                    String type1Encoding = st.nextToken();
+                                    setFontEncoding(
+                                        EncodingManager.INSTANCE.getEncoding(
+                                                COSName.getPDFName(type1Encoding)));
+                                    break;
+                                }
+                            }
+                            else if (line.startsWith("dup")) {
+                                StringTokenizer st = new StringTokenizer(line.replaceAll("/"," /"));
+                                // ignore the first token
+                                st.nextElement();
+                                int index = Integer.parseInt(st.nextToken());
+                                String name = st.nextToken();
+                                if(encoding == null)
+                                    log.warn("Unable to get character encoding.  Encoding defintion found without /Encoding line.");
+                                else
+                                    encoding.addCharacterEncoding(index, name.replace("/", ""));
+                            }
+                        }
+                        // according to the pdf reference, all font matrices should be same, except for type 3 fonts.
+                        // but obviously there are some type1 fonts with different matrix values, see pdf sample
+                        // attached to PDFBOX-935
+                        if (line.startsWith("/FontMatrix"))
+                        {
+                            String matrixValues = line.substring(line.indexOf("[")+1,line.lastIndexOf("]"));
+                            StringTokenizer st = new StringTokenizer(matrixValues);
+                            COSArray array = new COSArray();
+                            if (st.countTokens() >= 6)
+                            {
+                                try 
+                                {
+                                    for (int i=0;i<6;i++)
+                                    {
+                                        COSFloat floatValue = new COSFloat(Float.parseFloat(st.nextToken()));
+                                        array.add(floatValue);
+                                    }
+                                    fontMatrix = new PDMatrix(array);
+                                }
+                                catch (NumberFormatException exception)
+                                {
+                                    log.error("Can't read the fontmatrix from embedded font file!");
+                                }
+                            }
+                        }
+                    }
+                }
+                catch(IOException exception) 
+                {
+                    log.error("Error: Could not extract the encoding from the embedded type1 font.");
+                }
+                finally
+                {
+                    if (in != null)
+                    {
+                        try
+                        {
+                            in.close();
+                        }
+                        catch(IOException exception) 
+                        {
+                            log.error("An error occurs while closing the stream used to read the embedded type1 font.");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String encode(byte[] c, int offset, int length) throws IOException
+    {
+        if (type1CFont != null && getFontEncoding() == null)
+        {
+            return type1CFont.encode(c, offset, length);
+        }
+        else
+        {
+            return super.encode(c, offset, length);
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public PDMatrix getFontMatrix()
+    {
+        if (type1CFont != null)
+        {
+            return type1CFont.getFontMatrix();
+        }
+        else
+        {
+            return super.getFontMatrix();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType3Font.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/PDType3Font.java
new file mode 100644 (file)
index 0000000..da8eab0
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.geom.AffineTransform;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This is implementation of the Type3 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class PDType3Font extends PDSimpleFont
+{
+    //A map of character code to java.awt.Image for the glyph
+    private Map images = new HashMap();
+
+    /**
+     * Constructor.
+     */
+    public PDType3Font()
+    {
+        super();
+        font.setItem( COSName.SUBTYPE, COSName.TYPE3 );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param fontDictionary The font dictionary according to the PDF specification.
+     */
+    public PDType3Font( COSDictionary fontDictionary )
+    {
+        super( fontDictionary );
+    }
+
+    /**
+     * Type3 fonts have their glyphs defined as a content stream.  This
+     * will create the image that represents that character
+     *
+     * @throws IOException If there is an error creating the image.
+     */
+    private Image createImageIfNecessary( char character ) throws IOException
+    {
+        Character c = new Character( character );
+        Image retval = (Image)images.get( c );
+        if( retval == null )
+        {
+            COSDictionary charProcs = (COSDictionary)font.getDictionaryObject( COSName.CHAR_PROCS );
+            COSStream stream = (COSStream)charProcs.getDictionaryObject( COSName.getPDFName( "" + character ) );
+            if( stream != null )
+            {
+                Type3StreamParser parser = new Type3StreamParser();
+                retval = parser.createImage( stream );
+                images.put( c, retval );
+            }
+            else
+            {
+                //stream should not be null!!
+            }
+        }
+        return retval;
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void drawString( String string, Graphics g, float fontSize, AffineTransform at, float x, float y ) 
+        throws IOException
+    {
+        //if( string.equals( "V" )|| string.equals( "o" ) )
+        {
+            for(int i=0; i<string.length(); i++)
+            {
+                //todo need to use image observers and such
+                char c = string.charAt( i );
+                Image image = createImageIfNecessary( c );
+                if( image != null )
+                {
+                    int newWidth = (int)(.12*image.getWidth(null));
+                    int newHeight = (int)(.12*image.getHeight(null));
+                    if( newWidth > 0 && newHeight > 0 )
+                    {
+                        image = image.getScaledInstance( newWidth, newHeight, Image.SCALE_SMOOTH );
+                        g.drawImage( image, (int)x, (int)y, null );
+                        x+=newWidth;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the font matrix for this type3 font.
+     *
+     * @param matrix The font matrix for this type3 font.
+     */
+    public void setFontMatrix( PDMatrix matrix )
+    {
+        font.setItem( "FontMatrix", matrix );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/Type3StreamParser.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/Type3StreamParser.java
new file mode 100644 (file)
index 0000000..489fffe
--- /dev/null
@@ -0,0 +1,594 @@
+/*
+ * 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.pdfbox.pdmodel.font;
+
+import java.awt.Image;
+
+import java.io.IOException;
+
+import java.util.List;
+
+import org.apache.fontbox.util.BoundingBox;
+
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDInlinedImage;
+
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.PDFStreamEngine;
+
+/**
+ * This class will handle creating an image for a type 3 glyph.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class Type3StreamParser extends PDFStreamEngine
+{
+    private PDInlinedImage image = null;
+    private BoundingBox box = null;
+
+
+    /**
+     * This will parse a type3 stream and create an image from it.
+     *
+     * @param type3Stream The stream containing the operators to draw the image.
+     *
+     * @return The image that was created.
+     *
+     * @throws IOException If there is an error processing the stream.
+     */
+    public Image createImage( COSStream type3Stream ) throws IOException
+    {
+        processStream( null, null, type3Stream );
+        return image.createImage();
+    }
+
+    /**
+     * This is used to handle an operation.
+     *
+     * @param operator The operation to perform.
+     * @param arguments The list of arguments.
+     *
+     * @throws IOException If there is an error processing the operation.
+     */
+    protected void processOperator( PDFOperator operator, List arguments ) throws IOException
+    {
+        super.processOperator( operator, arguments );
+        String operation = operator.getOperation();
+        /**
+        if( operation.equals( "b" ) )
+        {
+            //Close, fill, and stroke path using nonzero winding number rule
+        }
+        else if( operation.equals( "B" ) )
+        {
+            //Fill and stroke path using nonzero winding number rule
+        }
+        else if( operation.equals( "b*" ) )
+        {
+            //Close, fill, and stroke path using even-odd rule
+        }
+        else if( operation.equals( "B*" ) )
+        {
+            //Fill and stroke path using even-odd rule
+        }
+        else if( operation.equals( "BDC" ) )
+        {
+            //(PDF 1.2) Begin marked-content sequence with property list
+        }
+        else **/if( operation.equals( "BI" ) )
+        {
+            ImageParameters params = operator.getImageParameters();
+            image = new PDInlinedImage();
+            image.setImageParameters( params );
+            image.setImageData( operator.getImageData() );
+            //begin inline image object
+        }/**
+        else if( operation.equals( "BMC" ) )
+        {
+            //(PDF 1.2) Begin marked-content sequence
+        }
+        else if( operation.equals( "BT" ) )
+        {
+            log.debug( "<BT>" );
+            textMatrix = new Matrix();
+            textLineMatrix = new Matrix();
+        }
+        else if( operation.equals( "BX" ) )
+        {
+            //(PDF 1.1) Begin compatibility section
+        }
+        else if( operation.equals( "c" ) )
+        {
+            //Append curved segment to path (three control points)
+        }
+        else if( operation.equals( "cm" ) )
+        {
+        }
+        else if( operation.equals( "cs" ) )
+        {
+        }
+        else if( operation.equals( "CS" ) )
+        {
+        }
+        else if( operation.equals( "d" ) )
+        {
+            //Set the line dash pattern in the graphics state
+        }
+        else */if( operation.equals( "d0" ) )
+        {
+            //set glyph with for a type3 font
+            //COSNumber horizontalWidth = (COSNumber)arguments.get( 0 );
+            //COSNumber verticalWidth = (COSNumber)arguments.get( 1 );
+            //width = horizontalWidth.intValue();
+            //height = verticalWidth.intValue();
+        }
+        else if( operation.equals( "d1" ) )
+        {
+            //set glyph with and bounding box for type 3 font
+            //COSNumber horizontalWidth = (COSNumber)arguments.get( 0 );
+            //COSNumber verticalWidth = (COSNumber)arguments.get( 1 );
+            COSNumber llx = (COSNumber)arguments.get( 2 );
+            COSNumber lly = (COSNumber)arguments.get( 3 );
+            COSNumber urx = (COSNumber)arguments.get( 4 );
+            COSNumber ury = (COSNumber)arguments.get( 5 );
+
+            //width = horizontalWidth.intValue();
+            //height = verticalWidth.intValue();
+            box = new BoundingBox();
+            box.setLowerLeftX( llx.floatValue() );
+            box.setLowerLeftY( lly.floatValue() );
+            box.setUpperRightX( urx.floatValue() );
+            box.setUpperRightY( ury.floatValue() );
+        }/*
+        else if( operation.equals( "Do" ) )
+        {
+            //invoke named object.
+        }
+        else if( operation.equals( "DP" ) )
+        {
+            //(PDF 1.2) De.ne marked-content point with property list
+        }
+        else if( operation.equals( "EI" ) )
+        {
+            //end inline image object
+        }
+        else if( operation.equals( "EMC" ) )
+        {
+            //End inline image object
+        }
+        else if( operation.equals( "ET" ) )
+        {
+            log.debug( "<ET>" );
+            textMatrix = null;
+            textLineMatrix = null;
+        }
+        else if( operation.equals( "EX" ) )
+        {
+            //(PDF 1.1) End compatibility section
+        }
+        else if( operation.equals( "f" ) )
+        {
+            //Fill the path, using the nonzero winding number rule to determine the region to .ll
+        }
+        else if( operation.equals( "F" ) )
+        {
+        }
+        else if( operation.equals( "f*" ) )
+        {
+            //Fill path using even-odd rule
+        }
+        else if( operation.equals( "g" ) )
+        {
+        }
+        else if( operation.equals( "G" ) )
+        {
+        }
+        else if( operation.equals( "gs" ) )
+        {
+        }
+        else if( operation.equals( "h" ) )
+        {
+            //close subpath
+        }
+        else if( operation.equals( "i" ) )
+        {
+            //set flatness tolerance, not sure what this does
+        }
+        else if( operation.equals( "ID" ) )
+        {
+            //begin inline image data
+        }
+        else if( operation.equals( "j" ) )
+        {
+            //Set the line join style in the graphics state
+            //System.out.println( "<j>" );
+        }
+        else if( operation.equals( "J" ) )
+        {
+            //Set the line cap style in the graphics state
+            //System.out.println( "<J>" );
+        }
+        else if( operation.equals( "k" ) )
+        {
+            //Set CMYK color for nonstroking operations
+        }
+        else if( operation.equals( "K" ) )
+        {
+            //Set CMYK color for stroking operations
+        }
+        else if( operation.equals( "l" ) )
+        {
+            //append straight line segment from the current point to the point.
+            COSNumber x = (COSNumber)arguments.get( 0 );
+            COSNumber y = (COSNumber)arguments.get( 1 );
+            linePath.lineTo( x.floatValue(), pageSize.getHeight()-y.floatValue() );
+        }
+        else if( operation.equals( "m" ) )
+        {
+            COSNumber x = (COSNumber)arguments.get( 0 );
+            COSNumber y = (COSNumber)arguments.get( 1 );
+            linePath.reset();
+            linePath.moveTo( x.floatValue(), pageSize.getHeight()-y.floatValue() );
+            //System.out.println( "<m x=\"" + x.getValue() + "\" y=\"" + y.getValue() + "\" >" );
+        }
+        else if( operation.equals( "M" ) )
+        {
+            //System.out.println( "<M>" );
+        }
+        else if( operation.equals( "MP" ) )
+        {
+            //(PDF 1.2) Define marked-content point
+        }
+        else if( operation.equals( "n" ) )
+        {
+            //End path without .lling or stroking
+            //System.out.println( "<n>" );
+        }
+        else if( operation.equals( "q" ) )
+        {
+            //save graphics state
+            if( log.isDebugEnabled() )
+            {
+                log.debug( "<" + operation + "> - save state" );
+            }
+            graphicsStack.push(graphicsState.clone());
+        }
+        else if( operation.equals( "Q" ) )
+        {
+            //restore graphics state
+            if( log.isDebugEnabled() )
+            {
+                log.debug( "<" + operation + "> - restore state" );
+            }
+            graphicsState = (PDGraphicsState)graphicsStack.pop();
+        }
+        else if( operation.equals( "re" ) )
+        {
+        }
+        else if( operation.equals( "rg" ) )
+        {
+            //Set RGB color for nonstroking operations
+        }
+        else if( operation.equals( "RG" ) )
+        {
+            //Set RGB color for stroking operations
+        }
+        else if( operation.equals( "ri" ) )
+        {
+            //Set color rendering intent
+        }
+        else if( operation.equals( "s" ) )
+        {
+            //Close and stroke path
+        }
+        else if( operation.equals( "S" ) )
+        {
+            graphics.draw( linePath );
+        }
+        else if( operation.equals( "sc" ) )
+        {
+            //set color for nonstroking operations
+            //System.out.println( "<sc>" );
+        }
+        else if( operation.equals( "SC" ) )
+        {
+            //set color for stroking operations
+            //System.out.println( "<SC>" );
+        }
+        else if( operation.equals( "scn" ) )
+        {
+            //set color for nonstroking operations special
+        }
+        else if( operation.equals( "SCN" ) )
+        {
+            //set color for stroking operations special
+        }
+        else if( operation.equals( "sh" ) )
+        {
+            //(PDF 1.3) Paint area de.ned by shading pattern
+        }
+        else if( operation.equals( "T*" ) )
+        {
+            if (log.isDebugEnabled())
+            {
+                log.debug("<T* graphicsState.getTextState().getLeading()=\"" +
+                    graphicsState.getTextState().getLeading() + "\">");
+            }
+            //move to start of next text line
+            if( graphicsState.getTextState().getLeading() == 0 )
+            {
+                graphicsState.getTextState().setLeading( -.01f );
+            }
+            Matrix td = new Matrix();
+            td.setValue( 2, 1, -1 * graphicsState.getTextState().getLeading() * textMatrix.getValue(1,1));
+            textLineMatrix = textLineMatrix.multiply( td );
+            textMatrix = textLineMatrix.copy();
+        }
+        else if( operation.equals( "Tc" ) )
+        {
+            //set character spacing
+            COSNumber characterSpacing = (COSNumber)arguments.get( 0 );
+            if (log.isDebugEnabled())
+            {
+                log.debug("<Tc characterSpacing=\"" + characterSpacing.floatValue() + "\" />");
+            }
+            graphicsState.getTextState().setCharacterSpacing( characterSpacing.floatValue() );
+        }
+        else if( operation.equals( "Td" ) )
+        {
+            COSNumber x = (COSNumber)arguments.get( 0 );
+            COSNumber y = (COSNumber)arguments.get( 1 );
+            if (log.isDebugEnabled())
+            {
+                log.debug("<Td x=\"" + x.floatValue() + "\" y=\"" + y.floatValue() + "\">");
+            }
+            Matrix td = new Matrix();
+            td.setValue( 2, 0, x.floatValue() * textMatrix.getValue(0,0) );
+            td.setValue( 2, 1, y.floatValue() * textMatrix.getValue(1,1) );
+            //log.debug( "textLineMatrix before " + textLineMatrix );
+            textLineMatrix = textLineMatrix.multiply( td );
+            //log.debug( "textLineMatrix after " + textLineMatrix );
+            textMatrix = textLineMatrix.copy();
+        }
+        else if( operation.equals( "TD" ) )
+        {
+            //move text position and set leading
+            COSNumber x = (COSNumber)arguments.get( 0 );
+            COSNumber y = (COSNumber)arguments.get( 1 );
+            if (log.isDebugEnabled())
+            {
+                log.debug("<TD x=\"" + x.floatValue() + "\" y=\"" + y.floatValue() + "\">");
+            }
+            graphicsState.getTextState().setLeading( -1 * y.floatValue() );
+            Matrix td = new Matrix();
+            td.setValue( 2, 0, x.floatValue() * textMatrix.getValue(0,0) );
+            td.setValue( 2, 1, y.floatValue() * textMatrix.getValue(1,1) );
+            //log.debug( "textLineMatrix before " + textLineMatrix );
+            textLineMatrix = textLineMatrix.multiply( td );
+            //log.debug( "textLineMatrix after " + textLineMatrix );
+            textMatrix = textLineMatrix.copy();
+        }
+        else if( operation.equals( "Tf" ) )
+        {
+            //set font and size
+            COSName fontName = (COSName)arguments.get( 0 );
+            graphicsState.getTextState().setFontSize( ((COSNumber)arguments.get( 1 ) ).floatValue() );
+
+            if (log.isDebugEnabled())
+            {
+                log.debug("<Tf font=\"" + fontName.getName() + "\" size=\"" +
+                    graphicsState.getTextState().getFontSize() + "\">");
+            }
+
+            //old way
+            //graphicsState.getTextState().getFont() = (COSObject)stream.getDictionaryObject( fontName );
+            //if( graphicsState.getTextState().getFont() == null )
+            //{
+            //    graphicsState.getTextState().getFont() = (COSObject)graphicsState.getTextState().getFont()
+            //                                           Dictionary.getItem( fontName );
+            //}
+            graphicsState.getTextState().setFont( (PDFont)fonts.get( fontName.getName() ) );
+            if( graphicsState.getTextState().getFont() == null )
+            {
+                throw new IOException( "Error: Could not find font(" + fontName + ") in map=" + fonts );
+            }
+            //log.debug( "Font Resource=" + fontResource );
+            //log.debug( "Current Font=" + graphicsState.getTextState().getFont() );
+            //log.debug( "graphicsState.getTextState().getFontSize()=" + graphicsState.getTextState().getFontSize() );
+        }
+        else if( operation.equals( "Tj" ) )
+        {
+            COSString string = (COSString)arguments.get( 0 );
+            TextPosition pos = showString( string.getBytes() );
+            if (log.isDebugEnabled())
+            {
+                log.debug("<Tj string=\"" + string.getString() + "\">");
+            }
+        }
+        else if( operation.equals( "TJ" ) )
+        {
+            Matrix td = new Matrix();
+
+            COSArray array = (COSArray)arguments.get( 0 );
+            for( int i=0; i<array.size(); i++ )
+            {
+                COSBase next = array.get( i );
+                if( next instanceof COSNumber )
+                {
+                    float value = -1*
+                                  (((COSNumber)next).floatValue()/1000) *
+                                  graphicsState.getTextState().getFontSize() *
+                                  textMatrix.getValue(1,1);
+
+                    if (log.isDebugEnabled())
+                    {
+                        log.debug( "<TJ(" + i + ") value=\"" + value +
+                                   "\", param=\"" + ((COSNumber)next).floatValue() +
+                                   "\", fontsize=\"" + graphicsState.getTextState().getFontSize() + "\">" );
+                    }
+                    td.setValue( 2, 0, value );
+                    textMatrix = textMatrix.multiply( td );
+                }
+                else if( next instanceof COSString )
+                {
+                    TextPosition pos = showString( ((COSString)next).getBytes() );
+                    if (log.isDebugEnabled())
+                    {
+                        log.debug("<TJ(" + i + ") string=\"" + pos.getString() + "\">");
+                    }
+                }
+                else
+                {
+                    throw new IOException( "Unknown type in array for TJ operation:" + next );
+                }
+            }
+        }
+        else if( operation.equals( "TL" ) )
+        {
+            COSNumber leading = (COSNumber)arguments.get( 0 );
+            graphicsState.getTextState().setLeading( leading.floatValue() );
+            if (log.isDebugEnabled())
+            {
+                log.debug("<TL leading=\"" + leading.floatValue() + "\" >");
+            }
+        }
+        else if( operation.equals( "Tm" ) )
+        {
+            //Set text matrix and text line matrix
+            COSNumber a = (COSNumber)arguments.get( 0 );
+            COSNumber b = (COSNumber)arguments.get( 1 );
+            COSNumber c = (COSNumber)arguments.get( 2 );
+            COSNumber d = (COSNumber)arguments.get( 3 );
+            COSNumber e = (COSNumber)arguments.get( 4 );
+            COSNumber f = (COSNumber)arguments.get( 5 );
+
+            if (log.isDebugEnabled())
+            {
+                log.debug("<Tm " +
+                          "a=\"" + a.floatValue() + "\" " +
+                          "b=\"" + b.floatValue() + "\" " +
+                          "c=\"" + c.floatValue() + "\" " +
+                          "d=\"" + d.floatValue() + "\" " +
+                          "e=\"" + e.floatValue() + "\" " +
+                          "f=\"" + f.floatValue() + "\" >");
+            }
+
+            textMatrix = new Matrix();
+            textMatrix.setValue( 0, 0, a.floatValue() );
+            textMatrix.setValue( 0, 1, b.floatValue() );
+            textMatrix.setValue( 1, 0, c.floatValue() );
+            textMatrix.setValue( 1, 1, d.floatValue() );
+            textMatrix.setValue( 2, 0, e.floatValue() );
+            textMatrix.setValue( 2, 1, f.floatValue() );
+            textLineMatrix = textMatrix.copy();
+        }
+        else if( operation.equals( "Tr" ) )
+        {
+            //Set text rendering mode
+            //System.out.println( "<Tr>" );
+        }
+        else if( operation.equals( "Ts" ) )
+        {
+            //Set text rise
+            //System.out.println( "<Ts>" );
+        }
+        else if( operation.equals( "Tw" ) )
+        {
+            //set word spacing
+            COSNumber wordSpacing = (COSNumber)arguments.get( 0 );
+            if (log.isDebugEnabled())
+            {
+                log.debug("<Tw wordSpacing=\"" + wordSpacing.floatValue() + "\" />");
+            }
+            graphicsState.getTextState().setWordSpacing( wordSpacing.floatValue() );
+        }
+        else if( operation.equals( "Tz" ) )
+        {
+            //Set horizontal text scaling
+        }
+        else if( operation.equals( "v" ) )
+        {
+            //Append curved segment to path (initial point replicated)
+        }
+        else if( operation.equals( "w" ) )
+        {
+            //Set the line width in the graphics state
+            //System.out.println( "<w>" );
+        }
+        else if( operation.equals( "W" ) )
+        {
+            //Set clipping path using nonzero winding number rule
+            //System.out.println( "<W>" );
+        }
+        else if( operation.equals( "W*" ) )
+        {
+            //Set clipping path using even-odd rule
+        }
+        else if( operation.equals( "y" ) )
+        {
+            //Append curved segment to path (final point replicated)
+        }
+        else if( operation.equals( "'" ) )
+        {
+            // Move to start of next text line, and show text
+            //
+            COSString string = (COSString)arguments.get( 0 );
+            if (log.isDebugEnabled())
+            {
+                log.debug("<' string=\"" + string.getString() + "\">");
+            }
+
+            Matrix td = new Matrix();
+            td.setValue( 2, 1, -1 * graphicsState.getTextState().getLeading() * textMatrix.getValue(1,1));
+            textLineMatrix = textLineMatrix.multiply( td );
+            textMatrix = textLineMatrix.copy();
+
+            showString( string.getBytes() );
+        }
+        else if( operation.equals( "\"" ) )
+        {
+            //Set word and character spacing, move to next line, and show text
+            //
+            COSNumber wordSpacing = (COSNumber)arguments.get( 0 );
+            COSNumber characterSpacing = (COSNumber)arguments.get( 1 );
+            COSString string = (COSString)arguments.get( 2 );
+
+            if (log.isDebugEnabled())
+            {
+                log.debug("<\" wordSpacing=\"" + wordSpacing +
+                          "\", characterSpacing=\"" + characterSpacing +
+                          "\", string=\"" + string.getString() + "\">");
+            }
+
+            graphicsState.getTextState().setCharacterSpacing( characterSpacing.floatValue() );
+            graphicsState.getTextState().setWordSpacing( wordSpacing.floatValue() );
+
+            Matrix td = new Matrix();
+            td.setValue( 2, 1, -1 * graphicsState.getTextState().getLeading() * textMatrix.getValue(1,1));
+            textLineMatrix = textLineMatrix.multiply( td );
+            textMatrix = textLineMatrix.copy();
+
+            showString( string.getBytes() );
+        }*/
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/font/package.html
new file mode 100644 (file)
index 0000000..15f4260
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+Classes to deal with font functionality in a PDF Document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDExtendedGraphicsState.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDExtendedGraphicsState.java
new file mode 100644 (file)
index 0000000..88399fa
--- /dev/null
@@ -0,0 +1,564 @@
+/*
+ * 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.pdfbox.pdmodel.graphics;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import java.io.IOException;
+
+
+/**
+ * This class represents the graphics state dictionary that is stored in the PDF document.
+ * The PDGraphicsStateValue holds the current runtime values as a stream is being executed.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDExtendedGraphicsState implements COSObjectable
+{
+    /**
+     * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+     */
+    public static final String RENDERING_INTENT_ABSOLUTE_COLORIMETRIC = "AbsoluteColorimetric";
+    /**
+     * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+     */
+    public static final String RENDERING_INTENT_RELATIVE_COLORIMETRIC = "RelativeColorimetric";
+    /**
+     * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+     */
+    public static final String RENDERING_INTENT_SATURATION = "Saturation";
+    /**
+     * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+     */
+    public static final String RENDERING_INTENT_PERCEPTUAL = "Perceptual";
+
+
+    private COSDictionary graphicsState;
+
+    /**
+     * Default constructor, creates blank graphics state.
+     */
+    public PDExtendedGraphicsState()
+    {
+        graphicsState = new COSDictionary();
+        graphicsState.setItem( COSName.TYPE, COSName.EXT_G_STATE );
+    }
+
+    /**
+     * Create a graphics state from an existing dictionary.
+     *
+     * @param dictionary The existing graphics state.
+     */
+    public PDExtendedGraphicsState( COSDictionary dictionary )
+    {
+        graphicsState = dictionary;
+    }
+
+    /**
+     * This will implement the gs operator.
+     *
+     * @param gs The state to copy this dictionaries values into.
+     *
+     * @throws IOException If there is an error copying font information.
+     */
+    public void copyIntoGraphicsState( PDGraphicsState gs ) throws IOException
+    {
+        for( COSName key : graphicsState.keySet() )
+        {
+            if( key.equals( COSName.LW ) )
+            {
+                gs.setLineWidth( getLineWidth().doubleValue() );
+            }
+            else if( key.equals( COSName.LC ) )
+            {
+                gs.setLineCap( getLineCapStyle() );
+            }
+            else if( key.equals( COSName.LJ ) )
+            {
+                gs.setLineJoin( getLineJoinStyle() );
+            }
+            else if( key.equals( COSName.ML ) )
+            {
+                gs.setMiterLimit( getMiterLimit().doubleValue() );
+            }
+            else if( key.equals( COSName.D ) )
+            {
+                gs.setLineDashPattern( getLineDashPattern() );
+            }
+            else if( key.equals( COSName.RI ) )
+            {
+                gs.setRenderingIntent( getRenderingIntent() );
+            }
+            else if( key.equals( COSName.OPM ) )
+            {
+                gs.setOverprintMode( getOverprintMode().doubleValue() );
+            }
+            else if( key.equals( COSName.FONT ) )
+            {
+                PDFontSetting setting = getFontSetting();
+                gs.getTextState().setFont( setting.getFont() );
+                gs.getTextState().setFontSize( setting.getFontSize() );
+            }
+            else if( key.equals( COSName.FL ) )
+            {
+                gs.setFlatness( getFlatnessTolerance().floatValue() );
+            }
+            else if( key.equals( COSName.SM ) )
+            {
+                gs.setSmoothness( getSmoothnessTolerance().floatValue() );
+            }
+            else if( key.equals( COSName.SA ) )
+            {
+                gs.setStrokeAdjustment( getAutomaticStrokeAdjustment() );
+            }
+            else if( key.equals( COSName.CA ) )
+            {
+                gs.setAlphaConstants( getStrokingAlpaConstant().floatValue() );
+            }
+            else if( key.equals( COSName.CA_NS ) )
+            {
+                gs.setNonStrokeAlphaConstants(getNonStrokingAlpaConstant().floatValue() );
+            }
+            else if( key.equals( COSName.AIS ) )
+            {
+                gs.setAlphaSource( getAlphaSourceFlag() );
+            }
+            else if( key.equals( COSName.TK ) )
+            {
+                gs.getTextState().setKnockoutFlag( getTextKnockoutFlag() );
+            }
+        }
+    }
+
+    /**
+     * This will get the underlying dictionary that this class acts on.
+     *
+     * @return The underlying dictionary for this class.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return graphicsState;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return graphicsState;
+    }
+
+    /**
+     * This will get the line width.  This will return null if there is no line width
+     *
+     * @return null or the LW value of the dictionary.
+     */
+    public Float getLineWidth()
+    {
+        return getFloatItem( COSName.LW );
+    }
+
+    /**
+     * This will set the line width.
+     *
+     * @param width The line width for the object.
+     */
+    public void setLineWidth( Float width )
+    {
+        setFloatItem( COSName.LW, width );
+    }
+
+    /**
+     * This will get the line cap style.
+     *
+     * @return null or the LC value of the dictionary.
+     */
+    public int getLineCapStyle()
+    {
+        return graphicsState.getInt( COSName.LC );
+    }
+
+    /**
+     * This will set the line cap style for the graphics state.
+     *
+     * @param style The new line cap style to set.
+     */
+    public void setLineCapStyle( int style )
+    {
+        graphicsState.setInt( COSName.LC, style );
+    }
+
+    /**
+     * This will get the line join style.
+     *
+     * @return null or the LJ value in the dictionary.
+     */
+    public int getLineJoinStyle()
+    {
+        return graphicsState.getInt( COSName.LJ );
+    }
+
+    /**
+     * This will set the line join style.
+     *
+     * @param style The new line join style.
+     */
+    public void setLineJoinStyle( int style )
+    {
+        graphicsState.setInt( COSName.LJ, style );
+    }
+
+
+    /**
+     * This will get the miter limit.
+     *
+     * @return null or the ML value in the dictionary.
+     */
+    public Float getMiterLimit()
+    {
+        return getFloatItem( COSName.ML );
+    }
+
+    /**
+     * This will set the miter limit for the graphics state.
+     *
+     * @param miterLimit The new miter limit value
+     */
+    public void setMiterLimit( Float miterLimit )
+    {
+        setFloatItem( COSName.ML, miterLimit );
+    }
+
+    /**
+     * This will get the dash pattern.
+     *
+     * @return null or the D value in the dictionary.
+     */
+    public PDLineDashPattern getLineDashPattern()
+    {
+        PDLineDashPattern retval = null;
+        COSArray dp = (COSArray)graphicsState.getDictionaryObject( COSName.D );
+        if( dp != null )
+        {
+            retval = new PDLineDashPattern( dp );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the dash pattern for the graphics state.
+     *
+     * @param dashPattern The dash pattern
+     */
+    public void setLineDashPattern( PDLineDashPattern dashPattern )
+    {
+        graphicsState.setItem( COSName.D, dashPattern.getCOSObject() );
+    }
+
+    /**
+     * This will get the rendering intent.
+     *
+     * @return null or the RI value in the dictionary.
+     */
+    public String getRenderingIntent()
+    {
+        return graphicsState.getNameAsString( "RI" );
+    }
+
+    /**
+     * This will set the rendering intent for the graphics state.
+     *
+     * @param ri The new rendering intent
+     */
+    public void setRenderingIntent( String ri )
+    {
+        graphicsState.setName( "RI", ri );
+    }
+
+    /**
+     * This will get the overprint control.
+     *
+     * @return The overprint control or null if one has not been set.
+     */
+    public boolean getStrokingOverprintControl()
+    {
+        return graphicsState.getBoolean( COSName.OP, false );
+    }
+
+    /**
+     * This will get the overprint control(OP).
+     *
+     * @param op The overprint control.
+     */
+    public void setStrokingOverprintControl( boolean op )
+    {
+        graphicsState.setBoolean( COSName.OP, op );
+    }
+
+    /**
+     * This will get the overprint control for non stroking operations.  If this
+     * value is null then the regular overprint control value will be returned.
+     *
+     * @return The overprint control or null if one has not been set.
+     */
+    public boolean getNonStrokingOverprintControl()
+    {
+        return graphicsState.getBoolean( COSName.OP_NS, getStrokingOverprintControl() );
+    }
+
+    /**
+     * This will get the overprint control(OP).
+     *
+     * @param op The overprint control.
+     */
+    public void setNonStrokingOverprintControl( boolean op )
+    {
+        graphicsState.setBoolean( COSName.OP_NS, op );
+    }
+
+    /**
+     * This will get the overprint control mode.
+     *
+     * @return The overprint control mode or null if one has not been set.
+     */
+    public Float getOverprintMode()
+    {
+        return getFloatItem( COSName.OPM );
+    }
+
+    /**
+     * This will get the overprint mode(OPM).
+     *
+     * @param overprintMode The overprint mode
+     */
+    public void setOverprintMode( Float overprintMode )
+    {
+        setFloatItem( COSName.OPM, overprintMode );
+    }
+
+    /**
+     * This will get the font setting of the graphics state.
+     *
+     * @return The font setting.
+     */
+    public PDFontSetting getFontSetting()
+    {
+        PDFontSetting setting = null;
+        COSArray font = (COSArray)graphicsState.getDictionaryObject( COSName.FONT );
+        if( font != null )
+        {
+            setting = new PDFontSetting( font );
+        }
+        return setting;
+    }
+
+    /**
+     * This will set the font setting for this graphics state.
+     *
+     * @param fs The new font setting.
+     */
+    public void setFontSetting( PDFontSetting fs )
+    {
+        graphicsState.setItem( COSName.FONT, fs );
+    }
+
+    /**
+     * This will get the flatness tolerance.
+     *
+     * @return The flatness tolerance or null if one has not been set.
+     */
+    public Float getFlatnessTolerance()
+    {
+        return getFloatItem( COSName.FL );
+    }
+
+    /**
+     * This will get the flatness tolerance.
+     *
+     * @param flatness The new flatness tolerance
+     */
+    public void setFlatnessTolerance( Float flatness )
+    {
+        setFloatItem( COSName.FL, flatness );
+    }
+
+    /**
+     * This will get the smothness tolerance.
+     *
+     * @return The smothness tolerance or null if one has not been set.
+     */
+    public Float getSmoothnessTolerance()
+    {
+        return getFloatItem( COSName.SM );
+    }
+
+    /**
+     * This will get the smoothness tolerance.
+     *
+     * @param smoothness The new smoothness tolerance
+     */
+    public void setSmoothnessTolerance( Float smoothness )
+    {
+        setFloatItem( COSName.SM, smoothness );
+    }
+
+    /**
+     * This will get the automatic stroke adjustment flag.
+     *
+     * @return The automatic stroke adjustment flag or null if one has not been set.
+     */
+    public boolean getAutomaticStrokeAdjustment()
+    {
+        return graphicsState.getBoolean( COSName.SA,false );
+    }
+
+    /**
+     * This will get the automatic stroke adjustment flag.
+     *
+     * @param sa The new automatic stroke adjustment flag.
+     */
+    public void setAutomaticStrokeAdjustment( boolean sa )
+    {
+        graphicsState.setBoolean( COSName.SA, sa );
+    }
+
+    /**
+     * This will get the stroking alpha constant.
+     *
+     * @return The stroking alpha constant or null if one has not been set.
+     */
+    public Float getStrokingAlpaConstant()
+    {
+        return getFloatItem( COSName.CA );
+    }
+
+    /**
+     * This will get the stroking alpha constant.
+     *
+     * @param alpha The new stroking alpha constant.
+     */
+    public void setStrokingAlphaConstant( Float alpha )
+    {
+        setFloatItem( COSName.CA, alpha );
+    }
+
+    /**
+     * This will get the non stroking alpha constant.
+     *
+     * @return The non stroking alpha constant or null if one has not been set.
+     */
+    public Float getNonStrokingAlpaConstant()
+    {
+        return getFloatItem( COSName.CA_NS );
+    }
+
+    /**
+     * This will get the non stroking alpha constant.
+     *
+     * @param alpha The new non stroking alpha constant.
+     */
+    public void setNonStrokingAlphaConstant( Float alpha )
+    {
+        setFloatItem( COSName.CA_NS, alpha );
+    }
+
+    /**
+     * This will get the alpha source flag.
+     *
+     * @return The alpha source flag.
+     */
+    public boolean getAlphaSourceFlag()
+    {
+        return graphicsState.getBoolean( COSName.AIS, false );
+    }
+
+    /**
+     * This will get the alpha source flag.
+     *
+     * @param alpha The alpha source flag.
+     */
+    public void setAlphaSourceFlag( boolean alpha )
+    {
+        graphicsState.setBoolean( COSName.AIS, alpha );
+    }
+
+    /**
+     * This will get the text knockout flag.
+     *
+     * @return The text knockout flag.
+     */
+    public boolean getTextKnockoutFlag()
+    {
+        return graphicsState.getBoolean( COSName.TK,true );
+    }
+
+    /**
+     * This will get the text knockout flag.
+     *
+     * @param tk The text knockout flag.
+     */
+    public void setTextKnockoutFlag( boolean tk )
+    {
+        graphicsState.setBoolean( COSName.TK, tk );
+    }
+
+    /**
+     * This will get a float item from the dictionary.
+     *
+     * @param key The key to the item.
+     *
+     * @return The value for that item.
+     */
+    private Float getFloatItem( COSName key )
+    {
+        Float retval = null;
+        COSNumber value = (COSNumber)graphicsState.getDictionaryObject( key );
+        if( value != null )
+        {
+            retval = new Float( value.floatValue() );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a float object.
+     *
+     * @param key The key to the data that we are setting.
+     * @param value The value that we are setting.
+     */
+    private void setFloatItem( COSName key, Float value )
+    {
+        if( value == null )
+        {
+            graphicsState.removeItem( key );
+        }
+        else
+        {
+            graphicsState.setItem( key, new COSFloat( value.floatValue() ) );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDFontSetting.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDFontSetting.java
new file mode 100644 (file)
index 0000000..7618f7e
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.pdfbox.pdmodel.graphics;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDFontFactory;
+
+import java.io.IOException;
+
+/**
+ * This class represents a font setting used for the graphics state.  A font setting is a font and a
+ * font size.  Maybe there is a better name for this?
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFontSetting implements COSObjectable
+{
+    private COSArray fontSetting = null;
+
+    /**
+     * Creates a blank font setting, font will be null, size will be 1.
+     */
+    public PDFontSetting()
+    {
+        fontSetting = new COSArray();
+        fontSetting.add( null );
+        fontSetting.add( new COSFloat( 1 ) );
+    }
+
+    /**
+     * Constructs a font setting from an existing array.
+     *
+     * @param fs The new font setting value.
+     */
+    public PDFontSetting( COSArray fs )
+    {
+        fontSetting = fs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return fontSetting;
+    }
+
+    /**
+     * This will get the font for this font setting.
+     *
+     * @return The font for this setting of null if one was not found.
+     *
+     * @throws IOException If there is an error getting the font.
+     */
+    public PDFont getFont() throws IOException
+    {
+        PDFont retval = null;
+        COSBase font = fontSetting.get( 0 );
+        if( font instanceof COSDictionary )
+        {
+            retval = PDFontFactory.createFont( (COSDictionary)font );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the font for this font setting.
+     *
+     * @param font The new font.
+     */
+    public void setFont( PDFont font )
+    {
+        fontSetting.set( 0, font );
+    }
+
+    /**
+     * This will get the size of the font.
+     *
+     * @return The size of the font.
+     */
+    public float getFontSize()
+    {
+        COSNumber size = (COSNumber)fontSetting.get( 1 );
+        return size.floatValue();
+    }
+
+    /**
+     * This will set the size of the font.
+     *
+     * @param size The new size of the font.
+     */
+    public void setFontSize( float size )
+    {
+        fontSetting.set( 1, new COSFloat( size ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDGraphicsState.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDGraphicsState.java
new file mode 100644 (file)
index 0000000..b5fe46e
--- /dev/null
@@ -0,0 +1,507 @@
+/*
+ * 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.pdfbox.pdmodel.graphics;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.text.PDTextState;
+import org.apache.pdfbox.util.Matrix;
+
+/**
+ * This class will hold the current state of the graphics parameters when executing a
+ * content stream.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDGraphicsState implements Cloneable
+{
+    private Matrix currentTransformationMatrix = new Matrix();
+
+    //Here are some attributes of the Graphics state, but have not been created yet.
+    //
+    //clippingPath
+    private PDColorState strokingColor = new PDColorState();
+    private PDColorState nonStrokingColor = new PDColorState();
+    private PDTextState textState = new PDTextState();
+    private double lineWidth = 0;
+    private int lineCap = 0;
+    private int lineJoin = 0;
+    private double miterLimit = 0;
+    private PDLineDashPattern lineDashPattern;
+    private String renderingIntent;
+    private boolean strokeAdjustment = false;
+    //blend mode
+    //soft mask
+    private double alphaConstants = 1.0;
+    private double nonStrokingAlphaConstants = 1.0;
+    private boolean alphaSource = false;
+
+    //DEVICE DEPENDENT parameters
+    private boolean overprint = false;
+    private double overprintMode = 0;
+    //black generation
+    //undercolor removal
+    //transfer
+    //halftone
+    private double flatness = 1.0;
+    private double smoothness = 0;
+
+    private GeneralPath currentClippingPath;
+
+    /**
+     * Default constructor.
+     */
+    public PDGraphicsState()
+    {
+    }
+
+    /**
+     * Constructor with a given pagesize to initialize the clipping path.
+     * @param page the size of the page
+     */
+    public PDGraphicsState(PDRectangle page)
+    {
+        currentClippingPath = new GeneralPath(new Rectangle(page.createDimension()));
+        if (page.getLowerLeftX() != 0 || page.getLowerLeftY() != 0)
+        {
+            //Compensate for offset
+            this.currentTransformationMatrix = this.currentTransformationMatrix.multiply(
+                    Matrix.getTranslatingInstance(-page.getLowerLeftX(), -page.getLowerLeftY()));
+        }
+    }
+
+    /**
+     * Get the value of the CTM.
+     *
+     * @return The current transformation matrix.
+     */
+    public Matrix getCurrentTransformationMatrix()
+    {
+        return currentTransformationMatrix;
+    }
+
+    /**
+     * Set the value of the CTM.
+     *
+     * @param value The current transformation matrix.
+     */
+    public void setCurrentTransformationMatrix(Matrix value)
+    {
+        currentTransformationMatrix = value;
+    }
+
+    /**
+     * Get the value of the line width.
+     *
+     * @return The current line width.
+     */
+    public double getLineWidth()
+    {
+        return lineWidth;
+    }
+
+    /**
+     * set the value of the line width.
+     *
+     * @param value The current line width.
+     */
+    public void setLineWidth(double value)
+    {
+        lineWidth = value;
+    }
+
+    /**
+     * Get the value of the line cap.
+     *
+     * @return The current line cap.
+     */
+    public int getLineCap()
+    {
+        return lineCap;
+    }
+
+    /**
+     * set the value of the line cap.
+     *
+     * @param value The current line cap.
+     */
+    public void setLineCap(int value)
+    {
+        lineCap = value;
+    }
+
+    /**
+     * Get the value of the line join.
+     *
+     * @return The current line join value.
+     */
+    public int getLineJoin()
+    {
+        return lineJoin;
+    }
+
+    /**
+     * Get the value of the line join.
+     *
+     * @param value The current line join
+     */
+    public void setLineJoin(int value)
+    {
+        lineJoin = value;
+    }
+
+    /**
+     * Get the value of the miter limit.
+     *
+     * @return The current miter limit.
+     */
+    public double getMiterLimit()
+    {
+        return miterLimit;
+    }
+
+    /**
+     * set the value of the miter limit.
+     *
+     * @param value The current miter limit.
+     */
+    public void setMiterLimit(double value)
+    {
+        miterLimit = value;
+    }
+
+    /**
+     * Get the value of the stroke adjustment parameter.
+     *
+     * @return The current stroke adjustment.
+     */
+    public boolean isStrokeAdjustment()
+    {
+        return strokeAdjustment;
+    }
+
+    /**
+     * set the value of the stroke adjustment.
+     *
+     * @param value The value of the stroke adjustment parameter.
+     */
+    public void setStrokeAdjustment(boolean value)
+    {
+        strokeAdjustment = value;
+    }
+
+    /**
+     * Get the value of the stroke alpha constants property.
+     *
+     * @return The value of the stroke alpha constants parameter.
+     */
+    public double getAlphaConstants()
+    {
+        return alphaConstants;
+    }
+
+    /**
+     * set the value of the stroke alpha constants property.
+     *
+     * @param value The value of the stroke alpha constants parameter.
+     */
+    public void setAlphaConstants(double value)
+    {
+        alphaConstants = value;
+    }
+
+    /**
+     * Get the value of the non-stroke alpha constants property.
+     *
+     * @return The value of the non-stroke alpha constants parameter.
+     */
+    public double getNonStrokeAlphaConstants()
+    {
+        return nonStrokingAlphaConstants;
+    }
+
+    /**
+     * set the value of the non-stroke alpha constants property.
+     *
+     * @param value The value of the non-stroke alpha constants parameter.
+     */
+    public void setNonStrokeAlphaConstants(double value)
+    {
+        nonStrokingAlphaConstants = value;
+    }
+
+    /**
+     * get the value of the stroke alpha source property.
+     *
+     * @return The value of the stroke alpha source parameter.
+     */
+    public boolean isAlphaSource()
+    {
+        return alphaSource;
+    }
+
+    /**
+     * set the value of the alpha source property.
+     *
+     * @param value The value of the alpha source parameter.
+     */
+    public void setAlphaSource(boolean value)
+    {
+        alphaSource = value;
+    }
+
+    /**
+     * get the value of the overprint property.
+     *
+     * @return The value of the overprint parameter.
+     */
+    public boolean isOverprint()
+    {
+        return overprint;
+    }
+
+    /**
+     * set the value of the overprint property.
+     *
+     * @param value The value of the overprint parameter.
+     */
+    public void setOverprint(boolean value)
+    {
+        overprint = value;
+    }
+
+    /**
+     * get the value of the overprint mode property.
+     *
+     * @return The value of the overprint mode parameter.
+     */
+    public double getOverprintMode()
+    {
+        return overprintMode;
+    }
+
+    /**
+     * set the value of the overprint mode property.
+     *
+     * @param value The value of the overprint mode parameter.
+     */
+    public void setOverprintMode(double value)
+    {
+        overprintMode = value;
+    }
+
+    /**
+     * get the value of the flatness property.
+     *
+     * @return The value of the flatness parameter.
+     */
+    public double getFlatness()
+    {
+        return flatness;
+    }
+
+    /**
+     * set the value of the flatness property.
+     *
+     * @param value The value of the flatness parameter.
+     */
+    public void setFlatness(double value)
+    {
+        flatness = value;
+    }
+
+    /**
+     * get the value of the smoothness property.
+     *
+     * @return The value of the smoothness parameter.
+     */
+    public double getSmoothness()
+    {
+        return smoothness;
+    }
+
+    /**
+     * set the value of the smoothness property.
+     *
+     * @param value The value of the smoothness parameter.
+     */
+    public void setSmoothness(double value)
+    {
+        smoothness = value;
+    }
+
+    /**
+     * This will get the graphics text state.
+     *
+     * @return The graphics text state.
+     */
+    public PDTextState getTextState()
+    {
+        return textState;
+    }
+
+    /**
+     * This will set the graphics text state.
+     *
+     * @param value The graphics text state.
+     */
+    public void setTextState(PDTextState value)
+    {
+        textState = value;
+    }
+
+    /**
+     * This will get the current line dash pattern.
+     *
+     * @return The line dash pattern.
+     */
+    public PDLineDashPattern getLineDashPattern()
+    {
+        return lineDashPattern;
+    }
+
+    /**
+     * This will set the current line dash pattern.
+     *
+     * @param value The new line dash pattern.
+     */
+    public void setLineDashPattern(PDLineDashPattern value)
+    {
+        lineDashPattern = value;
+    }
+
+    /**
+     * This will get the rendering intent.
+     *
+     * @see PDExtendedGraphicsState
+     *
+     * @return The rendering intent
+     */
+    public String getRenderingIntent()
+    {
+        return renderingIntent;
+    }
+
+    /**
+     * This will set the rendering intent.
+     *
+     * @param value The new rendering intent.
+     */
+    public void setRenderingIntent(String value)
+    {
+        renderingIntent = value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object clone()
+    {
+        PDGraphicsState clone = null;
+        try
+        {
+            clone = (PDGraphicsState)super.clone();
+            clone.setTextState( (PDTextState)textState.clone() );
+            clone.setCurrentTransformationMatrix( currentTransformationMatrix.copy() );
+            clone.strokingColor = (PDColorState)strokingColor.clone();
+            clone.nonStrokingColor = ((PDColorState)nonStrokingColor.clone());
+            if( lineDashPattern != null )
+            {
+                clone.setLineDashPattern( (PDLineDashPattern)lineDashPattern.clone() );
+            }
+            if (currentClippingPath != null)
+            {
+                clone.setCurrentClippingPath((GeneralPath)currentClippingPath.clone());
+            }
+        }
+        catch( CloneNotSupportedException e )
+        {
+            e.printStackTrace();
+        }
+        return clone;
+    }
+
+    /**
+     * Returns the stroking color state.
+     *
+     * @return stroking color state
+     */
+    public PDColorState getStrokingColor()
+    {
+        return strokingColor;
+    }
+
+    /**
+     * Returns the non-stroking color state.
+     *
+     * @return non-stroking color state
+     */
+    public PDColorState getNonStrokingColor()
+    {
+        return nonStrokingColor;
+    }
+
+    /**
+     * This will set the current clipping path.
+     *
+     * @param pCurrentClippingPath The current clipping path.
+     *
+     */
+    public void setCurrentClippingPath(Shape pCurrentClippingPath)
+    {
+        if (pCurrentClippingPath != null)
+        {
+            if (pCurrentClippingPath instanceof GeneralPath)
+            {
+                currentClippingPath = (GeneralPath)pCurrentClippingPath;
+            }
+            else
+            {
+                currentClippingPath = new GeneralPath();
+                currentClippingPath.append(pCurrentClippingPath,false);
+            }
+        }
+        else
+        {
+            currentClippingPath = null;
+        }
+    }
+
+    /**
+     * This will get the current clipping path.
+     *
+     * @return The current clipping path.
+     */
+    public Shape getCurrentClippingPath()
+    {
+        return currentClippingPath;
+    }
+
+    public Composite getStrokeJavaComposite() {
+
+        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) alphaConstants);
+    }
+
+    public Composite getNonStrokeJavaComposite() {
+
+        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) nonStrokingAlphaConstants);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDLineDashPattern.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDLineDashPattern.java
new file mode 100644 (file)
index 0000000..721b476
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * 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.pdfbox.pdmodel.graphics;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import java.util.List;
+
+/**
+ * This class represents the line dash pattern for a graphics state.  See PDF
+ * Reference 1.5 section 4.3.2
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PDLineDashPattern implements COSObjectable, Cloneable
+{
+    private COSArray lineDashPattern = null;
+
+    /**
+     * Creates a blank line dash pattern.  With no dashes and a phase of 0.
+     */
+    public PDLineDashPattern()
+    {
+        lineDashPattern = new COSArray();
+        lineDashPattern.add( new COSArray() );
+        lineDashPattern.add( COSInteger.ZERO );
+    }
+
+    /**
+     * Constructs a line dash pattern from an existing array.
+     *
+     * @param ldp The existing line dash pattern.
+     */
+    public PDLineDashPattern( COSArray ldp )
+    {
+        lineDashPattern = ldp;
+    }
+
+    /**
+     * Constructs a line dash pattern from an existing array.
+     *
+     * @param ldp The existing line dash pattern.
+     * @param phase The phase for the line dash pattern.
+     */
+    public PDLineDashPattern( COSArray ldp, int phase )
+    {
+        lineDashPattern = new COSArray();
+        lineDashPattern.add( ldp );
+        lineDashPattern.add( COSInteger.get( phase ) );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object clone()
+    {
+        PDLineDashPattern pattern = null;
+        try
+        {
+            pattern = (PDLineDashPattern)super.clone();
+            pattern.setDashPattern(getDashPattern());
+            pattern.setPhaseStart(getPhaseStart());
+        }
+        catch(CloneNotSupportedException exception)
+        {
+            exception.printStackTrace();
+        }
+        return pattern;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return lineDashPattern;
+    }
+
+    /**
+     * This will get the line dash pattern phase.  The dash phase specifies the
+     * distance into the dash pattern at which to start the dash.
+     *
+     * @return The line dash pattern phase.
+     */
+    public int getPhaseStart()
+    {
+        COSNumber phase = (COSNumber)lineDashPattern.get( 1 );
+        return phase.intValue();
+    }
+
+    /**
+     * This will set the line dash pattern phase.
+     *
+     * @param phase The new line dash patter phase.
+     */
+    public void setPhaseStart( int phase )
+    {
+        lineDashPattern.set( 1, phase );
+    }
+
+    /**
+     * This will return a list of java.lang.Integer objects that represent the line
+     * dash pattern appearance.
+     *
+     * @return The line dash pattern.
+     */
+    public List getDashPattern()
+    {
+        COSArray dashPatterns = (COSArray)lineDashPattern.get( 0 );
+        return COSArrayList.convertIntegerCOSArrayToList( dashPatterns );
+    }
+
+    /**
+     * Get the line dash pattern as a COS object.
+     *
+     * @return The cos array line dash pattern.
+     */
+    public COSArray getCOSDashPattern()
+    {
+        return (COSArray)lineDashPattern.get( 0 );
+    }
+
+    /**
+     * This will replace the existing line dash pattern.
+     *
+     * @param dashPattern A list of java.lang.Integer objects.
+     */
+    public void setDashPattern( List dashPattern )
+    {
+        lineDashPattern.set( 0, COSArrayList.converterToCOSArray( dashPattern ) );
+    }
+    
+    /**
+     * Checks if the dashPattern is empty or all values equals 0.
+     * 
+     * @return true if the dashPattern is empty or all values equals 0  
+     */
+    public boolean isDashPatternEmpty() 
+    {
+        float[] dashPattern = getCOSDashPattern().toFloatArray();
+        boolean dashPatternEmpty = true;
+        if (dashPattern != null) 
+        {
+            int arraySize = dashPattern.length;
+            for(int i=0;i<arraySize;i++) 
+            {
+                if (dashPattern[i] > 0) 
+                {
+                    dashPatternEmpty = false;
+                    break;
+                }
+            }
+        }
+        return dashPatternEmpty;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDShading.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/PDShading.java
new file mode 100644 (file)
index 0000000..855de46
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * 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.pdfbox.pdmodel.graphics;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.common.function.PDFunction;
+
+
+
+import java.io.IOException;
+
+/**
+ * This class represents a Shading Pattern color space.
+ *  See section 4.6.3 of the PDF 1.7 specification.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class PDShading implements COSObjectable
+{
+    private COSDictionary DictShading;
+    private COSName shadingname;
+    private COSArray domain = null;
+    private COSArray extend = null;
+    private PDFunction function = null;
+    private PDColorSpace colorspace = null;
+    
+    /**
+     * The name of this object.
+     */
+    public static final String NAME = "Shading";
+
+    /**
+     * Default constructor.
+     */
+    public PDShading()
+    {
+        DictShading = new COSDictionary();
+        //DictShading.add( COSName.getPDFName( NAME ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param shading The shading dictionary.
+     */
+    public PDShading(COSName name, COSDictionary shading)
+    {
+        DictShading = shading;
+        shadingname = name;
+    }
+
+    /**
+     * This will return the name of the object.
+     *
+     * @return The name of the object.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+    
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return COSName.SHADING;
+    }
+    
+    /**
+    * This will return the name of this particular shading dictionary
+    *
+    * @return The name of the shading dictionary
+    */
+    public COSName getShadingName()
+    {
+        return shadingname;
+    }
+    
+    /**
+    * This will return the ShadingType -- an integer between 1 and 7 that specifies the gradient type.
+    * Required in all Shading Dictionaries.
+    *
+    * @return The Shading Type
+    */
+    public int getShadingType()
+    {
+        return DictShading.getInt(COSName.SHADING_TYPE);
+    }
+    
+    /**
+    * This will return the Color Space.
+    * Required in all Shading Dictionaries.
+    *
+    * @return The Color Space of the shading dictionary
+    */
+    public PDColorSpace getColorSpace() throws IOException
+    {
+        if (colorspace == null)
+        {
+            colorspace = PDColorSpaceFactory.createColorSpace(DictShading.getDictionaryObject(COSName.COLORSPACE));
+        }
+        return colorspace;
+    }
+    
+    /**
+    * This will return a boolean flag indicating whether to antialias the shading pattern.
+    *
+    * @return The antialias flag, defaulting to False
+    */
+    public boolean getAntiAlias()
+    {
+        return DictShading.getBoolean(COSName.ANTI_ALIAS,false);
+    }
+    
+    /**
+    * Returns the coordinate array used by several of the gradient types. Interpretation depends on the ShadingType.
+    *
+    * @return The coordinate array.
+    */
+    public COSArray getCoords()
+    {
+        return (COSArray)(DictShading.getDictionaryObject(COSName.COORDS));
+    }
+    
+    /**
+    * Returns the function used by several of the gradient types. Interpretation depends on the ShadingType.
+    *
+    * @return The gradient function.
+    */
+    public PDFunction getFunction() throws IOException
+    {
+        if (function == null)
+        {
+            function = PDFunction.create(DictShading.getDictionaryObject(COSName.FUNCTION));
+        }
+        return function;
+    }
+    
+    /**
+    * Returns the Domain array used by several of the gradient types. Interpretation depends on the ShadingType.
+    *
+    * @return The Domain array.
+    */
+    public COSArray getDomain()
+    {
+        if (domain == null) 
+        {
+            domain = (COSArray)(DictShading.getDictionaryObject(COSName.DOMAIN));
+            // use default values
+            if (domain == null) 
+            {
+                domain = new COSArray();
+                domain.add(new COSFloat(0.0f));
+                domain.add(new COSFloat(1.0f));
+            }
+        }
+        return domain;
+    }
+    
+    /**
+    * Returns the Extend array used by several of the gradient types. Interpretation depends on the ShadingType.
+    * Default is {false, false}.
+    *
+    * @return The Extend array.
+    */
+    public COSArray getExtend()
+    {
+        if (extend == null)
+        {
+            extend = (COSArray)(DictShading.getDictionaryObject(COSName.EXTEND));
+            // use default values
+            if (extend == null)
+            {
+                extend = new COSArray();
+                extend.add(COSBoolean.FALSE);
+                extend.add(COSBoolean.FALSE);
+            }
+        }
+        return extend;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() 
+    {
+        String sColorSpace;
+        String sFunction;
+        try
+        {
+            sColorSpace = getColorSpace().toString();
+        }catch (IOException e)
+        {
+            sColorSpace = "Failure retrieving ColorSpace: " + e.toString();
+        }
+        try
+        {
+            sFunction = getFunction().toString();
+        }catch(IOException e)
+        {
+            sFunction = "n/a";
+        }
+        
+        
+        String s = "Shading " + shadingname + "\n"
+            + "\tShadingType: " + getShadingType() + "\n"
+            + "\tColorSpace: " + sColorSpace + "\n"
+            + "\tAntiAlias: " + getAntiAlias() + "\n"
+            + "\tCoords: " + (getCoords() != null ? getCoords().toString() : "") + "\n"
+            + "\tDomain: " + getDomain().toString() + "\n"
+            + "\tFunction: " + sFunction + "\n"
+            + "\tExtend: " + getExtend().toString() + "\n"
+            + "\tRaw Value:\n" +
+         DictShading.toString();
+        
+        return s;
+    }
+    
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceCMYK.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceCMYK.java
new file mode 100644 (file)
index 0000000..24cc699
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.color.ColorSpace;
+
+/**
+ * This class represents a CMYK color space.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class ColorSpaceCMYK extends ColorSpace
+{
+    /**
+     * IDfor serialization.
+     */
+    private static final long serialVersionUID = -6362864473145799405L;
+    
+    /**
+     * Constructor.
+     */
+    public ColorSpaceCMYK()
+    {
+        super(ColorSpace.TYPE_CMYK,4);
+    }
+
+    /**
+     *  Converts colorvalues from RGB-colorspace to CIEXYZ-colorspace.
+     *  @param rgbvalue RGB colorvalues to be converted.
+     *  @return Returns converted colorvalues.
+     */
+    private float[] fromRGBtoCIEXYZ(float[] rgbvalue) 
+    {
+        ColorSpace colorspaceRGB = ColorSpace.getInstance(CS_sRGB);
+        return colorspaceRGB.toCIEXYZ(rgbvalue);
+    }
+    
+    /**
+     *  Converts colorvalues from CIEXYZ-colorspace to RGB-colorspace.
+     *  @param rgbvalue CIEXYZ colorvalues to be converted.
+     *  @return Returns converted colorvalues.
+     */
+    private float[] fromCIEXYZtoRGB(float[] xyzvalue) 
+    {
+        ColorSpace colorspaceXYZ = ColorSpace.getInstance(CS_CIEXYZ);
+        return colorspaceXYZ.toRGB(xyzvalue);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] fromCIEXYZ(float[] colorvalue) 
+    {
+        if (colorvalue != null && colorvalue.length == 3)
+        {
+            // We have to convert from XYV to RGB to CMYK
+            return fromRGB(fromCIEXYZtoRGB(colorvalue));
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] fromRGB(float[] rgbvalue) 
+    {
+        if (rgbvalue != null && rgbvalue.length == 3) 
+        {
+            // First of all we have to convert from RGB to CMY
+            float c = 1 - rgbvalue[0];
+            float m = 1 - rgbvalue[1];
+            float y = 1 - rgbvalue[2];
+            // Now we have to convert from CMY to CMYK
+            float varK = 1;
+            float[] cmyk = new float[4];
+            if ( c < varK )
+            {
+                varK = c;
+            }
+            if ( m < varK )
+            {
+                varK = m;
+            }
+            if ( y < varK ) 
+            {
+                varK = y;
+            }
+            if ( varK == 1 ) 
+            {
+                cmyk[0] = cmyk[1] = cmyk[2] = 0;
+            }
+            else 
+            {
+                cmyk[0] = ( c - varK ) / ( 1 - varK );
+                cmyk[1] = ( m - varK ) / ( 1 - varK );
+                cmyk[2] = ( y - varK ) / ( 1 - varK );
+            }
+            cmyk[3] = varK;
+            return cmyk;
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] toCIEXYZ(float[] colorvalue) 
+    {
+        if (colorvalue != null && colorvalue.length == 4)
+        {
+            // We have to convert from CMYK to RGB to XYV
+            return fromRGBtoCIEXYZ(toRGB(colorvalue));
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] toRGB(float[] colorvalue) 
+    {
+        if (colorvalue != null && colorvalue.length == 4) 
+        {
+            // First of all we have to convert from CMYK to CMY
+            float k = colorvalue[3];
+            float c = ( colorvalue[0] * ( 1 - k ) + k );
+            float m = ( colorvalue[1] * ( 1 - k ) + k );
+            float y = ( colorvalue[2] * ( 1 - k ) + k );
+            // Now we have to convert from CMY to RGB
+            return new float[] { 1 - c, 1 - m, 1 - y };
+        }
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceCalRGB.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceCalRGB.java
new file mode 100644 (file)
index 0000000..28842a1
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.color.ColorSpace;
+
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+
+/**
+ * This class represents a CalRGB color space.
+ *
+ * In the first place this implementation is needed to support CalRGB.
+ * To keep it simple, the CalRGB colorspace is treated similar to a DeviceRGB colorspace.
+ * There is no conversion including the gamma, whitepoint, blackpoint or matrix values yet.
+ * This should be suitable for displaying and simple printings.
+ *  
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class ColorSpaceCalRGB extends ColorSpace
+{
+    private PDGamma gamma = null;
+    private PDTristimulus whitepoint = null;
+    private PDTristimulus blackpoint = null;
+    private PDMatrix matrix = null;
+    
+    /**
+     * ID for serialization.
+     */
+    private static final long serialVersionUID = -6362864473145799405L;
+
+    /**
+     *  Constructor.
+     */
+    public ColorSpaceCalRGB()
+    {
+        super(ColorSpace.TYPE_3CLR,3);
+    }
+
+    /**
+     * Constructor.
+     * @param gammaValue Gamma
+     * @param whitept Whitepoint
+     * @param blackpt Blackpoint
+     * @param linearMatrix Matrix value
+     */
+    public ColorSpaceCalRGB(PDGamma gammaValue, PDTristimulus whitept, PDTristimulus blackpt, PDMatrix linearMatrix)
+    {
+        this();
+        this.gamma = gammaValue;
+        this.whitepoint = whitept;
+        this.blackpoint = blackpt;
+        this.matrix = linearMatrix;
+    }
+
+    /**
+     *  Converts colorvalues from RGB-colorspace to CIEXYZ-colorspace.
+     *  @param rgbvalue RGB colorvalues to be converted.
+     *  @return Returns converted colorvalues.
+     */
+    private float[] fromRGBtoCIEXYZ(float[] rgbvalue) 
+    {
+        ColorSpace colorspaceRGB = ColorSpace.getInstance(CS_sRGB);
+        return colorspaceRGB.toCIEXYZ(rgbvalue);
+    }
+    
+    /**
+     *  Converts colorvalues from CIEXYZ-colorspace to RGB-colorspace.
+     *  @param rgbvalue CIEXYZ colorvalues to be converted.
+     *  @return Returns converted colorvalues.
+     */
+    private float[] fromCIEXYZtoRGB(float[] xyzvalue) 
+    {
+        ColorSpace colorspaceXYZ = ColorSpace.getInstance(CS_CIEXYZ);
+        return colorspaceXYZ.toRGB(xyzvalue);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] fromCIEXYZ(float[] colorvalue)
+    {
+        if (colorvalue != null && colorvalue.length == 3)
+        {
+            // We have to convert from XYV to RGB
+            return fromCIEXYZtoRGB(colorvalue);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] fromRGB(float[] rgbvalue) 
+    {
+        if (rgbvalue != null && rgbvalue.length == 3) 
+        {
+            return rgbvalue;
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] toCIEXYZ(float[] colorvalue) 
+    {
+        if (colorvalue != null && colorvalue.length == 4)
+        {
+            // We have to convert from RGB to XYV
+            return fromRGBtoCIEXYZ(toRGB(colorvalue));
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public float[] toRGB(float[] colorvalue) 
+    {
+        if (colorvalue != null && colorvalue.length == 3) 
+        {
+            return colorvalue;
+        }
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDCalGray.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDCalGray.java
new file mode 100644 (file)
index 0000000..627d4f4
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Cal Gray color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDCalGray extends PDColorSpace
+{
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "CalGray";
+
+    private COSArray array;
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDCalGray()
+    {
+        array = new COSArray();
+        dictionary = new COSDictionary();
+        array.add( COSName.CALGRAY );
+        array.add( dictionary );
+    }
+
+    /**
+     * Constructor with array.
+     *
+     * @param gray The underlying color space.
+     */
+    public PDCalGray( COSArray gray )
+    {
+        array = gray;
+        dictionary = (COSDictionary)array.getObject( 1 );
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return 1;
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected ColorSpace createColorSpace() throws IOException
+    {
+        throw new IOException( "Not implemented" );
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        throw new IOException( "Not implemented" );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return array;
+    }
+
+    /**
+     * This will get the gamma value.  If none is present then the default of 1
+     * will be returned.
+     *
+     * @return The gamma value.
+     */
+    public float getGamma()
+    {
+        float retval = 1.0f;
+        COSNumber gamma = (COSNumber)dictionary.getDictionaryObject( COSName.GAMMA );
+        if( gamma != null )
+        {
+            retval = gamma.floatValue();
+        }
+        return retval;
+    }
+
+    /**
+     * Set the gamma value.
+     *
+     * @param value The new gamma value.
+     */
+    public void setGamma( float value )
+    {
+        dictionary.setItem( COSName.GAMMA, new COSFloat( value ) );
+    }
+
+    /**
+     * This will return the whitepoint tristimulus.  As this is a required field
+     * this will never return null.  A default of 1,1,1 will be returned if the
+     * pdf does not have any values yet.
+     *
+     * @return The whitepoint tristimulus.
+     */
+    public PDTristimulus getWhitepoint()
+    {
+        COSArray wp = (COSArray)dictionary.getDictionaryObject( COSName.WHITE_POINT );
+        if( wp == null )
+        {
+            wp = new COSArray();
+            wp.add( new COSFloat( 1.0f ) );
+            wp.add( new COSFloat( 1.0f ) );
+            wp.add( new COSFloat( 1.0f ) );
+            dictionary.setItem( COSName.WHITE_POINT, wp );
+        }
+        return new PDTristimulus( wp );
+    }
+
+    /**
+     * This will set the whitepoint tristimulus.  As this is a required field
+     * this null should not be passed into this function.
+     *
+     * @param wp The whitepoint tristimulus.
+     */
+    public void setWhitepoint( PDTristimulus wp )
+    {
+        COSBase wpArray = wp.getCOSObject();
+        if( wpArray != null )
+        {
+            dictionary.setItem( COSName.WHITE_POINT, wpArray );
+        }
+    }
+
+    /**
+     * This will return the BlackPoint tristimulus.  This is an optional field but
+     * has defaults so this will never return null.
+     * A default of 0,0,0 will be returned if the pdf does not have any values yet.
+     *
+     * @return The blackpoint tristimulus.
+     */
+    public PDTristimulus getBlackPoint()
+    {
+        COSArray bp = (COSArray)dictionary.getDictionaryObject( COSName.BLACK_POINT );
+        if( bp == null )
+        {
+            bp = new COSArray();
+            bp.add( new COSFloat( 0.0f ) );
+            bp.add( new COSFloat( 0.0f ) );
+            bp.add( new COSFloat( 0.0f ) );
+            dictionary.setItem( COSName.BLACK_POINT, bp );
+        }
+        return new PDTristimulus( bp );
+    }
+
+    /**
+     * This will set the BlackPoint tristimulus.  As this is a required field
+     * this null should not be passed into this function.
+     *
+     * @param bp The BlackPoint tristimulus.
+     */
+    public void setBlackPoint( PDTristimulus bp )
+    {
+        COSBase bpArray = null;
+        if( bp != null )
+        {
+            bpArray = bp.getCOSObject();
+        }
+        dictionary.setItem( COSName.BLACK_POINT, bpArray );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDCalRGB.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDCalRGB.java
new file mode 100644 (file)
index 0000000..eeabf9e
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.io.IOException;
+
+/**
+ * This class represents a Cal RGB color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDCalRGB extends PDColorSpace
+{
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "CalRGB";
+
+    private COSArray array;
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDCalRGB()
+    {
+        array = new COSArray();
+        dictionary = new COSDictionary();
+        array.add( COSName.CALRGB );
+        array.add( dictionary );
+    }
+
+    /**
+     * Constructor with array.
+     *
+     * @param rgb The underlying color space.
+     */
+    public PDCalRGB( COSArray rgb )
+    {
+        array = rgb;
+        dictionary = (COSDictionary)array.getObject( 1 );
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return 3;
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     */
+    protected ColorSpace createColorSpace()
+    {
+        return new ColorSpaceCalRGB(getGamma(),getWhitepoint(),getBlackPoint(),getLinearInterpretation());
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        int[] nBits = {bpc, bpc, bpc};
+        return new ComponentColorModel( getJavaColorSpace(),
+                   nBits,
+                   false,
+                   false,
+                   Transparency.OPAQUE,
+                   DataBuffer.TYPE_BYTE);
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return array;
+    }
+
+    /**
+     * This will return the whitepoint tristimulus.  As this is a required field
+     * this will never return null.  A default of 1,1,1 will be returned if the
+     * pdf does not have any values yet.
+     *
+     * @return The whitepoint tristimulus.
+     */
+    public PDTristimulus getWhitepoint()
+    {
+        COSArray wp = (COSArray)dictionary.getDictionaryObject( COSName.WHITE_POINT );
+        if( wp == null )
+        {
+            wp = new COSArray();
+            wp.add( new COSFloat( 1.0f ) );
+            wp.add( new COSFloat( 1.0f ) );
+            wp.add( new COSFloat( 1.0f ) );
+            dictionary.setItem( COSName.WHITE_POINT, wp );
+        }
+        return new PDTristimulus( wp );
+    }
+
+    /**
+     * This will set the whitepoint tristimulus.  As this is a required field
+     * this null should not be passed into this function.
+     *
+     * @param wp The whitepoint tristimulus.
+     */
+    public void setWhitepoint( PDTristimulus wp )
+    {
+        COSBase wpArray = wp.getCOSObject();
+        if( wpArray != null )
+        {
+            dictionary.setItem( COSName.WHITE_POINT, wpArray );
+        }
+    }
+
+    /**
+     * This will return the BlackPoint tristimulus.  This is an optional field but
+     * has defaults so this will never return null.
+     * A default of 0,0,0 will be returned if the pdf does not have any values yet.
+     *
+     * @return The blackpoint tristimulus.
+     */
+    public PDTristimulus getBlackPoint()
+    {
+        COSArray bp = (COSArray)dictionary.getDictionaryObject( COSName.BLACK_POINT );
+        if( bp == null )
+        {
+            bp = new COSArray();
+            bp.add( new COSFloat( 0.0f ) );
+            bp.add( new COSFloat( 0.0f ) );
+            bp.add( new COSFloat( 0.0f ) );
+            dictionary.setItem( COSName.BLACK_POINT, bp );
+        }
+        return new PDTristimulus( bp );
+    }
+
+    /**
+     * This will set the BlackPoint tristimulus.  As this is a required field
+     * this null should not be passed into this function.
+     *
+     * @param bp The BlackPoint tristimulus.
+     */
+    public void setBlackPoint( PDTristimulus bp )
+    {
+
+        COSBase bpArray = null;
+        if( bp != null )
+        {
+            bpArray = bp.getCOSObject();
+        }
+        dictionary.setItem( COSName.BLACK_POINT, bpArray );
+    }
+
+    /**
+     * This will get the gamma value.  If none is present then the default of 1,1,1
+     * will be returned.
+     *
+     * @return The gamma value.
+     */
+    public PDGamma getGamma()
+    {
+        COSArray gamma = (COSArray)dictionary.getDictionaryObject( COSName.GAMMA );
+        if( gamma == null )
+        {
+            gamma = new COSArray();
+            gamma.add( new COSFloat( 1.0f ) );
+            gamma.add( new COSFloat( 1.0f ) );
+            gamma.add( new COSFloat( 1.0f ) );
+            dictionary.setItem( COSName.GAMMA, gamma );
+        }
+        return new PDGamma( gamma );
+    }
+
+    /**
+     * Set the gamma value.
+     *
+     * @param value The new gamma value.
+     */
+    public void setGamma( PDGamma value )
+    {
+        COSArray gamma = null;
+        if( value != null )
+        {
+            gamma = value.getCOSArray();
+        }
+        dictionary.setItem( COSName.GAMMA, gamma );
+    }
+
+    /**
+     * This will get the linear interpretation array.  This is guaranteed to not
+     * return null.  If the underlying dictionary contains null then the identity
+     * matrix will be returned.
+     *
+     * @return The linear interpretation matrix.
+     */
+    public PDMatrix getLinearInterpretation()
+    {
+        PDMatrix retval = null;
+        COSArray matrix = (COSArray)dictionary.getDictionaryObject( COSName.MATRIX );
+        if( matrix == null )
+        {
+            retval = new PDMatrix();
+            setLinearInterpretation( retval );
+        }
+        else
+        {
+            retval = new PDMatrix( matrix );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the linear interpretation matrix.  Passing in null will
+     * clear the matrix.
+     *
+     * @param matrix The new linear interpretation matrix.
+     */
+    public void setLinearInterpretation( PDMatrix matrix )
+    {
+        COSArray matrixArray = null;
+        if( matrix != null )
+        {
+            matrixArray = matrix.getCOSArray();
+        }
+        dictionary.setItem( COSName.MATRIX, matrixArray );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpace.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpace.java
new file mode 100644 (file)
index 0000000..191a977
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSArray;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import java.io.IOException;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+
+/**
+ * This class represents a color space in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public abstract class PDColorSpace implements COSObjectable
+{
+    /**
+     * array for the given parameters. 
+     */
+    protected COSArray array;
+
+    /**
+     * Cached Java AWT color space.
+     *
+     * @see #getJavaColorSpace()
+     */
+    private ColorSpace colorSpace = null;
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public abstract String getName();
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public abstract int getNumberOfComponents() throws IOException;
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return COSName.getPDFName( getName() );
+    }
+
+    /**
+     * Returns the Java AWT color space for this instance.
+     *
+     * @return Java AWT color space
+     * @throws IOException if the color space can not be created
+     */
+    public ColorSpace getJavaColorSpace() throws IOException {
+        if (colorSpace == null) {
+            colorSpace = createColorSpace();
+        }
+        return colorSpace;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected abstract ColorSpace createColorSpace() throws IOException;
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public abstract ColorModel createColorModel( int bpc ) throws IOException;
+
+    /*
+    Don't just tell me its color type -- tell me its contents!
+    */
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return  getName() + "{ " + (array==null? "" : array.toString() ) + " }";
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpaceFactory.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorSpaceFactory.java
new file mode 100644 (file)
index 0000000..552a500
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+
+/**
+ * This class represents a color space in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public final class PDColorSpaceFactory
+{
+    /**
+     * Private constructor for utility classes.
+     */
+    private PDColorSpaceFactory()
+    {
+        //utility class should not be implemented
+    }
+
+    /**
+     * This will create the correct color space given the name.
+     *
+     * @param colorSpace The color space object.
+     *
+     * @return The color space.
+     *
+     * @throws IOException If the color space name is unknown.
+     */
+    public static PDColorSpace createColorSpace( COSBase colorSpace ) throws IOException
+    {
+        return createColorSpace( colorSpace, null );
+    }
+
+    /**
+     * This will create the correct color space given the name.
+     *
+     * @param colorSpace The color space object.
+     * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+     *
+     * @return The color space.
+     *
+     * @throws IOException If the color space name is unknown.
+     */
+    public static PDColorSpace createColorSpace( COSBase colorSpace, Map<String, PDColorSpace> colorSpaces ) throws IOException
+    {
+        PDColorSpace retval = null;
+        if( colorSpace instanceof COSName )
+        {
+            retval = createColorSpace( ((COSName)colorSpace).getName(), colorSpaces );
+        }
+        else if( colorSpace instanceof COSArray )
+        {
+            COSArray array = (COSArray)colorSpace;
+            String name = ((COSName)array.getObject( 0 )).getName();
+            if( name.equals( PDCalGray.NAME ) )
+            {
+                retval = new PDCalGray( array );
+            }
+            else if( name.equals( PDDeviceRGB.NAME ) )
+            {
+                retval = PDDeviceRGB.INSTANCE;
+            }
+            else if( name.equals( PDDeviceGray.NAME ) )
+            {
+                retval = new PDDeviceGray();
+            }
+            else if( name.equals( PDDeviceCMYK.NAME ) )
+            {
+                retval = PDDeviceCMYK.INSTANCE;
+            }
+            else if( name.equals( PDCalRGB.NAME ) )
+            {
+                retval = new PDCalRGB( array );
+            }
+            else if( name.equals( PDDeviceN.NAME ) )
+            {
+                retval = new PDDeviceN( array );
+            }
+            else if( name.equals( PDIndexed.NAME ) ||
+                   name.equals( PDIndexed.ABBREVIATED_NAME ))
+            {
+                retval = new PDIndexed( array );
+            }
+            else if( name.equals( PDLab.NAME ) )
+            {
+                retval = new PDLab( array );
+            }
+            else if( name.equals( PDSeparation.NAME ) )
+            {
+                retval = new PDSeparation( array );
+            }
+            else if( name.equals( PDICCBased.NAME ) )
+            {
+                retval = new PDICCBased( array );
+            }
+            else if( name.equals( PDPattern.NAME ) )
+            {
+                retval = new PDPattern( array );
+            }
+            else
+            {
+                throw new IOException( "Unknown colorspace array type:" + name );
+            }
+        }
+        else
+        {
+            throw new IOException( "Unknown colorspace type:" + colorSpace );
+        }
+        return retval;
+    }
+
+    /**
+     * This will create the correct color space given the name.
+     *
+     * @param colorSpaceName The name of the colorspace.
+     *
+     * @return The color space.
+     *
+     * @throws IOException If the color space name is unknown.
+     */
+    public static PDColorSpace createColorSpace( String colorSpaceName ) throws IOException
+    {
+        return createColorSpace(colorSpaceName, null);
+    }
+
+    /**
+     * This will create the correct color space given the name.
+     *
+     * @param colorSpaceName The name of the colorspace.
+     * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+     *
+     * @return The color space.
+     *
+     * @throws IOException If the color space name is unknown.
+     */
+    public static PDColorSpace createColorSpace( String colorSpaceName, Map<String, PDColorSpace> colorSpaces ) throws IOException
+    {
+        PDColorSpace cs = null;
+        if( colorSpaceName.equals( PDDeviceCMYK.NAME ) ||
+                 colorSpaceName.equals( PDDeviceCMYK.ABBREVIATED_NAME ) )
+        {
+            cs = PDDeviceCMYK.INSTANCE;
+        }
+        else if( colorSpaceName.equals( PDDeviceRGB.NAME ) ||
+                 colorSpaceName.equals( PDDeviceRGB.ABBREVIATED_NAME ) )
+        {
+            cs = PDDeviceRGB.INSTANCE;
+        }
+        else if( colorSpaceName.equals( PDDeviceGray.NAME ) ||
+                 colorSpaceName.equals( PDDeviceGray.ABBREVIATED_NAME ))
+        {
+            cs = new PDDeviceGray();
+        }
+        else if( colorSpaces != null && colorSpaces.get( colorSpaceName ) != null )
+        {
+            cs = (PDColorSpace)colorSpaces.get( colorSpaceName );
+        }
+        else if( colorSpaceName.equals( PDLab.NAME ) )
+        {
+            cs = new PDLab();
+        }
+        else if( colorSpaceName.equals( PDPattern.NAME ) )
+        {
+            cs = new PDPattern();
+        }
+        else
+        {
+            throw new IOException( "Error: Unknown colorspace '" + colorSpaceName + "'" );
+        }
+        return cs;
+    }
+
+    /**
+     * This will create the correct color space from a java colorspace.
+     *
+     * @param doc The doc to potentiall write information to.
+     * @param cs The awt colorspace.
+     *
+     * @return The color space.
+     *
+     * @throws IOException If the color space name is unknown.
+     */
+    public static PDColorSpace createColorSpace( PDDocument doc, ColorSpace cs ) throws IOException
+    {
+        PDColorSpace retval = null;
+        if( cs.isCS_sRGB() )
+        {
+            retval = PDDeviceRGB.INSTANCE;
+        }
+        else if( cs instanceof ICC_ColorSpace )
+        {
+            ICC_ColorSpace ics = (ICC_ColorSpace)cs;
+            PDICCBased pdCS = new PDICCBased( doc );
+            retval = pdCS;
+            COSArray ranges = new COSArray();
+            for( int i=0; i<cs.getNumComponents(); i++ )
+            {
+                ranges.add( new COSFloat( ics.getMinValue( i ) ) );
+                ranges.add( new COSFloat( ics.getMaxValue( i ) ) );
+            }
+            PDStream iccData = pdCS.getPDStream();
+            OutputStream output = null;
+            try
+            {
+                output = iccData.createOutputStream();
+                output.write( ics.getProfile().getData() );
+            }
+            finally
+            {
+                if( output != null )
+                {
+                    output.close();
+                }
+            }
+            pdCS.setNumberOfComponents( cs.getNumComponents() );
+        }
+        else
+        {
+            throw new IOException( "Not yet implemented:" + cs );
+        }
+        return retval;
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorState.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDColorState.java
new file mode 100644 (file)
index 0000000..4cf3a44
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.Color;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+
+/**
+ * This class represents a color space and the color value for that colorspace.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PDColorState implements Cloneable
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDColorState.class);
+
+    /**
+     * The default color that can be set to replace all colors in
+     * {@link ICC_ColorSpace ICC color spaces}.
+     *
+     * @see #setIccOverrideColor(Color)
+     */
+    private static volatile Color iccOverrideColor =
+        Color.getColor("org.apache.pdfbox.ICC_override_color");
+
+    /**
+     * Sets the default color to replace all colors in
+     * {@link ICC_ColorSpace ICC color spaces}. This will work around
+     * a potential JVM crash caused by broken native ICC color manipulation
+     * code in the Sun class libraries.
+     * <p>
+     * The default override can be specified by setting the color code in
+     * <code>org.apache.pdfbox.ICC_override_color</code> system property
+     * (see {@link Color#getColor(String)}. If this system property is not
+     * specified, then the override is not enabled unless this method is
+     * explicitly called.
+     *
+     * @param color ICC override color,
+     *              or <code>null</code> to disable the override
+     * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-511">PDFBOX-511</a>
+     * @since Apache PDFBox 0.8.1
+     */
+    public static void setIccOverrideColor(Color color) {
+        iccOverrideColor = color;
+    }
+
+    private PDColorSpace colorSpace = new PDDeviceGray();
+    private COSArray colorSpaceValue = new COSArray();
+
+    /**
+     * Cached Java AWT color based on the current color space and value.
+     * The value is cleared whenever the color space or value is set.
+     *
+     * @see #getJavaColor()
+     */
+    private Color color = null;
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDColorState()
+    {
+        setColorSpaceValue( new float[] {0});
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object clone()
+    {
+        PDColorState retval = new PDColorState();
+        retval.colorSpace = this.colorSpace;
+        retval.colorSpaceValue.clear();
+        retval.colorSpaceValue.addAll( this.colorSpaceValue );
+        return retval;
+    }
+
+    /**
+     * Returns the Java AWT color based on the current color space and value.
+     *
+     * @return current Java AWT color
+     * @throws IOException if the current color can not be created
+     */
+    public Color getJavaColor() throws IOException {
+        if (color == null) {
+            color = createColor();
+        }
+        return color;
+    }
+
+    /**
+     * Create the current color from the colorspace and values.
+     * @return The current awt color.
+     * @throws IOException If there is an error creating the color.
+     */
+    private Color createColor() throws IOException
+    {
+        float[] components = colorSpaceValue.toFloatArray();
+        try
+        {
+            if( colorSpace.getName().equals(PDDeviceRGB.NAME) && components.length == 3 )
+            {
+                //for some reason, when using RGB and the RGB colorspace
+                //the new Color doesn't maintain exactly the same values
+                //I think some color conversion needs to take place first
+                //for now we will just make rgb a special case.
+                return new Color( components[0], components[1], components[2] );
+            }
+            else
+            {
+                if (components.length == 1) 
+                {
+                    if (colorSpace.getName().equals(PDSeparation.NAME))
+                    {
+                        //Use that component as a single-integer RGB value
+                        return new Color((int)components[0]);
+                    }
+                    if (colorSpace.getName().equals(PDDeviceGray.NAME))
+                    {
+                        // Handling DeviceGray as a special case as with JVM 1.5.0_15 
+                        // and maybe others printing on Windows fails with an 
+                        // ArrayIndexOutOfBoundsException when selecting colors
+                        // and strokes e.g. sun.awt.windows.WPrinterJob.setTextColor
+                        return new Color(components[0],components[0],components[0]);
+                    }
+                }
+                Color override = iccOverrideColor;
+                ColorSpace cs = colorSpace.getJavaColorSpace();
+                if (cs instanceof ICC_ColorSpace && override != null)
+                {
+                    log.warn(
+                            "Using an ICC override color to avoid a potential"
+                            + " JVM crash (see PDFBOX-511)");
+                    return override;
+                }
+                else
+                {
+                    return new Color( cs, components, 1f );
+                }
+            }
+        }
+        // Catch IOExceptions from PDColorSpace.getJavaColorSpace(), but
+        // possibly also IllegalArgumentExceptions or other RuntimeExceptions
+        // from the potentially complex color management code.
+        catch (Exception e)
+        {
+            Color cGuess;
+            String sMsg = "Unable to create the color instance "
+                    + Arrays.toString(components) + " in color space "
+                    + colorSpace + "; guessing color ... ";
+            try
+            {
+                switch(components.length)
+                {
+                    case 1://Use that component as a single-integer RGB value
+                        cGuess = new Color((int)components[0]);
+                        sMsg += "\nInterpretating as single-integer RGB";
+                        break;
+                    case 3: //RGB
+                        cGuess = new Color(components[0],components[1],components[2]);
+                        sMsg += "\nInterpretating as RGB";
+                        break;
+                    case 4: //CMYK
+                        //do a rough conversion to RGB as I'm not getting the CMYK to work.
+                        //http://www.codeproject.com/KB/applications/xcmyk.aspx
+                        float R, G, B, K;
+                        K = components[3];
+
+                        R = components[0] * (1f - K) + K;
+                        G = components[1] * (1f - K) + K;
+                        B = components[2] * (1f - K) + K;
+                    
+                        R = (1f - R);
+                        G = (1f - G);
+                        B = (1f - B);
+                                
+                        cGuess = new Color( R,G,B );
+                        sMsg += "\nInterpretating as CMYK";
+                        break;
+                    default:
+                        
+                        sMsg += "\nUnable to guess using " + components.length + " components; using black instead";
+                         cGuess = Color.BLACK;
+                }
+            }
+            catch (Exception e2)
+            {
+                sMsg += "\nColor interpolation failed; using black instead\n";
+                sMsg += e2.toString();
+                cGuess = Color.BLACK;
+            }
+            log.warn(sMsg, e);
+            return cGuess;
+        }
+    }
+
+    /**
+     * Constructor with an existing color set.  Default colorspace is PDDeviceGray.
+     *
+     * @param csValues The color space values.
+     */
+    public PDColorState( COSArray csValues )
+    {
+        colorSpaceValue = csValues;
+    }
+
+
+    /**
+     * This will get the current colorspace.
+     *
+     * @return The current colorspace.
+     */
+    public PDColorSpace getColorSpace()
+    {
+        return colorSpace;
+    }
+
+    /**
+     * This will set the current colorspace.
+     *
+     * @param value The new colorspace.
+     */
+    public void setColorSpace(PDColorSpace value)
+    {
+        colorSpace = value;
+        // Clear color cache
+        color = null;
+    }
+
+    /**
+     * This will get the color space values.  Either 1 for gray or 3 for RGB.
+     *
+     * @return The colorspace values.
+     */
+    public float[] getColorSpaceValue()
+    {
+        return colorSpaceValue.toFloatArray();
+    }
+
+    /**
+     * This will get the color space values.  Either 1 for gray or 3 for RGB.
+     *
+     * @return The colorspace values.
+     */
+    public COSArray getCOSColorSpaceValue()
+    {
+        return colorSpaceValue;
+    }
+
+    /**
+     * This will update the colorspace values.
+     *
+     * @param value The new colorspace values.
+     */
+    public void setColorSpaceValue(float[] value)
+    {
+        colorSpaceValue.setFloatArray( value );
+        // Clear color cache
+        color = null;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceCMYK.java
new file mode 100644 (file)
index 0000000..6d8e5e1
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.graphics.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.io.IOException;
+
+import java.awt.Transparency;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+
+
+/**
+ * This class represents a CMYK color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDDeviceCMYK extends PDColorSpace
+{
+    /**
+     * The single instance of this class.
+     */
+    public static final PDDeviceCMYK INSTANCE = new PDDeviceCMYK();
+
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "DeviceCMYK";
+
+    /**
+     * The abbreviated name of this color space.
+     */
+    public static final String ABBREVIATED_NAME = "CMYK";
+
+    private PDDeviceCMYK()
+    {
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return 4;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     */
+    protected ColorSpace createColorSpace()
+    {
+        return new ColorSpaceCMYK();
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+
+        int[] nbBits = { bpc, bpc, bpc, bpc };
+        ComponentColorModel componentColorModel = 
+            new ComponentColorModel( getJavaColorSpace(), 
+                         nbBits, 
+                         false,                     
+                         false,              
+                         Transparency.OPAQUE,
+                         DataBuffer.TYPE_BYTE );
+           return componentColorModel;
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java
new file mode 100644 (file)
index 0000000..bd63ab3
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.color.ColorSpace;
+
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.Transparency;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Gray color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDDeviceGray extends PDColorSpace
+{
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "DeviceGray";
+
+    /**
+     * The abbreviated name of this color space.
+     */
+    public static final String ABBREVIATED_NAME = "G";
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return 1;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     */
+    protected ColorSpace createColorSpace()
+    {
+        return ColorSpace.getInstance( ColorSpace.CS_GRAY );
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
+        int[] nBits = {bpc};
+        ColorModel colorModel = new ComponentColorModel(cs, nBits, false,false,
+                Transparency.OPAQUE,DataBuffer.TYPE_BYTE);
+        return colorModel;
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceN.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceN.java
new file mode 100644 (file)
index 0000000..f494b9c
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.function.PDFunction;
+import org.apache.pdfbox.pdmodel.common.function.PDFunctionType4;
+
+/**
+ * This class represents a DeviceN color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDDeviceN extends PDColorSpace
+{
+   
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDDeviceN.class);
+
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "DeviceN";
+
+    private COSArray array;
+
+    /**
+     * Constructor.
+     */
+    public PDDeviceN()
+    {
+        array = new COSArray();
+        array.add( COSName.DEVICEN );
+        array.add( COSName.getPDFName( "" ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param separation The array containing all separation information.
+     */
+    public PDDeviceN( COSArray separation )
+    {
+        array = separation;
+    }
+
+    /**
+     * This will return the name of the color space.  For a PDSeparation object
+     * this will always return "Separation"
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return getColorantNames().size();
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected ColorSpace createColorSpace() throws IOException
+    {
+        try
+        {
+            PDColorSpace alt = getAlternateColorSpace();
+            return alt.getJavaColorSpace();
+        }
+        catch (IOException ioexception)
+        {
+            log.error(ioexception, ioexception);
+            throw ioexception;
+        }
+        catch (Exception exception)
+        {
+            log.error(exception, exception);
+            throw new IOException("Failed to Create ColorSpace");
+        }
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        log.info("About to create ColorModel for " + getAlternateColorSpace().toString());
+        return getAlternateColorSpace().createColorModel(bpc);
+    }
+
+    /**
+     * This will get the colorant names.  A list of string objects.
+     *
+     * @return A list of colorants
+     */
+    public List<COSBase> getColorantNames()
+    {
+        COSArray names = (COSArray)array.getObject( 1 );
+        return COSArrayList.convertCOSNameCOSArrayToList( names );
+    }
+
+    /**
+     * This will set the list of colorants.
+     *
+     * @param names The list of colorant names.
+     */
+    public void setColorantNames( List<COSBase> names )
+    {
+        COSArray namesArray = COSArrayList.convertStringListToCOSNameCOSArray( names );
+        array.set( 1, namesArray );
+    }
+
+    /**
+     * This will get the alternate color space for this separation.
+     *
+     * @return The alternate color space.
+     *
+     * @throws IOException If there is an error getting the alternate color space.
+     */
+    public PDColorSpace getAlternateColorSpace() throws IOException
+    {
+        COSBase alternate = array.getObject( 2 );
+        return PDColorSpaceFactory.createColorSpace( alternate );
+    }
+
+    /**
+     * This will set the alternate color space.
+     *
+     * @param cs The alternate color space.
+     */
+    public void setAlternateColorSpace( PDColorSpace cs )
+    {
+        COSBase space = null;
+        if( cs != null )
+        {
+            space = cs.getCOSObject();
+        }
+        array.set( 2, space );
+    }
+
+    /**
+     * This will get the tint transform function.
+     *
+     * @return The tint transform function.
+     *
+     * @throws IOException if there is an error creating the function.
+     */
+    public PDFunction getTintTransform() throws IOException
+    {
+        return  PDFunction.create( array.getObject( 3 ) );
+    }
+
+    /**
+     * This will set the tint transform function.
+     *
+     * @param tint The tint transform function.
+     */
+    public void setTintTransform( PDFunction tint )
+    {
+        array.set( 3, tint );
+    }
+
+    /**
+     * This will get the attributes that are associated with the deviceN
+     * color space.
+     *
+     * @return The DeviceN attributes.
+     */
+    public PDDeviceNAttributes getAttributes()
+    {
+        PDDeviceNAttributes retval = null;
+        if( array.size() <5)
+        {
+            retval = new PDDeviceNAttributes();
+            setAttributes( retval );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the color space attributes.  If null is passed in then
+     * all attribute will be removed.
+     *
+     * @param attributes The color space attributes.
+     */
+    public void setAttributes( PDDeviceNAttributes attributes )
+    {
+        if( attributes == null )
+        {
+            array.remove( 4 );
+        }
+        else
+        {
+            //make sure array is large enough
+            while( array.size() < 5 )
+            {
+                array.add( COSNull.NULL );
+            }
+            array.set( 4, attributes.getCOSDictionary() );
+        }
+    }
+    
+    /**
+     * Returns the components of the color in the alternate colorspace for the given tint value.
+     * @return COSArray with the color components
+     * @throws IOException If the tint function is not supported
+     */
+    public COSArray calculateColorValues(List<COSBase> tintValues) throws IOException
+    {
+        PDFunction tintTransform = getTintTransform();
+        if(tintTransform instanceof PDFunctionType4)
+        {
+            log.warn("Unsupported tint transformation type: "+tintTransform.getClass().getName() 
+                    + " in "+getClass().getName()+".getColorValues()"
+                    + " using color black instead.");
+            int numberOfComponents = getAlternateColorSpace().getNumberOfComponents();
+            // To get black as color:
+            // 0.0f is used for the single value(s) if the colorspace is gray or RGB based
+            // 1.0f is used for the single value if the colorspace is CMYK based
+            float colorValue = numberOfComponents == 4 ? 1.0f : 0.0f;
+            COSArray retval = new COSArray();
+            for (int i=0;i<numberOfComponents;i++) 
+            {
+                retval.add(new COSFloat(colorValue));
+            }
+            return retval;
+        }
+        else
+        {
+            COSArray tint = new COSArray();
+            tint.addAll(tintValues);
+            return tintTransform.eval(tint);
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceNAttributes.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceNAttributes.java
new file mode 100644 (file)
index 0000000..caee91c
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This class represents attributes for a DeviceN color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDDeviceNAttributes
+{
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDDeviceNAttributes()
+    {
+        dictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param attributes A dictionary that has all of the attributes.
+     */
+    public PDDeviceNAttributes( COSDictionary attributes )
+    {
+        dictionary = attributes;
+    }
+
+    /**
+     * This will get the underlying cos dictionary.
+     *
+     * @return The dictionary that this object wraps.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * This will get a map of colorants.  See the PDF Reference for more details about
+     * this attribute.  The map will contain a java.lang.String as the key, a colorant name,
+     * and a PDColorSpace as the value.
+     *
+     * @return The colorant map.
+     *
+     * @throws IOException If there is an error getting the colorspaces.
+     */
+    public Map getColorants() throws IOException
+    {
+        Map actuals = new HashMap();
+        COSDictionary colorants = (COSDictionary)dictionary.getDictionaryObject( COSName.COLORANTS );
+        if( colorants == null )
+        {
+            colorants = new COSDictionary();
+            dictionary.setItem( COSName.COLORANTS, colorants );
+        }
+        for( COSName name : colorants.keySet() )
+        {
+            COSBase value = colorants.getDictionaryObject( name );
+            actuals.put( name.getName(), PDColorSpaceFactory.createColorSpace( value ) );
+        }
+        return new COSDictionaryMap( actuals, colorants );
+    }
+
+    /**
+     * This will replace the existing colorant attribute.  The key should be strings
+     * and the values should be PDColorSpaces.
+     *
+     * @param colorants The map of colorants.
+     */
+    public void setColorants( Map colorants )
+    {
+        COSDictionary colorantDict = null;
+        if( colorants != null )
+        {
+            colorantDict = COSDictionaryMap.convert( colorants );
+        }
+        dictionary.setItem( COSName.COLORANTS, colorantDict );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java
new file mode 100644 (file)
index 0000000..87307d3
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.Transparency;
+
+import java.awt.color.ColorSpace;
+
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+
+import java.io.IOException;
+
+/**
+ * This class represents an RGB color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class PDDeviceRGB extends PDColorSpace
+{
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "DeviceRGB";
+
+    /**
+     * The abbreviated name of this color space.
+     */
+    public static final String ABBREVIATED_NAME = "RGB";
+
+    /**
+     * This is the single instance of this class.
+     */
+    public static final PDDeviceRGB INSTANCE = new PDDeviceRGB();
+
+    /**
+     * This class is immutable.
+     */
+    private PDDeviceRGB()
+    {
+        //only here to make immutable.
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return 3;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     */
+    protected ColorSpace createColorSpace()
+    {
+        return ColorSpace.getInstance( ColorSpace.CS_sRGB );
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        int[] nbBits = { bpc, bpc, bpc };
+        ComponentColorModel componentColorModel =
+                new ComponentColorModel( getJavaColorSpace(),
+                                         nbBits,
+                                         false,
+                                         false,
+                                         Transparency.OPAQUE,
+                                         DataBuffer.TYPE_BYTE );
+
+        return componentColorModel;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDGamma.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDGamma.java
new file mode 100644 (file)
index 0000000..3d0fb77
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * A gamma array, or collection of three floating point parameters used for
+ * color operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDGamma implements COSObjectable
+{
+    private COSArray values = null;
+
+    /**
+     * Constructor.  Defaults all values to 0, 0, 0.
+     */
+    public PDGamma()
+    {
+        values = new COSArray();
+        values.add( new COSFloat( 0.0f ) );
+        values.add( new COSFloat( 0.0f ) );
+        values.add( new COSFloat( 0.0f ) );
+    }
+
+    /**
+     * Constructor from COS object.
+     *
+     * @param array The array containing the XYZ values.
+     */
+    public PDGamma( COSArray array )
+    {
+        values = array;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return values;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSArray getCOSArray()
+    {
+        return values;
+    }
+
+    /**
+     * This will get the r value of the tristimulus.
+     *
+     * @return The R value.
+     */
+    public float getR()
+    {
+        return ((COSNumber)values.get( 0 )).floatValue();
+    }
+
+    /**
+     * This will set the r value of the tristimulus.
+     *
+     * @param r The r value for the tristimulus.
+     */
+    public void setR( float r )
+    {
+        values.set( 0, new COSFloat( r ) );
+    }
+
+    /**
+     * This will get the g value of the tristimulus.
+     *
+     * @return The g value.
+     */
+    public float getG()
+    {
+        return ((COSNumber)values.get( 1 )).floatValue();
+    }
+
+    /**
+     * This will set the g value of the tristimulus.
+     *
+     * @param g The g value for the tristimulus.
+     */
+    public void setG( float g )
+    {
+        values.set( 1, new COSFloat( g ) );
+    }
+
+    /**
+     * This will get the b value of the tristimulus.
+     *
+     * @return The B value.
+     */
+    public float getB()
+    {
+        return ((COSNumber)values.get( 2 )).floatValue();
+    }
+
+    /**
+     * This will set the b value of the tristimulus.
+     *
+     * @param b The b value for the tristimulus.
+     */
+    public void setB( float b )
+    {
+        values.set( 2, new COSFloat( b ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDICCBased.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDICCBased.java
new file mode 100644 (file)
index 0000000..85e917e
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents a ICC profile color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDICCBased extends PDColorSpace
+{
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "ICCBased";
+
+    //private COSArray array;
+    private PDStream stream;
+
+    /**
+     * Default constructor, creates empty stream.
+     *
+     * @param doc The document to store the icc data.
+     */
+    public PDICCBased( PDDocument doc )
+    {
+        array = new COSArray();
+        array.add( COSName.ICCBASED );
+        array.add( new PDStream( doc ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param iccArray The ICC stream object.
+     */
+    public PDICCBased( COSArray iccArray )
+    {
+        array = iccArray;
+        stream = new PDStream( (COSStream)iccArray.getObject( 1 ) );
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return array;
+    }
+
+    /**
+     * Get the pd stream for this icc color space.
+     *
+     * @return Get the stream for this icc based color space.
+     */
+    public PDStream getPDStream()
+    {
+        return stream;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected ColorSpace createColorSpace() throws IOException
+    {
+        InputStream profile = null;
+        ColorSpace cSpace = null;
+        try
+        {
+            profile = stream.createInputStream();
+            ICC_Profile iccProfile = ICC_Profile.getInstance( profile );
+            cSpace = new ICC_ColorSpace( iccProfile );
+        }
+        finally
+        {
+            if( profile != null )
+            {
+                profile.close();
+            }
+        }
+        return cSpace;
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+
+            int[] nbBits = { bpc, bpc, bpc, bpc }; //added 4th bpc to handle CMYK
+            ComponentColorModel componentColorModel =
+                    new ComponentColorModel( getJavaColorSpace(),
+                                             nbBits,
+                                             false,
+                                             false,
+                                             Transparency.OPAQUE,
+                                             DataBuffer.TYPE_BYTE );
+
+            return componentColorModel;
+        
+    }
+
+    /**
+     * This will return the number of color components.  As of PDF 1.4 this will
+     * be 1,3,4.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        COSNumber n = (COSNumber)stream.getStream().getDictionaryObject( COSName.N );
+        return n.intValue();
+    }
+
+    /**
+     * This will set the number of color components.
+     *
+     * @param n The number of color components.
+     */
+    public void setNumberOfComponents( int n )
+    {
+        stream.getStream().setInt( COSName.N, n );
+    }
+
+    /**
+     * This will return a list of alternate color spaces(PDColorSpace) if the display application
+     * does not support this icc stream.
+     *
+     * @return A list of alternate color spaces.
+     *
+     * @throws IOException If there is an error getting the alternate color spaces.
+     */
+    public List getAlternateColorSpaces() throws IOException
+    {
+        COSBase alternate = stream.getStream().getDictionaryObject( COSName.ALTERNATE );
+        COSArray alternateArray = null;
+        if( alternate == null )
+        {
+            alternateArray = new COSArray();
+            int numComponents = getNumberOfComponents();
+            COSName csName = null;
+            if( numComponents == 1 )
+            {
+                csName = COSName.DEVICEGRAY;
+            }
+            else if( numComponents == 3 )
+            {
+                csName = COSName.DEVICERGB;
+            }
+            else if( numComponents == 4 )
+            {
+                csName = COSName.DEVICECMYK;
+            }
+            else
+            {
+                throw new IOException( "Unknown colorspace number of components:" + numComponents );
+            }
+            alternateArray.add( csName );
+        }
+        else
+        {
+            if( alternate instanceof COSArray )
+            {
+                alternateArray = (COSArray)alternate;
+            }
+            else if( alternate instanceof COSName )
+            {
+                alternateArray = new COSArray();
+                alternateArray.add( alternate );
+            }
+            else
+            {
+                throw new IOException( "Error: expected COSArray or COSName and not " +
+                    alternate.getClass().getName() );
+            }
+        }
+        List retval = new ArrayList();
+        for( int i=0; i<alternateArray.size(); i++ )
+        {
+            retval.add( PDColorSpaceFactory.createColorSpace( alternateArray.get( i ) ) );
+        }
+        return new COSArrayList( retval, alternateArray );
+    }
+
+    /**
+     * This will set the list of alternate color spaces.  This should be a list
+     * of PDColorSpace objects.
+     *
+     * @param list The list of colorspace objects.
+     */
+    public void setAlternateColorSpaces( List list )
+    {
+        COSArray altArray = null;
+        if( list != null )
+        {
+            altArray = COSArrayList.converterToCOSArray( list );
+        }
+        stream.getStream().setItem( COSName.ALTERNATE, altArray );
+    }
+
+    private COSArray getRangeArray( int n )
+    {
+        COSArray rangeArray = (COSArray)stream.getStream().getDictionaryObject( COSName.RANGE);
+        if( rangeArray == null )
+        {
+            rangeArray = new COSArray();
+            stream.getStream().setItem( COSName.RANGE, rangeArray );
+            while( rangeArray.size() < n*2 )
+            {
+                rangeArray.add( new COSFloat( -100 ) );
+                rangeArray.add( new COSFloat( 100 ) );
+            }
+        }
+        return rangeArray;
+    }
+
+    /**
+     * This will get the range for a certain component number.  This is will never
+     * return null.  If it is not present then the range -100 to 100 will
+     * be returned.
+     *
+     * @param n The component number to get the range for.
+     *
+     * @return The range for this component.
+     */
+    public PDRange getRangeForComponent( int n )
+    {
+        COSArray rangeArray = getRangeArray( n );
+        return new PDRange( rangeArray, n );
+    }
+
+    /**
+     * This will set the a range for this color space.
+     *
+     * @param range The new range for the a component.
+     * @param n The component to set the range for.
+     */
+    public void setRangeForComponent( PDRange range, int n )
+    {
+        COSArray rangeArray = getRangeArray( n );
+        rangeArray.set( n*2, new COSFloat( range.getMin() ) );
+        rangeArray.set( n*2+1, new COSFloat( range.getMax() ) );
+    }
+
+    /**
+     * This will get the metadata stream for this object.  Null if there is no
+     * metadata stream.
+     *
+     * @return The metadata stream, if it exists.
+     */
+    public COSStream getMetadata()
+    {
+        return (COSStream)stream.getStream().getDictionaryObject( COSName.METADATA );
+    }
+
+    /**
+     * This will set the metadata stream that is associated with this color space.
+     *
+     * @param metadata The new metadata stream.
+     */
+    public void setMetadata( COSStream metadata )
+    {
+        stream.getStream().setItem( COSName.METADATA, metadata );
+    }
+    
+    // Need more info on the ICCBased ones ... Array contains very little.
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        String retVal = super.toString() + "\n\t Number of Components: ";
+        try
+        {
+            retVal = retVal + getNumberOfComponents();
+        }
+        catch (IOException exception)
+        {
+            retVal = retVal + exception.toString();
+        }
+        return retVal;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java
new file mode 100644 (file)
index 0000000..ce0a477
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * This class represents an Indexed color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDIndexed extends PDColorSpace
+{
+
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "Indexed";
+
+    /**
+     * The abbreviated name of this color space.
+     */
+    public static final String ABBREVIATED_NAME = "I";
+
+    private COSArray array;
+
+    /**
+     * The lookup data as byte array.
+     */
+    private byte[] lookupData;
+    
+    /**
+     * Constructor, default DeviceRGB, hival 255.
+     */
+    public PDIndexed()
+    {
+        array = new COSArray();
+        array.add( COSName.INDEXED );
+        array.add( COSName.DEVICERGB );
+        array.add( COSInteger.get( 255 ) );
+        array.add( org.apache.pdfbox.cos.COSNull.NULL );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param indexedArray The array containing the indexed parameters
+     */
+    public PDIndexed( COSArray indexedArray )
+    {
+        array = indexedArray;
+    }
+
+    /**
+     * This will return the number of color components.  This will return the
+     * number of color components in the base color.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return getBaseColorSpace().getNumberOfComponents();
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected ColorSpace createColorSpace() throws IOException
+    {
+        throw new IOException( "Not implemented" );
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        int size = getHighValue();
+        byte[] index = getLookupData();
+        PDColorSpace baseColorSpace = getBaseColorSpace();
+        ColorModel cm = null;
+        if( baseColorSpace instanceof PDDeviceRGB )
+        {
+            cm = new IndexColorModel(bpc, size+1, index,0,false);
+        }
+        else
+        {
+            ColorModel baseColorModel = baseColorSpace.createColorModel(bpc);
+            if( baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE )
+            {
+                throw new IOException( "Not implemented" );
+            }
+            byte[] r = new byte[size+1];
+            byte[] g = new byte[size+1];
+            byte[] b = new byte[size+1];
+            byte[] a = baseColorModel.hasAlpha() ? new byte[size+1] : null;
+            byte[] inData = new byte[baseColorModel.getNumComponents()];
+            for( int i = 0; i <= size; i++ )
+            {
+                System.arraycopy(index, i * inData.length, inData, 0, inData.length);
+                r[i] = (byte)baseColorModel.getRed(inData);
+                g[i] = (byte)baseColorModel.getGreen(inData);
+                b[i] = (byte)baseColorModel.getBlue(inData);
+                if(a != null)
+                {
+                    a[i] = (byte)baseColorModel.getAlpha(inData);
+                }
+            }
+            cm = a == null ? new IndexColorModel(bpc, size+1, r, g, b) : new IndexColorModel(bpc, size+1, r, g, b, a);
+        }
+        return cm;
+    }
+
+    /**
+     * This will get the color space that acts as the index for this color space.
+     *
+     * @return The base color space.
+     *
+     * @throws IOException If there is error creating the base color space.
+     */
+    public PDColorSpace getBaseColorSpace() throws IOException
+    {
+        COSBase base = array.getObject( 1 );
+        return PDColorSpaceFactory.createColorSpace( base );
+    }
+
+    /**
+     * This will set the base color space.
+     *
+     * @param base The base color space to use as the index.
+     */
+    public void setBaseColorSpace( PDColorSpace base )
+    {
+        array.set( 1, base.getCOSObject() );
+    }
+
+    /**
+     * Get the highest value for the lookup.
+     *
+     * @return The hival entry.
+     */
+    public int getHighValue()
+    {
+        return ((COSNumber)array.getObject( 2 )).intValue();
+    }
+
+    /**
+     * This will set the highest value that is allowed.  This cannot be higher
+     * than 255.
+     *
+     * @param high The highest value for the lookup table.
+     */
+    public void setHighValue( int high )
+    {
+        array.set( 2, high );
+    }
+
+    /**
+     * This will perform a lookup into the color lookup table.
+     *
+     * @param lookupIndex The zero-based index into the table, should not exceed the high value.
+     * @param componentNumber The component number, probably 1,2,3,3.
+     *
+     * @return The value that was from the lookup table.
+     *
+     * @throws IOException If there is an error looking up the color.
+     */
+    public int lookupColor( int lookupIndex, int componentNumber ) throws IOException
+    {
+        PDColorSpace baseColor = getBaseColorSpace();
+        byte[] data = getLookupData();
+        int numberOfComponents = baseColor.getNumberOfComponents();
+        return (data[lookupIndex*numberOfComponents + componentNumber]+256)%256;
+    }
+
+    /**
+     * Get the lookup data table.
+     * 
+     * @return a byte array containing the the lookup data.
+     * @throws IOException if an error occurs.
+     */
+    public byte[] getLookupData() throws IOException
+    {
+        if ( lookupData == null)
+        {
+            COSBase lookupTable = array.getObject( 3 );
+            if( lookupTable instanceof COSString )
+            {
+                lookupData = ((COSString)lookupTable).getBytes();
+            }
+            else if( lookupTable instanceof COSStream )
+            {
+                //Data will be small so just load the whole thing into memory for
+                //easier processing
+                COSStream lookupStream = (COSStream)lookupTable;
+                InputStream input = lookupStream.getUnfilteredStream();
+                ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
+                byte[] buffer = new byte[ 1024 ];
+                int amountRead;
+                while( (amountRead = input.read(buffer, 0, buffer.length)) != -1 )
+                {
+                    output.write( buffer, 0, amountRead );
+                }
+                lookupData = output.toByteArray();
+            }
+            else if( lookupTable == null )
+            {
+                lookupData = new byte[0];
+            }
+            else
+            {
+                throw new IOException( "Error: Unknown type for lookup table " + lookupTable );
+            }
+        }
+        return lookupData;
+    }
+
+    /**
+     * This will set a color in the color lookup table.
+     *
+     * @param lookupIndex The zero-based index into the table, should not exceed the high value.
+     * @param componentNumber The component number, probably 1,2,3,3.
+     * @param color The color that will go into the table.
+     *
+     * @throws IOException If there is an error looking up the color.
+     */
+    public void setLookupColor( int lookupIndex, int componentNumber, int color ) throws IOException
+    {
+        PDColorSpace baseColor = getBaseColorSpace();
+        int numberOfComponents = baseColor.getNumberOfComponents();
+        byte[] data = getLookupData();
+        data[lookupIndex*numberOfComponents + componentNumber] = (byte)color;
+        COSString string = new COSString( data );
+        array.set( 3, string );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java
new file mode 100644 (file)
index 0000000..487e84a
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Lab color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDLab extends PDColorSpace
+{
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "Lab";
+
+    private COSArray array;
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDLab()
+    {
+        array = new COSArray();
+        dictionary = new COSDictionary();
+        array.add( COSName.LAB );
+        array.add( dictionary );
+    }
+
+    /**
+     * Constructor with array.
+     *
+     * @param lab The underlying color space.
+     */
+    public PDLab( COSArray lab )
+    {
+        array = lab;
+        dictionary = (COSDictionary)array.getObject( 1 );
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return array;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected ColorSpace createColorSpace() throws IOException
+    {
+        throw new IOException( "Not implemented" );
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        throw new IOException( "Not implemented" );
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        //BJL
+        //hmm is this correct, I am not 100% sure.
+        return 3;
+    }
+
+    /**
+     * This will return the whitepoint tristimulus.  As this is a required field
+     * this will never return null.  A default of 1,1,1 will be returned if the
+     * pdf does not have any values yet.
+     *
+     * @return The whitepoint tristimulus.
+     */
+    public PDTristimulus getWhitepoint()
+    {
+        COSArray wp = (COSArray)dictionary.getDictionaryObject( COSName.WHITE_POINT );
+        if( wp == null )
+        {
+            wp = new COSArray();
+            wp.add( new COSFloat( 1.0f ) );
+            wp.add( new COSFloat( 1.0f ) );
+            wp.add( new COSFloat( 1.0f ) );
+            dictionary.setItem( COSName.WHITE_POINT, wp );
+        }
+        return new PDTristimulus( wp );
+    }
+
+    /**
+     * This will set the whitepoint tristimulus.  As this is a required field
+     * this null should not be passed into this function.
+     *
+     * @param wp The whitepoint tristimulus.
+     */
+    public void setWhitepoint( PDTristimulus wp )
+    {
+        COSBase wpArray = wp.getCOSObject();
+        if( wpArray != null )
+        {
+            dictionary.setItem( COSName.WHITE_POINT, wpArray );
+        }
+    }
+
+    /**
+     * This will return the BlackPoint tristimulus.  This is an optional field but
+     * has defaults so this will never return null.
+     * A default of 0,0,0 will be returned if the pdf does not have any values yet.
+     *
+     * @return The blackpoint tristimulus.
+     */
+    public PDTristimulus getBlackPoint()
+    {
+        COSArray bp = (COSArray)dictionary.getDictionaryObject( COSName.BLACK_POINT );
+        if( bp == null )
+        {
+            bp = new COSArray();
+            bp.add( new COSFloat( 0.0f ) );
+            bp.add( new COSFloat( 0.0f ) );
+            bp.add( new COSFloat( 0.0f ) );
+            dictionary.setItem( COSName.BLACK_POINT, bp );
+        }
+        return new PDTristimulus( bp );
+    }
+
+    /**
+     * This will set the BlackPoint tristimulus.  As this is a required field
+     * this null should not be passed into this function.
+     *
+     * @param bp The BlackPoint tristimulus.
+     */
+    public void setBlackPoint( PDTristimulus bp )
+    {
+
+        COSBase bpArray = null;
+        if( bp != null )
+        {
+            bpArray = bp.getCOSObject();
+        }
+        dictionary.setItem( COSName.BLACK_POINT, bpArray );
+    }
+
+    private COSArray getRangeArray()
+    {
+        COSArray range = (COSArray)dictionary.getDictionaryObject( COSName.RANGE );
+        if( range == null )
+        {
+            range = new COSArray();
+            dictionary.setItem( COSName.RANGE, array );
+            range.add( new COSFloat( -100 ) );
+            range.add( new COSFloat( 100 ) );
+            range.add( new COSFloat( -100 ) );
+            range.add( new COSFloat( 100 ) );
+        }
+        return range;
+    }
+
+    /**
+     * This will get the valid range for the a component.  If none is found
+     * then the default will be returned, which is -100 to 100.
+     *
+     * @return The a range.
+     */
+    public PDRange getARange()
+    {
+        COSArray range = getRangeArray();
+        return new PDRange( range, 0 );
+    }
+
+    /**
+     * This will set the a range for this color space.
+     *
+     * @param range The new range for the a component.
+     */
+    public void setARange( PDRange range )
+    {
+        COSArray rangeArray = null;
+        //if null then reset to defaults
+        if( range == null )
+        {
+            rangeArray = getRangeArray();
+            rangeArray.set( 0, new COSFloat( -100 ) );
+            rangeArray.set( 1, new COSFloat( 100 ) );
+        }
+        else
+        {
+            rangeArray = range.getCOSArray();
+        }
+        dictionary.setItem( COSName.RANGE, rangeArray );
+    }
+
+    /**
+     * This will get the valid range for the b component.  If none is found
+     * then the default will be returned, which is -100 to 100.
+     *
+     * @return The b range.
+     */
+    public PDRange getBRange()
+    {
+        COSArray range = getRangeArray();
+        return new PDRange( range, 1 );
+    }
+
+    /**
+     * This will set the b range for this color space.
+     *
+     * @param range The new range for the b component.
+     */
+    public void setBRange( PDRange range )
+    {
+        COSArray rangeArray = null;
+        //if null then reset to defaults
+        if( range == null )
+        {
+            rangeArray = getRangeArray();
+            rangeArray.set( 2, new COSFloat( -100 ) );
+            rangeArray.set( 3, new COSFloat( 100 ) );
+        }
+        else
+        {
+            rangeArray = range.getCOSArray();
+        }
+        dictionary.setItem( COSName.RANGE, rangeArray );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDPattern.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDPattern.java
new file mode 100644 (file)
index 0000000..e22b5ac
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSName;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Pattern color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDPattern extends PDColorSpace
+{
+    private COSArray array;
+
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "Pattern";
+
+    /**
+     * Default constructor.
+     */
+    public PDPattern()
+    {
+        array = new COSArray();
+        array.add( COSName.PATTERN );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param pattern The pattern array.
+     */
+    public PDPattern( COSArray pattern)
+    {
+        array = pattern;
+    }
+
+    /**
+     * This will return the name of the color space.
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return -1;
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected ColorSpace createColorSpace() throws IOException
+    {
+        throw new IOException( "Not implemented" );
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        throw new IOException( "Not implemented" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDSeparation.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDSeparation.java
new file mode 100644 (file)
index 0000000..96041c4
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.function.PDFunction;
+import org.apache.pdfbox.pdmodel.common.function.PDFunctionType4;
+
+/**
+ * This class represents a Separation color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDSeparation extends PDColorSpace
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDSeparation.class);
+
+    /**
+     * The name of this color space.
+     */
+    public static final String NAME = "Separation";
+
+
+    /**
+     * Constructor.
+     */
+    public PDSeparation()
+    {
+        array = new COSArray();
+        array.add( COSName.SEPARATION );
+        array.add( COSName.getPDFName( "" ) );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param separation The array containing all separation information.
+     */
+    public PDSeparation( COSArray separation )
+    {
+        array = separation;
+    }
+
+    /**
+     * This will return the name of the color space.  For a PDSeparation object
+     * this will always return "Separation"
+     *
+     * @return The name of the color space.
+     */
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /**
+     * This will get the number of components that this color space is made up of.
+     *
+     * @return The number of components in this color space.
+     *
+     * @throws IOException If there is an error getting the number of color components.
+     */
+    public int getNumberOfComponents() throws IOException
+    {
+        return getAlternateColorSpace().getNumberOfComponents();
+    }
+
+    /**
+     * Create a Java colorspace for this colorspace.
+     *
+     * @return A color space that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color space.
+     */
+    protected ColorSpace createColorSpace() throws IOException
+    {
+        try
+        {
+            PDColorSpace alt = getAlternateColorSpace();
+            return alt.getJavaColorSpace();
+        }
+        catch (IOException ioexception)
+        {
+            log.error(ioexception, ioexception);
+
+            throw ioexception;
+        }
+        catch (Exception exception)
+        {
+            log.error(exception, exception);
+            throw new IOException("Failed to Create ColorSpace");
+        }
+    }
+
+    /**
+     * Create a Java color model for this colorspace.
+     *
+     * @param bpc The number of bits per component.
+     *
+     * @return A color model that can be used for Java AWT operations.
+     *
+     * @throws IOException If there is an error creating the color model.
+     */
+    public ColorModel createColorModel( int bpc ) throws IOException
+    {
+        log.info("About to create ColorModel for " + getAlternateColorSpace().toString());
+        return getAlternateColorSpace().createColorModel(bpc);
+    }
+
+    /**
+     * This will get the separation name.
+     *
+     * @return The name in the separation.
+     */
+    public String getColorantName()
+    {
+        COSName name = (COSName)array.getObject( 1 );
+        return name.getName();
+    }
+
+    /**
+     * This will set the separation name.
+     *
+     * @param name The separation name.
+     */
+    public void setColorantName( String name )
+    {
+        array.set( 1, COSName.getPDFName( name ) );
+    }
+
+    /**
+     * This will get the alternate color space for this separation.
+     *
+     * @return The alternate color space.
+     *
+     * @throws IOException If there is an error getting the alternate color space.
+     */
+    public PDColorSpace getAlternateColorSpace() throws IOException
+    {
+        COSBase alternate = array.getObject( 2 );
+        PDColorSpace cs = PDColorSpaceFactory.createColorSpace( alternate );
+        return cs;
+    }
+
+    /**
+     * This will set the alternate color space.
+     *
+     * @param cs The alternate color space.
+     */
+    public void setAlternateColorSpace( PDColorSpace cs )
+    {
+        COSBase space = null;
+        if( cs != null )
+        {
+            space = cs.getCOSObject();
+        }
+        array.set( 2, space );
+    }
+
+    /**
+     * This will get the tint transform function.
+     *
+     * @return The tint transform function.
+     *
+     * @throws IOException If there is an error creating the PDFunction
+     */
+    public PDFunction getTintTransform() throws IOException
+    {
+        return PDFunction.create( array.getObject( 3 ) );
+    }
+
+    /**
+     * This will set the tint transform function.
+     *
+     * @param tint The tint transform function.
+     */
+    public void setTintTransform( PDFunction tint )
+    {
+        array.set( 3, tint );
+    }
+    
+    /**
+     * Returns the components of the color in the alternate colorspace for the given tint value.
+     * @return COSArray with the color components
+     * @throws IOException If the tint function is not supported
+     */
+    public COSArray calculateColorValues(COSBase tintValue) throws IOException
+    {
+        PDFunction tintTransform = getTintTransform();
+        if(tintTransform instanceof PDFunctionType4)
+        {
+            log.warn("Unsupported tint transformation type: "+tintTransform.getClass().getName() 
+                    + " in "+getClass().getName()+".getColorValues()"
+                    + " using color black instead.");
+            int numberOfComponents = getAlternateColorSpace().getNumberOfComponents();
+            // To get black as color:
+            // 0.0f is used for the single value(s) if the colorspace is gray or RGB based
+            // 1.0f is used for the single value if the colorspace is CMYK based
+            float colorValue = numberOfComponents == 4 ? 1.0f : 0.0f;
+            COSArray retval = new COSArray();
+            for (int i=0;i<numberOfComponents;i++) 
+            {
+                retval.add(new COSFloat(colorValue));
+            }
+            return retval;
+        }
+        else
+        {
+            COSArray tint = new COSArray();
+            tint.add(tintValue);
+            return tintTransform.eval(tint);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDTristimulus.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/PDTristimulus.java
new file mode 100644 (file)
index 0000000..6013bff
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * A tristimulus, or collection of three floating point parameters used for
+ * color operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDTristimulus implements COSObjectable
+{
+    private COSArray values = null;
+
+    /**
+     * Constructor.  Defaults all values to 0, 0, 0.
+     */
+    public PDTristimulus()
+    {
+        values = new COSArray();
+        values.add( new COSFloat( 0.0f ) );
+        values.add( new COSFloat( 0.0f ) );
+        values.add( new COSFloat( 0.0f ) );
+    }
+
+    /**
+     * Constructor from COS object.
+     *
+     * @param array The array containing the XYZ values.
+     */
+    public PDTristimulus( COSArray array )
+    {
+        values = array;
+    }
+
+    /**
+     * Constructor from COS object.
+     *
+     * @param array The array containing the XYZ values.
+     */
+    public PDTristimulus( float[] array )
+    {
+        values = new COSArray();
+        for( int i=0; i<array.length && i<3; i++ )
+        {
+            values.add( new COSFloat( array[i] ) );
+        }
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return values;
+    }
+
+    /**
+     * This will get the x value of the tristimulus.
+     *
+     * @return The X value.
+     */
+    public float getX()
+    {
+        return ((COSNumber)values.get( 0 )).floatValue();
+    }
+
+    /**
+     * This will set the x value of the tristimulus.
+     *
+     * @param x The x value for the tristimulus.
+     */
+    public void setX( float x )
+    {
+        values.set( 0, new COSFloat( x ) );
+    }
+
+    /**
+     * This will get the y value of the tristimulus.
+     *
+     * @return The Y value.
+     */
+    public float getY()
+    {
+        return ((COSNumber)values.get( 1 )).floatValue();
+    }
+
+    /**
+     * This will set the y value of the tristimulus.
+     *
+     * @param y The y value for the tristimulus.
+     */
+    public void setY( float y )
+    {
+        values.set( 1, new COSFloat( y ) );
+    }
+
+    /**
+     * This will get the z value of the tristimulus.
+     *
+     * @return The Z value.
+     */
+    public float getZ()
+    {
+        return ((COSNumber)values.get( 2 )).floatValue();
+    }
+
+    /**
+     * This will set the z value of the tristimulus.
+     *
+     * @param z The z value for the tristimulus.
+     */
+    public void setZ( float z )
+    {
+        values.set( 2, new COSFloat( z ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/color/package.html
new file mode 100644 (file)
index 0000000..d16b62c
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package deals with colors that are stored in a PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java
new file mode 100644 (file)
index 0000000..f8b8bbe
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.graphics.optionalcontent;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents an optional content group (OCG).
+ *
+ * @since PDF 1.5
+ * @version $Revision$
+ */
+public class PDOptionalContentGroup implements COSObjectable
+{
+
+    private COSDictionary ocg;
+
+    /**
+     * Creates a new optional content group (OCG).
+     * @param name the name of the content group
+     */
+    public PDOptionalContentGroup(String name)
+    {
+        this.ocg = new COSDictionary();
+        this.ocg.setItem(COSName.TYPE, COSName.OCG);
+        setName(name);
+    }
+
+    /**
+     * Creates a new instance based on a given {@link COSDictionary}.
+     * @param dict the dictionary
+     */
+    public PDOptionalContentGroup(COSDictionary dict)
+    {
+        if (!dict.getItem(COSName.TYPE).equals(COSName.OCG))
+        {
+            throw new IllegalArgumentException(
+                    "Provided dictionary is not of type '" + COSName.OCG + "'");
+        }
+        this.ocg = dict;
+    }
+
+    /** {@inheritDoc} */
+    public COSBase getCOSObject()
+    {
+        return this.ocg;
+    }
+
+    /**
+     * Returns the name of the optional content group.
+     * @return the name
+     */
+    public String getName()
+    {
+        return this.ocg.getString(COSName.NAME);
+    }
+
+    /**
+     * Sets the name of the optional content group.
+     * @param name the name
+     */
+    public void setName(String name)
+    {
+        this.ocg.setString(COSName.NAME, name);
+    }
+
+    //TODO Add support for "Intent" and "Usage"
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString()
+    {
+        return super.toString() + " (" + getName() + ")";
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java
new file mode 100644 (file)
index 0000000..612e0dd
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.optionalcontent;
+
+import java.util.Collection;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents the optional content properties dictionary.
+ *
+ * @since PDF 1.5
+ * @version $Revision$
+ */
+public class PDOptionalContentProperties implements COSObjectable
+{
+
+    /**
+     * Enumeration for the BaseState dictionary entry on the "D" dictionary.
+     */
+    public static enum BaseState
+    {
+
+        /** The "ON" value. */
+        ON(COSName.ON),
+        /** The "OFF" value. */
+        OFF(COSName.OFF),
+        /** The "Unchanged" value. */
+        UNCHANGED(COSName.UNCHANGED);
+
+        private COSName name;
+
+        private BaseState(COSName value)
+        {
+            this.name = value;
+        }
+
+        /**
+         * Returns the PDF name for the state.
+         * @return the name of the state
+         */
+        public COSName getName()
+        {
+            return this.name;
+        }
+
+        /**
+         * Returns the base state represented by the given {@link COSName}.
+         * @param state the state name
+         * @return the state enum value
+         */
+        public static BaseState valueOf(COSName state)
+        {
+            if (state == null)
+            {
+                return BaseState.ON;
+            }
+            return BaseState.valueOf(state.getName().toUpperCase());
+        }
+
+    }
+
+    private COSDictionary dict;
+
+    /**
+     * Creates a new optional content properties dictionary.
+     */
+    public PDOptionalContentProperties()
+    {
+        this.dict = new COSDictionary();
+        this.dict.setItem(COSName.OCGS, new COSArray());
+        this.dict.setItem(COSName.D, new COSDictionary());
+    }
+
+    /**
+     * Creates a new instance based on a given {@link COSDictionary}.
+     * @param props the dictionary
+     */
+    public PDOptionalContentProperties(COSDictionary props)
+    {
+        this.dict = props;
+    }
+
+    /** {@inheritDoc} */
+    public COSBase getCOSObject()
+    {
+        return this.dict;
+    }
+
+    private COSArray getOCGs()
+    {
+        COSArray ocgs = (COSArray)this.dict.getItem(COSName.OCGS);
+        if (ocgs == null)
+        {
+            ocgs = new COSArray();
+            this.dict.setItem(COSName.OCGS, ocgs); //OCGs is required
+        }
+        return ocgs;
+    }
+
+    private COSDictionary getD()
+    {
+        COSDictionary d = (COSDictionary)this.dict.getDictionaryObject(COSName.D);
+        if (d == null)
+        {
+            d = new COSDictionary();
+            this.dict.setItem(COSName.D, d); //D is required
+        }
+        return d;
+    }
+
+    /**
+     * Returns the optional content group of the given name.
+     * @param name the group name
+     * @return the optional content group or null, if there is no such group
+     */
+    public PDOptionalContentGroup getGroup(String name)
+    {
+        COSArray ocgs = getOCGs();
+        for (COSBase o : ocgs)
+        {
+            COSDictionary ocg = toDictionary(o);
+            String groupName = ocg.getString(COSName.NAME);
+            if (groupName.equals(name))
+            {
+                return new PDOptionalContentGroup(ocg);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds an optional content group (OCG).
+     * @param ocg the optional content group
+     */
+    public void addGroup(PDOptionalContentGroup ocg)
+    {
+        COSArray ocgs = getOCGs();
+        ocgs.add(ocg.getCOSObject());
+
+        //By default, add new group to the "Order" entry so it appears in the user interface
+        COSArray order = (COSArray)getD().getDictionaryObject(COSName.ORDER);
+        if (order == null)
+        {
+            order = new COSArray();
+            getD().setItem(COSName.ORDER, order);
+        }
+        order.add(ocg);
+    }
+
+    /**
+     * Returns the collection of all optional content groups.
+     * @return the optional content groups
+     */
+    public Collection<PDOptionalContentGroup> getOptionalContentGroups()
+    {
+        Collection<PDOptionalContentGroup> coll = new java.util.ArrayList<PDOptionalContentGroup>();
+        COSArray ocgs = getOCGs();
+        for (COSBase base : ocgs)
+        {
+            COSObject obj = (COSObject)base; //Children must be indirect references
+            coll.add(new PDOptionalContentGroup((COSDictionary)obj.getObject()));
+        }
+        return coll;
+    }
+
+    /**
+     * Returns the base state for optional content groups.
+     * @return the base state
+     */
+    public BaseState getBaseState()
+    {
+        COSDictionary d = getD();
+        COSName name = (COSName)d.getItem(COSName.BASE_STATE);
+        return BaseState.valueOf(name);
+    }
+
+    /**
+     * Sets the base state for optional content groups.
+     * @param state the base state
+     */
+    public void setBaseState(BaseState state)
+    {
+        COSDictionary d = getD();
+        d.setItem(COSName.BASE_STATE, state.getName());
+    }
+
+    /**
+     * Lists all optional content group names.
+     * @return an array of all names
+     */
+    public String[] getGroupNames()
+    {
+        COSArray ocgs = (COSArray)dict.getDictionaryObject(COSName.OCGS);
+        int size = ocgs.size();
+        String[] groups = new String[size];
+        for (int i = 0; i < size; i++)
+        {
+            COSBase obj = (COSBase)ocgs.get(i);
+            COSDictionary ocg = toDictionary(obj);
+            groups[i] = ocg.getString(COSName.NAME);
+        }
+        return groups;
+    }
+
+    /**
+     * Indicates whether a particular optional content group is found in the PDF file.
+     * @param groupName the group name
+     * @return true if the group exists, false otherwise
+     */
+    public boolean hasGroup(String groupName)
+    {
+        String[] layers = getGroupNames();
+        for (String layer : layers)
+        {
+            if (layer.equals(groupName))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether an optional content group is enabled.
+     * @param groupName the group name
+     * @return true if the group is enabled
+     */
+    public boolean isGroupEnabled(String groupName)
+    {
+        //TODO handle Optional Content Configuration Dictionaries,
+        //i.e. OCProperties/Configs
+
+        COSDictionary d = getD();
+        COSArray on = (COSArray)d.getDictionaryObject(COSName.ON);
+        if (on != null)
+        {
+            for (COSBase o : on)
+            {
+                COSDictionary group = toDictionary(o);
+                String name = group.getString(COSName.NAME);
+                if (name.equals(groupName))
+                {
+                    return true;
+                }
+            }
+        }
+
+        COSArray off = (COSArray)d.getDictionaryObject(COSName.OFF);
+        if (off != null)
+        {
+            for (COSBase o : off)
+            {
+                COSDictionary group = toDictionary(o);
+                String name = group.getString(COSName.NAME);
+                if (name.equals(groupName))
+                {
+                    return false;
+                }
+            }
+        }
+
+        BaseState baseState = getBaseState();
+        boolean enabled = !baseState.equals(BaseState.OFF);
+        //TODO What to do with BaseState.Unchanged?
+        return enabled;
+    }
+
+    private COSDictionary toDictionary(COSBase o)
+    {
+        if (o instanceof COSObject)
+        {
+            return (COSDictionary)((COSObject)o).getObject();
+        }
+        else
+        {
+            return (COSDictionary)o;
+        }
+    }
+
+    /**
+     * Enables or disables an optional content group.
+     * @param groupName the group name
+     * @param enable true to enable, false to disable
+     * @return true if the group already had an on or off setting, false otherwise
+     */
+    public boolean setGroupEnabled(String groupName, boolean enable)
+    {
+        COSDictionary d = getD();
+        COSArray on = (COSArray)d.getDictionaryObject(COSName.ON);
+        if (on == null)
+        {
+            on = new COSArray();
+            d.setItem(COSName.ON, on);
+        }
+        COSArray off = (COSArray)d.getDictionaryObject(COSName.OFF);
+        if (off == null)
+        {
+            off = new COSArray();
+            d.setItem(COSName.OFF, off);
+        }
+
+        boolean found = false;
+        for (COSBase o : on)
+        {
+            COSDictionary group = toDictionary(o);
+            String name = group.getString(COSName.NAME);
+            if (!enable && name.equals(groupName))
+            {
+                //disable group
+                on.remove(group);
+                off.add(group);
+                found = true;
+                break;
+            }
+        }
+        for (COSBase o : off)
+        {
+            COSDictionary group = toDictionary(o);
+            String name = group.getString(COSName.NAME);
+            if (enable && name.equals(groupName))
+            {
+                //enable group
+                off.remove(group);
+                on.add(group);
+                found = true;
+                break;
+            }
+        }
+
+        if (!found)
+        {
+            PDOptionalContentGroup ocg = getGroup(groupName);
+            if (enable)
+            {
+                on.add(ocg.getCOSObject());
+            }
+            else
+            {
+                off.add(ocg.getCOSObject());
+            }
+        }
+        return found;
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/package.html
new file mode 100644 (file)
index 0000000..00daf36
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel graphics package deals with graphics states, operations, and parameters within the PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Average.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Average.java
new file mode 100644 (file)
index 0000000..a036c94
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.graphics.predictor;
+
+/**
+ * We can use raw on the right hand side of
+ * the decoding formula because it is already decoded.
+ *
+ * <code>average(i,j) = raw(i,j) + (raw(i-1,j)+raw(i,j-1)/2</code>
+ *
+ * decoding
+ *
+ * <code>raw(i,j) = avarage(i,j) - (raw(i-1,j)+raw(i,j-1)/2</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Average extends PredictorAlgorithm
+{
+    /**
+     * Not an optimal version, but close to the def.
+     *
+     * {@inheritDoc}
+     */
+    public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth() * getBpp();
+        for (int x = 0; x < bpl; x++)
+        {
+            dest[x + destOffset] = (byte) (src[x + srcOffset] - ((leftPixel(
+                    src, srcOffset, srcDy, x) + abovePixel(src, srcOffset,
+                    srcDy, x)) >>> 2));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth() * getBpp();
+        for (int x = 0; x < bpl; x++)
+        {
+            dest[x + destOffset] = (byte) (src[x + srcOffset] + ((leftPixel(
+                    dest, destOffset, destDy, x) + abovePixel(dest,
+                    destOffset, destDy, x)) >>> 2));
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/None.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/None.java
new file mode 100644 (file)
index 0000000..a437725
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.predictor;
+
+/**
+ * The none algorithm.
+ *
+ * <code>None(i,j) = Raw(i,j)</code>
+ *
+ * <code>Raw(i,j) = None(i,j)</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class None extends PredictorAlgorithm
+{
+    /**
+     * encode a byte array full of image data using the filter that this object
+     * implements.
+     *
+     * @param src
+     *            buffer
+     * @param dest
+     *            buffer
+     */
+    public void encode(byte[] src, byte[] dest)
+    {
+        checkBufsiz(dest, src);
+        System.arraycopy(src,0,dest,0,src.length);
+    }
+
+    /**
+     * decode a byte array full of image data using the filter that this object
+     * implements.
+     *
+     * @param src
+     *            buffer
+     * @param dest
+     *            buffer
+     */
+    public void decode(byte[] src, byte[] dest)
+    {
+        System.arraycopy(src,0,dest,0,src.length);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth() * getBpp();
+        for (int x = 0; x < bpl; x++)
+        {
+            dest[destOffset + x] = src[srcOffset + x];
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth() * getBpp();
+        for (int x = 0; x < bpl; x++)
+        {
+            dest[destOffset + x] = src[srcOffset + x];
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Optimum.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Optimum.java
new file mode 100644 (file)
index 0000000..a83d765
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.predictor;
+
+/**
+ *
+ *
+ * In an Uptimum encoded image, each line takes up width*bpp+1 bytes. The first
+ * byte holds a number that signifies which algorithm encoded the line.
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.1 $
+ */
+public class Optimum extends PredictorAlgorithm
+{
+    /**
+     * {@inheritDoc}
+     */
+    public void checkBufsiz(byte[] filtered, byte[] raw)
+    {
+        if (filtered.length != (getWidth() * getBpp() + 1) * getHeight())
+        {
+
+            throw new IllegalArgumentException(
+                    "filtered.length != (width*bpp + 1) * height, "
+                            + filtered.length + " "
+                            + (getWidth() * getBpp() + 1) * getHeight()
+                            + "w,h,bpp=" + getWidth() + "," + getHeight() + ","
+                            + getBpp());
+        }
+        if (raw.length != getWidth() * getHeight() * getBpp())
+        {
+            throw new IllegalArgumentException(
+                    "raw.length != width * height * bpp, raw.length="
+                            + raw.length + " w,h,bpp=" + getWidth() + ","
+                            + getHeight() + "," + getBpp());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        throw new UnsupportedOperationException("encodeLine");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        throw new UnsupportedOperationException("decodeLine");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encode(byte[] src, byte[] dest)
+    {
+        checkBufsiz(dest, src);
+        throw new UnsupportedOperationException("encode");
+    }
+
+    /**
+     * Filter indexed by byte code.
+     */
+    PredictorAlgorithm[] filter = { new None(), new Sub(), new Up(), new Average(),
+            new Paeth() };
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setBpp(int bpp)
+    {
+        super.setBpp(bpp);
+        for (int i = 0; i < filter.length; i++)
+        {
+            filter[i].setBpp(bpp);
+        }
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public void setHeight(int height)
+    {
+        super.setHeight(height);
+        for (int i = 0; i < filter.length; i++)
+        {
+            filter[i].setHeight(height);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setWidth(int width)
+    {
+        super.setWidth(width);
+        for (int i = 0; i < filter.length; i++)
+        {
+            filter[i].setWidth(width);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decode(byte[] src, byte[] dest)
+    {
+        checkBufsiz(src, dest);
+        int bpl = getWidth() * getBpp();
+        int srcDy = bpl + 1;
+        for (int y = 0; y < getHeight(); y++)
+        {
+            PredictorAlgorithm f = filter[src[y * srcDy]];
+            int srcOffset = y * srcDy + 1;
+            f.decodeLine(src, dest, srcDy, srcOffset, bpl, y * bpl);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Paeth.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Paeth.java
new file mode 100644 (file)
index 0000000..a2f2fe7
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.predictor;
+
+/**
+ * From http://www.w3.org/TR/PNG-Filters.html: The Paeth filter computes a
+ * simple linear function of the three neighboring pixels (left, above, upper
+ * left), then chooses as predictor the neighboring pixel closest to the
+ * computed value. This technique is due to Alan W. Paeth [PAETH].
+ *
+ * To compute the Paeth filter, apply the following formula to each byte of the
+ * scanline:
+ *
+ * <code>Paeth(i,j) = Raw(i,j) - PaethPredictor(Raw(i-1,j), Raw(i,j-1), Raw(i-1,j-1))</code>
+ *
+ * To decode the Paeth filter
+ *
+ * <code>Raw(i,j) = Paeth(i,j) - PaethPredictor(Raw(i-1,j), Raw(i,j-1), Raw(i-1,j-1))</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Paeth extends PredictorAlgorithm
+{
+    /**
+     * The paeth predictor function.
+     *
+     * This function is taken almost directly from the PNG definition on
+     * http://www.w3.org/TR/PNG-Filters.html
+     *
+     * @param a
+     *            left
+     * @param b
+     *            above
+     * @param c
+     *            upper left
+     * @return The result of the paeth predictor.
+     */
+    public int paethPredictor(int a, int b, int c)
+    {
+        int p = a + b - c; // initial estimate
+        int pa = Math.abs(p - a); // distances to a, b, c
+        int pb = Math.abs(p - b);
+        int pc = Math.abs(p - c);
+        // return nearest of a,b,c,
+        // breaking ties in order a,b,c.
+        if (pa <= pb && pa <= pc)
+        {
+            return a;
+        }
+        else if (pb <= pc)
+        {
+            return b;
+        }
+        else
+        {
+            return c;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth() * getBpp();
+        for (int x = 0; x < bpl; x++)
+        {
+            dest[x + destOffset] = (byte) (src[x + srcOffset] - paethPredictor(
+                    leftPixel(src, srcOffset, srcDy, x), abovePixel(src,
+                            srcOffset, srcDy, x), aboveLeftPixel(src,
+                            srcOffset, srcDy, x)));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth() * getBpp();
+        for (int x = 0; x < bpl; x++)
+        {
+            dest[x + destOffset] = (byte) (src[x + srcOffset] + paethPredictor(
+                    leftPixel(dest, destOffset, destDy, x), abovePixel(dest,
+                            destOffset, destDy, x), aboveLeftPixel(dest,
+                            destOffset, destDy, x)));
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/PredictorAlgorithm.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/PredictorAlgorithm.java
new file mode 100644 (file)
index 0000000..ef80bee
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.predictor;
+
+import java.util.Random;
+
+/**
+ * Implements different PNG predictor algorithms that is used in PDF files.
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.4 $
+ * @see <a href="http://www.w3.org/TR/PNG-Filters.html">PNG Filters</a>
+ */
+public abstract class PredictorAlgorithm
+{
+    private int width;
+
+    private int height;
+
+    private int bpp;
+
+    /**
+     * check that buffer sizes matches width,height,bpp. This implementation is
+     * used by most of the filters, but not Uptimum.
+     *
+     * @param src The source buffer.
+     * @param dest The destination buffer.
+     */
+    public void checkBufsiz(byte[] src, byte[] dest)
+    {
+        if (src.length != dest.length)
+        {
+            throw new IllegalArgumentException("src.length != dest.length");
+        }
+        if (src.length != getWidth() * getHeight() * getBpp())
+        {
+            throw new IllegalArgumentException(
+                    "src.length != width * height * bpp");
+        }
+    }
+
+    /**
+     * encode line of pixel data in src from srcOffset and width*bpp bytes
+     * forward, put the decoded bytes into dest.
+     *
+     * @param src
+     *            raw image data
+     * @param dest
+     *            encoded data
+     * @param srcDy
+     *            byte offset between lines
+     * @param srcOffset
+     *            beginning of line data
+     * @param destDy
+     *            byte offset between lines
+     * @param destOffset
+     *            beginning of line data
+     */
+    public abstract void encodeLine(byte[] src, byte[] dest, int srcDy,
+            int srcOffset, int destDy, int destOffset);
+
+    /**
+     * decode line of pixel data in src from src_offset and width*bpp bytes
+     * forward, put the decoded bytes into dest.
+     *
+     * @param src
+     *            encoded image data
+     * @param dest
+     *            raw data
+     * @param srcDy
+     *            byte offset between lines
+     * @param srcOffset
+     *            beginning of line data
+     * @param destDy
+     *            byte offset between lines
+     * @param destOffset
+     *            beginning of line data
+     */
+    public abstract void decodeLine(byte[] src, byte[] dest, int srcDy,
+            int srcOffset, int destDy, int destOffset);
+
+    /**
+     * Simple command line program to test the algorithm.
+     *
+     * @param args The command line arguments.
+     */
+    public static void main(String[] args)
+    {
+        Random rnd = new Random();
+        int width = 5;
+        int height = 5;
+        int bpp = 3;
+        byte[] raw = new byte[width * height * bpp];
+        rnd.nextBytes(raw);
+        System.out.println("raw:   ");
+        dump(raw);
+        for (int i = 10; i < 15; i++)
+        {
+            byte[] decoded = new byte[width * height * bpp];
+            byte[] encoded = new byte[width * height * bpp];
+
+            PredictorAlgorithm filter = PredictorAlgorithm.getFilter(i);
+            filter.setWidth(width);
+            filter.setHeight(height);
+            filter.setBpp(bpp);
+            filter.encode(raw, encoded);
+            filter.decode(encoded, decoded);
+            System.out.println(filter.getClass().getName());
+            dump(decoded);
+        }
+    }
+
+    /**
+     * Get the left pixel from the buffer.
+     *
+     * @param buf The buffer.
+     * @param offset The offset into the buffer.
+     * @param dy The dy value.
+     * @param x The x value.
+     *
+     * @return The left pixel.
+     */
+    public int leftPixel(byte[] buf, int offset, int dy, int x)
+    {
+        return x >= getBpp() ? buf[offset + x - getBpp()] : 0;
+    }
+
+    /**
+     * Get the above pixel from the buffer.
+     *
+     * @param buf The buffer.
+     * @param offset The offset into the buffer.
+     * @param dy The dy value.
+     * @param x The x value.
+     *
+     * @return The above pixel.
+     */
+    public int abovePixel(byte[] buf, int offset, int dy, int x)
+    {
+        return offset >= dy ? buf[offset + x - dy] : 0;
+    }
+
+    /**
+     * Get the above-left pixel from the buffer.
+     *
+     * @param buf The buffer.
+     * @param offset The offset into the buffer.
+     * @param dy The dy value.
+     * @param x The x value.
+     *
+     * @return The above-left pixel.
+     */
+    public int aboveLeftPixel(byte[] buf, int offset, int dy, int x)
+    {
+        return offset >= dy && x >= getBpp() ? buf[offset + x - dy - getBpp()]
+                : 0;
+    }
+
+    /**
+     * Simple helper to print out a buffer.
+     *
+     * @param raw The bytes to print out.
+     */
+    private static void dump(byte[] raw)
+    {
+        for (int i = 0; i < raw.length; i++)
+        {
+            System.out.print(raw[i] + " ");
+        }
+        System.out.println();
+    }
+
+    /**
+     * @return Returns the bpp.
+     */
+    public int getBpp()
+    {
+        return bpp;
+    }
+
+    /**
+     * @param newBpp
+     *            The bpp to set.
+     */
+    public void setBpp(int newBpp)
+    {
+        bpp = newBpp;
+    }
+
+    /**
+     * @return Returns the height.
+     */
+    public int getHeight()
+    {
+        return height;
+    }
+
+    /**
+     * @param newHeight
+     *            The height to set.
+     */
+    public void setHeight(int newHeight)
+    {
+        height = newHeight;
+    }
+
+    /**
+     * @return Returns the width.
+     */
+    public int getWidth()
+    {
+        return width;
+    }
+
+    /**
+     * @param newWidth
+     *            The width to set.
+     */
+    public void setWidth(int newWidth)
+    {
+        this.width = newWidth;
+    }
+
+
+    /**
+     * encode a byte array full of image data using the filter that this object
+     * implements.
+     *
+     * @param src
+     *            buffer
+     * @param dest
+     *            buffer
+     */
+    public void encode(byte[] src, byte[] dest)
+    {
+        checkBufsiz(dest, src);
+        int dy = getWidth()*getBpp();
+        for (int y = 0; y < height; y++)
+        {
+            int yoffset = y * dy;
+            encodeLine(src, dest, dy, yoffset, dy, yoffset);
+        }
+    }
+
+    /**
+     * decode a byte array full of image data using the filter that this object
+     * implements.
+     *
+     * @param src
+     *            buffer
+     * @param dest
+     *            buffer
+     */
+    public void decode(byte[] src, byte[] dest)
+    {
+        checkBufsiz(src, dest);
+        int dy = width * bpp;
+        for (int y = 0; y < height; y++)
+        {
+            int yoffset = y * dy;
+            decodeLine(src, dest, dy, yoffset, dy, yoffset);
+        }
+    }
+
+    /**
+     * @param predictor
+     *            <ul>
+     *            <li>1 No prediction (the default value)
+     *            <li>2 TIFF Predictor 2
+     *            <li>10 PNG prediction (on encoding, PNG None on all rows)
+     *            <li>11 PNG prediction (on encoding, PNG Sub on all rows)
+     *            <li>12 PNG prediction (on encoding, PNG Up on all rows)
+     *            <li>13 PNG prediction (on encoding, PNG Average on all rows)
+     *            <li>14 PNG prediction (on encoding, PNG Paeth on all rows)
+     *            <li>15 PNG prediction (on encoding, PNG optimum)
+     *            </ul>
+     *
+     * @return The predictor class based on the predictor code.
+     */
+    public static PredictorAlgorithm getFilter(int predictor)
+    {
+        PredictorAlgorithm filter;
+        switch (predictor)
+        {
+            case 10:
+                filter = new None();
+                break;
+            case 11:
+                filter = new Sub();
+                break;
+            case 12:
+                filter = new Up();
+                break;
+            case 13:
+                filter = new Average();
+                break;
+            case 14:
+                filter = new Paeth();
+                break;
+            case 15:
+                filter = new Optimum();
+                break;
+            default:
+                filter = new None();
+        }
+        return filter;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Sub.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Sub.java
new file mode 100644 (file)
index 0000000..269760d
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.predictor;
+
+/**
+ * The sub algorithm.
+ *
+ * <code>Sub(i,j) = Raw(i,j) - Raw(i-1,j)</code>
+ *
+ * <code>Raw(i,j) = Sub(i,j) + Raw(i-1,j)</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Sub extends PredictorAlgorithm
+{
+    /**
+     * {@inheritDoc}
+     */
+    public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth()*getBpp();
+        int bpp = getBpp();
+        // case: x < bpp
+        for (int x = 0; x < bpl && x < bpp; x++)
+        {
+            dest[x + destOffset] = src[x + srcOffset];
+        }
+        // otherwise
+        for (int x = getBpp(); x < bpl; x++)
+        {
+            dest[x + destOffset] = (byte) (src[x + srcOffset] - src[x
+                    + srcOffset - bpp]);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth()*getBpp();
+        int bpp = getBpp();
+        // case: x < bpp
+        for (int x = 0; x < bpl && x < bpp; x++)
+        {
+            dest[x + destOffset] = src[x + srcOffset];
+        }
+        // otherwise
+        for (int x = getBpp(); x < bpl; x++)
+        {
+            dest[x + destOffset] = (byte) (src[x + srcOffset] + dest[x
+                    + destOffset - bpp]);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Up.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/Up.java
new file mode 100644 (file)
index 0000000..6883478
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.graphics.predictor;
+
+/**
+ * The up algorithm.
+ *
+ * <code>Up(i,j) = Raw(i,j) - Raw(i,j-1)</code>
+ *
+ * <code>Raw(i,j) = Up(i,j) + Raw(i,j-1)</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Up extends PredictorAlgorithm
+{
+    /**
+     * {@inheritDoc}
+     */
+    public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        int bpl = getWidth()*getBpp();
+        // case: y = 0;
+        if (srcOffset - srcDy < 0)
+        {
+            if (0 < getHeight())
+            {
+                for (int x = 0; x < bpl; x++)
+                {
+                    dest[destOffset + x] = src[srcOffset + x];
+                }
+            }
+        }
+        else
+        {
+            for (int x = 0; x < bpl; x++)
+            {
+                dest[destOffset + x] = (byte) (src[srcOffset + x] - src[srcOffset
+                        + x - srcDy]);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+            int destDy, int destOffset)
+    {
+        // case: y = 0;
+        int bpl = getWidth()*getBpp();
+        if (destOffset - destDy < 0)
+        {
+            if (0 < getHeight())
+            {
+                for (int x = 0; x < bpl; x++)
+                {
+                    dest[destOffset + x] = src[srcOffset + x];
+                }
+            }
+        }
+        else
+        {
+            for (int x = 0; x < bpl; x++)
+            {
+                dest[destOffset + x] = (byte) (src[srcOffset + x] + dest[destOffset
+                        + x - destDy]);
+            }
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/predictor/package.html
new file mode 100644 (file)
index 0000000..bf05d25
--- /dev/null
@@ -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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The predictor package contains code for different PNG predictor algorithms that
+are present in PDF documents.  These classes are used internally by PDFBox.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/CompositeImage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/CompositeImage.java
new file mode 100644 (file)
index 0000000..20fd45b
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+
+
+/**
+ * This class is responsible for combining a base image with an SMask-based transparency
+ * image to form a composite image.
+ * See section 11.5 of the pdf specification for details on Soft Masks.
+ * <p/>
+ * Briefly however, an Smask is a supplementary greyscale image whose RGB-values define
+ * a transparency mask which, when combined appropriately with the base image,
+ * allows per-pixel transparency to be applied.
+ * <p/>
+ * Note that Smasks are not required for any image and if the smask is not present
+ * in the pdf file, the image will have no transparent pixels.
+ *
+ * @author Neil McErlean
+ */
+public class CompositeImage
+{
+       private BufferedImage baseImage;
+       private BufferedImage smaskImage;
+       
+    /**
+     * Standard constructor.
+     * @param baseImage the base Image.
+     * @param smaskImage the transparency image.
+     *
+     */
+    public CompositeImage(BufferedImage baseImage, BufferedImage smaskImage)
+    {
+       this.baseImage = baseImage;
+       this.smaskImage = smaskImage;
+    }
+    
+    /**
+     * This method applies the specified transparency mask to a given image and returns a new BufferedImage
+     * whose alpha values are computed from the transparency mask (smask) image.
+     */
+    public BufferedImage createMaskedImage(COSArray decodeArray) throws IOException
+    {
+       // The decode array should only be [0 1] or [1 0]. See PDF spec.
+       // [0 1] means the smask's RGB values give transparency. Default: see PDF spec section 8.9.5.1
+       // [1 0] means the smask's RGB values give opacity.
+
+       boolean isOpaque = false;
+       if (decodeArray != null)
+       {
+           isOpaque = decodeArray.getInt(0) > decodeArray.getInt(1);
+       }
+       
+       final int baseImageWidth = baseImage.getWidth();
+       final int baseImageHeight = baseImage.getHeight();
+       
+       BufferedImage result = new BufferedImage(baseImageWidth, baseImageHeight, BufferedImage.TYPE_INT_ARGB);
+       for (int x = 0; x < baseImageWidth; x++)
+       {
+               for (int y = 0; y < baseImageHeight; y++)
+               {
+                       int rgb = baseImage.getRGB(x, y);
+                       int alpha = smaskImage.getRGB(x, y);
+                       
+                       // The smask image defines a transparency mask but it has no alpha values itself, instead
+                       // using the greyscale values to indicate transparency.
+                       // 0xAARRGGBB
+                       
+                       // We need to remove any alpha value in the main image.
+                       int rgbOnly = 0x00FFFFFF & rgb;
+                       
+                       // We need to use one of the rgb values as the new alpha value for the main image.
+                       // It seems the mask is greyscale, so it shouldn't matter whether we use R, G or B
+                       // as the indicator of transparency.
+                       if (isOpaque)
+                       {
+                               alpha = ~alpha;
+                       }
+                       int alphaOnly = alpha << 24;
+                       
+                       result.setRGB(x, y, rgbOnly | alphaOnly);
+               }
+       }
+       return result;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDCcitt.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDCcitt.java
new file mode 100644 (file)
index 0000000..7226e4c
--- /dev/null
@@ -0,0 +1,706 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.awt.Transparency;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBufferByte;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+
+/**
+ * An image class for CCITT Fax.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @author paul king
+ * @version $Revision: 1.6 $
+ */
+public class PDCcitt extends PDXObjectImage
+{
+
+    private static final List<String> FAX_FILTERS = new ArrayList<String>();
+    
+    static
+    {
+        FAX_FILTERS.add( COSName.CCITTFAX_DECODE.getName() );
+        FAX_FILTERS.add( COSName.CCITTFAX_DECODE_ABBREVIATION.getName() );
+    }
+
+    /**
+     * Standard constructor.
+     *
+     * @param ccitt The PDStream that already contains all ccitt information.
+     */
+    public PDCcitt(PDStream ccitt)
+    {
+        super(ccitt, "tiff");
+
+    }
+
+    /**
+     * Construct from a tiff file.
+     *
+     * @param doc The document to create the image as part of.
+     * @param raf The random access TIFF file which contains a suitable CCITT compressed image
+     * @throws IOException If there is an error reading the tiff data.
+     */
+
+    public PDCcitt( PDDocument doc, RandomAccess raf ) throws IOException
+    {
+        super( new PDStream(doc),"tiff");
+        // super( new PDStream( doc, null, true ), "tiff" );
+
+        COSDictionary decodeParms = new COSDictionary();
+
+        COSDictionary dic = getCOSStream();
+
+        extractFromTiff(raf, getCOSStream().createFilteredStream(),decodeParms);
+
+        dic.setItem( COSName.FILTER, COSName.CCITTFAX_DECODE);
+        dic.setItem( COSName.SUBTYPE, COSName.IMAGE);
+        dic.setItem( COSName.TYPE, COSName.XOBJECT );
+        dic.setItem( COSName.DECODE_PARMS, decodeParms);
+
+        setBitsPerComponent( 1 );
+        setColorSpace( new PDDeviceGray() );
+        setWidth( decodeParms.getInt(COSName.COLUMNS) );
+        setHeight( decodeParms.getInt(COSName.ROWS) );
+
+    }
+
+    /**
+     * Returns an image of the CCITT Fax, or null if TIFFs are not supported. (Requires additional JAI Image filters )
+     *
+     * {@inheritDoc}
+     */
+    public BufferedImage getRGBImage() throws IOException
+    {
+        COSStream stream = getCOSStream();
+        COSBase decodeP = stream.getDictionaryObject(COSName.DECODE_PARMS);
+        COSDictionary decodeParms = null;
+        if (decodeP instanceof COSDictionary)
+        {
+            decodeParms = (COSDictionary)decodeP;
+        }
+        else if (decodeP instanceof COSArray)
+        {
+            decodeParms =  (COSDictionary)((COSArray)decodeP).get(0);
+        }
+        int cols = decodeParms.getInt(COSName.COLUMNS, 1728);
+        int rows = decodeParms.getInt(COSName.ROWS, 0);
+        int height = stream.getInt(COSName.HEIGHT, 0);
+        if (rows > 0 && height > 0)
+        {
+            // ensure that rows doesn't contain implausible data, see PDFBOX-771
+            rows = Math.min(rows, height);
+        }
+        else 
+        {
+            // at least one of the values has to have a valid value
+            rows = Math.max(rows, height);
+        }
+        boolean blackIsOne = decodeParms.getBoolean(COSName.BLACK_IS_1, false);
+        
+        byte[] map;
+        if (blackIsOne)
+        {
+            map = new byte[] {(byte)0x00, (byte)0xff};
+        }
+        else
+        {
+            map = new byte[] {(byte)0xff};
+        }
+        ColorModel cm = new IndexColorModel(1, map.length, map, map, map, Transparency.OPAQUE);
+        WritableRaster raster = cm.createCompatibleWritableRaster( cols, rows );
+        DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
+        byte[] bufferData = buffer.getData();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int bytesRead;
+        byte[] data = new byte[16384];
+        InputStream unfilteredStream = stream.getUnfilteredStream();
+        while ((bytesRead = unfilteredStream.read(data, 0, data.length)) != -1) 
+        {
+            baos.write(data, 0, bytesRead);
+        }
+        baos.flush();
+        System.arraycopy( baos.toByteArray(), 0,bufferData, 0, 
+                (baos.size() < bufferData.length ? baos.size() : bufferData.length) );
+        return new BufferedImage(cm, raster, false, null);
+    }
+    
+    /**
+     * This writes a tiff to out.
+     *
+     * {@inheritDoc}
+     */
+    public void write2OutputStream(OutputStream out) throws IOException
+    {
+        // We should use another format than TIFF to get rid of the TiffWrapper
+        InputStream data = new TiffWrapper(getPDStream().getPartiallyFilteredStream( FAX_FILTERS ),getCOSStream());
+        byte[] buf = new byte[1024];
+        int amountRead = -1;
+        while( (amountRead = data.read( buf )) != -1 )
+        {
+            out.write( buf, 0, amountRead );
+        }
+    }
+
+    /**
+     * Extract the ccitt stream from the tiff file.
+     *
+     * @param raf  - TIFF File
+     * @param os   - Stream to write raw ccitt data two
+     * @param parms - COSDictionary which the encoding parameters are added to
+     * @throws IOException If there is an error reading/writing to/from the stream
+     */
+    private void extractFromTiff(RandomAccess raf, OutputStream os, COSDictionary parms) throws IOException
+    {
+        try
+        {
+
+            // First check the basic tiff header
+            raf.seek(0);
+            char endianess = (char) raf.read();
+            if ((char) raf.read() != endianess)
+            {
+                throw new IOException("Not a valid tiff file");
+            }
+            //ensure that endianess is either M or I
+            if (endianess != 'M' && endianess != 'I')
+            {
+                throw new IOException("Not a valid tiff file");
+            }
+            int magicNumber = readshort(endianess, raf);
+            if( magicNumber != 42)
+            {
+                throw new  IOException("Not a valid tiff file");
+            }
+
+            // Relocate to the first set of tags
+            raf.seek(readlong(endianess, raf));
+
+            int numtags = readshort(endianess, raf);
+
+            //    The number 50 is somewhat arbitary, it just stops us load up junk from somewhere and tramping on
+            if (numtags > 50)
+            {
+                throw new IOException("Not a valid tiff file");
+            }
+
+            // Loop through the tags, some will convert to items in the parms dictionary
+            // Other point us to where to find the data stream
+            // The only parm which might change as a result of other options is K, so
+            // We'll deal with that as a special;
+
+            int k=-1000; // Default Non CCITT compression
+            int dataoffset=0;
+            int datalength=0;
+
+            for (int i=0; i < numtags; i++)
+            {
+                int tag = readshort(endianess, raf);
+                int type = readshort(endianess, raf);
+                int count = readlong(endianess, raf);
+                int val = readlong(endianess, raf); // See note
+
+                // Note, we treated that value as a long. The value always occupies 4 bytes
+                // But it might only use the first byte or two. Depending on endianess we might need to correct
+                // Note we ignore all other types, they are of little interest for PDFs/CCITT Fax
+                if (endianess == 'M')
+                {
+                    switch (type)
+                    {
+                        case 1:
+                        {
+                            val = val >> 24;
+                            break; // byte value
+                        }
+                        case 3:
+                        {
+                            val = val >> 16;
+                            break; // short value
+                        }
+                        case 4:
+                        {
+                            break; // long value
+                        }
+                        default:
+                        {
+                            //do nothing
+                        }
+                    }
+                }
+                switch (tag)
+                {
+                    case 256:
+                    {
+                        parms.setInt(COSName.COLUMNS,val);
+                        break;
+                    }
+                    case 257:
+                    {
+                        parms.setInt(COSName.ROWS,val);
+                        break;
+                    }
+                    case 259:
+                    {
+                        if (val == 4)
+                        {
+                            k=-1;
+                        }
+                        if (val == 3)
+                        {
+                            k=0;
+                        }
+                        break;  // T6/T4 Compression
+                    }
+                    case 262:
+                    {
+                        if (val == 1)
+                        {
+                            parms.setBoolean(COSName.BLACK_IS_1, true);
+                        }
+                        break;
+                    }
+                    case 273:
+                    {
+                        if (count == 1)
+                        {
+                            dataoffset=val;
+                        }
+                        break;
+                    }
+                    case 279:
+                    {
+                        if (count == 1)
+                        {
+                            datalength=val;
+                        }
+                        break;
+                    }
+                    case 292:
+                    {
+                        if (val == 1)
+                        {
+                            k=50;  // T4 2D - arbitary K value
+                        }
+                        break;
+                    }
+                    case 324:
+                    {
+                        if (count == 1)
+                        {
+                            dataoffset=val;
+                        }
+                        break;
+                    }
+                    case 325:
+                    {
+                        if (count == 1)
+                        {
+                            datalength=val;
+                        }
+                        break;
+                    }
+                    default:
+                    {
+                        //do nothing
+                    }
+                }
+            }
+
+            if (k == -1000)
+            {
+                throw new IOException("First image in tiff is not CCITT T4 or T6 compressed");
+            }
+            if (dataoffset == 0)
+            {
+                throw new IOException("First image in tiff is not a single tile/strip");
+            }
+
+            parms.setInt(COSName.K,k);
+
+            raf.seek(dataoffset);
+
+            byte[] buf = new byte[8192];
+            int amountRead = -1;
+            while( (amountRead = raf.read( buf,0, Math.min(8192,datalength) )) > 0 )
+            {
+                datalength -= amountRead;
+                os.write( buf, 0, amountRead );
+            }
+
+        }
+        finally
+        {
+            os.close();
+        }
+    }
+
+    private int readshort(char endianess, RandomAccess raf) throws IOException
+    {
+        if (endianess == 'I')
+        {
+            return raf.read() | (raf.read() << 8);
+        }
+        return (raf.read() << 8) | raf.read();
+    }
+
+    private int readlong(char endianess, RandomAccess raf) throws IOException
+    {
+        if (endianess == 'I')
+        {
+            return raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24);
+        }
+        return (raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) | raf.read();
+    }
+
+
+    /**
+     * Extends InputStream to wrap the data from the CCITT Fax with a suitable TIFF Header.
+     * For details see www.tiff.org, which contains useful information including pointers to the
+     * TIFF 6.0 Specification
+     *
+     */
+    private class TiffWrapper extends InputStream
+    {
+
+        private int currentOffset;     // When reading, where in the tiffheader are we.
+        private byte[] tiffheader;      // Byte array to store tiff header data
+        private InputStream datastream; // Original InputStream
+
+        private TiffWrapper(InputStream rawstream, COSDictionary options)
+        {
+                buildHeader(options);
+                currentOffset=0;
+                datastream = rawstream;
+        }
+
+        // Implement basic methods from InputStream
+        /**
+         * {@inheritDoc}
+         */
+        public boolean markSupported()
+        {
+            return false;
+        }
+        /**
+         * {@inheritDoc}
+         */
+        public void reset() throws IOException
+        {
+            throw new IOException("reset not supported");
+        }
+
+        /**
+         * For simple read, take a byte from the tiffheader array or pass through.
+         *
+         * {@inheritDoc}
+         */
+        public int read() throws IOException
+        {
+            if (currentOffset < tiffheader.length)
+            {
+                return tiffheader[currentOffset++];
+            }
+            return datastream.read();
+        }
+
+        /**
+         * For read methods only return as many bytes as we have left in the header
+         * if we've exhausted the header, pass through to the InputStream of the raw CCITT data.
+         *
+         * {@inheritDoc}
+         */
+        public int read(byte[] data) throws IOException
+        {
+            if (currentOffset < tiffheader.length)
+            {
+                int length = java.lang.Math.min(tiffheader.length - currentOffset, data.length);
+                if (length > 0)
+                {
+                    System.arraycopy(tiffheader, currentOffset, data, 0, length);
+                }
+                currentOffset += length;
+                return length;
+            }
+            else
+            {
+                return datastream.read(data);
+            }
+        }
+
+        /**
+         * For read methods only return as many bytes as we have left in the header
+         * if we've exhausted the header, pass through to the InputStream of the raw CCITT data.
+         *
+         *  {@inheritDoc}
+         */
+        public int read(byte[] data, int off, int len) throws IOException
+        {
+            if (currentOffset < tiffheader.length)
+            {
+                int length = java.lang.Math.min(tiffheader.length - currentOffset, len);
+                if (length > 0)
+                {
+                    System.arraycopy(tiffheader, currentOffset, data, off, length);
+                }
+                currentOffset += length;
+                return length;
+            }
+            else
+            {
+                return datastream.read(data,off,len);
+            }
+        }
+
+        /**
+         * When skipping if any header data not yet read, only allow to skip what we've in the buffer
+         * Otherwise just pass through.
+         *
+         * {@inheritDoc}
+         */
+        public long skip(long n) throws IOException
+        {
+            if (currentOffset < tiffheader.length)
+            {
+                long length = Math.min(tiffheader.length - currentOffset, n);
+                currentOffset += length;
+                return length;
+            }
+            else
+            {
+                return datastream.skip(n);
+            }
+        }
+
+        // Static data for the beginning of the TIFF header
+        private final byte[] basicHeader = {
+                'I','I',42,0,8,0,0,0,       // File introducer and pointer to first IFD
+                0,0};                       // Number of tags start with two
+
+
+        private int additionalOffset;      // Offset in header to additional data
+
+        // Builds up the tiffheader based on the options passed through.
+        private void buildHeader(COSDictionary options)
+        {
+
+            final int numOfTags = 10; // The maximum tags we'll fill
+            final int maxAdditionalData = 24; // The maximum amount of additional data
+                                                // outside the IFDs. (bytes)
+
+            // The length of the header will be the length of the basic header (10)
+            // plus 12 bytes for each IFD, 4 bytes as a pointer to the next IFD (will be 0)
+            // plus the length of the additional data
+
+            int ifdSize = 10 + (12 * numOfTags ) + 4;
+            tiffheader = new byte[ifdSize + maxAdditionalData];
+            java.util.Arrays.fill(tiffheader,(byte)0);
+            System.arraycopy(basicHeader,0,tiffheader,0,basicHeader.length);
+
+            // Additional data outside the IFD starts after the IFD's and pointer to the next IFD (0)
+            additionalOffset = ifdSize;
+
+            // Now work out the variable values from TIFF defaults,
+            // PDF Defaults and the Dictionary for this XObject
+            short cols = 1728;
+            short rows = 0;
+            short blackis1 = 0;
+            short comptype = 3; // T4 compression
+            long t4options = 0; // Will set if 1d or 2d T4
+
+            COSArray decode = getDecode();
+            // we have to invert the b/w-values,
+            // if the Decode array exists and consists of (1,0)
+            if (decode != null && decode.getInt(0) == 1)
+            {
+                blackis1 = 1;
+            }
+            COSBase dicOrArrayParms = options.getDictionaryObject(COSName.DECODE_PARMS);
+            COSDictionary decodeParms = null;
+            if( dicOrArrayParms instanceof COSDictionary )
+            {
+                decodeParms = (COSDictionary)dicOrArrayParms;
+            }
+            else
+            {
+                COSArray parmsArray = (COSArray)dicOrArrayParms;
+                if( parmsArray.size() == 1 )
+                {
+                    decodeParms = (COSDictionary)parmsArray.getObject( 0 );
+                }
+                else
+                {
+                    //else find the first dictionary with Row/Column info and use that.
+                    for( int i=0; i<parmsArray.size() && decodeParms == null; i++ )
+                    {
+                        COSDictionary dic = (COSDictionary)parmsArray.getObject( i );
+                        if (dic != null &&
+                                ( dic.getDictionaryObject(COSName.COLUMNS) != null ||
+                                        dic.getDictionaryObject(COSName.ROWS) != null))
+                        {
+                            decodeParms = dic;
+                        }
+                    }
+                }
+            }
+
+            if (decodeParms != null)
+            {
+                cols = (short) decodeParms.getInt(COSName.COLUMNS, cols);
+                rows = (short) decodeParms.getInt(COSName.ROWS, rows);
+                if (decodeParms.getBoolean(COSName.BLACK_IS_1, false))
+                {
+                    blackis1 = 1;
+                }
+                int k = decodeParms.getInt(COSName.K, 0);  // Mandatory parm
+                if (k < 0)
+                {
+                    //T6
+                    comptype = 4;
+                }
+                if (k > 0)
+                {
+                    //T4 2D
+                    comptype = 3;
+                    t4options = 1;
+                }
+                // else k = 0, leave as default T4 1D compression
+            }
+
+            // If we couldn't get the number of rows, use the main item from XObject
+            if (rows == 0)
+            {
+                rows = (short) options.getInt(COSName.HEIGHT, rows);
+            }
+
+            // Now put the tags into the tiffheader
+            // These musn't exceed the maximum set above, and by TIFF spec should be sorted into
+            // Numeric sequence.
+
+            addTag(256, cols); // Columns
+            addTag(257, rows); // Rows
+            addTag(259, comptype);    // T6
+            addTag(262, blackis1); // Photometric Interpretation
+            addTag(273, tiffheader.length); // Offset to start of image data - updated below
+            addTag(279, options.getInt(COSName.LENGTH)); // Length of image data
+            addTag(282, 300, 1); // X Resolution 300 (default unit Inches) This is arbitary
+            addTag(283, 300, 1); // Y Resolution 300 (default unit Inches) This is arbitary
+            if (comptype == 3)
+            {
+                addTag(292, t4options);
+            }
+            addTag(305, "PDFBOX"); // Software generating image
+        }
+
+        /* Tiff types 1 = byte, 2=ascii, 3=short, 4=ulong 5=rational */
+
+        private void addTag(int tag,long value)
+        {
+        // Adds a tag of type 4 (ulong)
+            int count = ++tiffheader[8];
+            int offset = (count-1)*12 + 10;
+            tiffheader[offset]=(byte)(tag & 0xff);
+            tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+            tiffheader[offset+2]=4;  // Type Long
+            tiffheader[offset+4]=1;  // One Value
+            tiffheader[offset+8]=(byte)(value & 0xff);
+            tiffheader[offset+9]=(byte)((value>>8) & 0xff);
+            tiffheader[offset+10]=(byte)((value>>16) & 0xff);
+            tiffheader[offset+11]=(byte)((value>>24) & 0xff);
+        }
+
+        private void addTag(int tag, short value)
+        {
+            // Adds a tag of type 3 (short)
+            int count = ++tiffheader[8];
+            int offset = (count-1)*12 + 10;
+            tiffheader[offset]=(byte)(tag & 0xff);
+            tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+            tiffheader[offset+2]=3;  // Type Short
+            tiffheader[offset+4]=1;  // One Value
+            tiffheader[offset+8]=(byte)(value & 0xff);
+            tiffheader[offset+9]=(byte)((value>>8) & 0xff);
+        }
+
+        private void addTag(int tag, String value)
+        {
+            // Adds a tag of type 2 (ascii)
+            int count = ++tiffheader[8];
+            int offset = (count-1)*12 + 10;
+            tiffheader[offset]=(byte)(tag & 0xff);
+            tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+            tiffheader[offset+2]=2;  // Type Ascii
+            int len = value.length() + 1;
+            tiffheader[offset+4]=(byte)(len & 0xff);
+            tiffheader[offset+8]=(byte)(additionalOffset & 0xff);
+            tiffheader[offset+9]=(byte)((additionalOffset>>8) & 0xff);
+            tiffheader[offset+10]=(byte)((additionalOffset>>16) & 0xff);
+            tiffheader[offset+11]=(byte)((additionalOffset>>24) & 0xff);
+            try {
+                System.arraycopy(value.getBytes("US-ASCII"), 0,
+                        tiffheader, additionalOffset, value.length());
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException("Incompatible VM without US-ASCII encoding", e);
+            }
+            additionalOffset += len;
+        }
+
+        private void addTag(int tag, long numerator, long denominator)
+        {
+            // Adds a tag of type 5 (rational)
+            int count = ++tiffheader[8];
+            int offset = (count-1)*12 + 10;
+            tiffheader[offset]=(byte)(tag & 0xff);
+            tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+            tiffheader[offset+2]=5;  // Type Rational
+            tiffheader[offset+4]=1;  // One Value
+            tiffheader[offset+8]=(byte)(additionalOffset & 0xff);
+            tiffheader[offset+9]=(byte)((additionalOffset>>8) & 0xff);
+            tiffheader[offset+10]=(byte)((additionalOffset>>16) & 0xff);
+            tiffheader[offset+11]=(byte)((additionalOffset>>24) & 0xff);
+            tiffheader[additionalOffset++]=(byte) ((numerator) & 0xFF);
+            tiffheader[additionalOffset++]=(byte) ((numerator>>8) & 0xFF);
+            tiffheader[additionalOffset++]=(byte) ((numerator>>16) & 0xFF);
+            tiffheader[additionalOffset++]=(byte) ((numerator>>24) & 0xFF);
+            tiffheader[additionalOffset++]=(byte) ((denominator) & 0xFF);
+            tiffheader[additionalOffset++]=(byte) ((denominator>>8) & 0xFF);
+            tiffheader[additionalOffset++]=(byte) ((denominator>>16) & 0xFF);
+            tiffheader[additionalOffset++]=(byte) ((denominator>>24) & 0xFF);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDInlinedImage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDInlinedImage.java
new file mode 100644 (file)
index 0000000..95fb2f3
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterManager;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.util.ImageParameters;
+
+/**
+ * This class represents an inlined image.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDInlinedImage
+{
+    private ImageParameters params;
+    private byte[] imageData;
+
+    /**
+     * This will get the image parameters.
+     *
+     * @return The image parameters.
+     */
+    public ImageParameters getImageParameters()
+    {
+        return params;
+    }
+
+    /**
+     * This will set the image parameters for this image.
+     *
+     * @param imageParams The imageParams.
+     */
+    public void setImageParameters( ImageParameters imageParams )
+    {
+        params = imageParams;
+    }
+
+    /**
+     * Get the bytes for the image.
+     *
+     * @return The image data.
+     */
+    public byte[] getImageData()
+    {
+        return imageData;
+    }
+
+    /**
+     * Set the bytes that make up the image.
+     *
+     * @param value The image data.
+     */
+    public void setImageData(byte[] value)
+    {
+        imageData = value;
+    }
+
+    /**
+     * This will take the inlined image information and create a java.awt.Image from
+     * it.
+     *
+     * @return The image that this object represents.
+     *
+     * @throws IOException If there is an error creating the image.
+     */
+    public BufferedImage createImage() throws IOException
+    {
+        return createImage( null );
+    }
+
+    /**
+     * This will take the inlined image information and create a java.awt.Image from
+     * it.
+     * 
+     * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+     *
+     * @return The image that this object represents.
+     *
+     * @throws IOException If there is an error creating the image.
+     */
+    public BufferedImage createImage( Map colorSpaces ) throws IOException
+    {
+        /*
+         * This was the previous implementation, not sure which is better right now.
+         *         byte[] transparentColors = new byte[]{(byte)0xFF,(byte)0xFF};
+        byte[] colors=new byte[]{0, (byte)0xFF};
+        IndexColorModel colorModel = new IndexColorModel( 1, 2, colors, colors, colors, transparentColors );
+        BufferedImage image = new BufferedImage(
+            params.getWidth(),
+            params.getHeight(),
+            BufferedImage.TYPE_BYTE_BINARY,
+            colorModel );
+        DataBufferByte buffer = new DataBufferByte( getImageData(), 1 );
+        WritableRaster raster =
+            Raster.createPackedRaster(
+                buffer,
+                params.getWidth(),
+                params.getHeight(),
+                params.getBitsPerComponent(),
+                new Point(0,0) );
+        image.setData( raster );
+        return image;
+         */
+
+
+        //verify again pci32.pdf before changing below
+        PDColorSpace pcs = params.getColorSpace( colorSpaces );
+        ColorModel colorModel = null;
+        if(pcs != null)
+        {
+            colorModel =
+                pcs.createColorModel(
+                        params.getBitsPerComponent() );
+        }
+        else
+        {
+            byte[] transparentColors = new
+            byte[]{(byte)0xFF,(byte)0xFF};
+            byte[] colors=new byte[]{0, (byte)0xFF};
+            colorModel = new IndexColorModel( 1, 2,
+                    colors, colors, colors, transparentColors );
+        }
+        List filters = params.getFilters();
+        byte[] finalData = null;
+        if( filters == null )
+        {
+            finalData = getImageData();
+        }
+        else
+        {
+            ByteArrayInputStream in = new ByteArrayInputStream( getImageData() );
+            ByteArrayOutputStream out = new ByteArrayOutputStream(getImageData().length);
+            FilterManager filterManager = new FilterManager();
+            for( int i=0; i<filters.size(); i++ )
+            {
+                out.reset();
+                Filter filter = filterManager.getFilter( (String)filters.get( i ) );
+                filter.decode( in, out, params.getDictionary(), i );
+                in = new ByteArrayInputStream( out.toByteArray() );
+            }
+            finalData = out.toByteArray();
+        }
+
+        WritableRaster raster = colorModel.createCompatibleWritableRaster( params.getWidth(), params.getHeight() );
+        /*    Raster.createPackedRaster(
+                buffer,
+                params.getWidth(),
+                params.getHeight(),
+                params.getBitsPerComponent(),
+                new Point(0,0) );
+                */
+        DataBuffer rasterBuffer = raster.getDataBuffer();
+        if( rasterBuffer instanceof DataBufferByte )
+        {
+            DataBufferByte byteBuffer = (DataBufferByte)rasterBuffer;
+            byte[] data = byteBuffer.getData();
+            System.arraycopy( finalData, 0, data, 0, data.length );
+        }
+        else if( rasterBuffer instanceof DataBufferInt )
+        {
+            DataBufferInt byteBuffer = (DataBufferInt)rasterBuffer;
+            int[] data = byteBuffer.getData();
+            for( int i=0; i<finalData.length; i++ )
+            {
+                data[i] = (finalData[i]+256)%256;
+            }
+        }
+        BufferedImage image = new BufferedImage(
+                colorModel, raster, false, null );
+        image.setData( raster );
+        return image;
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDJpeg.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDJpeg.java
new file mode 100644 (file)
index 0000000..4ad5236
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.IIOException;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.stream.ImageOutputStream;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+
+/**
+ * An image class for JPegs.
+ *
+ * @author mathiak
+ * @version $Revision: 1.5 $
+ */
+public class PDJpeg extends PDXObjectImage
+{
+
+    private static final String JPG = "jpg";
+    
+    private static final List<String> DCT_FILTERS = new ArrayList<String>();
+
+    private final static float defaultCompressionLevel = 0.75f;
+    
+    static
+    {
+        DCT_FILTERS.add( COSName.DCT_DECODE.getName() );
+        DCT_FILTERS.add( COSName.DCT_DECODE_ABBREVIATION.getName() );
+    }
+
+    /**
+     * Standard constructor.
+     *
+     * @param jpeg The COSStream from which to extract the JPeg
+     */
+    public PDJpeg(PDStream jpeg)
+    {
+        super(jpeg, JPG);
+    }
+
+    /**
+     * Construct from a stream.
+     *
+     * @param doc The document to create the image as part of.
+     * @param is The stream that contains the jpeg data.
+     * @throws IOException If there is an error reading the jpeg data.
+     */
+    public PDJpeg( PDDocument doc, InputStream is ) throws IOException
+    {
+        super( new PDStream( doc, is, true ), JPG);
+        COSDictionary dic = getCOSStream();
+        dic.setItem( COSName.FILTER, COSName.DCT_DECODE );
+        dic.setItem( COSName.SUBTYPE, COSName.IMAGE);
+        dic.setItem( COSName.TYPE, COSName.XOBJECT );
+
+        BufferedImage image = getRGBImage();
+        if (image != null) 
+        {
+            setBitsPerComponent( 8 );
+            setColorSpace( PDDeviceRGB.INSTANCE );
+            setHeight( image.getHeight() );
+            setWidth( image.getWidth() );
+        }
+
+    }
+
+    /**
+     * Construct from a buffered image.
+     * The default compression level of 0.75 will be used. 
+     *
+     * @param doc The document to create the image as part of.
+     * @param bi The image to convert to a jpeg
+     * @throws IOException If there is an error processing the jpeg data.
+     */
+    public PDJpeg( PDDocument doc, BufferedImage bi ) throws IOException
+    {
+        super( new PDStream( doc ) , JPG);
+        createImageStream(doc, bi, defaultCompressionLevel);
+    }
+    
+    /**
+     * Construct from a buffered image.
+     *
+     * @param doc The document to create the image as part of.
+     * @param bi The image to convert to a jpeg
+     * @param compressionQuality The quality level which is used to compress the image
+     * @throws IOException If there is an error processing the jpeg data.
+     */
+    public PDJpeg( PDDocument doc, BufferedImage bi, float compressionQuality ) throws IOException
+    {
+        super( new PDStream( doc ), JPG);
+        createImageStream(doc, bi, compressionQuality);
+    }
+    
+    private void createImageStream(PDDocument doc, BufferedImage bi, float compressionQuality) throws IOException
+    {
+        BufferedImage alpha = null;
+        if (bi.getColorModel().hasAlpha())
+        {
+            alpha = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
+            Graphics2D g = alpha.createGraphics();
+            g.setColor(Color.BLACK);
+            g.drawRect(0, 0, bi.getWidth(), bi.getHeight());
+            g.setColor(Color.WHITE);
+            g.drawImage(bi, 0, 0, null);
+            int alphaHeight = alpha.getHeight();
+            int alphaWidth = alpha.getWidth();
+            int whiteRGB = (Color.WHITE).getRGB();
+            for(int y = 0; y < alphaHeight; y++)
+            {
+                for(int x = 0; x < alphaWidth; x++)
+                {
+                    Color color = new Color(alpha.getRGB(x, y));
+                    if(color.getRed() != 0 && color.getGreen() != 0 && color.getBlue() != 0)
+                    {
+                        alpha.setRGB(x, y, whiteRGB);
+                    }
+                }
+            }
+            BufferedImage image = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_USHORT_565_RGB);
+            g = image.createGraphics();
+            g.drawImage(bi, 0, 0, null);
+            bi = image;
+        }
+        
+        java.io.OutputStream os = getCOSStream().createFilteredStream();
+        try
+        {
+            ImageWriter writer = null;
+            Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(JPG);
+            if (iter.hasNext()) 
+            {
+                writer = iter.next();
+            }
+            ImageOutputStream ios = ImageIO.createImageOutputStream(os);
+            writer.setOutput(ios);
+                
+            // Set the compression quality
+            JPEGImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
+            iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            iwparam.setCompressionQuality(compressionQuality);
+                
+            // Write the image
+            writer.write(null, new IIOImage(bi, null, null), iwparam);
+                
+            writer.dispose();
+            
+            COSDictionary dic = getCOSStream();
+            dic.setItem( COSName.FILTER, COSName.DCT_DECODE );
+            dic.setItem( COSName.SUBTYPE, COSName.IMAGE);
+            dic.setItem( COSName.TYPE, COSName.XOBJECT );
+            PDXObjectImage alphaPdImage = null;
+            if(alpha != null)
+            {
+                alphaPdImage = new PDJpeg(doc, alpha, compressionQuality);
+                dic.setItem(COSName.SMASK, alphaPdImage);
+            }
+            setBitsPerComponent( 8 );
+            if (bi.getColorModel().getNumComponents() == 3) 
+            {
+                setColorSpace( PDDeviceRGB.INSTANCE );
+            } 
+            else
+            {
+                if (bi.getColorModel().getNumComponents() == 1) 
+                {
+                    setColorSpace( new PDDeviceGray() );
+                } 
+                else 
+                {
+                    throw new IllegalStateException();
+                }
+            }
+            setHeight( bi.getHeight() );
+            setWidth( bi.getWidth() );
+        }
+        finally
+        {
+            os.close();
+        }
+    }
+
+    /**
+     * Returns an image of the JPeg, or null if JPegs are not supported. (They should be. )
+     * {@inheritDoc}
+     */
+    public BufferedImage getRGBImage() throws IOException
+    {   //TODO PKOCH
+        BufferedImage bi = null;
+        boolean readError = false;
+        try 
+        {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            write2OutputStream(os);
+            os.close();
+            byte[] img = os.toByteArray();
+
+            // 1. try to read jpeg image
+            try 
+            {
+                bi = ImageIO.read(new ByteArrayInputStream(img));
+            } 
+            catch (IIOException iioe) 
+            {
+                // cannot read jpeg
+                readError = true;
+            } 
+            catch (Exception ignore) 
+            {
+            }
+
+            // 2. try to read jpeg again. some jpegs have some strange header containing
+            //    "Adobe " at some place. so just replace the header with a valid jpeg header.
+            // TODO : not sure if it works for all cases
+            if (bi == null && readError) 
+            {
+                byte[] newImage = replaceHeader(img);
+
+                ByteArrayInputStream bai = new ByteArrayInputStream(newImage);
+
+                bi = ImageIO.read(bai);
+            }
+        } 
+        finally 
+        {
+        }
+        
+        // If there is a 'soft mask' image then we use that as a transparency mask.
+        PDXObjectImage smask = getSMaskImage();
+        if (smask != null)
+        {
+            BufferedImage smaskBI = smask.getRGBImage();
+            
+               COSArray decodeArray = smask.getDecode();
+               CompositeImage compositeImage = new CompositeImage(bi, smaskBI);
+               BufferedImage rgbImage = compositeImage.createMaskedImage(decodeArray);
+
+            return rgbImage;
+        }
+        else
+        {
+               // But if there is no soft mask, use the unaltered image.
+            return bi;
+        }
+    }
+    
+    /**
+     * This writes the JPeg to out.
+     * {@inheritDoc}
+     */
+    public void write2OutputStream(OutputStream out) throws IOException
+    {
+        InputStream data = getPDStream().getPartiallyFilteredStream( DCT_FILTERS );
+        byte[] buf = new byte[1024];
+        int amountRead = -1;
+        while( (amountRead = data.read( buf )) != -1 )
+        {
+            out.write( buf, 0, amountRead );
+        }
+    }
+
+    /**
+     * Returns the given file as byte array.
+     * @param file File to be read
+     * @return given file as byte array
+     * @throws IOException if somethin went wrong during reading the file
+     */
+    public static byte[] getBytesFromFile(File file) throws IOException 
+    {
+        InputStream is = new FileInputStream(file);
+        long length = file.length();
+
+        if (length > Integer.MAX_VALUE) 
+        {
+            // File is too large
+            throw new IOException("File is tooo large");
+        }
+
+        // Create the byte array to hold the data
+        byte[] bytes = new byte[(int)length];
+
+        // Read in the bytes
+        int offset = 0;
+        int numRead = 0;
+
+        while (offset < bytes.length
+                && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) 
+        {
+            offset += numRead;
+        }
+
+        // Ensure all the bytes have been read in
+        if (offset < bytes.length) 
+        {
+            throw new IOException("Could not completely read file "+file.getName());
+        }
+        is.close();
+        return bytes;
+    }
+
+    private int getHeaderEndPos(byte[] image) 
+    {
+        for (int i = 0; i < image.length; i++) 
+        {
+            byte b = image[i];
+            if (b == (byte) 0xDB) 
+            {        
+                // TODO : check for ff db
+                return i -2;
+            }
+        }
+        return 0;
+    }
+
+    private byte[] replaceHeader(byte[] image)
+    {
+        // get end position of wrong header respectively startposition of "real jpeg data"
+        int pos = getHeaderEndPos(image);
+
+        // simple correct header
+        byte[] header = new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE0, (byte) 0x00,
+                (byte) 0x10, (byte) 0x4A, (byte) 0x46, (byte) 0x49, (byte) 0x46, (byte) 0x00, (byte) 0x01,
+                (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x60, (byte) 0x00, (byte) 0x60, (byte) 0x00, (byte) 0x00};
+
+        // concat
+        byte[] newImage = new byte[image.length - pos + header.length - 1];
+        System.arraycopy(header, 0, newImage, 0, header.length);
+        System.arraycopy(image, pos + 1, newImage, header.length, image.length - pos - 1);
+
+        return newImage;
+    }
+}
+
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java
new file mode 100644 (file)
index 0000000..476ac61
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.awt.Transparency;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDIndexed;
+
+
+
+/**
+ * This class contains a PixelMap Image.
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author mathiak
+ * @version $Revision: 1.10 $
+ */
+public class PDPixelMap extends PDXObjectImage
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDPixelMap.class);
+
+    private BufferedImage image = null;
+
+    /**
+     * Standard constructor. Basically does nothing.
+     * @param pdStream The stream that holds the pixel map.
+     */
+    public PDPixelMap(PDStream pdStream)
+    {
+        super(pdStream, "png");
+    }
+
+    /**
+     * Construct a pixel map image from an AWT image.
+     *
+     * @param doc The PDF document to embed the image in.
+     * @param awtImage The image to read data from.
+     *
+     * @throws IOException If there is an error while embedding this image.
+     */
+    /*
+     * This method is broken and needs to be implemented, any takers?
+    public PDPixelMap(PDDocument doc, BufferedImage awtImage) throws IOException
+    {
+        super( doc, "png");
+        image = awtImage;
+        setWidth( image.getWidth() );
+        setHeight( image.getHeight() );
+
+        ColorModel cm = image.getColorModel();
+        ColorSpace cs = cm.getColorSpace();
+        PDColorSpace pdColorSpace = PDColorSpaceFactory.createColorSpace( doc, cs );
+        setColorSpace( pdColorSpace );
+        //setColorSpace( )
+
+        PDStream stream = getPDStream();
+        OutputStream output = null;
+        try
+        {
+            output = stream.createOutputStream();
+            DataBuffer buffer = awtImage.getRaster().getDataBuffer();
+            if( buffer instanceof DataBufferByte )
+            {
+                DataBufferByte byteBuffer = (DataBufferByte)buffer;
+                byte[] data = byteBuffer.getData();
+                output.write( data );
+            }
+            setBitsPerComponent( cm.getPixelSize() );
+        }
+        finally
+        {
+            if( output != null )
+            {
+                output.close();
+            }
+        }
+    }*/
+
+    /**
+     * Returns a {@link java.awt.image.BufferedImage} of the COSStream
+     * set in the constructor or null if the COSStream could not be encoded.
+     *
+     * @return {@inheritDoc}
+     *
+     * @throws IOException {@inheritDoc}
+     */
+    public BufferedImage getRGBImage() throws IOException
+    {
+        if( image != null )
+        {
+            return image;
+        }
+
+        try
+        {
+            int width = getWidth();
+            int height = getHeight();
+            int bpc = getBitsPerComponent();
+            
+            byte[] array = getPDStream().getByteArray();
+    
+            // Get the ColorModel right
+            PDColorSpace colorspace = getColorSpace();
+            if (colorspace == null)
+            {
+                log.error("getColorSpace() returned NULL.  Predictor = " + getPredictor());
+                return null;
+            }
+            
+            ColorModel cm = null;
+            if (colorspace instanceof PDIndexed)
+            {
+                PDIndexed csIndexed = (PDIndexed)colorspace;
+                ColorModel baseColorModel = csIndexed.getBaseColorSpace().createColorModel(bpc);
+                int size = csIndexed.getHighValue();
+                byte[] index = csIndexed.getLookupData();
+                boolean hasAlpha = baseColorModel.hasAlpha();
+                COSArray maskArray = getMask();
+                if( baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE )
+                {
+                    throw new IOException( "Not implemented" );
+                }
+                byte[] r = new byte[size+1];
+                byte[] g = new byte[size+1];
+                byte[] b = new byte[size+1];
+                byte[] a = hasAlpha ? new byte[size+1] : null;
+                byte[] inData = new byte[baseColorModel.getNumComponents()];
+                for( int i = 0; i <= size; i++ )
+                {
+                    System.arraycopy(index, i * inData.length, inData, 0, inData.length);
+                    r[i] = (byte)baseColorModel.getRed(inData);
+                    g[i] = (byte)baseColorModel.getGreen(inData);
+                    b[i] = (byte)baseColorModel.getBlue(inData);
+                    if( hasAlpha )
+                    {
+                        a[i] = (byte)baseColorModel.getAlpha(inData);
+                    }
+                }
+                if (hasAlpha)
+                {
+                    cm = new IndexColorModel(bpc, size+1, r, g, b, a);
+                }
+                else {
+                    if (maskArray != null)
+                    {
+                        cm = new IndexColorModel(bpc, size+1, r, g, b, maskArray.getInt(0));
+                    }
+                    else
+                    {
+                        cm = new IndexColorModel(bpc, size+1, r, g, b);
+                    }
+                }
+            }
+            else if (bpc == 1)
+            {
+                byte[] map = null;
+                if (colorspace instanceof PDDeviceGray)
+                {
+                    COSArray decode = getDecode();
+                    // we have to invert the b/w-values, 
+                    // if the Decode array exists and consists of (1,0)
+                    if (decode != null && decode.getInt(0) == 1)
+                    {
+                        map = new byte[] {(byte)0xff};
+                    }
+                    else
+                    {
+                        map = new byte[] {(byte)0x00, (byte)0xff};
+                    }
+                }
+                else if (colorspace instanceof PDICCBased)
+                {
+                    if ( ((PDICCBased)colorspace).getNumberOfComponents() == 1) 
+                    {
+                        map = new byte[] {(byte)0xff};
+                    }
+                    else
+                    {
+                        map = new byte[] {(byte)0x00, (byte)0xff};
+                    }
+                }
+                else
+                {
+                    map = new byte[] {(byte)0x00, (byte)0xff};
+                }
+                cm = new IndexColorModel(bpc, map.length, map, map, map, Transparency.OPAQUE);
+            }
+            else
+            {
+                if (colorspace instanceof PDICCBased) 
+                {
+                    if (((PDICCBased)colorspace).getNumberOfComponents() == 1) 
+                    {
+                        byte[] map = new byte[] {(byte)0xff};
+                        cm = new IndexColorModel(bpc, 1, map, map, map, Transparency.OPAQUE);
+                    }
+                    else
+                        cm = colorspace.createColorModel( bpc );
+                }
+                else
+                    cm = colorspace.createColorModel( bpc );
+            }
+
+            log.debug("ColorModel: " + cm.toString());
+            WritableRaster raster = cm.createCompatibleWritableRaster( width, height );
+            DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
+            byte[] bufferData = buffer.getData();
+        
+            System.arraycopy( array, 0,bufferData, 0, 
+                    (array.length<bufferData.length?array.length: bufferData.length) );
+            image = new BufferedImage(cm, raster, false, null);
+            
+               // If there is a 'soft mask' image then we use that as a transparency mask.
+               PDXObjectImage smask = getSMaskImage();
+               if (smask != null)
+               {
+                BufferedImage smaskBI = smask.getRGBImage();
+                
+                       COSArray decodeArray = smask.getDecode();
+                       
+                       CompositeImage compositeImage = new CompositeImage(image, smaskBI);
+                       BufferedImage rgbImage = compositeImage.createMaskedImage(decodeArray);
+
+                return rgbImage;
+               }
+               else
+               {
+                       // But if there is no soft mask, use the unaltered image.
+                   return image;
+               }
+        } 
+        catch (Exception exception)
+        {
+            log.error(exception, exception);
+            //A NULL return is caught in pagedrawer.Invoke.process() so don't re-throw.
+            //Returning the NULL falls through to Phillip Koch's TODO section.
+            return null;
+        }
+    }
+
+    /**
+     * Writes the image as .png.
+     *
+     * {@inheritDoc}
+     */
+    public void write2OutputStream(OutputStream out) throws IOException
+    {
+        getRGBImage();
+        if (image!=null)
+        {
+            ImageIO.write(image, "png", out);
+        }
+    }
+
+    /**
+     * DecodeParms is an optional parameter for filters.
+     *
+     * It is provided if any of the filters has nondefault parameters. If there
+     * is only one filter it is a dictionary, if there are multiple filters it
+     * is an array with an entry for each filter. An array entry can hold a null
+     * value if only the default values are used or a dictionary with
+     * parameters.
+     *
+     * @return The decoding parameters.
+     *
+     */
+    public COSDictionary getDecodeParams()
+    {
+        COSBase decodeParms = getCOSStream().getDictionaryObject(COSName.DECODE_PARMS);
+        if (decodeParms != null)
+        {
+            if (decodeParms instanceof COSDictionary)
+            {
+                return (COSDictionary) decodeParms;
+            }
+            else if (decodeParms instanceof COSArray)
+            {
+                // not implemented yet, which index should we use?
+                return null;//(COSDictionary)((COSArray)decodeParms).get(0);
+            }
+            else
+            {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * A code that selects the predictor algorithm.
+     *
+     * <ul>
+     * <li>1 No prediction (the default value)
+     * <li>2 TIFF Predictor 2
+     * <li>10 PNG prediction (on encoding, PNG None on all rows)
+     * <li>11 PNG prediction (on encoding, PNG Sub on all rows)
+     * <li>12 PNG prediction (on encoding, PNG Up on all rows)
+     * <li>13 PNG prediction (on encoding, PNG Average on all rows)
+     * <li>14 PNG prediction (on encoding, PNG Path on all rows)
+     * <li>15 PNG prediction (on encoding, PNG optimum)
+     * </ul>
+     *
+     * Default value: 1.
+     *
+     * @return predictor algorithm code
+     */
+    public int getPredictor()
+    {
+        COSDictionary decodeParms = getDecodeParams();
+        if (decodeParms != null)
+        {
+            int i = decodeParms.getInt(COSName.PREDICTOR);
+            if (i != -1)
+            {
+                return i;
+            }
+        }
+        return 1;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java
new file mode 100644 (file)
index 0000000..afe199b
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * The base class for all XObjects in the PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author mathiak
+ * @author Marcel Kammer
+ * @version $Revision: 1.14 $
+ */
+public abstract class PDXObject implements COSObjectable
+{
+    
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDXObject.class);
+
+    private PDStream xobject;
+
+    /**
+     * Standard constructor.
+     *
+     * @param xobj The XObject dictionary.
+     */
+    public PDXObject(COSStream xobj)
+    {
+        xobject = new PDStream( xobj );
+        xobject.getStream().setName( COSName.TYPE, "XObject" );
+    }
+
+    /**
+     * Standard constuctor.
+     *
+     * @param xobj The XObject dictionary.
+     */
+    public PDXObject(PDStream xobj)
+    {
+        xobject = xobj;
+        xobject.getStream().setName( COSName.TYPE, "XObject" );
+    }
+
+    /**
+     * Standard constuctor.
+     *
+     * @param doc The doc to store the object contents.
+     */
+    public PDXObject(PDDocument doc)
+    {
+        xobject = new PDStream(doc);
+        xobject.getStream().setName( COSName.TYPE, "XObject" );
+    }
+
+    /**
+     * Returns the stream.
+     *
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return xobject.getCOSObject();
+    }
+
+    /**
+     * Returns the stream.
+     * @return The stream for this object.
+     */
+    public COSStream getCOSStream()
+    {
+        return xobject.getStream();
+    }
+
+    /**
+     * Returns the stream.
+     * @return The stream for this object.
+     */
+    public PDStream getPDStream()
+    {
+        return xobject;
+    }
+
+    /**
+     * Create the correct xobject from the cos base.
+     *
+     * @param xobject The cos level xobject to create.
+     *
+     * @return a pdmodel xobject
+     * @throws IOException If there is an error creating the xobject.
+     */
+    public static PDXObject createXObject( COSBase xobject ) throws IOException
+    {
+        PDXObject retval = null;
+        if( xobject == null )
+        {
+            retval = null;
+        }
+        else if( xobject instanceof COSStream )
+        {
+            COSStream xstream = (COSStream)xobject;
+            String subtype = xstream.getNameAsString( COSName.SUBTYPE );
+            if( subtype.equals( PDXObjectImage.SUB_TYPE ) )
+            {
+                PDStream image = new PDStream( xstream );
+                // See if filters are DCT or JPX otherwise treat as Bitmap-like
+                // There might be a problem with several filters, but that's ToDo until
+                // I find an example
+                List filters = image.getFilters();
+                if( filters != null && filters.contains( COSName.DCT_DECODE.getName() ) )
+                {
+                    return new PDJpeg(image);
+                }
+                else if ( filters != null && filters.contains( COSName.CCITTFAX_DECODE.getName() ) )
+                {
+                    return new PDCcitt(image);
+                }
+                else if( filters != null && filters.contains(COSName.JPX_DECODE.getName()))
+                {
+                    //throw new IOException( "JPXDecode has not been implemented for images" );
+                    //JPX Decode is not really supported right now, but if we are just doing
+                    //text extraction then we don't want to throw an exception, so for now
+                    //just return a PDPixelMap, which will break later on if it is
+                    //actually used, but for text extraction it is not used.
+                    return new PDPixelMap( image );
+                }
+                else
+                {
+                    retval = new PDPixelMap(image);
+                }
+            }
+            else if( subtype.equals( PDXObjectForm.SUB_TYPE ) )
+            {
+                retval = new PDXObjectForm( xstream );
+            }
+            else
+            {
+                log.warn( "Skipping unknown XObject subtype '" + subtype + "'" );
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Get the metadata that is part of the document catalog.  This will
+     * return null if there is no meta data for this object.
+     *
+     * @return The metadata for this object.
+     */
+    public PDMetadata getMetadata()
+    {
+        PDMetadata retval = null;
+        COSStream mdStream = (COSStream)xobject.getStream().getDictionaryObject( COSName.METADATA );
+        if( mdStream != null )
+        {
+            retval = new PDMetadata( mdStream );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the metadata for this object.  This can be null.
+     *
+     * @param meta The meta data for this object.
+     */
+    public void setMetadata( PDMetadata meta )
+    {
+        xobject.getStream().setItem( COSName.METADATA, meta );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java
new file mode 100644 (file)
index 0000000..5886fb0
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.awt.geom.AffineTransform;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.Matrix;
+
+/**
+ * A form xobject.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDXObjectForm extends PDXObject
+{
+    /**
+     * The XObject subtype.
+     */
+    public static final String SUB_TYPE = "Form";
+
+    /**
+     * Standard constuctor.
+     *
+     * @param formStream The XObject is passed as a COSStream.
+     */
+    public PDXObjectForm(PDStream formStream)
+    {
+        super( formStream );
+        getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+    }
+
+    /**
+     * Standard constuctor.
+     *
+     * @param formStream The XObject is passed as a COSStream.
+     */
+    public PDXObjectForm(COSStream formStream)
+    {
+        super( formStream );
+        getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+    }
+
+    /**
+     * This will get the form type, currently 1 is the only form type.
+     *
+     * @return The form type.
+     */
+    public int getFormType()
+    {
+        return getCOSStream().getInt( "FormType",1 );
+    }
+
+    /**
+     * Set the form type.
+     *
+     * @param formType The new form type.
+     */
+    public void setFormType( int formType )
+    {
+        getCOSStream().setInt( "FormType", formType );
+    }
+
+    /**
+     * This will get the resources at this page and not look up the hierarchy.
+     * This attribute is inheritable, and findResources() should probably used.
+     * This will return null if no resources are available at this level.
+     *
+     * @return The resources at this level in the hierarchy.
+     */
+    public PDResources getResources()
+    {
+        PDResources retval = null;
+        COSDictionary resources = (COSDictionary)getCOSStream().getDictionaryObject( COSName.RESOURCES );
+        if( resources != null )
+        {
+            retval = new PDResources( resources );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the resources for this page.
+     *
+     * @param resources The new resources for this page.
+     */
+    public void setResources( PDResources resources )
+    {
+        getCOSStream().setItem( COSName.RESOURCES, resources );
+    }
+
+    /**
+     * An array of four numbers in the form coordinate system (see
+     * below), giving the coordinates of the left, bottom, right, and top edges,
+     * respectively, of the form XObject's bounding box. These boundaries are used
+     * to clip the form XObject and to determine its size for caching.
+     *
+     * @return The BBox of the form.
+     */
+    public PDRectangle getBBox()
+    {
+        PDRectangle retval = null;
+        COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.BBOX );
+        if( array != null )
+        {
+            retval = new PDRectangle( array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the BBox (bounding box) for this form.
+     *
+     * @param bbox The new BBox for this form.
+     */
+    public void setBBox(PDRectangle bbox)
+    {
+        if( bbox == null )
+        {
+            getCOSStream().removeItem( COSName.BBOX );
+        }
+        else
+        {
+            getCOSStream().setItem( COSName.BBOX, bbox.getCOSArray() );
+        }
+    }
+
+    /**
+     * This will get the optional Matrix of an XObjectForm.
+     * It maps the form space into the user space
+     * @return the form matrix
+     */
+    public Matrix getMatrix()
+    {
+        Matrix retval = null;
+        COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.MATRIX );
+        if( array != null )
+        {
+            retval = new Matrix();
+            retval.setValue(0, 0, ((COSNumber) array.get(0)).floatValue());
+            retval.setValue(0, 1, ((COSNumber) array.get(1)).floatValue());
+            retval.setValue(1, 0, ((COSNumber) array.get(2)).floatValue());
+            retval.setValue(1, 1, ((COSNumber) array.get(3)).floatValue());
+            retval.setValue(2, 0, ((COSNumber) array.get(4)).floatValue());
+            retval.setValue(2, 1, ((COSNumber) array.get(5)).floatValue());
+        }
+        return retval;
+    }
+
+    /**
+     * Sets the optional Matrix entry for the form XObject.
+     * @param transform the transformation matrix
+     */
+    public void setMatrix(AffineTransform transform)
+    {
+        COSArray matrix = new COSArray();
+        double[] values = new double[6];
+        transform.getMatrix(values);
+        for (double v : values)
+        {
+            matrix.add(new COSFloat((float)v));
+        }
+        getCOSStream().setItem(COSName.MATRIX, matrix);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java
new file mode 100644 (file)
index 0000000..67d2835
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ * 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.pdfbox.pdmodel.graphics.xobject;
+
+import java.awt.image.BufferedImage;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.File;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+
+/**
+ * The prototype for all PDImages.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author mathiak
+ * @version $Revision: 1.9 $
+ */
+public abstract class PDXObjectImage extends PDXObject
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDXObjectImage.class);
+
+    /**
+     * The XObject subtype.
+     */
+    public static final String SUB_TYPE = "Image";
+
+    /**
+     * This contains the suffix used when writing to file.
+     */
+    private String suffix;
+    
+    private PDGraphicsState graphicsState;
+
+    /**
+     * Standard constuctor.
+     *
+     * @param imageStream The XObject is passed as a COSStream.
+     * @param fileSuffix The file suffix, jpg/png.
+     */
+    public PDXObjectImage(PDStream imageStream, String fileSuffix)
+    {
+        super( imageStream );
+        suffix = fileSuffix;
+    }
+
+    /**
+     * Standard constuctor.
+     *
+     * @param doc The document to store the stream in.
+     * @param fileSuffix The file suffix, jpg/png.
+     */
+    public PDXObjectImage(PDDocument doc, String fileSuffix)
+    {
+        super( doc );
+        getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+        suffix = fileSuffix;
+    }
+
+    /**
+     * Returns an java.awt.Image, that can be used for display etc.
+     *
+     * @return This PDF object as an AWT image.
+     *
+     * @throws IOException If there is an error creating the image.
+     */
+    public abstract BufferedImage getRGBImage() throws IOException;
+
+    /**
+     * Returns a PDXObjectImage of the SMask image, if there is one.
+     * See section 11.5 of the pdf specification for details on Soft Masks.
+     *
+     * @return the PDXObjectImage of the SMask if there is one, else <code>null</code>.
+     */
+    public PDXObjectImage getSMaskImage() throws IOException
+    {
+       COSStream cosStream = getPDStream().getStream();
+       COSBase smask = cosStream.getDictionaryObject(COSName.SMASK);
+       
+       if (smask == null)
+       {
+               return null;
+       }
+       else
+       {
+               return (PDXObjectImage)PDXObject.createXObject(smask);
+       }
+    }
+    
+    /**
+     * Writes the Image to out.
+     * @param out the OutputStream that the Image is written to.
+     * @throws IOException when somethings wrong with out
+     */
+    public abstract void write2OutputStream(OutputStream out) throws IOException;
+
+    /**
+     * Writes the image to a file with the filename + an appropriate suffix, like "Image.jpg".
+     * The suffix is automatically set by the
+     * @param filename the filename
+     * @throws IOException When somethings wrong with the corresponding file.
+     */
+    public void write2file(String filename) throws IOException
+    {
+        FileOutputStream out = null;
+        try
+        {
+            out = new FileOutputStream(filename + "." + suffix);
+            write2OutputStream(out);
+            out.flush();
+        }
+        finally
+        {
+            if( out != null )
+            {
+                out.close();
+            }
+        }
+    }
+
+        /**
+     * Writes the image to a file with the filename + an appropriate
+suffix, like "Image.jpg".
+     * The suffix is automatically set by the
+     * @param file the file
+     * @throws IOException When somethings wrong with the corresponding
+file.
+     */
+    public void write2file(File file) throws IOException
+    {
+        FileOutputStream out = null;
+        try
+        {
+            out = new FileOutputStream(file);
+            write2OutputStream(out);
+            out.flush();
+        }
+        finally
+        {
+            if( out != null )
+            {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Get the height of the image.
+     *
+     * @return The height of the image.
+     */
+    public int getHeight()
+    {
+        return getCOSStream().getInt( COSName.HEIGHT, -1 );
+    }
+
+    /**
+     * Set the height of the image.
+     *
+     * @param height The height of the image.
+     */
+    public void setHeight( int height )
+    {
+        getCOSStream().setInt( COSName.HEIGHT, height );
+    }
+
+    /**
+     * Get the width of the image.
+     *
+     * @return The width of the image.
+     */
+    public int getWidth()
+    {
+        return getCOSStream().getInt( COSName.WIDTH, -1 );
+    }
+
+    /**
+     * Set the width of the image.
+     *
+     * @param width The width of the image.
+     */
+    public void setWidth( int width )
+    {
+        getCOSStream().setInt( COSName.WIDTH, width );
+    }
+
+    /**
+     * The bits per component of this image.  This will return -1 if one has not
+     * been set.
+     *
+     * @return The number of bits per component.
+     */
+    public int getBitsPerComponent()
+    {
+        return getCOSStream().getInt( new String[] { "BPC", "BitsPerComponent"}, -1 );
+    }
+
+    /**
+     * Set the number of bits per component.
+     *
+     * @param bpc The number of bits per component.
+     */
+    public void setBitsPerComponent( int bpc )
+    {
+        getCOSStream().setInt( "BitsPerComponent", bpc );
+    }
+
+    /**
+     * This will get the color space or null if none exists.
+     *
+     * @return The color space for this image.
+     *
+     * @throws IOException If there is an error getting the colorspace.
+     */
+    public PDColorSpace getColorSpace() throws IOException
+    {
+        COSBase cs = getCOSStream().getDictionaryObject( new String[]{ "CS", "ColorSpace" } );
+        PDColorSpace retval = null;
+        if( cs != null )
+        {
+            retval = PDColorSpaceFactory.createColorSpace( cs );
+            if (retval == null) 
+                {
+                    log.info("About to return NULL from createColorSpace branch");
+                }
+        }
+        else
+        {
+            //there are some cases where the 'required' CS value is not present
+            //but we know that it will be grayscale for a CCITT filter.
+            COSBase filter = getCOSStream().getDictionaryObject( "Filter" );
+            if( COSName.CCITTFAX_DECODE.equals( filter ) ||
+                COSName.CCITTFAX_DECODE_ABBREVIATION.equals( filter ) )
+            {
+                retval = new PDDeviceGray();
+                if (retval == null) 
+                    {
+                        log.info("About to return NULL from CCITT branch");
+                    }
+            }
+            else if( COSName.JBIG2_DECODE.equals( filter ) )
+            {
+                retval = new PDDeviceGray();
+                if (retval == null) 
+                {
+                    log.info("About to return NULL from JBIG2 branch");
+                }
+            }
+            else if (getImageMask())
+            {
+                //Stencil Mask branch.  Section 4.8.5 of the reference, page 350 in version 1.7.
+                retval = graphicsState.getNonStrokingColor().getColorSpace();
+                log.info("Stencil Mask branch returning " + retval.toString());
+                //throw new IOException("Trace the Stencil Mask!!!!");
+            
+            }
+            else
+            {
+                log.info("About to return NULL from unhandled branch."
+                        + " filter = " + filter);
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the color space for this image.
+     *
+     * @param cs The color space for this image.
+     */
+    public void setColorSpace( PDColorSpace cs )
+    {
+        COSBase base = null;
+        if( cs != null )
+        {
+            base = cs.getCOSObject();
+        }
+        getCOSStream().setItem( COSName.COLORSPACE, base );
+    }
+
+    /**
+     * This will get the suffix for this image type, jpg/png.
+     *
+     * @return The image suffix.
+     */
+    public String getSuffix()
+    {
+        return suffix;
+    }
+    
+    /**
+     * Get the ImageMask flag. Used in Stencil Masking.  Section 4.8.5 of the spec.
+     *
+     * @return The ImageMask flag.  This is optional and defaults to False, so if it does not exist, we return False
+     */
+    public boolean getImageMask()
+    {
+        return getCOSStream().getBoolean( COSName.IMAGE_MASK, false );
+    }
+    
+    /**
+     * Allow the Invoke operator to set the graphics state so that, 
+     * in the case of an Image Mask, we can get to the current nonstroking colorspace.
+     * @param newGS The new graphicstate
+     */
+    public void setGraphicsState(PDGraphicsState newGS)
+    {
+        graphicsState = newGS;
+    }
+
+    /**
+     * Returns the Decode Array of an XObjectImage.
+     * @return the decode array
+     */
+    public COSArray getDecode() {
+        COSBase decode = getCOSStream().getDictionaryObject( COSName.DECODE );
+        if (decode != null && decode instanceof COSArray) 
+        {
+            return (COSArray)decode;
+        }
+        return null;
+    }
+
+    /** 
+     * Returns the optional mask of a XObjectImage if there is one.
+     * 
+     * @return The mask as COSArray otherwise null.
+     */
+    public COSArray getMask()
+    {
+        COSBase mask = getCOSStream().getDictionaryObject(COSName.MASK);
+        if (mask != null)
+        {
+            return (COSArray)mask;
+        }
+        return null;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/graphics/xobject/package.html
new file mode 100644 (file)
index 0000000..0f7b141
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package deals with images that are stored in a PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDActionFactory.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDActionFactory.java
new file mode 100644 (file)
index 0000000..dea8492
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.interactive.action;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionGoTo;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionJavaScript;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionLaunch;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionRemoteGoTo;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+
+/**
+ * This class will take a dictionary and determine which type of action to create.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDActionFactory
+{
+    /**
+     * Utility Class.
+     */
+    private PDActionFactory()
+    {
+        //utility class
+    }
+
+    /**
+     * This will create the correct type of action based on the type specified
+     * in the dictionary.
+     *
+     * @param action An action dictionary.
+     *
+     * @return An action of the correct type.
+     */
+    public static PDAction createAction( COSDictionary action )
+    {
+        PDAction retval = null;
+        if( action != null )
+        {
+            String type = action.getNameAsString( "S" );
+            if( PDActionJavaScript.SUB_TYPE.equals( type ) )
+            {
+                retval = new PDActionJavaScript( action );
+            }
+            else if( PDActionGoTo.SUB_TYPE.equals( type ) )
+            {
+                retval = new PDActionGoTo( action );
+            }
+            else if( PDActionLaunch.SUB_TYPE.equals( type ) )
+            {
+                retval = new PDActionLaunch( action );
+            }
+            else if( PDActionRemoteGoTo.SUB_TYPE.equals( type ) )
+            {
+                retval = new PDActionRemoteGoTo( action );
+            }
+            else if( PDActionURI.SUB_TYPE.equals( type ) )
+            {
+                retval = new PDActionURI( action );
+            }
+        }
+        return retval;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDAdditionalActions.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDAdditionalActions.java
new file mode 100644 (file)
index 0000000..797b6a6
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This represents a dictionary of actions that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDAdditionalActions implements COSObjectable
+{
+    private COSDictionary actions;
+
+    /**
+     * Default constructor.
+     */
+    public PDAdditionalActions()
+    {
+        actions = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDAdditionalActions( COSDictionary a )
+    {
+        actions = a;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return actions;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return actions;
+    }
+
+    /**
+     * Get the F action.
+     *
+     * @return The F action.
+     */
+    public PDAction getF()
+    {
+        return PDActionFactory.createAction( (COSDictionary)actions.getDictionaryObject("F" ) );
+    }
+
+    /**
+     * Set the F action.
+     *
+     * @param action Get the F action.
+     */
+    public void setF( PDAction action )
+    {
+        actions.setItem( "F", action );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDAnnotationAdditionalActions.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDAnnotationAdditionalActions.java
new file mode 100644 (file)
index 0000000..85b6abb
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents an annotation's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationAdditionalActions implements COSObjectable
+{
+    private COSDictionary actions;
+
+    /**
+     * Default constructor.
+     */
+    public PDAnnotationAdditionalActions()
+    {
+        actions = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDAnnotationAdditionalActions( COSDictionary a )
+    {
+        actions = a;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return actions;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return actions;
+    }
+
+    /**
+     * This will get an action to be performed when the cursor
+     * enters the annotation's active area.
+     *
+     * @return The E entry of annotation's additional actions dictionary.
+     */
+    public PDAction getE()
+    {
+        COSDictionary e = (COSDictionary)actions.getDictionaryObject( "E" );
+        PDAction retval = null;
+        if( e != null )
+        {
+            retval = PDActionFactory.createAction( e );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the cursor
+     * enters the annotation's active area.
+     *
+     * @param e The action to be performed.
+     */
+    public void setE( PDAction e )
+    {
+        actions.setItem( "E", e );
+    }
+
+    /**
+     * This will get an action to be performed when the cursor
+     * exits the annotation's active area.
+     *
+     * @return The X entry of annotation's additional actions dictionary.
+     */
+    public PDAction getX()
+    {
+        COSDictionary x = (COSDictionary)actions.getDictionaryObject( "X" );
+        PDAction retval = null;
+        if( x != null )
+        {
+            retval = PDActionFactory.createAction( x );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the cursor
+     * exits the annotation's active area.
+     *
+     * @param x The action to be performed.
+     */
+    public void setX( PDAction x )
+    {
+        actions.setItem( "X", x );
+    }
+
+    /**
+     * This will get an action to be performed when the mouse button
+     * is pressed inside the annotation's active area.
+     * The name D stands for "down".
+     *
+     * @return The d entry of annotation's additional actions dictionary.
+     */
+    public PDAction getD()
+    {
+        COSDictionary d = (COSDictionary)actions.getDictionaryObject( "D" );
+        PDAction retval = null;
+        if( d != null )
+        {
+            retval = PDActionFactory.createAction( d );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the mouse button
+     * is pressed inside the annotation's active area.
+     * The name D stands for "down".
+     *
+     * @param d The action to be performed.
+     */
+    public void setD( PDAction d )
+    {
+        actions.setItem( "D", d );
+    }
+
+    /**
+     * This will get an action to be performed when the mouse button
+     * is released inside the annotation's active area.
+     * The name U stands for "up".
+     *
+     * @return The U entry of annotation's additional actions dictionary.
+     */
+    public PDAction getU()
+    {
+        COSDictionary u = (COSDictionary)actions.getDictionaryObject( "U" );
+        PDAction retval = null;
+        if( u != null )
+        {
+            retval = PDActionFactory.createAction( u );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the mouse button
+     * is released inside the annotation's active area.
+     * The name U stands for "up".
+     *
+     * @param u The action to be performed.
+     */
+    public void setU( PDAction u )
+    {
+        actions.setItem( "U", u );
+    }
+
+    /**
+     * This will get an action to be performed when the annotation
+     * receives the input focus.
+     *
+     * @return The Fo entry of annotation's additional actions dictionary.
+     */
+    public PDAction getFo()
+    {
+        COSDictionary fo = (COSDictionary)actions.getDictionaryObject( "Fo" );
+        PDAction retval = null;
+        if( fo != null )
+        {
+            retval = PDActionFactory.createAction( fo );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the annotation
+     * receives the input focus.
+     *
+     * @param fo The action to be performed.
+     */
+    public void setFo( PDAction fo )
+    {
+        actions.setItem( "Fo", fo );
+    }
+
+    /**
+     * This will get an action to be performed when the annotation
+     * loses the input focus.
+     * The name Bl stands for "blurred".
+     *
+     * @return The Bl entry of annotation's additional actions dictionary.
+     */
+    public PDAction getBl()
+    {
+        COSDictionary bl = (COSDictionary)actions.getDictionaryObject( "Bl" );
+        PDAction retval = null;
+        if( bl != null )
+        {
+            retval = PDActionFactory.createAction( bl );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the annotation
+     * loses the input focus.
+     * The name Bl stands for "blurred".
+     *
+     * @param bl The action to be performed.
+     */
+    public void setBl( PDAction bl )
+    {
+        actions.setItem( "Bl", bl );
+    }
+
+    /**
+     * This will get an action to be performed when the page containing
+     * the annotation is opened. The action is executed after the O action
+     * in the page's additional actions dictionary and the OpenAction entry
+     * in the document catalog, if such actions are present.
+     *
+     * @return The PO entry of annotation's additional actions dictionary.
+     */
+    public PDAction getPO()
+    {
+        COSDictionary po = (COSDictionary)actions.getDictionaryObject( "PO" );
+        PDAction retval = null;
+        if( po != null )
+        {
+            retval = PDActionFactory.createAction( po );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the page containing
+     * the annotation is opened. The action is executed after the O action
+     * in the page's additional actions dictionary and the OpenAction entry
+     * in the document catalog, if such actions are present.
+     *
+     * @param po The action to be performed.
+     */
+    public void setPO( PDAction po )
+    {
+        actions.setItem( "PO", po );
+    }
+
+    /**
+     * This will get an action to be performed when the page containing
+     * the annotation is closed. The action is executed before the C action
+     * in the page's additional actions dictionary, if present.
+     *
+     * @return The PC entry of annotation's additional actions dictionary.
+     */
+    public PDAction getPC()
+    {
+        COSDictionary pc = (COSDictionary)actions.getDictionaryObject( "PC" );
+        PDAction retval = null;
+        if( pc != null )
+        {
+            retval = PDActionFactory.createAction( pc );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the page containing
+     * the annotation is closed. The action is executed before the C action
+     * in the page's additional actions dictionary, if present.
+     *
+     * @param pc The action to be performed.
+     */
+    public void setPC( PDAction pc )
+    {
+        actions.setItem( "PC", pc );
+    }
+
+    /**
+     * This will get an action to be performed when the page containing
+     * the annotation becomes visible in the viewer application's user interface.
+     *
+     * @return The PV entry of annotation's additional actions dictionary.
+     */
+    public PDAction getPV()
+    {
+        COSDictionary pv = (COSDictionary)actions.getDictionaryObject( "PV" );
+        PDAction retval = null;
+        if( pv != null )
+        {
+            retval = PDActionFactory.createAction( pv );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the page containing
+     * the annotation becomes visible in the viewer application's user interface.
+     *
+     * @param pv The action to be performed.
+     */
+    public void setPV( PDAction pv )
+    {
+        actions.setItem( "PV", pv );
+    }
+
+    /**
+     * This will get an action to be performed when the page containing the annotation
+     * is no longer visible in the viewer application's user interface.
+     *
+     * @return The PI entry of annotation's additional actions dictionary.
+     */
+    public PDAction getPI()
+    {
+        COSDictionary pi = (COSDictionary)actions.getDictionaryObject( "PI" );
+        PDAction retval = null;
+        if( pi != null )
+        {
+            retval = PDActionFactory.createAction( pi );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the page containing the annotation
+     * is no longer visible in the viewer application's user interface.
+     *
+     * @param pi The action to be performed.
+     */
+    public void setPI( PDAction pi )
+    {
+        actions.setItem( "PI", pi );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDDocumentCatalogAdditionalActions.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDDocumentCatalogAdditionalActions.java
new file mode 100644 (file)
index 0000000..f8527a6
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents a document catalog's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDDocumentCatalogAdditionalActions implements COSObjectable
+{
+    private COSDictionary actions;
+
+    /**
+     * Default constructor.
+     */
+    public PDDocumentCatalogAdditionalActions()
+    {
+        actions = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDDocumentCatalogAdditionalActions( COSDictionary a )
+    {
+        actions = a;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return actions;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return actions;
+    }
+
+    /**
+     * This will get a JavaScript action to be performed
+     * before closing a document.
+     * The name WC stands for "will close".
+     *
+     * @return The WC entry of document catalog's additional actions dictionary.
+     */
+    public PDAction getWC()
+    {
+        COSDictionary wc = (COSDictionary)actions.getDictionaryObject( "WC" );
+        PDAction retval = null;
+        if( wc != null )
+        {
+            retval = PDActionFactory.createAction( wc );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed
+     * before closing a document.
+     * The name WC stands for "will close".
+     *
+     * @param wc The action to be performed.
+     */
+    public void setWC( PDAction wc )
+    {
+        actions.setItem( "WC", wc );
+    }
+
+    /**
+     * This will get a JavaScript action to be performed
+     * before saving a document.
+     * The name WS stands for "will save".
+     *
+     * @return The WS entry of document catalog's additional actions dictionary.
+     */
+    public PDAction getWS()
+    {
+        COSDictionary ws = (COSDictionary)actions.getDictionaryObject( "WS" );
+        PDAction retval = null;
+        if( ws != null )
+        {
+            retval = PDActionFactory.createAction( ws );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed
+     * before saving a document.
+     * The name WS stands for "will save".
+     *
+     * @param ws The action to be performed.
+     */
+    public void setWS( PDAction ws )
+    {
+        actions.setItem( "WS", ws );
+    }
+
+    /**
+     * This will get a JavaScript action to be performed
+     * after saving a document.
+     * The name DS stands for "did save".
+     *
+     * @return The DS entry of document catalog's additional actions dictionary.
+     */
+    public PDAction getDS()
+    {
+        COSDictionary ds = (COSDictionary)actions.getDictionaryObject( "DS" );
+        PDAction retval = null;
+        if( ds != null )
+        {
+            retval = PDActionFactory.createAction( ds );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed
+     * after saving a document.
+     * The name DS stands for "did save".
+     *
+     * @param ds The action to be performed.
+     */
+    public void setDS( PDAction ds )
+    {
+        actions.setItem( "DS", ds );
+    }
+
+    /**
+     * This will get a JavaScript action to be performed
+     * before printing a document.
+     * The name WP stands for "will print".
+     *
+     * @return The WP entry of document catalog's additional actions dictionary.
+     */
+    public PDAction getWP()
+    {
+        COSDictionary wp = (COSDictionary)actions.getDictionaryObject( "WP" );
+        PDAction retval = null;
+        if( wp != null )
+        {
+            retval = PDActionFactory.createAction( wp );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed
+     * before printing a document.
+     * The name WP stands for "will print".
+     *
+     * @param wp The action to be performed.
+     */
+    public void setWP( PDAction wp )
+    {
+        actions.setItem( "WP", wp );
+    }
+
+    /**
+     * This will get a JavaScript action to be performed
+     * after printing a document.
+     * The name DP stands for "did print".
+     *
+     * @return The DP entry of document catalog's additional actions dictionary.
+     */
+    public PDAction getDP()
+    {
+        COSDictionary dp = (COSDictionary)actions.getDictionaryObject( "DP" );
+        PDAction retval = null;
+        if( dp != null )
+        {
+            retval = PDActionFactory.createAction( dp );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed
+     * after printing a document.
+     * The name DP stands for "did print".
+     *
+     * @param dp The action to be performed.
+     */
+    public void setDP( PDAction dp )
+    {
+        actions.setItem( "DP", dp );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDFormFieldAdditionalActions.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDFormFieldAdditionalActions.java
new file mode 100644 (file)
index 0000000..9ec8ee0
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents a form field's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDFormFieldAdditionalActions implements COSObjectable
+{
+    private COSDictionary actions;
+
+    /**
+     * Default constructor.
+     */
+    public PDFormFieldAdditionalActions()
+    {
+        actions = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDFormFieldAdditionalActions( COSDictionary a )
+    {
+        actions = a;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return actions;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return actions;
+    }
+
+    /**
+     * This will get a JavaScript action to be performed when the user
+     * types a keystroke into a text field or combo box or modifies the
+     * selection in a scrollable list box. This allows the keystroke to
+     * be checked for validity and rejected or modified.
+     *
+     * @return The K entry of form field's additional actions dictionary.
+     */
+    public PDAction getK()
+    {
+        COSDictionary k = (COSDictionary)actions.getDictionaryObject( "K" );
+        PDAction retval = null;
+        if( k != null )
+        {
+            retval = PDActionFactory.createAction( k );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed when the user
+     * types a keystroke into a text field or combo box or modifies the
+     * selection in a scrollable list box. This allows the keystroke to
+     * be checked for validity and rejected or modified.
+     *
+     * @param k The action to be performed.
+     */
+    public void setK( PDAction k )
+    {
+        actions.setItem( "K", k );
+    }
+
+    /**
+     * This will get a JavaScript action to be performed before
+     * the field is formatted to display its current value. This
+     * allows the field's value to be modified before formatting.
+     *
+     * @return The F entry of form field's additional actions dictionary.
+     */
+    public PDAction getF()
+    {
+        COSDictionary f = (COSDictionary)actions.getDictionaryObject( "F" );
+        PDAction retval = null;
+        if( f != null )
+        {
+            retval = PDActionFactory.createAction( f );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed before
+     * the field is formatted to display its current value. This
+     * allows the field's value to be modified before formatting.
+     *
+     * @param f The action to be performed.
+     */
+    public void setF( PDAction f )
+    {
+        actions.setItem( "F", f );
+    }
+
+    /**
+     * This will get a JavaScript action to be performed
+     * when the field's value is changed. This allows the
+     * new value to be checked for validity.
+     * The name V stands for "validate".
+     *
+     * @return The V entry of form field's additional actions dictionary.
+     */
+    public PDAction getV()
+    {
+        COSDictionary v = (COSDictionary)actions.getDictionaryObject( "V" );
+        PDAction retval = null;
+        if( v != null )
+        {
+            retval = PDActionFactory.createAction( v );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed
+     * when the field's value is changed. This allows the
+     * new value to be checked for validity.
+     * The name V stands for "validate".
+     *
+     * @param v The action to be performed.
+     */
+    public void setV( PDAction v )
+    {
+        actions.setItem( "V", v );
+    }
+
+    /**
+     * This will get a JavaScript action to be performed in order to recalculate
+     * the value of this field when that of another field changes. The order in which
+     * the document's fields are recalculated is defined by the CO entry in the
+     * interactive form dictionary.
+     * The name C stands for "calculate".
+     *
+     * @return The C entry of form field's additional actions dictionary.
+     */
+    public PDAction getC()
+    {
+        COSDictionary c = (COSDictionary)actions.getDictionaryObject( "C" );
+        PDAction retval = null;
+        if( c != null )
+        {
+            retval = PDActionFactory.createAction( c );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a JavaScript action to be performed in order to recalculate
+     * the value of this field when that of another field changes. The order in which
+     * the document's fields are recalculated is defined by the CO entry in the
+     * interactive form dictionary.
+     * The name C stands for "calculate".
+     *
+     * @param c The action to be performed.
+     */
+    public void setC( PDAction c )
+    {
+        actions.setItem( "C", c );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDPageAdditionalActions.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/PDPageAdditionalActions.java
new file mode 100644 (file)
index 0000000..299f49c
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents a page object's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDPageAdditionalActions implements COSObjectable
+{
+    private COSDictionary actions;
+
+    /**
+     * Default constructor.
+     */
+    public PDPageAdditionalActions()
+    {
+        actions = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDPageAdditionalActions( COSDictionary a )
+    {
+        actions = a;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return actions;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return actions;
+    }
+
+    /**
+     * This will get an action to be performed when the page
+     * is opened. This action is independent of any that may be
+     * defined by the OpenAction entry in the document catalog,
+     * and is executed after such an action.
+     *
+     * @return The O entry of page object's additional actions dictionary.
+     */
+    public PDAction getO()
+    {
+        COSDictionary o = (COSDictionary)actions.getDictionaryObject( "O" );
+        PDAction retval = null;
+        if( o != null )
+        {
+            retval = PDActionFactory.createAction( o );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the page
+     * is opened. This action is independent of any that may be
+     * defined by the OpenAction entry in the document catalog,
+     * and is executed after such an action.
+     *
+     * @param o The action to be performed.
+     */
+    public void setO( PDAction o )
+    {
+        actions.setItem( "O", o );
+    }
+
+    /**
+     * This will get an action to be performed when the page
+     * is closed. This action applies to the page being closed,
+     * and is executed before any other page opened.
+     *
+     * @return The C entry of page object's additional actions dictionary.
+     */
+    public PDAction getC()
+    {
+        COSDictionary c = (COSDictionary)actions.getDictionaryObject( "C" );
+        PDAction retval = null;
+        if( c != null )
+        {
+            retval = PDActionFactory.createAction( c );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set an action to be performed when the page
+     * is closed. This action applies to the page being closed,
+     * and is executed before any other page opened.
+     *
+     * @param c The action to be performed.
+     */
+    public void setC( PDAction c )
+    {
+        actions.setItem( "C", c );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/package.html
new file mode 100644 (file)
index 0000000..cc39b56
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package represents actions that can be performed in a PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDAction.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDAction.java
new file mode 100644 (file)
index 0000000..60f1dae
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action.type;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+
+/**
+ * This represents an action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.3 $
+ */
+public abstract class PDAction implements PDDestinationOrAction
+{
+    /**
+     * The type of PDF object.
+     */
+    public static final String TYPE = "Action";
+
+    /**
+     * The action dictionary.
+     */
+    protected COSDictionary action;
+
+    /**
+     * Default constructor.
+     */
+    public PDAction()
+    {
+        action = new COSDictionary();
+        setType( TYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDAction( COSDictionary a )
+    {
+        action = a;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return action;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return action;
+    }
+
+    /**
+     * This will get the type of PDF object that the actions dictionary describes.
+     * If present must be Action for an action dictionary.
+     *
+     * @return The Type of PDF object.
+     */
+    public String getType()
+    {
+       return action.getNameAsString( "Type" );
+    }
+
+    /**
+     * This will set the type of PDF object that the actions dictionary describes.
+     * If present must be Action for an action dictionary.
+     *
+     * @param type The new Type for the PDF object.
+     */
+    public void setType( String type )
+    {
+       action.setName( "Type", type );
+    }
+
+    /**
+     * This will get the type of action that the actions dictionary describes.
+     * If present, must be Action for an action dictionary.
+     *
+     * @return The S entry of actions dictionary.
+     */
+    public String getSubType()
+    {
+       return action.getNameAsString( "S" );
+    }
+
+    /**
+     * This will set the type of action that the actions dictionary describes.
+     * If present, must be Action for an action dictionary.
+     *
+     * @param s The new type of action.
+     */
+    public void setSubType( String s )
+    {
+       action.setName( "S", s );
+    }
+
+    /**
+     * This will get the next action, or sequence of actions, to be performed after this one.
+     * The value is either a single action dictionary or an array of action dictionaries
+     * to be performed in order.
+     *
+     * @return The Next action or sequence of actions.
+     */
+    public List getNext()
+    {
+        List retval = null;
+        COSBase next = action.getDictionaryObject( "Next" );
+        if( next instanceof COSDictionary )
+        {
+            PDAction pdAction = PDActionFactory.createAction( (COSDictionary) next );
+            retval = new COSArrayList(pdAction, next, action, COSName.getPDFName( "Next" ));
+        }
+        else if( next instanceof COSArray )
+        {
+            COSArray array = (COSArray)next;
+            List actions = new ArrayList();
+            for( int i=0; i<array.size(); i++ )
+            {
+                actions.add( PDActionFactory.createAction( (COSDictionary) array.getObject( i )));
+            }
+            retval = new COSArrayList( actions, array );
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the next action, or sequence of actions, to be performed after this one.
+     * The value is either a single action dictionary or an array of action dictionaries
+     * to be performed in order.
+     *
+     * @param next The Next action or sequence of actions.
+     */
+    public void setNext( List next )
+    {
+        action.setItem( "Next", COSArrayList.converterToCOSArray( next ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionGoTo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionGoTo.java
new file mode 100644 (file)
index 0000000..8db8902
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action.type;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+
+/**
+ * This represents a go-to action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDActionGoTo extends PDAction
+{
+    /**
+     * This type of action this object represents.
+     */
+    public static final String SUB_TYPE = "GoTo";
+
+    /**
+     * Default constructor.
+     */
+    public PDActionGoTo()
+    {
+        super();
+        setSubType( SUB_TYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDActionGoTo( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * This will get the destination to jump to.
+     *
+     * @return The D entry of the specific go-to action dictionary.
+     *
+     * @throws IOException If there is an error creating the destination.
+     */
+    public PDDestination getDestination() throws IOException
+    {
+        return PDDestination.create( getCOSDictionary().getDictionaryObject( "D" ) );
+    }
+
+    /**
+     * This will set the destination to jump to.
+     *
+     * @param d The destination.
+     */
+    public void setDestination( PDDestination d )
+    {
+        getCOSDictionary().setItem( "D", d );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionJavaScript.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionJavaScript.java
new file mode 100644 (file)
index 0000000..f299eac
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+
+/**
+ * This represents a JavaScript action.
+ *
+ * @author Michael Schwarzenberger (mi2kee@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class PDActionJavaScript extends PDAction
+{
+    /**
+     * This type of action this object represents.
+     */
+    public static final String SUB_TYPE = "JavaScript";
+
+    /**
+     * Constructor #1.
+     */
+    public PDActionJavaScript()
+    {
+        super();
+        setSubType( SUB_TYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param js Some javascript code.
+     */
+    public PDActionJavaScript( String js )
+    {
+        this();
+        setAction( js );
+    }
+
+    /**
+     * Constructor #2.
+     *
+     *  @param a The action dictionary.
+     */
+    public PDActionJavaScript(COSDictionary a)
+    {
+        super(a);
+    }
+
+    /**
+     * @param sAction The JavaScript.
+     */
+    public void setAction(PDTextStream sAction)
+    {
+        action.setItem("JS", sAction);
+    }
+
+    /**
+     * @param sAction The JavaScript.
+     */
+    public void setAction(String sAction)
+    {
+        action.setString("JS", sAction);
+    }
+
+    /**
+     * @return The Javascript Code.
+     */
+    public PDTextStream getAction()
+    {
+        return PDTextStream.createTextStream( action.getDictionaryObject("JS") );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionLaunch.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionLaunch.java
new file mode 100644 (file)
index 0000000..5cd8409
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action.type;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This represents a launch action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.5 $
+ */
+public class PDActionLaunch extends PDAction
+{
+
+    /**
+     * This type of action this object represents.
+     */
+    public static final String SUB_TYPE = "Launch";
+
+    /**
+     * Default constructor.
+     */
+    public PDActionLaunch()
+    {
+        super();
+        setSubType( SUB_TYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDActionLaunch( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * This will get the application to be launched or the document
+     * to be opened or printed. It is required if none of the entries
+     * Win, Mac or Unix is present. If this entry is absent and the
+     * viewer application does not understand any of the alternative
+     * entries it should do nothing.
+     *
+     * @return The F entry of the specific launch action dictionary.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public PDFileSpecification getFile() throws IOException
+    {
+        return PDFileSpecification.createFS( getCOSDictionary().getDictionaryObject( "F" ) );
+    }
+
+    /**
+     * This will set the application to be launched or the document
+     * to be opened or printed. It is required if none of the entries
+     * Win, Mac or Unix is present. If this entry is absent and the
+     * viewer application does not understand any of the alternative
+     * entries it should do nothing.
+     *
+     * @param fs The file specification.
+     */
+    public void setFile( PDFileSpecification fs )
+    {
+        getCOSDictionary().setItem( "F", fs );
+    }
+
+    /**
+     * This will get a dictionary containing Windows-specific launch parameters.
+     *
+     * @return The Win entry of of the specific launch action dictionary.
+     */
+    public PDWindowsLaunchParams getWinLaunchParams()
+    {
+        COSDictionary win = (COSDictionary)action.getDictionaryObject( "Win" );
+        PDWindowsLaunchParams retval = null;
+        if( win != null )
+        {
+            retval = new PDWindowsLaunchParams( win );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set a dictionary containing Windows-specific launch parameters.
+     *
+     * @param win The action to be performed.
+     */
+    public void setWinLaunchParams( PDWindowsLaunchParams win )
+    {
+        action.setItem( "Win", win );
+    }
+
+    /**
+     * This will get the file name to be launched or the document to be opened
+     * or printed, in standard Windows pathname format. If the name string includes
+     * a backslash character (\), the backslash must itself be preceded by a backslash.
+     * This value must be a single string; it is not a file specification.
+     *
+     * @return The F entry of the specific Windows launch parameter dictionary.
+     */
+    public String getF()
+    {
+        return action.getString( "F" );
+    }
+
+    /**
+     * This will set the file name to be launched or the document to be opened
+     * or printed, in standard Windows pathname format. If the name string includes
+     * a backslash character (\), the backslash must itself be preceded by a backslash.
+     * This value must be a single string; it is not a file specification.
+     *
+     * @param f The file name to be launched.
+     */
+    public void setF( String f )
+    {
+        action.setString( "F", f );
+    }
+
+    /**
+     * This will get the string specifying the default directory in standard DOS syntax.
+     *
+     * @return The D entry of the specific Windows launch parameter dictionary.
+     */
+    public String getD()
+    {
+        return action.getString( "D" );
+    }
+
+    /**
+     * This will set the string specifying the default directory in standard DOS syntax.
+     *
+     * @param d The default directory.
+     */
+    public void setD( String d )
+    {
+        action.setString( "D", d );
+    }
+
+    /**
+     * This will get the string specifying the operation to perform:
+     * open to open a document
+     * print to print a document
+     * If the F entry designates an application instead of a document, this entry
+     * is ignored and the application is launched. Default value: open.
+     *
+     * @return The O entry of the specific Windows launch parameter dictionary.
+     */
+    public String getO()
+    {
+        return action.getString( "O" );
+    }
+
+    /**
+     * This will set the string specifying the operation to perform:
+     * open to open a document
+     * print to print a document
+     * If the F entry designates an application instead of a document, this entry
+     * is ignored and the application is launched. Default value: open.
+     *
+     * @param o The operation to perform.
+     */
+    public void setO( String o )
+    {
+        action.setString( "O", o );
+    }
+
+    /**
+     * This will get a parameter string to be passed to the application designated by the F entry.
+     * This entry should be omitted if F designates a document.
+     *
+     * @return The P entry of the specific Windows launch parameter dictionary.
+     */
+    public String getP()
+    {
+        return action.getString( "P" );
+    }
+
+    /**
+     * This will set a parameter string to be passed to the application designated by the F entry.
+     * This entry should be omitted if F designates a document.
+     *
+     * @param p The parameter string.
+     */
+    public void setP( String p )
+    {
+        action.setString( "P", p );
+    }
+
+    /**
+     * This will specify whether to open the destination document in a new window.
+     * If this flag is false, the destination document will replace the current
+     * document in the same window. If this entry is absent, the viewer application
+     * should behave in accordance with the current user preference. This entry is
+     * ignored if the file designated by the F entry is not a PDF document.
+     *
+     * @return A flag specifying whether to open the destination document in a new window.
+     */
+    public boolean shouldOpenInNewWindow()
+    {
+        return action.getBoolean( "NewWindow", true );
+    }
+
+    /**
+     * This will specify the destination document to open in a new window.
+     *
+     * @param value The flag value.
+     */
+    public void setOpenInNewWindow( boolean value )
+    {
+        action.setBoolean( "NewWindow", value );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionRemoteGoTo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionRemoteGoTo.java
new file mode 100644 (file)
index 0000000..c5e3427
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action.type;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This represents a remote go-to action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.4 $
+ */
+public class PDActionRemoteGoTo extends PDAction
+{
+    /**
+     * This type of action this object represents.
+     */
+    public static final String SUB_TYPE = "GoToR";
+
+    /**
+     * Default constructor.
+     */
+    public PDActionRemoteGoTo()
+    {
+        action = new COSDictionary();
+        setSubType( SUB_TYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDActionRemoteGoTo( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return action;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return action;
+    }
+
+    /**
+     * This will get the type of action that the actions dictionary describes.
+     * It must be GoToR for a remote go-to action.
+     *
+     * @return The S entry of the specific remote go-to action dictionary.
+     */
+    public String getS()
+    {
+       return action.getNameAsString( "S" );
+    }
+
+    /**
+     * This will set the type of action that the actions dictionary describes.
+     * It must be GoToR for a remote go-to action.
+     *
+     * @param s The remote go-to action.
+     */
+    public void setS( String s )
+    {
+       action.setName( "S", s );
+    }
+
+    /**
+     * This will get the file in which the destination is located.
+     *
+     * @return The F entry of the specific remote go-to action dictionary.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public PDFileSpecification getFile() throws IOException
+    {
+        return PDFileSpecification.createFS( action.getDictionaryObject( "F" ) );
+    }
+
+    /**
+     * This will set the file in which the destination is located.
+     *
+     * @param fs The file specification.
+     */
+    public void setFile( PDFileSpecification fs )
+    {
+        action.setItem( "F", fs );
+    }
+
+    /**
+     * This will get the destination to jump to.
+     * If the value is an array defining an explicit destination,
+     * its first element must be a page number within the remote
+     * document rather than an indirect reference to a page object
+     * in the current document. The first page is numbered 0.
+     *
+     * @return The D entry of the specific remote go-to action dictionary.
+     */
+
+    // Array or String.
+    public COSBase getD()
+    {
+        return action.getDictionaryObject( "D" );
+    }
+
+    /**
+     * This will set the destination to jump to.
+     * If the value is an array defining an explicit destination,
+     * its first element must be a page number within the remote
+     * document rather than an indirect reference to a page object
+     * in the current document. The first page is numbered 0.
+     *
+     * @param d The destination.
+     */
+
+    // In case the value is an array.
+    public void setD( COSBase d )
+    {
+        action.setItem( "D", d );
+    }
+
+    /**
+     * This will specify whether to open the destination document in a new window.
+     * If this flag is false, the destination document will replace the current
+     * document in the same window. If this entry is absent, the viewer application
+     * should behave in accordance with the current user preference.
+     *
+     * @return A flag specifying whether to open the destination document in a new window.
+     */
+    public boolean shouldOpenInNewWindow()
+    {
+        return action.getBoolean( "NewWindow", true );
+    }
+
+    /**
+     * This will specify the destination document to open in a new window.
+     *
+     * @param value The flag value.
+     */
+    public void setOpenInNewWindow( boolean value )
+    {
+        action.setBoolean( "NewWindow", value );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionURI.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDActionURI.java
new file mode 100644 (file)
index 0000000..023a9a8
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This represents a URI action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.3 $
+ */
+public class PDActionURI extends PDAction
+{
+    /**
+     * This type of action this object represents.
+     */
+    public static final String SUB_TYPE = "URI";
+
+    /**
+     * Default constructor.
+     */
+    public PDActionURI()
+    {
+        action = new COSDictionary();
+        setSubType( SUB_TYPE );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param a The action dictionary.
+     */
+    public PDActionURI( COSDictionary a )
+    {
+        super( a );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return action;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return action;
+    }
+
+    /**
+     * This will get the type of action that the actions dictionary describes.
+     * It must be URI for a URI action.
+     *
+     * @return The S entry of the specific URI action dictionary.
+     */
+    public String getS()
+    {
+       return action.getNameAsString( "S" );
+    }
+
+    /**
+     * This will set the type of action that the actions dictionary describes.
+     * It must be URI for a URI action.
+     *
+     * @param s The URI action.
+     */
+    public void setS( String s )
+    {
+       action.setName( "S", s );
+    }
+
+    /**
+     * This will get the uniform resource identifier to resolve, encoded in 7-bit ASCII.
+     *
+     * @return The URI entry of the specific URI action dictionary.
+     */
+    public String getURI()
+    {
+        return action.getString( "URI" );
+    }
+
+    /**
+     * This will set the uniform resource identifier to resolve, encoded in 7-bit ASCII.
+     *
+     * @param uri The uniform resource identifier.
+     */
+    public void setURI( String uri )
+    {
+        action.setString( "URI", uri );
+    }
+
+    /**
+     * This will specify whether to track the mouse position when the URI is resolved.
+     * Default value: false.
+     * This entry applies only to actions triggered by the user's clicking an annotation;
+     * it is ignored for actions associated with outline items or with a document's OpenAction entry.
+     *
+     * @return A flag specifying whether to track the mouse position when the URI is resolved.
+     */
+    public boolean shouldTrackMousePosition()
+    {
+        return this.action.getBoolean("IsMap", false);
+    }
+
+    /**
+     * This will specify whether to track the mouse position when the URI is resolved.
+     *
+     * @param value The flag value.
+     */
+    public void setTrackMousePosition( boolean value )
+    {
+        this.action.setBoolean("IsMap", value);
+    }
+
+    // TODO this must go into PDURIDictionary
+    /**
+     * This will get the base URI to be used in resolving relative URI references.
+     * URI actions within the document may specify URIs in partial form, to be interpreted
+     * relative to this base address. If no base URI is specified, such partial URIs
+     * will be interpreted relative to the location of the document itself.
+     * The use of this entry is parallel to that of the body element &lt;BASE&gt;, as described
+     * in the HTML 4.01 Specification.
+     *
+     * @return The URI entry of the specific URI dictionary.
+     * @deprecated use {@link PDURIDictionary#getBase()} instead
+     */
+    public String getBase()
+    {
+        return action.getString( "Base" );
+    }
+
+    // TODO this must go into PDURIDictionary
+    /**
+     * This will set the base URI to be used in resolving relative URI references.
+     * URI actions within the document may specify URIs in partial form, to be interpreted
+     * relative to this base address. If no base URI is specified, such partial URIs
+     * will be interpreted relative to the location of the document itself.
+     * The use of this entry is parallel to that of the body element &lt;BASE&gt;, as described
+     * in the HTML 4.01 Specification.
+     *
+     * @param base The the base URI to be used.
+     * @deprecated use {@link PDURIDictionary#setBase(String)} instead
+     */
+    public void setBase( String base )
+    {
+        action.setString( "Base", base );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDURIDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDURIDictionary.java
new file mode 100644 (file)
index 0000000..0fa9233
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This is the implementation of an URI dictionary.
+ *
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDURIDictionary implements COSObjectable
+{
+
+    private COSDictionary uriDictionary;
+
+    /**
+     * Constructor.
+     * 
+     */
+    public PDURIDictionary()
+    {
+        this.uriDictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param dictionary the corresponding dictionary
+     */
+    public PDURIDictionary(COSDictionary dictionary)
+    {
+        this.uriDictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.uriDictionary;
+    }
+
+    /**
+     * Returns the corresponding dictionary.
+     * @return the dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return this.uriDictionary;
+    }
+
+    /**
+     * This will get the base URI to be used in resolving relative URI references.
+     * URI actions within the document may specify URIs in partial form, to be interpreted
+     * relative to this base address. If no base URI is specified, such partial URIs
+     * will be interpreted relative to the location of the document itself.
+     * The use of this entry is parallel to that of the body element &lt;BASE&gt;, as described
+     * in the HTML 4.01 Specification.
+     *
+     * @return The URI entry of the specific URI dictionary.
+     */
+    public String getBase()
+    {
+        return this.getDictionary().getString("Base");
+    }
+
+    /**
+     * This will set the base URI to be used in resolving relative URI references.
+     * URI actions within the document may specify URIs in partial form, to be interpreted
+     * relative to this base address. If no base URI is specified, such partial URIs
+     * will be interpreted relative to the location of the document itself.
+     * The use of this entry is parallel to that of the body element &lt;BASE&gt;, as described
+     * in the HTML 4.01 Specification.
+     *
+     * @param base The the base URI to be used.
+     */
+    public void setBase(String base)
+    {
+        this.getDictionary().setString("Base", base);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDWindowsLaunchParams.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/PDWindowsLaunchParams.java
new file mode 100644 (file)
index 0000000..e377de4
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * Launch paramaters for the windows OS.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDWindowsLaunchParams implements COSObjectable
+{
+    /**
+     * The open operation for the launch.
+     */
+    public static final String OPERATION_OPEN = "open";
+    /**
+     * The print operation for the lanuch.
+     */
+    public static final String OPERATION_PRINT = "print";
+
+    /**
+     * The params dictionary.
+     */
+    protected COSDictionary params;
+
+    /**
+     * Default constructor.
+     */
+    public PDWindowsLaunchParams()
+    {
+        params = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param p The params dictionary.
+     */
+    public PDWindowsLaunchParams( COSDictionary p )
+    {
+        params = p;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return params;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return params;
+    }
+
+    /**
+     * The file to launch.
+     *
+     * @return The executable/document to launch.
+     */
+    public String getFilename()
+    {
+        return params.getString( "F" );
+    }
+
+    /**
+     * Set the file to launch.
+     *
+     * @param file The executable/document to launch.
+     */
+    public void setFilename( String file )
+    {
+        params.setString( "F", file );
+    }
+
+    /**
+     * The dir to launch from.
+     *
+     * @return The dir of the executable/document to launch.
+     */
+    public String getDirectory()
+    {
+        return params.getString( "D" );
+    }
+
+    /**
+     * Set the dir to launch from.
+     *
+     * @param dir The dir of the executable/document to launch.
+     */
+    public void setDirectory( String dir )
+    {
+        params.setString( "D", dir );
+    }
+
+    /**
+     * Get the operation to perform on the file.  This method will not return null,
+     * OPERATION_OPEN is the default.
+     *
+     * @return The operation to perform for the file.
+     * @see PDWindowsLaunchParams#OPERATION_OPEN
+     * @see PDWindowsLaunchParams#OPERATION_PRINT
+     */
+    public String getOperation()
+    {
+        return params.getString( "O", OPERATION_OPEN );
+    }
+
+    /**
+     * Set the operation to perform..
+     *
+     * @param op The operation to perform on the file.
+     */
+    public void setOperation( String op )
+    {
+        params.setString( "D", op );
+    }
+
+    /**
+     * A parameter to pass the executable.
+     *
+     * @return The parameter to pass the executable.
+     */
+    public String getExecuteParam()
+    {
+        return params.getString( "P" );
+    }
+
+    /**
+     * Set the parameter to pass the executable.
+     *
+     * @param param The parameter for the executable.
+     */
+    public void setExecuteParam( String param )
+    {
+        params.setString( "P", param );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/action/type/package.html
new file mode 100644 (file)
index 0000000..c392004
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains all of the available PDF action types.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java
new file mode 100644 (file)
index 0000000..ca3b5c0
--- /dev/null
@@ -0,0 +1,644 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+import org.apache.pdfbox.util.BitFlagHelper;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This class represents a PDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.16 $
+ */
+public abstract class PDAnnotation implements COSObjectable
+{
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDAnnotation.class);
+
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_INVISIBLE = 1 << 0;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_HIDDEN = 1 << 1;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_PRINTED = 1 << 2;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_NO_ZOOM = 1 << 3;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_NO_ROTATE = 1 << 4;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_NO_VIEW = 1 << 5;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_READ_ONLY = 1 << 6;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_LOCKED = 1 << 7;
+    /**
+     * An annotation flag.
+     */
+    public static final int FLAG_TOGGLE_NO_VIEW = 1 << 8;
+
+
+
+    private COSDictionary dictionary;
+
+    /**
+     * Create the correct annotation from the base COS object.
+     *
+     * @param base The COS object that is the annotation.
+     * @return The correctly typed annotation object.
+     * @throws IOException If there is an error while creating the annotation.
+     */
+    // TODO not yet implemented: 
+    // Movie, Screen, PrinterMark, TrapNet, Watermark, 3D, Redact
+    public static PDAnnotation createAnnotation( COSBase base ) throws IOException
+    {
+        PDAnnotation annot = null;
+        if( base instanceof COSDictionary )
+        {
+            COSDictionary annotDic = (COSDictionary)base;
+            String subtype = annotDic.getNameAsString( COSName.SUBTYPE );
+            if( PDAnnotationFileAttachment.SUB_TYPE.equals(subtype) )
+            {
+                annot = new PDAnnotationFileAttachment( annotDic );
+            }
+            else if( PDAnnotationLine.SUB_TYPE.equals(subtype) )
+            {
+                annot = new PDAnnotationLine( annotDic );
+            }
+            else if( PDAnnotationLink.SUB_TYPE.equals(subtype) )
+            {
+                annot = new PDAnnotationLink(annotDic);
+            }
+            else if( PDAnnotationPopup.SUB_TYPE.equals(subtype) )
+            {
+                annot = new PDAnnotationPopup(annotDic);
+            }
+            else if( PDAnnotationRubberStamp.SUB_TYPE.equals(subtype) )
+            {
+                annot = new PDAnnotationRubberStamp(annotDic);
+            }
+            else if( PDAnnotationSquareCircle.SUB_TYPE_SQUARE.equals(subtype) ||
+                    PDAnnotationSquareCircle.SUB_TYPE_CIRCLE.equals(subtype) )
+            {
+                annot = new PDAnnotationSquareCircle( annotDic );
+            }
+            else if( PDAnnotationText.SUB_TYPE.equals(subtype) )
+            {
+                annot = new PDAnnotationText( annotDic);
+            }
+            else if( PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT.equals(subtype) ||
+                    PDAnnotationTextMarkup.SUB_TYPE_UNDERLINE.equals(subtype) ||
+                    PDAnnotationTextMarkup.SUB_TYPE_SQUIGGLY.equals(subtype) ||
+                    PDAnnotationTextMarkup.SUB_TYPE_STRIKEOUT.equals(subtype) )
+            {
+                annot = new PDAnnotationTextMarkup( annotDic );
+            }
+            else if( PDAnnotationLink.SUB_TYPE.equals(subtype) )
+            {
+                annot = new PDAnnotationLink( annotDic );
+            }
+            else if( PDAnnotationWidget.SUB_TYPE.equals(subtype) )
+            {
+               annot = new PDAnnotationWidget( annotDic );
+            }
+            else if( PDAnnotationMarkup.SUB_TYPE_FREETEXT.equals(subtype) ||
+                    PDAnnotationMarkup.SUB_TYPE_POLYGON.equals(subtype) ||
+                    PDAnnotationMarkup.SUB_TYPE_POLYLINE.equals(subtype) ||
+                    PDAnnotationMarkup.SUB_TYPE_CARET.equals(subtype) ||
+                    PDAnnotationMarkup.SUB_TYPE_INK.equals(subtype) ||
+                    PDAnnotationMarkup.SUB_TYPE_SOUND.equals(subtype) )
+            {
+                annot = new PDAnnotationMarkup( annotDic );
+            }
+            else
+            {
+                annot = new PDAnnotationUnknown( annotDic );
+                log.debug("Unknown or unsupported annotation subtype "+subtype);
+            }
+        }
+        else
+        {
+            throw new IOException( "Error: Unknown annotation type " + base );
+        }
+
+        return annot;
+    }
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotation()
+    {
+        dictionary = new COSDictionary();
+        dictionary.setItem( COSName.TYPE, COSName.ANNOT );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict The annotations dictionary.
+     */
+    public PDAnnotation( COSDictionary dict )
+    {
+        dictionary = dict;
+    }
+
+    /**
+     * returns the dictionary.
+     * @return the dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * The annotation rectangle, defining the location of the annotation
+     * on the page in default user space units.  This is usually required and should
+     * not return null on valid PDF documents.  But where this is a parent form field
+     * with children, such as radio button collections then the rectangle will be null.
+     *
+     * @return The Rect value of this annotation.
+     */
+    public PDRectangle getRectangle()
+    {
+        COSArray rectArray = (COSArray)dictionary.getDictionaryObject( COSName.RECT );
+        PDRectangle rectangle = null;
+        if( rectArray != null )
+        {
+            rectangle = new PDRectangle( rectArray );
+        }
+        return rectangle;
+    }
+
+    /**
+     * This will set the rectangle for this annotation.
+     *
+     * @param rectangle The new rectangle values.
+     */
+    public void setRectangle( PDRectangle rectangle )
+    {
+        dictionary.setItem( COSName.RECT, rectangle.getCOSArray() );
+    }
+
+   /**
+     * This will get the flags for this field.
+     *
+     * @return flags The set of flags.
+     */
+    public int getAnnotationFlags()
+    {
+        return getDictionary().getInt( COSName.F, 0 );
+    }
+
+    /**
+     * This will set the flags for this field.
+     *
+     * @param flags The new flags.
+     */
+    public void setAnnotationFlags( int flags )
+    {
+        getDictionary().setInt( COSName.F, flags );
+    }
+
+    /**
+     * Interface method for COSObjectable.
+     *
+     * @return This object as a standard COS object.
+     */
+    public COSBase getCOSObject()
+    {
+        return getDictionary();
+    }
+
+    /**
+     * This will get the name of the current appearance stream if any.
+     *
+     * @return The name of the appearance stream.
+     */
+    public String getAppearanceStream()
+    {
+        String retval = null;
+        COSName name = (COSName)getDictionary().getDictionaryObject( COSName.AS );
+        if( name != null )
+        {
+            retval = name.getName();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the annotations appearance stream name.
+     *
+     * @param as The name of the appearance stream.
+     */
+    public void setAppearanceStream( String as )
+    {
+        if( as == null )
+        {
+            getDictionary().removeItem( COSName.AS );
+        }
+        else
+        {
+            getDictionary().setItem( COSName.AS, COSName.getPDFName( as ) );
+        }
+    }
+
+    /**
+     * This will get the appearance dictionary associated with this annotation.
+     * This may return null.
+     *
+     * @return This annotations appearance.
+     */
+    public PDAppearanceDictionary getAppearance()
+    {
+        PDAppearanceDictionary ap = null;
+        COSDictionary apDic = (COSDictionary)dictionary.getDictionaryObject( COSName.AP );
+        if( apDic != null )
+        {
+            ap = new PDAppearanceDictionary( apDic );
+        }
+        return ap;
+    }
+
+    /**
+     * This will set the appearance associated with this annotation.
+     *
+     * @param appearance The appearance dictionary for this annotation.
+     */
+    public void setAppearance( PDAppearanceDictionary appearance )
+    {
+        COSDictionary ap = null;
+        if( appearance != null )
+        {
+            ap = appearance.getDictionary();
+        }
+        dictionary.setItem( COSName.AP, ap );
+    }
+
+    /**
+     * Get the invisible flag.
+     *
+     * @return The invisible flag.
+     */
+    public boolean isInvisible()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_INVISIBLE );
+    }
+
+    /**
+     * Set the invisible flag.
+     *
+     * @param invisible The new invisible flag.
+     */
+    public void setInvisible( boolean invisible )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_INVISIBLE, invisible );
+    }
+
+    /**
+     * Get the hidden flag.
+     *
+     * @return The hidden flag.
+     */
+    public boolean isHidden()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_HIDDEN );
+    }
+
+    /**
+     * Set the hidden flag.
+     *
+     * @param hidden The new hidden flag.
+     */
+    public void setHidden( boolean hidden )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_HIDDEN, hidden );
+    }
+
+    /**
+     * Get the printed flag.
+     *
+     * @return The printed flag.
+     */
+    public boolean isPrinted()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_PRINTED );
+    }
+
+    /**
+     * Set the printed flag.
+     *
+     * @param printed The new printed flag.
+     */
+    public void setPrinted( boolean printed )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_PRINTED, printed );
+    }
+
+    /**
+     * Get the noZoom flag.
+     *
+     * @return The noZoom flag.
+     */
+    public boolean isNoZoom()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM );
+    }
+
+    /**
+     * Set the noZoom flag.
+     *
+     * @param noZoom The new noZoom flag.
+     */
+    public void setNoZoom( boolean noZoom )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM, noZoom );
+    }
+
+    /**
+     * Get the noRotate flag.
+     *
+     * @return The noRotate flag.
+     */
+    public boolean isNoRotate()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE );
+    }
+
+    /**
+     * Set the noRotate flag.
+     *
+     * @param noRotate The new noRotate flag.
+     */
+    public void setNoRotate( boolean noRotate )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE, noRotate );
+    }
+
+    /**
+     * Get the noView flag.
+     *
+     * @return The noView flag.
+     */
+    public boolean isNoView()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_VIEW );
+    }
+
+    /**
+     * Set the noView flag.
+     *
+     * @param noView The new noView flag.
+     */
+    public void setNoView( boolean noView )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_VIEW, noView );
+    }
+
+    /**
+     * Get the readOnly flag.
+     *
+     * @return The readOnly flag.
+     */
+    public boolean isReadOnly()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_READ_ONLY );
+    }
+
+    /**
+     * Set the readOnly flag.
+     *
+     * @param readOnly The new readOnly flag.
+     */
+    public void setReadOnly( boolean readOnly )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_READ_ONLY, readOnly );
+    }
+
+    /**
+     * Get the locked flag.
+     *
+     * @return The locked flag.
+     */
+    public boolean isLocked()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_LOCKED );
+    }
+
+    /**
+     * Set the locked flag.
+     *
+     * @param locked The new locked flag.
+     */
+    public void setLocked( boolean locked )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_LOCKED, locked );
+    }
+
+    /**
+     * Get the toggleNoView flag.
+     *
+     * @return The toggleNoView flag.
+     */
+    public boolean isToggleNoView()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW );
+    }
+
+    /**
+     * Set the toggleNoView flag.
+     *
+     * @param toggleNoView The new toggleNoView flag.
+     */
+    public void setToggleNoView( boolean toggleNoView )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW, toggleNoView );
+    }
+
+    /**
+     * Get the "contents" of the field.
+     *
+     * @return the value of the contents.
+     */
+    public String getContents()
+    {
+        return dictionary.getString(COSName.CONTENTS);
+    }
+
+    /**
+     * Set the "contents" of the field.
+     *
+     * @param value the value of the contents.
+     */
+    public void setContents( String value)
+    {
+        dictionary.setString(COSName.CONTENTS, value);
+    }
+
+    /**
+     * This will retrieve the date and time the annotation was modified.
+     *
+     * @return the modified date/time (often in date format, but can be an arbitary string).
+     */
+    public String getModifiedDate()
+    {
+        return getDictionary().getString( COSName.M );
+    }
+
+    /**
+     * This will set the the date and time the annotation was modified.
+     *
+     * @param m
+     *            the date and time the annotation was created.
+     */
+    public void setModifiedDate( String m )
+    {
+        getDictionary().setString( COSName.M, m );
+    }
+
+    /**
+     * This will get the name, a string intended to uniquely identify each annotation
+     * within a page. Not to be confused with some annotations Name entry which
+     * impact the default image drawn for them.
+     *
+     * @return The identifying name for the Annotation.
+     */
+    public String getAnnotationName()
+    {
+        return getDictionary().getString( COSName.NM );
+    }
+
+    /**
+     * This will set the name, a string intended to uniquely identify each annotation
+     * within a page. Not to be confused with some annotations Name entry which
+     * impact the default image drawn for them.
+     *
+     * @param nm The identifying name for the annotation.
+     */
+    public void setAnnotationName( String nm )
+    {
+        getDictionary().setString( COSName.NM, nm );
+    }
+
+    /**
+     * This will set the colour used in drawing various elements.
+     * As of PDF 1.6 these are : Background of icon when closed
+     *                           Title bar of popup window
+     *                           Border of a link annotation
+     *
+     * Colour is in DeviceRGB colourspace
+     *
+     * @param c
+     *            colour in the DeviceRGB colourspace
+     *
+     */
+    public void setColour( PDGamma c )
+    {
+        getDictionary().setItem( COSName.C, c );
+    }
+
+    /**
+     * This will retrieve the colour used in drawing various elements.
+     * As of PDF 1.6 these are : Background of icon when closed
+     *                           Title bar of popup window
+     *                           Border of a link annotation
+     *
+     * Colour is in DeviceRGB colourspace
+     *
+     * @return PDGamma object representing the colour
+     *
+     */
+    public PDGamma getColour()
+    {
+        COSArray c = (COSArray) getDictionary().getItem(COSName.C );
+        if (c != null)
+        {
+            return new PDGamma( c );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /** 
+     * This will retrieve the subtype of the annotation.
+     * 
+     * @return the subtype
+     */
+    public String getSubtype()
+    {
+        return this.getDictionary().getNameAsString(COSName.SUBTYPE);
+    }
+
+    /**
+     * This will set the corresponding page for this annotation.
+     * 
+     * @param page is the corresponding page
+     */
+    public void setPage(PDPage page)
+    {
+        this.getDictionary().setItem(COSName.P, page);
+    }
+    
+    /**
+     * This will retrieve the corresponding page of this annotation.
+     * 
+     * @return the corresponding page
+     */
+    public PDPage getPage()
+    {
+        COSDictionary p = (COSDictionary) this.getDictionary().getDictionaryObject(COSName.P);
+        if (p != null)
+        {
+            return new PDPage(p);
+        }
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationFileAttachment.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationFileAttachment.java
new file mode 100644 (file)
index 0000000..36639da
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This is the class that represents a file attachement.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationFileAttachment extends PDAnnotationMarkup
+{
+    /**
+     * See get/setAttachmentName.
+     */
+    public static final String ATTACHMENT_NAME_PUSH_PIN = "PushPin";
+    /**
+     * See get/setAttachmentName.
+     */
+    public static final String ATTACHMENT_NAME_GRAPH = "Graph";
+    /**
+     * See get/setAttachmentName.
+     */
+    public static final String ATTACHMENT_NAME_PAPERCLIP = "Paperclip";
+    /**
+     * See get/setAttachmentName.
+     */
+    public static final String ATTACHMENT_NAME_TAG = "Tag";
+
+    /**
+     * The type of annotation.
+     */
+    public static final String SUB_TYPE = "FileAttachment";
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationFileAttachment()
+    {
+        super();
+        getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+    }
+
+    /**
+     * Creates a Link annotation from a COSDictionary, expected to be
+     * a correct object definition.
+     *
+     * @param field the PDF objet to represent as a field.
+     */
+    public PDAnnotationFileAttachment(COSDictionary field)
+    {
+        super( field );
+    }
+
+    /**
+     * Return the attached file.
+     *
+     * @return The attached file.
+     *
+     * @throws IOException If there is an error creating the file spec.
+     */
+    public PDFileSpecification getFile() throws IOException
+    {
+        return PDFileSpecification.createFS( getDictionary().getDictionaryObject( "FS" ) );
+    }
+
+    /**
+     * Set the attached file.
+     *
+     * @param file The file that is attached.
+     */
+    public void setFile( PDFileSpecification file )
+    {
+        getDictionary().setItem( "FS", file );
+    }
+
+    /**
+     * This is the name used to draw the type of attachment.
+     * See the ATTACHMENT_NAME_XXX constants.
+     *
+     * @return The name that describes the visual cue for the attachment.
+     */
+    public String getAttachmentName()
+    {
+        return getDictionary().getNameAsString( "Name", ATTACHMENT_NAME_PUSH_PIN );
+    }
+
+    /**
+     * Set the name used to draw the attachement icon.
+     * See the ATTACHMENT_NAME_XXX constants.
+     *
+     * @param name The name of the visual icon to draw.
+     */
+    public void setAttachementName( String name )
+    {
+        getDictionary().setName( "Name", name );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationLine.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationLine.java
new file mode 100644 (file)
index 0000000..0a84c6d
--- /dev/null
@@ -0,0 +1,493 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+
+/**
+ * This is the class that represents a line annotation.
+ * Introduced in PDF 1.3 specification
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationLine extends PDAnnotationMarkup
+{
+
+
+    /*
+     * The various values for intent (get/setIT, see the PDF 1.6 reference Table
+     * 8.22
+     */
+
+    /**
+     * Constant for annotation intent of Arrow.
+     */
+    public static final String IT_LINE_ARROW = "LineArrow";
+
+    /**
+     * Constant for annotation intent of a dimension line.
+     */
+    public static final String IT_LINE_DIMENSION = "LineDimension";
+
+    /*
+     * The various values for line ending styles, see the PDF 1.6 reference
+     * Table 8.23
+     */
+
+    /**
+     * Constant for a square line ending.
+     */
+    public static final String LE_SQUARE = "Square";
+
+    /**
+     * Constant for a circle line ending.
+     */
+    public static final String LE_CIRCLE = "Circle";
+
+    /**
+     * Constant for a diamond line ending.
+     */
+    public static final String LE_DIAMOND = "Diamond";
+
+    /**
+     * Constant for a open arrow line ending.
+     */
+    public static final String LE_OPEN_ARROW = "OpenArrow";
+
+    /**
+     * Constant for a closed arrow line ending.
+     */
+    public static final String LE_CLOSED_ARROW = "ClosedArrow";
+
+    /**
+     * Constant for no line ending.
+     */
+    public static final String LE_NONE = "None";
+
+    /**
+     * Constant for a butt line ending.
+     */
+    public static final String LE_BUTT = "Butt";
+
+    /**
+     * Constant for a reversed open arrow line ending.
+     */
+    public static final String LE_R_OPEN_ARROW = "ROpenArrow";
+
+    /**
+     * Constant for a revered closed arrow line ending.
+     */
+    public static final String LE_R_CLOSED_ARROW = "RClosedArrow";
+
+    /**
+     * Constant for a slash line ending.
+     */
+    public static final String LE_SLASH = "Slash";
+
+    /**
+     * The type of annotation.
+     */
+    public static final String SUB_TYPE = "Line";
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationLine()
+    {
+        super();
+        getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+        // Dictionary value L is mandatory, fill in with arbitary value
+        setLine( new float[] { 0, 0, 0, 0 } );
+
+    }
+
+    /**
+     * Creates a Line annotation from a COSDictionary, expected to be a correct
+     * object definition.
+     *
+     * @param field
+     *            the PDF object to represent as a field.
+     */
+    public PDAnnotationLine( COSDictionary field )
+    {
+        super( field );
+    }
+
+    /**
+     * This will set start and end coordinates of the line (or leader line if LL
+     * entry is set).
+     *
+     * @param l
+     *            array of 4 floats [x1, y1, x2, y2] line start and end points
+     *            in default user space.
+     */
+    public void setLine( float[] l )
+    {
+        COSArray newL = new COSArray();
+        newL.setFloatArray( l );
+        getDictionary().setItem( "L", newL );
+    }
+
+    /**
+     * This will retrieve the start and end coordinates of the line (or leader
+     * line if LL entry is set).
+     *
+     * @return array of floats [x1, y1, x2, y2] line start and end points in
+     *         default user space.
+     */
+    public float[] getLine()
+    {
+        COSArray l = (COSArray) getDictionary().getDictionaryObject( "L" );
+        return l.toFloatArray();
+    }
+
+    /**
+     * This will set the line ending style for the start point,
+     * see the LE_ constants for the possible values.
+     *
+     * @param style The new style.
+     */
+    public void setStartPointEndingStyle( String style )
+    {
+        if( style == null )
+        {
+            style = LE_NONE;
+        }
+        COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+        if( array == null )
+        {
+            array = new COSArray();
+            array.add( COSName.getPDFName( style ) );
+            array.add( COSName.getPDFName( LE_NONE ) );
+            getDictionary().setItem( "LE", array );
+        }
+        else
+        {
+            array.setName( 0, style );
+        }
+    }
+
+    /**
+     * This will retrieve the line ending style for the start point,
+     * possible values shown in the LE_ constants section.
+     *
+     * @return The ending style for the start point.
+     */
+    public String getStartPointEndingStyle()
+    {
+        String retval = LE_NONE;
+        COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+        if( array != null )
+        {
+            retval = array.getName( 0 );
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the line ending style for the end point,
+     * see the LE_ constants for the possible values.
+     *
+     * @param style The new style.
+     */
+    public void setEndPointEndingStyle( String style )
+    {
+        if( style == null )
+        {
+            style = LE_NONE;
+        }
+        COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+        if( array == null )
+        {
+            array = new COSArray();
+            array.add( COSName.getPDFName( LE_NONE ) );
+            array.add( COSName.getPDFName( style ) );
+            getDictionary().setItem( "LE", array );
+        }
+        else
+        {
+            array.setName( 1, style );
+        }
+    }
+
+    /**
+     * This will retrieve the line ending style for the end point,
+     * possible values shown in the LE_ constants section.
+     *
+     * @return The ending style for the end point.
+     */
+    public String getEndPointEndingStyle()
+    {
+        String retval = LE_NONE;
+        COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+        if( array != null )
+        {
+            retval = array.getName( 1 );
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set interior colour of the line endings defined in the LE
+     * entry. Colour is in DeviceRGB colourspace.
+     *
+     * @param ic
+     *            colour in the DeviceRGB colourspace.
+     *
+     */
+    public void setInteriorColour( PDGamma ic )
+    {
+        getDictionary().setItem( "IC", ic );
+    }
+
+    /**
+     * This will retrieve the interior colour of the line endings defined in the
+     * LE entry. Colour is in DeviceRGB colourspace.
+     *
+     *
+     * @return PDGamma object representing the colour.
+     *
+     */
+    public PDGamma getInteriorColour()
+    {
+
+        COSArray ic = (COSArray) getDictionary().getDictionaryObject( "IC" );
+        if (ic != null)
+        {
+            return new PDGamma( ic );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * This will set if the contents are shown as a caption to the line.
+     *
+     * @param cap
+     *            Boolean value.
+     */
+    public void setCaption( boolean cap )
+    {
+        getDictionary().setBoolean( "Cap", cap );
+    }
+
+    /**
+     * This will retrieve if the contents are shown as a caption or not.
+     *
+     * @return boolean if the content is shown as a caption.
+     */
+    public boolean getCaption()
+    {
+        return getDictionary().getBoolean( "Cap", false );
+    }
+
+    /**
+     * This will set the border style dictionary, specifying the width and dash
+     * pattern used in drawing the line.
+     *
+     * @param bs the border style dictionary to set.
+     *
+     */
+    public void setBorderStyle( PDBorderStyleDictionary bs )
+    {
+        this.getDictionary().setItem( "BS", bs);
+    }
+
+    /**
+     * This will retrieve the border style dictionary, specifying the width and
+     * dash pattern used in drawing the line.
+     *
+     * @return the border style dictionary.
+     */
+    public PDBorderStyleDictionary getBorderStyle()
+    {
+        COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+                COSName.getPDFName( "BS" ) );
+        if (bs != null)
+        {
+            return new PDBorderStyleDictionary( bs );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * This will retrieve the length of the leader line.
+     * 
+     * @return the length of the leader line
+     */
+    public float getLeaderLineLength()
+    {
+        return this.getDictionary().getFloat("LL");
+    }
+
+    /**
+     * This will set the length of the leader line.
+     * 
+     * @param leaderLineLength length of the leader line
+     */
+    public void setLeaderLineLength(float leaderLineLength)
+    {
+        this.getDictionary().setFloat("LL", leaderLineLength);
+    }
+
+    /**
+     * This will retrieve the length of the leader line extensions.
+     * 
+     * @return the length of the leader line extensions
+     */
+    public float getLeaderLineExtensionLength()
+    {
+        return this.getDictionary().getFloat("LLE");
+    }
+
+    /**
+     * This will set the length of the leader line extensions.
+     * 
+     * @param leaderLineExtensionLength length of the leader line extensions
+     */
+    public void setLeaderLineExtensionLength(float leaderLineExtensionLength)
+    {
+        this.getDictionary().setFloat("LLE", leaderLineExtensionLength);
+    }
+
+    /**
+     * This will retrieve the length of the leader line offset.
+     * 
+     * @return the length of the leader line offset
+     */
+    public float getLeaderLineOffsetLength()
+    {
+        return this.getDictionary().getFloat("LLO");
+    }
+
+    /**
+     * This will set the length of the leader line offset.
+     * 
+     * @param leaderLineOffsetLength length of the leader line offset
+     */
+    public void setLeaderLineOffsetLength(float leaderLineOffsetLength)
+    {
+        this.getDictionary().setFloat("LLO", leaderLineOffsetLength);
+    }
+
+    /**
+     * This will retrieve the caption positioning.
+     * 
+     * @return the caption positioning
+     */
+    public String getCaptionPositioning()
+    {
+        return this.getDictionary().getString("CP");
+    }
+
+    /**
+     * This will set the caption positioning.
+     * Allowed values are: "Inline" and "Top"
+     * 
+     * @param captionPositioning caption positioning
+     */
+    public void setCaptionPositioning(String captionPositioning)
+    {
+        this.getDictionary().setString("CP", captionPositioning);
+    }
+
+    /**
+     * This will set the horizontal offset of the caption.
+     * 
+     * @param offset the horizontal offset of the caption
+     */
+    public void setCaptionHorizontalOffset( float offset )
+    {
+        COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+        if( array == null )
+        {
+            array = new COSArray();
+            array.setFloatArray(new float[] {offset, 0.f});
+            this.getDictionary().setItem( "CO", array );
+        }
+        else
+        {
+            array.set(0, new COSFloat(offset) );
+        }
+    }
+
+    /**
+     * This will retrieve the horizontal offset of the caption.
+     * 
+     * @return the the horizontal offset of the caption
+     */
+    public float getCaptionHorizontalOffset()
+    {
+        float retval = 0.f;
+        COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+        if( array != null )
+        {
+            retval = array.toFloatArray()[0];
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the vertical offset of the caption.
+     * 
+     * @param offset vertical offset of the caption
+     */
+    public void setCaptionVerticalOffset( float offset )
+    {
+        COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+        if( array == null )
+        {
+            array = new COSArray();
+            array.setFloatArray(new float[] {0.f, offset});
+            this.getDictionary().setItem( "CO", array );
+        }
+        else
+        {
+            array.set(1, new COSFloat(offset) );
+        }
+    }
+
+    /**
+     * This will retrieve the vertical offset of the caption.
+     * 
+     * @return the vertical offset of the caption
+     */
+    public float getCaptionVerticalOffset()
+    {
+        float retval = 0.f;
+        COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+        if( array != null )
+        {
+            retval = array.toFloatArray()[1];
+        }
+        return retval;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationLink.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationLink.java
new file mode 100644 (file)
index 0000000..9f17e2a
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+
+/**
+ * This is the class that represents a link annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Paul King
+ * @version $Revision: 1.3 $
+ */
+public class PDAnnotationLink extends PDAnnotation
+{
+
+
+    /**
+     * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+     */
+    public static final String HIGHLIGHT_MODE_NONE = "N";
+    /**
+     * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+     */
+    public static final String HIGHLIGHT_MODE_INVERT = "I";
+    /**
+     * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+     */
+    public static final String HIGHLIGHT_MODE_OUTLINE = "O";
+    /**
+     * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+     */
+    public static final String HIGHLIGHT_MODE_PUSH = "P";
+
+
+    /**
+     * The type of annotation.
+     */
+    public static final String SUB_TYPE = "Link";
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationLink()
+    {
+        super();
+        getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+    }
+
+    /**
+     * Creates a Link annotation from a COSDictionary, expected to be
+     * a correct object definition.
+     *
+     * @param field the PDF objet to represent as a field.
+     */
+    public PDAnnotationLink(COSDictionary field)
+    {
+        super( field );
+    }
+
+    /**
+     * Get the action to be performed when this annotation is to be activated.
+     *
+     * @return The action to be performed when this annotation is activated.
+     *
+     * TODO not all annotations have an A entry
+     */
+    public PDAction getAction()
+    {
+        COSDictionary action = (COSDictionary)
+            this.getDictionary().getDictionaryObject( COSName.A );
+        return PDActionFactory.createAction( action );
+    }
+
+    /**
+     * Set the annotation action.
+     * As of PDF 1.6 this is only used for Widget Annotations
+     * @param action The annotation action.
+     * TODO not all annotations have an A entry
+     */
+    public void setAction( PDAction action )
+    {
+        this.getDictionary().setItem( COSName.A, action );
+    }
+
+    /**
+     * This will set the border style dictionary, specifying the width and dash
+     * pattern used in drawing the line.
+     *
+     * @param bs the border style dictionary to set.
+     * TODO not all annotations may have a BS entry
+     *
+     */
+    public void setBorderStyle( PDBorderStyleDictionary bs )
+    {
+        this.getDictionary().setItem( "BS", bs);
+    }
+
+    /**
+     * This will retrieve the border style dictionary, specifying the width and
+     * dash pattern used in drawing the line.
+     *
+     * @return the border style dictionary.
+     * TODO not all annotations may have a BS entry
+     */
+    public PDBorderStyleDictionary getBorderStyle()
+    {
+        COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+                COSName.getPDFName( "BS" ) );
+        if (bs != null)
+        {
+            return new PDBorderStyleDictionary( bs );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Get the destination to be displayed when the annotation is activated.  Either
+     * this or the A should be set but not both.
+     *
+     * @return The destination for this annotation.
+     *
+     * @throws IOException If there is an error creating the destination.
+     */
+    public PDDestination getDestination() throws IOException
+    {
+        COSBase base = getDictionary().getDictionaryObject( COSName.DEST );
+        PDDestination retval = PDDestination.create( base );
+
+        return retval;
+    }
+
+    /**
+     * The new destination value.
+     *
+     * @param dest The updated destination.
+     */
+    public void setDestination( PDDestination dest )
+    {
+        getDictionary().setItem( COSName.DEST, dest );
+    }
+
+    /**
+     * Set the highlight mode for when the mouse is depressed.
+     * See the HIGHLIGHT_MODE_XXX constants.
+     *
+     * @return The string representation of the highlight mode.
+     */
+    public String getHighlightMode()
+    {
+        return getDictionary().getNameAsString( COSName.H, HIGHLIGHT_MODE_INVERT );
+    }
+
+    /**
+     * Set the highlight mode.  See the HIGHLIGHT_MODE_XXX constants.
+     *
+     * @param mode The new highlight mode.
+     */
+    public void setHighlightMode( String mode )
+    {
+        getDictionary().setName( COSName.H, mode );
+    }
+
+    /**
+     * This will set the previous URI action, in case it
+     * needs to be retrieved at later date.
+     *
+     * @param pa The previous URI.
+     */
+    public void setPreviousURI( PDActionURI pa )
+    {
+        getDictionary().setItem( "PA", pa );
+    }
+
+    /**
+     * This will set the previous URI action, in case it's
+     * needed.
+     *
+     * @return The previous URI.
+     */
+    public PDActionURI getPreviousURI()
+    {
+        COSDictionary pa = (COSDictionary) getDictionary().getDictionaryObject("PA");
+        if ( pa != null )
+        {
+            return new PDActionURI( pa );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * This will set the set of quadpoints which encompass the areas of this
+     * annotation which will activate.
+     *
+     * @param quadPoints
+     *            an array representing the set of area covered.
+     */
+    public void setQuadPoints( float[] quadPoints )
+    {
+        COSArray newQuadPoints = new COSArray();
+        newQuadPoints.setFloatArray( quadPoints );
+        getDictionary().setItem( "QuadPoints", newQuadPoints );
+    }
+
+    /**
+     * This will retrieve the set of quadpoints which encompass the areas of
+     * this annotation which will activate.
+     *
+     * @return An array of floats representing the quad points.
+     */
+    public float[] getQuadPoints()
+    {
+        COSArray quadPoints = (COSArray) getDictionary().getDictionaryObject( "QuadPoints" );
+        if (quadPoints != null)
+        {
+            return quadPoints.toFloatArray();
+        }
+        else
+        {
+            return null; // Should never happen as this is a required item
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java
new file mode 100644 (file)
index 0000000..23aa5af
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+import org.apache.pdfbox.cos.COSBase;
+
+import java.io.IOException;
+
+import java.util.Calendar;
+
+/**
+ * This class represents the additonal fields of a Markup type Annotation.  See
+ * section 12.5.6 of ISO32000-1:2008 (starting with page 390) for details on
+ * annotation types.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationMarkup extends PDAnnotation
+{
+    /**
+     * Constant for a FreeText type of annotation.
+     */
+    public static final String SUB_TYPE_FREETEXT = "FreeText";
+    /**
+     * Constant for an Polygon type of annotation.
+     */
+    public static final String SUB_TYPE_POLYGON = "Polygon";
+    /**
+     * Constant for an PolyLine type of annotation.
+     */
+    public static final String SUB_TYPE_POLYLINE = "PolyLine";
+    /**
+     * Constant for an Caret type of annotation.
+     */
+    public static final String SUB_TYPE_CARET = "Caret";
+    /**
+     * Constant for an Ink type of annotation.
+     */
+    public static final String SUB_TYPE_INK = "Ink";
+    /**
+     * Constant for an Sound type of annotation.
+     */
+    public static final String SUB_TYPE_SOUND = "Sound";
+  
+    /*
+     * The various values of the reply type as defined in the PDF 1.7 reference
+     * Table 170
+     */
+
+    /**
+     * Constant for an annotation reply type.
+     */
+    public static final String RT_REPLY = "R";
+
+    /**
+     * Constant for an annotation reply type.
+     */
+    public static final String RT_GROUP = "Group";
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationMarkup()
+    {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict
+     *            The annotations dictionary.
+     */
+    public PDAnnotationMarkup( COSDictionary dict )
+    {
+        super( dict );
+    }
+
+    /**
+     * Retrieve the string used as the title of the popup window shown when open
+     * and active (by convention this identifies who added the annotation).
+     *
+     * @return The title of the popup.
+     */
+    public String getTitlePopup()
+    {
+        return getDictionary().getString( "T" );
+    }
+
+    /**
+     * Set the string used as the title of the popup window shown when open and
+     * active (by convention this identifies who added the annotation).
+     *
+     * @param t
+     *            The title of the popup.
+     */
+    public void setTitlePopup( String t )
+    {
+        getDictionary().setString( "T", t );
+    }
+
+    /**
+     * This will retrieve the popup annotation used for entering/editing the
+     * text for this annotation.
+     *
+     * @return the popup annotation.
+     */
+    public PDAnnotationPopup getPopup()
+    {
+        COSDictionary popup = (COSDictionary) getDictionary().getDictionaryObject( "Popup" );
+        if (popup != null)
+        {
+            return new PDAnnotationPopup( popup );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * This will set the popup annotation used for entering/editing the text for
+     * this annotation.
+     *
+     * @param popup
+     *            the popup annotation.
+     */
+    public void setPopup( PDAnnotationPopup popup )
+    {
+        getDictionary().setItem( "Popup", popup );
+    }
+
+    /**
+     * This will retrieve the constant opacity value used when rendering the
+     * annotation (excluing any popup).
+     *
+     * @return the constant opacity value.
+     */
+    public float getConstantOpacity()
+    {
+        return getDictionary().getFloat( "CA", 1 );
+    }
+
+    /**
+     * This will set the constant opacity value used when rendering the
+     * annotation (excluing any popup).
+     *
+     * @param ca
+     *            the constant opacity value.
+     */
+    public void setConstantOpacity( float ca )
+    {
+        getDictionary().setFloat( "CA", ca );
+    }
+
+    /**
+     * This will retrieve the rich text stream which is displayed in the popup
+     * window.
+     *
+     * @return the rich text stream.
+     */
+    public PDTextStream getRichContents()
+    {
+        COSBase rc = getDictionary().getDictionaryObject( "RC" );
+        if (rc != null)
+        {
+            return PDTextStream.createTextStream( rc );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * This will set the rich text stream which is displayed in the popup window.
+     *
+     * @param rc
+     *            the rich text stream.
+     */
+    public void setRichContents( PDTextStream rc )
+    {
+        getDictionary().setItem( "RC", rc);
+    }
+
+    /**
+     * This will retrieve the date and time the annotation was created.
+     *
+     * @return the creation date/time.
+     * @throws IOException
+     *             if there is a format problem when converting the date.
+     */
+    public Calendar getCreationDate() throws IOException
+    {
+        return getDictionary().getDate( "CreationDate" );
+    }
+
+    /**
+     * This will set the the date and time the annotation was created.
+     *
+     * @param creationDate
+     *            the date and time the annotation was created.
+     */
+    public void setCreationDate( Calendar creationDate )
+    {
+        getDictionary().setDate( "CreationDate", creationDate );
+    }
+
+    /**
+     * This will retrieve the annotation to which this one is "In Reply To" the
+     * actual relationship is specified by the RT entry.
+     *
+     * @return the other annotation.
+     * @throws IOException
+     *             if there is an error with the annotation.
+     */
+    public PDAnnotation getInReplyTo() throws IOException
+    {
+        COSBase irt = getDictionary().getDictionaryObject( "IRT" );
+        return PDAnnotation.createAnnotation( irt );
+    }
+
+    /**
+     * This will set the annotation to which this one is "In Reply To" the
+     * actual relationship is specified by the RT entry.
+     *
+     * @param irt the annotation this one is "In Reply To".
+     */
+    public void setInReplyTo( PDAnnotation irt )
+    {
+        getDictionary().setItem( "IRT", irt );
+    }
+
+    /**
+     * This will retrieve the short description of the subject of the annotation.
+     *
+     * @return the subject.
+     */
+    public String getSubject()
+    {
+        return getDictionary().getString( "Subj" );
+    }
+
+    /**
+     * This will set the short description of the subject of the annotation.
+     *
+     * @param subj short description of the subject.
+     */
+    public void setSubject( String subj )
+    {
+        getDictionary().setString( "Subj", subj );
+    }
+
+    /**
+     * This will retrieve the Reply Type (relationship) with the annotation in
+     * the IRT entry See the RT_* constants for the available values.
+     *
+     * @return the relationship.
+     */
+    public String getReplyType()
+    {
+        return getDictionary().getNameAsString( "RT", RT_REPLY );
+    }
+
+    /**
+     * This will set the Reply Type (relationship) with the annotation in the
+     * IRT entry See the RT_* constants for the available values.
+     *
+     * @param rt the reply type.
+     */
+    public void setReplyType( String rt )
+    {
+        getDictionary().setName( "RT", rt );
+    }
+
+    /**
+     * This will retrieve the intent of the annotation The values and meanings
+     * are specific to the actual annotation See the IT_* constants for the
+     * annotation classes.
+     *
+     * @return the intent
+     */
+    public String getIntent()
+    {
+        return getDictionary().getNameAsString( "IT" );
+    }
+
+    /**
+     * This will set the intent of the annotation The values and meanings are
+     * specific to the actual annotation See the IT_* constants for the
+     * annotation classes.
+     *
+     * @param it the intent
+     */
+    public void setIntent( String it )
+    {
+        getDictionary().setName( "IT", it );
+    }
+
+    /**
+     * This will return the external data dictionary.
+     * 
+     * @return the external data dictionary
+     */
+    public PDExternalDataDictionary getExternalData()
+    {
+        COSBase exData = this.getDictionary().getDictionaryObject("ExData");
+        if (exData instanceof COSDictionary)
+        {
+            return new PDExternalDataDictionary((COSDictionary) exData);
+        }
+        return null;
+    }
+
+    /**
+     * This will set the external data dictionary.
+     * 
+     * @param externalData the external data dictionary
+     */
+    public void setExternalData(PDExternalDataDictionary externalData)
+    {
+        this.getDictionary().setItem("ExData", externalData);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationPopup.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationPopup.java
new file mode 100644 (file)
index 0000000..dd9e7f9
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the class that represents a popup annotation.
+ * Introduced in PDF 1.3 specification
+ *
+ * @author Paul King
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationPopup extends PDAnnotation
+{
+
+    /**
+     * The type of annotation.
+     */
+    public static final String SUB_TYPE = "Popup";
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationPopup()
+    {
+        super();
+        getDictionary()
+                .setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+    }
+
+    /**
+     * Creates a popup annotation from a COSDictionary, expected to be a correct
+     * object definition.
+     *
+     * @param field
+     *            the PDF objet to represent as a field.
+     */
+    public PDAnnotationPopup( COSDictionary field )
+    {
+        super( field );
+    }
+
+    /**
+     * This will set inital state of the annotation, open or closed.
+     *
+     * @param open
+     *            Boolean value, true = open false = closed.
+     */
+    public void setOpen( boolean open )
+    {
+        getDictionary().setBoolean( "Open" , open );
+    }
+
+    /**
+     * This will retrieve the initial state of the annotation, open Or closed
+     * (default closed).
+     *
+     * @return The initial state, true = open false = closed.
+     */
+    public boolean getOpen()
+    {
+        return getDictionary().getBoolean( "Open" , false );
+    }
+
+    /**
+     * This will set the markup annotation which this popup relates to.
+     *
+     * @param annot
+     *            the markup annotation.
+     */
+    public void setParent( PDAnnotationMarkup annot )
+    {
+        getDictionary().setItem( COSName.PARENT, annot.getDictionary() );
+    }
+
+    /**
+     * This will retrieve the markup annotation which this popup relates to.
+     *
+     * @return The parent markup annotation.
+     */
+    public PDAnnotationMarkup getParent()
+    {
+        PDAnnotationMarkup am = null;
+        try
+        {
+            am = (PDAnnotationMarkup)
+                PDAnnotation.createAnnotation( getDictionary().getDictionaryObject( "Parent", "P" ) );
+        }
+        catch (IOException ioe)
+        {
+            // Couldn't construct the annotation, so return null i.e. do nothing
+        }
+        return am;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationRubberStamp.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationRubberStamp.java
new file mode 100644 (file)
index 0000000..a2b09fd
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the class that represents a rubber stamp annotation.
+ * Introduced in PDF 1.3 specification
+ *
+ * @author Paul King
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationRubberStamp extends PDAnnotationMarkup
+{
+
+    /*
+     * The various values of the rubber stamp as defined in
+     * the PDF 1.6 reference Table 8.28
+     */
+
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_APPROVED = "Approved";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_EXPERIMENTAL = "Experimental";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_NOT_APPROVED = "NotApproved";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_AS_IS = "AsIs";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_EXPIRED = "Expired";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_NOT_FOR_PUBLIC_RELEASE = "NotForPublicRelease";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_FOR_PUBLIC_RELEASE = "ForPublicRelease";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_DRAFT = "Draft";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_FOR_COMMENT = "ForComment";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_TOP_SECRET = "TopSecret";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_DEPARTMENTAL = "Departmental";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_CONFIDENTIAL = "Confidential";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_FINAL = "Final";
+    /**
+     * Constant for the name of a rubber stamp.
+     */
+    public static final String NAME_SOLD = "Sold";
+
+    /**
+     * The type of annotation.
+     */
+    public static final String SUB_TYPE = "Stamp";
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationRubberStamp()
+    {
+        super();
+        getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+    }
+
+    /**
+     * Creates a Rubber Stamp annotation from a COSDictionary, expected to be
+     * a correct object definition.
+     *
+     * @param field the PDF objet to represent as a field.
+     */
+    public PDAnnotationRubberStamp(COSDictionary field)
+    {
+        super( field );
+    }
+
+    /**
+     * This will set the name (and hence appearance, AP taking precedence)
+     * For this annotation.   See the NAME_XXX constants for valid values.
+     *
+     * @param name The name of the rubber stamp.
+     */
+    public void setName( String name )
+    {
+        getDictionary().setName(COSName.NAME, name);
+    }
+
+    /**
+     * This will retrieve the name (and hence appearance, AP taking precedence)
+     * For this annotation.  The default is DRAFT.
+     *
+     * @return The name of this rubber stamp, see the NAME_XXX constants.
+     */
+    public String getName()
+    {
+        return getDictionary().getNameAsString(COSName.NAME, NAME_DRAFT);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java
new file mode 100644 (file)
index 0000000..627926a
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This is the class that represents a rectangular or eliptical annotation
+ * Introduced in PDF 1.3 specification .
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationSquareCircle extends PDAnnotationMarkup
+{
+
+    /**
+     * Constant for a Rectangular type of annotation.
+     */
+    public static final String SUB_TYPE_SQUARE = "Square";
+    /**
+     * Constant for an Eliptical type of annotation.
+     */
+    public static final String SUB_TYPE_CIRCLE = "Circle";
+
+    /**
+     * Creates a Circle or Square annotation of the specified sub type.
+     *
+     * @param subType the subtype the annotation represents.
+         */
+    public PDAnnotationSquareCircle( String subType )
+    {
+        super();
+        setSubtype( subType );
+    }
+
+    /**
+     * Creates a Line annotation from a COSDictionary, expected to be a correct
+     * object definition.
+     *
+     * @param field
+     *            the PDF objet to represent as a field.
+     */
+    public PDAnnotationSquareCircle( COSDictionary field )
+    {
+        super( field );
+    }
+
+
+    /**
+     * This will set interior colour of the drawn area
+     * Colour is in DeviceRGB colourspace.
+     *
+     * @param ic
+     *            colour in the DeviceRGB colourspace.
+     *
+     */
+    public void setInteriorColour( PDGamma ic )
+    {
+        getDictionary().setItem( "IC", ic );
+    }
+
+    /**
+     * This will retrieve the interior colour of the drawn area
+     * Colour is in DeviceRGB colourspace.
+     *
+     *
+     * @return PDGamma object representing the colour.
+     *
+     */
+    public PDGamma getInteriorColour()
+    {
+
+        COSArray ic = (COSArray) getDictionary().getItem(
+                COSName.getPDFName( "IC" ) );
+        if (ic != null)
+        {
+            return new PDGamma( ic );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+
+    /**
+     * This will set the border effect dictionary, specifying effects to be applied
+     * when drawing the line.
+     *
+     * @param be The border effect dictionary to set.
+     *
+     */
+    public void setBorderEffect( PDBorderEffectDictionary be )
+    {
+        getDictionary().setItem( "BE", be );
+    }
+
+    /**
+     * This will retrieve the border effect dictionary, specifying effects to be
+     * applied used in drawing the line.
+     *
+     * @return The border effect dictionary
+     */
+    public PDBorderEffectDictionary getBorderEffect()
+    {
+        COSDictionary be = (COSDictionary) getDictionary().getDictionaryObject( "BE" );
+        if (be != null)
+        {
+            return new PDBorderEffectDictionary( be );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * This will set the rectangle difference rectangle. Giving the difference
+     * between the annotations rectangle and where the drawing occurs.
+         * (To take account of any effects applied through the BE entry forexample)
+     *
+     * @param rd the rectangle difference
+     *
+     */
+    public void setRectDifference( PDRectangle rd )
+    {
+        getDictionary().setItem( "RD", rd );
+    }
+
+    /**
+     * This will get the rectangle difference rectangle. Giving the difference
+     * between the annotations rectangle and where the drawing occurs.
+         * (To take account of any effects applied through the BE entry forexample)
+     *
+     * @return the rectangle difference
+     */
+    public PDRectangle getRectDifference()
+    {
+        COSArray rd = (COSArray) getDictionary().getDictionaryObject( "RD" );
+        if (rd != null)
+        {
+            return new PDRectangle( rd );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * This will set the sub type (and hence appearance, AP taking precedence) For
+     * this annotation. See the SUB_TYPE_XXX constants for valid values.
+     *
+     * @param subType The subtype of the annotation
+     */
+    public void setSubtype( String subType )
+    {
+        getDictionary().setName( COSName.SUBTYPE, subType );
+    }
+
+    /**
+     * This will retrieve the sub type (and hence appearance, AP taking precedence)
+     * For this annotation.
+     *
+     * @return The subtype of this annotation, see the SUB_TYPE_XXX constants.
+     */
+    public String getSubtype()
+    {
+        return getDictionary().getNameAsString( COSName.SUBTYPE);
+    }
+
+    /**
+     * This will set the border style dictionary, specifying the width and dash
+     * pattern used in drawing the line.
+     *
+     * @param bs the border style dictionary to set.
+     * TODO not all annotations may have a BS entry
+     *
+     */
+    public void setBorderStyle( PDBorderStyleDictionary bs )
+    {
+        this.getDictionary().setItem( "BS", bs);
+    }
+
+    /**
+     * This will retrieve the border style dictionary, specifying the width and
+     * dash pattern used in drawing the line.
+     *
+     * @return the border style dictionary.
+     * TODO not all annotations may have a BS entry
+     */
+    public PDBorderStyleDictionary getBorderStyle()
+    {
+        COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+                COSName.getPDFName( "BS" ) );
+        if (bs != null)
+        {
+            return new PDBorderStyleDictionary( bs );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java
new file mode 100644 (file)
index 0000000..8cafd58
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the class that represents a text annotation.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationText extends PDAnnotationMarkup
+{
+
+    /*
+     * The various values of the Text as defined in the PDF 1.7 reference Table
+     * 172
+     */
+
+    /**
+     * Constant for the name of a text annotation.
+     */
+    public static final String NAME_COMMENT = "Comment";
+
+    /**
+     * Constant for the name of a text annotation.
+     */
+    public static final String NAME_KEY = "Key";
+
+    /**
+     * Constant for the name of a text annotation.
+     */
+    public static final String NAME_NOTE = "Note";
+
+    /**
+     * Constant for the name of a text annotation.
+     */
+    public static final String NAME_HELP = "Help";
+
+    /**
+     * Constant for the name of a text annotation.
+     */
+    public static final String NAME_NEW_PARAGRAPH = "NewParagraph";
+
+    /**
+     * Constant for the name of a text annotation.
+     */
+    public static final String NAME_PARAGRAPH = "Paragraph";
+
+    /**
+     * Constant for the name of a text annotation.
+     */
+    public static final String NAME_INSERT = "Insert";
+
+    /**
+     * The type of annotation.
+     */
+    public static final String SUB_TYPE = "Text";
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationText()
+    {
+        super();
+        getDictionary()
+                .setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+    }
+
+    /**
+     * Creates a Text annotation from a COSDictionary, expected to be a correct
+     * object definition.
+     *
+     * @param field
+     *            the PDF object to represent as a field.
+     */
+    public PDAnnotationText( COSDictionary field )
+    {
+        super( field );
+    }
+
+    /**
+     * This will set initial state of the annotation, open or closed.
+     *
+     * @param open
+     *            Boolean value, true = open false = closed
+     */
+    public void setOpen( boolean open )
+    {
+        getDictionary().setBoolean( COSName.getPDFName( "Open" ), open );
+    }
+
+    /**
+     * This will retrieve the initial state of the annotation, open Or closed
+     * (default closed).
+     *
+     * @return The initial state, true = open false = closed
+     */
+    public boolean getOpen()
+    {
+        return getDictionary().getBoolean( COSName.getPDFName( "Open" ), false );
+    }
+
+    /**
+     * This will set the name (and hence appearance, AP taking precedence) For
+     * this annotation. See the NAME_XXX constants for valid values.
+     *
+     * @param name
+     *            The name of the annotation
+     */
+    public void setName( String name )
+    {
+        getDictionary().setName( COSName.NAME, name );
+    }
+
+    /**
+     * This will retrieve the name (and hence appearance, AP taking precedence)
+     * For this annotation. The default is NOTE.
+     *
+     * @return The name of this annotation, see the NAME_XXX constants.
+     */
+    public String getName()
+    {
+        return getDictionary().getNameAsString( COSName.NAME, NAME_NOTE );
+    }
+
+    /**
+     * This will retrieve the annotation state.
+     * 
+     * @return the annotation state
+     */
+    public String getState()
+    {
+        return this.getDictionary().getString("State");
+    }
+
+    /**
+     * This will set the annotation state.
+     * 
+     * @param state the annotation state 
+     */
+    public void setState(String state)
+    {
+        this.getDictionary().setString("State", state);
+    }
+
+    /**
+     * This will retrieve the annotation state model.
+     * 
+     * @return the annotation state model
+     */
+    public String getStateModel()
+    {
+        return this.getDictionary().getString("StateModel");
+    }
+
+    /**
+     * This will set the annotation state model.
+     * Allowed values are "Marked" and "Review"
+     * 
+     * @param stateModel the annotation state model
+     */
+    public void setStateModel(String stateModel)
+    {
+        this.getDictionary().setString("StateModel", stateModel);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java
new file mode 100644 (file)
index 0000000..20a200d
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the abstract class that represents a text markup annotation
+ * Introduced in PDF 1.3 specification, except Squiggly lines in 1.4.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationTextMarkup extends PDAnnotationMarkup
+{
+
+    /**
+     * The types of annotation.
+     */
+    public static final String SUB_TYPE_HIGHLIGHT = "Highlight";
+    /**
+     * The types of annotation.
+     */
+    public static final String SUB_TYPE_UNDERLINE = "Underline";
+    /**
+     * The types of annotation.
+     */
+    public static final String SUB_TYPE_SQUIGGLY = "Squiggly";
+    /**
+     * The types of annotation.
+     */
+    public static final String SUB_TYPE_STRIKEOUT = "StrikeOut";
+
+
+    private PDAnnotationTextMarkup()
+    {
+        // Must be constructed with a subType or dictionary parameter
+    }
+
+    /**
+     * Creates a TextMarkup annotation of the specified sub type.
+     *
+     * @param subType the subtype the annotation represents
+     */
+    public PDAnnotationTextMarkup(String subType)
+    {
+        super();
+        setSubtype( subType );
+
+        // Quad points are required, set and empty array
+        setQuadPoints( new float[0] );
+    }
+
+    /**
+     * Creates a TextMarkup annotation from a COSDictionary, expected to be a
+     * correct object definition.
+     *
+     * @param field the PDF objet to represent as a field.
+     */
+    public PDAnnotationTextMarkup( COSDictionary field )
+    {
+        super( field );
+    }
+
+    /**
+     * This will set the set of quadpoints which encompass the areas of this
+     * annotation.
+     *
+     * @param quadPoints
+     *            an array representing the set of area covered
+     */
+    public void setQuadPoints( float[] quadPoints )
+    {
+        COSArray newQuadPoints = new COSArray();
+        newQuadPoints.setFloatArray( quadPoints );
+        getDictionary().setItem( "QuadPoints", newQuadPoints );
+    }
+
+    /**
+     * This will retrieve the set of quadpoints which encompass the areas of
+     * this annotation.
+     *
+     * @return An array of floats representing the quad points.
+     */
+    public float[] getQuadPoints()
+    {
+        COSArray quadPoints = (COSArray) getDictionary().getDictionaryObject( "QuadPoints" );
+        if (quadPoints != null)
+        {
+            return quadPoints.toFloatArray();
+        }
+        else
+        {
+            return null; // Should never happen as this is a required item
+        }
+    }
+
+    /**
+     * This will set the sub type (and hence appearance, AP taking precedence) For
+     * this annotation. See the SUB_TYPE_XXX constants for valid values.
+     *
+     * @param subType The subtype of the annotation
+     */
+    public void setSubtype( String subType )
+    {
+        getDictionary().setName( COSName.SUBTYPE, subType );
+    }
+
+    /**
+     * This will retrieve the sub type (and hence appearance, AP taking precedence)
+     * For this annotation.
+     *
+     * @return The subtype of this annotation, see the SUB_TYPE_XXX constants.
+     */
+    public String getSubtype()
+    {
+        return getDictionary().getNameAsString( COSName.SUBTYPE);
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationUnknown.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationUnknown.java
new file mode 100644 (file)
index 0000000..eeb3c89
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the class that represents an arbitary Unknown Annotation type.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationUnknown extends PDAnnotation
+{
+
+   /**
+     * Creates an arbitary annotation from a COSDictionary, expected to be
+     * a correct object definition for some sort of annotation.
+     *
+     * @param dic The dictionary which represents this Annotation.
+     */
+    public PDAnnotationUnknown(COSDictionary dic)
+    {
+        super( dic );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationWidget.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationWidget.java
new file mode 100644 (file)
index 0000000..ddba0c4
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.PDAnnotationAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This is the class that represents a widget.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDAnnotationWidget extends PDAnnotation
+{
+    /**
+     * The type of annotation.
+     */
+    public static final String SUB_TYPE = "Widget";
+
+
+    /**
+     * Constructor.
+     */
+    public PDAnnotationWidget()
+    {
+        super();
+        getDictionary().setName( COSName.SUBTYPE, SUB_TYPE);
+    }
+
+
+    /**
+     * Creates a PDWidget from a COSDictionary, expected to be
+     * a correct object definition for a field in PDF.
+     *
+     * @param field the PDF objet to represent as a field.
+     */
+    public PDAnnotationWidget(COSDictionary field)
+    {
+        super( field );
+    }
+
+    /**
+     * Returns the highlighting mode. Default value: <code>I</code>
+     * <dl>
+     *   <dt><code>N</code></dt>
+     *     <dd>(None) No highlighting.</dd>
+     *   <dt><code>I</code></dt>
+     *     <dd>(Invert) Invert the contents of the annotation rectangle.</dd>
+     *   <dt><code>O</code></dt>
+     *     <dd>(Outline) Invert the annotation's border.</dd>
+     *   <dt><code>P</code></dt>
+     *     <dd>(Push) Display the annotation's down appearance, if any. If no
+     *      down appearance is defined, the contents of the annotation rectangle
+     *      shall be offset to appear as if it were pushed below the surface of
+     *      the page</dd>
+     *   <dt><code>T</code></dt>
+     *     <dd>(Toggle) Same as <code>P</code> (which is preferred).</dd>
+     * </dl>
+     * 
+     * @return the highlighting mode
+     */
+    public String getHighlightingMode()
+    {
+        return this.getDictionary().getNameAsString(COSName.H, "I");
+    }
+
+    /**
+     * Sets the highlighting mode.
+     * <dl>
+     *   <dt><code>N</code></dt>
+     *     <dd>(None) No highlighting.</dd>
+     *   <dt><code>I</code></dt>
+     *     <dd>(Invert) Invert the contents of the annotation rectangle.</dd>
+     *   <dt><code>O</code></dt>
+     *     <dd>(Outline) Invert the annotation's border.</dd>
+     *   <dt><code>P</code></dt>
+     *     <dd>(Push) Display the annotation's down appearance, if any. If no
+     *      down appearance is defined, the contents of the annotation rectangle
+     *      shall be offset to appear as if it were pushed below the surface of
+     *      the page</dd>
+     *   <dt><code>T</code></dt>
+     *     <dd>(Toggle) Same as <code>P</code> (which is preferred).</dd>
+     * </dl>
+     * 
+     * @param highlightingMode the highlighting mode
+     *  the defined values
+     */
+    public void setHighlightingMode(String highlightingMode)
+    {
+        if ((highlightingMode == null)
+            || "N".equals(highlightingMode) || "I".equals(highlightingMode)
+            || "O".equals(highlightingMode) || "P".equals(highlightingMode)
+            || "T".equals(highlightingMode))
+        {
+            this.getDictionary().setName(COSName.H, highlightingMode);
+        }
+        else
+        {
+            throw new IllegalArgumentException( "Valid values for highlighting mode are " +
+                "'N', 'N', 'O', 'P' or 'T'" );
+        }
+    }
+
+    /**
+     * Returns the appearance characteristics dictionary.
+     * 
+     * @return the appearance characteristics dictionary
+     */
+    public PDAppearanceCharacteristicsDictionary getAppearanceCharacteristics()
+    {
+        COSBase mk = this.getDictionary().getDictionaryObject(COSName.getPDFName("MK"));
+        if (mk instanceof COSDictionary)
+        {
+            return new PDAppearanceCharacteristicsDictionary((COSDictionary) mk);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the appearance characteristics dictionary.
+     * 
+     * @param appearanceCharacteristics the appearance characteristics dictionary
+     */
+    public void setAppearanceCharacteristics(PDAppearanceCharacteristicsDictionary appearanceCharacteristics)
+    {
+        this.getDictionary().setItem("MK", appearanceCharacteristics);
+    }
+
+    /**
+     * Get the action to be performed when this annotation is to be activated.
+     *
+     * @return The action to be performed when this annotation is activated.
+     */
+    public PDAction getAction()
+    {
+        COSDictionary action = (COSDictionary)
+            this.getDictionary().getDictionaryObject( COSName.A );
+        return PDActionFactory.createAction( action );
+    }
+
+    /**
+     * Set the annotation action.
+     * As of PDF 1.6 this is only used for Widget Annotations
+     * @param action The annotation action.
+     */
+    public void setAction( PDAction action )
+    {
+        this.getDictionary().setItem( COSName.A, action );
+    }
+
+    /**
+     * Get the additional actions for this field.  This will return null
+     * if there are no additional actions for this field.
+     * As of PDF 1.6 this is only used for Widget Annotations.
+     *
+     * @return The actions of the field.
+     */
+    public PDAnnotationAdditionalActions getActions()
+    {
+        COSDictionary aa = (COSDictionary)this.getDictionary().getDictionaryObject( "AA" );
+        PDAnnotationAdditionalActions retval = null;
+        if( aa != null )
+        {
+            retval = new PDAnnotationAdditionalActions( aa );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the actions of the field.
+     *
+     * @param actions The field actions.
+     */
+    public void setActions( PDAnnotationAdditionalActions actions )
+    {
+        this.getDictionary().setItem( "AA", actions );
+    }
+
+    /**
+     * This will set the border style dictionary, specifying the width and dash
+     * pattern used in drawing the line.
+     *
+     * @param bs the border style dictionary to set.
+     *
+     */
+    public void setBorderStyle( PDBorderStyleDictionary bs )
+    {
+        this.getDictionary().setItem( "BS", bs);
+    }
+
+    /**
+     * This will retrieve the border style dictionary, specifying the width and
+     * dash pattern used in drawing the line.
+     *
+     * @return the border style dictionary.
+     */
+    public PDBorderStyleDictionary getBorderStyle()
+    {
+        COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+                COSName.getPDFName( "BS" ) );
+        if (bs != null)
+        {
+            return new PDBorderStyleDictionary( bs );
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    // TODO where to get acroForm from?
+//    public PDField getParent() throws IOException
+//    {
+//        COSBase parent = this.getDictionary().getDictionaryObject(COSName.PARENT);
+//        if (parent instanceof COSDictionary)
+//        {
+//            PDAcroForm acroForm = null;
+//            return PDFieldFactory.createField(acroForm, (COSDictionary) parent);
+//        }
+//        return null;
+//    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceCharacteristicsDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceCharacteristicsDictionary.java
new file mode 100644 (file)
index 0000000..3dc1e55
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+
+/**
+ * This class represents an appearance characteristics dictionary.
+ *
+ * @version $Revision: 1.0 $ 
+ *
+ */
+public class PDAppearanceCharacteristicsDictionary implements COSObjectable
+{
+
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     * 
+     * @param dict dictionary
+     */
+    public PDAppearanceCharacteristicsDictionary(COSDictionary dict)
+    {
+        this.dictionary = dict;
+    }
+
+
+    /**
+     * returns the dictionary.
+     * @return the dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     */
+    public COSBase getCOSObject()
+    {
+        return this.dictionary;
+    }
+
+    /**
+     * This will retrieve the rotation of the annotation widget.
+     * It must be a multiple of 90. Default is 0 
+     * @return the rotation
+     */
+    public int getRotation()
+    {
+        return this.getDictionary().getInt(COSName.R, 0);
+    }
+
+    /**
+     * This will set the rotation.
+     * 
+     * @param rotation the rotation as a multiple of 90
+     */
+    public void setRotation(int rotation)
+    {
+        this.getDictionary().setInt(COSName.R, rotation);
+    }
+
+    /**
+     * This will retrieve the border color.
+     * 
+     * @return the border color.
+     */
+    public PDGamma getBorderColour()
+    {
+        COSBase c = this.getDictionary().getItem(COSName.getPDFName("BC"));
+        if (c instanceof COSArray)
+        {
+            return new PDGamma((COSArray) c);
+        }
+        return null;
+    }
+
+    /**
+     * This will set the border color.
+     * 
+     * @param c the border color
+     */
+    public void setBorderColour(PDGamma c)
+    {
+        this.getDictionary().setItem("BC", c);
+    }
+
+    /**
+     * This will retrieve the background color.
+     * 
+     * @return the background color.
+     */
+    public PDGamma getBackground()
+    {
+        COSBase c = this.getDictionary().getItem(COSName.getPDFName("BG"));
+        if (c instanceof COSArray)
+        {
+            return new PDGamma((COSArray) c);
+        }
+        return null;
+    }
+
+    /**
+     * This will set the background color.
+     * 
+     * @param c the background color
+     */
+    public void setBackground(PDGamma c)
+    {
+        this.getDictionary().setItem("BG", c);
+    }
+
+    /**
+     * This will retrieve the normal caption.
+     * 
+     * @return the normal caption.
+     */
+    public String getNormalCaption()
+    {
+        return this.getDictionary().getString("CA");
+    }
+
+    /**
+     * This will set the normal caption.
+     * 
+     * @param caption the normal caption
+     */
+    public void setNormalCaption(String caption)
+    {
+        this.getDictionary().setString("CA", caption);
+    }
+
+    /**
+     * This will retrieve the rollover caption.
+     * 
+     * @return the rollover caption.
+     */
+    public String getRolloverCaption()
+    {
+        return this.getDictionary().getString("RC");
+    }
+
+    /**
+     * This will set the rollover caption.
+     * 
+     * @param caption the rollover caption
+     */
+    public void setRolloverCaption(String caption)
+    {
+        this.getDictionary().setString("RC", caption);
+    }
+
+    /**
+     * This will retrieve the alternate caption.
+     * 
+     * @return the alternate caption.
+     */
+    public String getAlternateCaption()
+    {
+        return this.getDictionary().getString("AC");
+    }
+
+    /**
+     * This will set the alternate caption.
+     * 
+     * @param caption the alternate caption
+     */
+    public void setAlternateCaption(String caption)
+    {
+        this.getDictionary().setString("AC", caption);
+    }
+
+    /**
+     * This will retrieve the normal icon.
+     * 
+     * @return the normal icon.
+     */
+    public PDXObjectForm getNormalIcon()
+    {
+        COSBase i = this.getDictionary().getDictionaryObject("I");
+        if (i instanceof COSStream)
+        {
+            return new PDXObjectForm((COSStream) i);
+        }
+        return null;
+    }
+
+    /**
+     * This will retrieve the rollover icon.
+     * 
+     * @return the rollover icon
+     */
+    public PDXObjectForm getRolloverIcon()
+    {
+        COSBase i = this.getDictionary().getDictionaryObject("RI");
+        if (i instanceof COSStream)
+        {
+            return new PDXObjectForm((COSStream) i);
+        }
+        return null;
+    }
+
+    /**
+     * This will retrieve the alternate icon.
+     * 
+     * @return the alternate icon.
+     */
+    public PDXObjectForm getAlternateIcon()
+    {
+        COSBase i = this.getDictionary().getDictionaryObject("IX");
+        if (i instanceof COSStream)
+        {
+            return new PDXObjectForm((COSStream) i);
+        }
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java
new file mode 100644 (file)
index 0000000..1afc373
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class represents a PDF /AP entry the appearance dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDAppearanceDictionary implements COSObjectable
+{
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDAppearanceDictionary()
+    {
+        dictionary = new COSDictionary();
+        //the N entry is required.
+        dictionary.setItem( COSName.getPDFName( "N" ), new COSDictionary() );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict The annotations dictionary.
+     */
+    public PDAppearanceDictionary( COSDictionary dict )
+    {
+        dictionary = dict;
+    }
+
+    /**
+     * returns the dictionary.
+     * @return the dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * returns the dictionary.
+     * @return the dictionary
+     */
+    public COSBase getCOSObject()
+    {
+        return dictionary;
+    }
+
+    /**
+     * This will return a list of appearances.  In the case where there is
+     * only one appearance the map will contain one entry whose key is the string
+     * "default".
+     *
+     * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs
+     */
+    public Map getNormalAppearance()
+    {
+        COSBase ap = dictionary.getDictionaryObject( COSName.getPDFName( "N" ) );
+        if ( ap == null )
+        { 
+            return null; 
+        }
+        else if( ap instanceof COSStream )
+        {
+            COSStream aux = (COSStream) ap;
+            ap = new COSDictionary();
+            ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux );
+        }
+        COSDictionary map = (COSDictionary)ap;
+        Map<String, PDAppearanceStream> actuals = new HashMap<String, PDAppearanceStream>();
+        Map retval = new COSDictionaryMap( actuals, map );
+        for( COSName asName : map.keySet() )
+        {
+            COSStream as = (COSStream)map.getDictionaryObject( asName );
+            actuals.put( asName.getName(), new PDAppearanceStream( as ) );
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set a list of appearances.  If you would like to set the single
+     * appearance then you should use the key "default", and when the PDF is written
+     * back to the filesystem then there will only be one stream.
+     *
+     * @param appearanceMap The updated map with the appearance.
+     */
+    public void setNormalAppearance( Map appearanceMap )
+    {
+        dictionary.setItem( COSName.getPDFName( "N" ), COSDictionaryMap.convert( appearanceMap ) );
+    }
+
+    /**
+     * This will set the normal appearance when there is only one appearance
+     * to be shown.
+     *
+     * @param ap The appearance stream to show.
+     */
+    public void setNormalAppearance( PDAppearanceStream ap )
+    {
+        dictionary.setItem( COSName.getPDFName( "N" ), ap.getStream() );
+    }
+
+    /**
+     * This will return a list of appearances.  In the case where there is
+     * only one appearance the map will contain one entry whose key is the string
+     * "default".  If there is no rollover appearance then the normal appearance
+     * will be returned.  Which means that this method will never return null.
+     *
+     * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs
+     */
+    public Map getRolloverAppearance()
+    {
+        Map retval = null;
+        COSBase ap = dictionary.getDictionaryObject( COSName.getPDFName( "R" ) );
+        if( ap == null )
+        {
+            retval = getNormalAppearance();
+        }
+        else
+        {
+            if( ap instanceof COSStream )
+            {
+                COSStream aux = (COSStream) ap;
+                ap = new COSDictionary();
+                ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux );
+            }
+            COSDictionary map = (COSDictionary)ap;
+            Map<String, PDAppearanceStream> actuals = new HashMap<String, PDAppearanceStream>();
+            retval = new COSDictionaryMap( actuals, map );
+            for( COSName asName : map.keySet() )
+            {
+                COSStream as = (COSStream)map.getDictionaryObject( asName );
+                actuals.put( asName.getName(), new PDAppearanceStream( as ) );
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set a list of appearances.  If you would like to set the single
+     * appearance then you should use the key "default", and when the PDF is written
+     * back to the filesystem then there will only be one stream.
+     *
+     * @param appearanceMap The updated map with the appearance.
+     */
+    public void setRolloverAppearance( Map appearanceMap )
+    {
+        dictionary.setItem( COSName.getPDFName( "R" ), COSDictionaryMap.convert( appearanceMap ) );
+    }
+
+    /**
+     * This will return a list of appearances.  In the case where there is
+     * only one appearance the map will contain one entry whose key is the string
+     * "default".  If there is no rollover appearance then the normal appearance
+     * will be returned.  Which means that this method will never return null.
+     *
+     * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs
+     */
+    public Map getDownAppearance()
+    {
+        Map retval = null;
+        COSBase ap = dictionary.getDictionaryObject( COSName.getPDFName( "D" ) );
+        if( ap == null )
+        {
+            retval = getNormalAppearance();
+        }
+        else
+        {
+            if( ap instanceof COSStream )
+            {
+                COSStream aux = (COSStream) ap;
+                ap = new COSDictionary();
+                ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux );
+            }
+            COSDictionary map = (COSDictionary)ap;
+            Map<String, PDAppearanceStream> actuals =
+                new HashMap<String, PDAppearanceStream>();
+            retval = new COSDictionaryMap( actuals, map );
+            for( COSName asName : map.keySet() )
+            {
+                COSStream as = (COSStream)map.getDictionaryObject( asName );
+                actuals.put( asName.getName(), new PDAppearanceStream( as ) );
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set a list of appearances.  If you would like to set the single
+     * appearance then you should use the key "default", and when the PDF is written
+     * back to the filesystem then there will only be one stream.
+     *
+     * @param appearanceMap The updated map with the appearance.
+     */
+    public void setDownAppearance( Map appearanceMap )
+    {
+        dictionary.setItem( COSName.getPDFName( "D" ), COSDictionaryMap.convert( appearanceMap ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java
new file mode 100644 (file)
index 0000000..b114cba
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.PDResources;
+
+
+/**
+ * This class represents an appearance for an annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDAppearanceStream implements COSObjectable
+{
+    private COSStream stream = null;
+
+
+    /**
+     * Constructor.
+     *
+     * @param s The cos stream for this appearance.
+     */
+    public PDAppearanceStream( COSStream s )
+    {
+        stream = s;
+    }
+
+    /**
+     * This will return the underlying stream.
+     *
+     * @return The wrapped stream.
+     */
+    public COSStream getStream()
+    {
+        return stream;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return stream;
+    }
+
+    /**
+     * Get the bounding box for this appearance.  This may return null in which
+     * case the Rectangle from the annotation should be used.
+     *
+     * @return The bounding box for this appearance.
+     */
+    public PDRectangle getBoundingBox()
+    {
+        PDRectangle box = null;
+        COSArray bbox = (COSArray)stream.getDictionaryObject( COSName.getPDFName( "BBox" ) );
+        if( bbox != null )
+        {
+            box = new PDRectangle( bbox );
+        }
+        return box;
+    }
+
+    /**
+     * This will set the bounding box for this appearance stream.
+     *
+     * @param rectangle The new bounding box.
+     */
+    public void setBoundingBox( PDRectangle rectangle )
+    {
+        COSArray array = null;
+        if( rectangle != null )
+        {
+            array = rectangle.getCOSArray();
+        }
+        stream.setItem( COSName.getPDFName( "BBox" ), array );
+    }
+
+    /**
+     * This will get the resources for this appearance stream.
+     *
+     * @return The appearance stream resources.
+     */
+    public PDResources getResources()
+    {
+        PDResources retval = null;
+        COSDictionary dict = (COSDictionary)stream.getDictionaryObject( COSName.RESOURCES );
+        if( dict != null )
+        {
+            retval = new PDResources( dict );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the new resources.
+     *
+     * @param resources The new resources.
+     */
+    public void setResources( PDResources resources )
+    {
+        COSDictionary dict = null;
+        if( resources != null )
+        {
+            dict = resources.getCOSDictionary();
+        }
+        stream.setItem( COSName.RESOURCES, dict );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDBorderEffectDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDBorderEffectDictionary.java
new file mode 100644 (file)
index 0000000..56b82ac
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents a PDF /BE entry the border effect dictionary.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDBorderEffectDictionary implements COSObjectable
+{
+
+    /*
+     * The various values of the effect applied to the border as defined in the
+     * PDF 1.6 reference Table 8.14
+     */
+
+    /**
+     * Constant for the name for no effect.
+     */
+    public static final String STYLE_SOLID = "S";
+
+    /**
+     * Constant for the name of a cloudy effect.
+     */
+    public static final String STYLE_CLOUDY = "C";
+
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDBorderEffectDictionary()
+    {
+        dictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict
+     *            a border style dictionary.
+     */
+    public PDBorderEffectDictionary( COSDictionary dict )
+    {
+        dictionary = dict;
+    }
+
+    /**
+     * returns the dictionary.
+     *
+     * @return the dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * returns the dictionary.
+     *
+     * @return the dictionary
+     */
+    public COSBase getCOSObject()
+    {
+        return dictionary;
+    }
+
+    /**
+     * This will set the intensity of the applied effect.
+     *
+     * @param i
+     *            the intensity of the effect values 0 to 2
+     */
+    public void setIntensity( float i )
+    {
+        getDictionary().setFloat( "I", i );
+    }
+
+    /**
+     * This will retrieve the intensity of the applied effect.
+     *
+     * @return the intensity value 0 to 2
+     */
+    public float getIntensity()
+    {
+        return getDictionary().getFloat( "I", 0 );
+    }
+
+    /**
+     * This will set the border effect, see the STYLE_* constants for valid values.
+     *
+     * @param s
+     *            the border effect to use
+     */
+    public void setStyle( String s )
+    {
+        getDictionary().setName( "S", s );
+    }
+
+    /**
+     * This will retrieve the border effect, see the STYLE_* constants for valid
+     * values.
+     *
+     * @return the effect of the border
+     */
+    public String getStyle()
+    {
+        return getDictionary().getNameAsString( "S", STYLE_SOLID );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDBorderStyleDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDBorderStyleDictionary.java
new file mode 100644 (file)
index 0000000..b53b113
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSInteger;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+
+/**
+ * This class represents a PDF /BS entry the border style dictionary.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDBorderStyleDictionary implements COSObjectable
+{
+
+    /*
+     * The various values of the style for the border as defined in the PDF 1.6
+     * reference Table 8.13
+     */
+
+    /**
+     * Constant for the name of a solid style.
+     */
+    public static final String STYLE_SOLID = "S";
+
+    /**
+     * Constant for the name of a dashed style.
+     */
+    public static final String STYLE_DASHED = "D";
+
+    /**
+     * Constant for the name of a beveled style.
+     */
+    public static final String STYLE_BEVELED = "B";
+
+    /**
+     * Constant for the name of a inset style.
+     */
+    public static final String STYLE_INSET = "I";
+
+    /**
+     * Constant for the name of a underline style.
+     */
+    public static final String STYLE_UNDERLINE = "U";
+
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDBorderStyleDictionary()
+    {
+        dictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dict
+     *            a border style dictionary.
+     */
+    public PDBorderStyleDictionary( COSDictionary dict )
+    {
+        dictionary = dict;
+    }
+
+    /**
+     * returns the dictionary.
+     *
+     * @return the dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * returns the dictionary.
+     *
+     * @return the dictionary
+     */
+    public COSBase getCOSObject()
+    {
+        return dictionary;
+    }
+
+    /**
+     * This will set the border width in points, 0 = no border.
+     *
+     * @param w
+     *            float the width in points
+     */
+    public void setWidth( float w )
+    {
+        getDictionary().setFloat( "W", w );
+    }
+
+    /**
+     * This will retrieve the border width in points, 0 = no border.
+     *
+     * @return flaot the width of the border in points
+     */
+    public float getWidth()
+    {
+        return getDictionary().getFloat( "W", 1 );
+    }
+
+    /**
+     * This will set the border style, see the STYLE_* constants for valid values.
+     *
+     * @param s
+     *            the border style to use
+     */
+    public void setStyle( String s )
+    {
+        getDictionary().setName( "S", s );
+    }
+
+    /**
+     * This will retrieve the border style, see the STYLE_* constants for valid
+     * values.
+     *
+     * @return the style of the border
+     */
+    public String getStyle()
+    {
+        return getDictionary().getNameAsString( "S", STYLE_SOLID );
+    }
+
+    /**
+     * This will set the dash style used for drawing the border.
+     *
+     * @param d
+     *            the dash style to use
+     */
+    public void setDashStyle( PDLineDashPattern d )
+    {
+        COSArray array = null;
+        if( d != null )
+        {
+            array = d.getCOSDashPattern();
+        }
+        getDictionary().setItem( "D", array );
+    }
+
+    /**
+     * This will retrieve the dash style used for drawing the border.
+     *
+     * @return the dash style of the border
+     */
+    public PDLineDashPattern getDashStyle()
+    {
+        COSArray d = (COSArray) getDictionary().getDictionaryObject( "D" );
+        if (d == null)
+        {
+            d = new COSArray();
+            d.add( COSInteger.THREE );
+            getDictionary().setItem( "D", d );
+        }
+        return new PDLineDashPattern( d, 0 );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDExternalDataDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/PDExternalDataDictionary.java
new file mode 100644 (file)
index 0000000..066f9ed
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents an external data dictionary.
+ * 
+ * @version $Revision: 1.0 $
+ * 
+ */
+public class PDExternalDataDictionary implements COSObjectable
+{
+
+    private COSDictionary dataDictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDExternalDataDictionary()
+    {
+        this.dataDictionary = new COSDictionary();
+        this.dataDictionary.setName(COSName.TYPE, "ExData");
+    }
+
+    /**
+     * Constructor.
+     * 
+     *  @param dictionary Dictionary
+     */
+    public PDExternalDataDictionary(COSDictionary dictionary)
+    {
+        this.dataDictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.dataDictionary;
+    }
+
+    /**
+     * returns the dictionary.
+     *
+     * @return the dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return this.dataDictionary;
+    }
+
+    /**
+     * returns the type of the external data dictionary.
+     * It must be "ExData", if present
+     * @return the type of the external data dictionary
+     */
+    public String getType()
+    {
+        return this.getDictionary().getNameAsString(COSName.TYPE, "ExData");
+    }
+
+    /**
+     * returns the subtype of the external data dictionary.
+     * @return the subtype of the external data dictionary
+     */
+    public String getSubtype()
+    {
+        return this.getDictionary().getNameAsString(COSName.SUBTYPE);
+    }
+
+    /**
+     * This will set the subtype of the external data dictionary.
+     * @param subtype the subtype of the external data dictionary
+     */
+    public void setSubtype(String subtype)
+    {
+        this.getDictionary().setName(COSName.SUBTYPE, subtype);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/annotation/package.html
new file mode 100644 (file)
index 0000000..58cdca4
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The annotation package contains classes that work with PDF annotation elements.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature.java
new file mode 100644 (file)
index 0000000..71431ba
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.digitalsignature;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.pdfwriter.COSFilterInputStream;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents a digital signature that can be attached to a document.
+ * 
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Thomas Chojecki
+ * @version $Revision: 1.2 $
+ */
+public class PDSignature implements COSObjectable
+{
+
+  private COSDictionary dictionary;
+
+  /**
+   * A signature filter value.
+   */
+  public static final COSName FILTER_ADOBE_PPKLITE = COSName.ADOBE_PPKLITE;
+
+  /**
+   * A signature filter value.
+   */
+  public static final COSName FILTER_ENTRUST_PPKEF = COSName.ENTRUST_PPKEF;
+
+  /**
+   * A signature filter value.
+   */
+  public static final COSName FILTER_CICI_SIGNIT = COSName.CICI_SIGNIT;
+
+  /**
+   * A signature filter value.
+   */
+  public static final COSName FILTER_VERISIGN_PPKVS = COSName.VERISIGN_PPKVS;
+
+  /**
+   * A signature subfilter value.
+   */
+  public static final COSName SUBFILTER_ADBE_X509_RSA_SHA1 = COSName.ADBE_X509_RSA_SHA1;
+
+  /**
+   * A signature subfilter value.
+   */
+  public static final COSName SUBFILTER_ADBE_PKCS7_DETACHED = COSName.ADBE_PKCS7_DETACHED;
+
+  /**
+   * A signature subfilter value.
+   */
+  public static final COSName SUBFILTER_ETSI_CADES_DETACHED = COSName.getPDFName("ETSI.CAdES.detached");
+
+  /**
+   * A signature subfilter value.
+   */
+  public static final COSName SUBFILTER_ADBE_PKCS7_SHA1 = COSName.ADBE_PKCS7_SHA1;
+
+  /**
+   * Default constructor.
+   */
+  public PDSignature()
+  {
+    dictionary = new COSDictionary();
+    dictionary.setItem(COSName.TYPE, COSName.SIG);
+  }
+
+  /**
+   * Constructor.
+   * 
+   * @param dict The signature dictionary.
+   */
+  public PDSignature(COSDictionary dict)
+  {
+    dictionary = dict;
+  }
+
+  /**
+   * Convert this standard java object to a COS object.
+   * 
+   * @return The cos object that matches this Java object.
+   */
+  public COSBase getCOSObject()
+  {
+    return getDictionary();
+  }
+
+  /**
+   * Convert this standard java object to a COS dictionary.
+   * 
+   * @return The COS dictionary that matches this Java object.
+   */
+  public COSDictionary getDictionary()
+  {
+    return dictionary;
+  }
+
+  public void setFilter(COSName filter)
+  {
+    dictionary.setItem(COSName.FILTER, filter);
+  }
+
+  /**
+   * Set a subfilter that specify the signature that should be used. 
+   * 
+   * @param subfilter the subfilter that shall be used.
+   */
+  public void setSubFilter(COSName subfilter)
+  {
+    dictionary.setItem(COSName.SUBFILTER, subfilter);
+  }
+
+  public void setName(String name)
+  {
+    dictionary.setString(COSName.NAME, name);
+  }
+
+  public void setLocation(String location)
+  {
+    dictionary.setString(COSName.LOCATION, location);
+  }
+
+  public void setReason(String reason)
+  {
+    dictionary.setString(COSName.REASON, reason);
+  }
+
+  public void setSignDate(Calendar cal)
+  {
+    dictionary.setDate("M", cal);
+  }
+
+  public String getFilter()
+  {
+    return ((COSName)dictionary.getItem(COSName.FILTER)).getName();
+  }
+
+  public String getSubFilter()
+  {
+    return ((COSName)dictionary.getItem(COSName.SUBFILTER)).getName();
+  }
+
+  public String getName()
+  {
+    return dictionary.getString(COSName.NAME);
+  }
+
+  public String getLocation()
+  {
+    return dictionary.getString(COSName.LOCATION);
+  }
+
+  public String getReason()
+  {
+    return dictionary.getString(COSName.REASON);
+  }
+
+  public Calendar getSignDate()
+  {
+    try
+    {
+      return dictionary.getDate("M");
+    }
+    catch (IOException e)
+    {
+      return null;
+    }
+  }
+
+  public void setByteRange(int[] range) 
+  {
+    if (range.length!=4)
+      return;
+
+    COSArray ary = new COSArray();
+    for ( int i : range )
+    {
+      ary.add(COSInteger.get(i));
+    }
+    
+    dictionary.setItem("ByteRange", ary);
+  }
+
+  /**
+   * Read out the byterange from the file
+   * 
+   * @return a integer array with the byterange
+   */
+  public int[] getByteRange()
+  {
+    COSArray byteRange = (COSArray)dictionary.getDictionaryObject("ByteRange");
+    int[] ary = new int[byteRange.size()];
+    for (int i = 0; i<ary.length;++i)
+      ary[i] = byteRange.getInt(i);
+    
+    return ary;
+  }
+  
+  /**
+   * Will return the embedded signature between the byterange gap.
+   * 
+   * @param pdfFile The signed pdf file as InputStream
+   * @return a byte array containing the signature
+   * @throws IOException if the pdfFile can't be read
+   */
+  public byte[] getContents(InputStream pdfFile) throws IOException
+  {
+    int[] byteRange = getByteRange();
+    int begin = byteRange[0]+byteRange[1]+1;
+    int end = byteRange[2]-begin;
+    
+    return getContents(new COSFilterInputStream(pdfFile,new int[] {begin,end}));
+  }
+  
+  /**
+   * Will return the embedded signature between the byterange gap.
+   * 
+   * @param pdfFile The signed pdf file as byte array
+   * @return a byte array containing the signature
+   * @throws IOException if the pdfFile can't be read
+   */
+  public byte[] getContents(byte[] pdfFile) throws IOException
+  {
+    int[] byteRange = getByteRange();
+    int begin = byteRange[0]+byteRange[1]+1;
+    int end = byteRange[2]-begin;
+    
+    return getContents(new COSFilterInputStream(pdfFile,new int[] {begin,end}));
+  }
+
+  private byte[] getContents(COSFilterInputStream fis) throws IOException 
+  {
+    ByteArrayOutputStream byteOS = new ByteArrayOutputStream(1024);
+    byte[] buffer = new byte[1024];
+    int c;
+    while ((c = fis.read(buffer)) != -1)
+    {
+      // Filter < and (
+      if(buffer[0]==0x3C || buffer[0]==0x28)
+        byteOS.write(buffer, 1, c);
+      // Filter > and )
+      else if(buffer[c-1]==0x3E || buffer[c-1]==0x29)
+        byteOS.write(buffer, 0, c-1);
+      else 
+        byteOS.write(buffer, 0, c);
+    }
+    fis.close();
+    
+    return COSString.createFromHexString(byteOS.toString()).getBytes();
+  }
+  
+  public void setContents(byte[] bytes)
+  {
+    COSString string = new COSString(bytes);
+    string.setForceHexForm(true);
+    dictionary.setItem("Contents", string);
+  }
+  
+  /**
+   * Will return the signed content of the document.
+   * 
+   * @param pdfFile The signed pdf file as InputStream
+   * @return a byte array containing only the signed part of the content
+   * @throws IOException if the pdfFile can't be read
+   */
+  public byte[] getSignedContent(InputStream pdfFile) throws IOException
+  {
+    COSFilterInputStream fis=null;
+    
+    try 
+    {
+    fis = new COSFilterInputStream(pdfFile,getByteRange());
+    return fis.toByteArray();
+    } 
+    finally 
+    {
+      if (fis != null)
+        fis.close();
+    }
+  }
+  
+  /**
+   * Will return the signed content of the document.
+   * 
+   * @param pdfFile The signed pdf file as byte array
+   * @return a byte array containing only the signed part of the content
+   * @throws IOException if the pdfFile can't be read
+   */
+  public byte[] getSignedContent(byte[] pdfFile) throws IOException
+  {
+    COSFilterInputStream fis=null;
+    
+    try 
+    {
+    fis = new COSFilterInputStream(pdfFile,getByteRange());
+    return fis.toByteArray();
+    } 
+    finally 
+    {
+      if (fis != null)
+        fis.close();
+    }
+  }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureInterface.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureInterface.java
new file mode 100644 (file)
index 0000000..c264399
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.digitalsignature;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.pdfbox.exceptions.SignatureException;
+
+/**
+ * Providing an interface for accessing necessary functions for signing a pdf document.
+ * 
+ * @author <a href="mailto:mail@thomas-chojecki.de">Thomas Chojecki</a>
+ * @version $
+ */
+public interface SignatureInterface
+{
+
+  /**
+   * Creates a cms signature for the given content
+   * 
+   * @param content is the content as a (Filter)InputStream
+   * @return signature as a byte array
+   */
+  public byte[] sign (InputStream content) throws SignatureException, IOException;
+  
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java
new file mode 100644 (file)
index 0000000..918ad53
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.interactive.digitalsignature;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.pdfparser.VisualSignatureParser;
+
+
+public class SignatureOptions
+{
+    private COSDocument visualSignature;
+
+    private int preferedSignatureSize;
+    
+    private int pageNo;
+  
+    /**
+     * Set the page number.
+     * 
+     * @param pageNo the page number
+     * 
+     */
+    public void setPage(int pageNo)
+    {
+        this.pageNo = pageNo;
+    }
+  
+    /**
+     * Get the page number.
+     * 
+     * @return the page number
+     */
+    public int getPage() 
+    {
+        return pageNo;
+    }
+  
+    /**
+     * Reads the visual signature from the given input stream.
+     *  
+     * @param is the input stream containing the visual signature
+     * 
+     * @throws IOException when something went wrong during parsing 
+     */
+    public void setVisualSignature(InputStream is) throws IOException
+    { 
+        VisualSignatureParser visParser = new VisualSignatureParser(is);
+        visParser.parse();
+        visualSignature = visParser.getDocument();
+    }
+
+    /**
+     * Get the visual signature.
+     * 
+     * @return the visual signature
+     */
+    public COSDocument getVisualSignature()
+    {
+        return visualSignature;
+    }
+  
+    /**
+     * Get the preferred size of the signature.
+     * 
+     * @return the preferred size
+     */
+    public int getPreferedSignatureSize()
+    {
+      return preferedSignatureSize;
+    }
+  
+    /**
+     * Set the preferred size of the signature.
+     * 
+     * @param size the size of the signature
+     */
+    public void setPreferedSignatureSize(int size)
+    {
+        if (size > 0)
+        {
+            preferedSignatureSize = size;
+        }
+    } 
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/digitalsignature/package.html
new file mode 100644 (file)
index 0000000..0f75d8d
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The digitial signature library will manage signatures that are stored in the PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDDestination.java
new file mode 100644 (file)
index 0000000..9ff99fc
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
+
+/**
+ * This represents a destination in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public abstract class PDDestination implements PDDestinationOrAction
+{
+
+    /**
+     * This will create a new destination depending on the type of COSBase
+     * that is passed in.
+     *
+     * @param base The base level object.
+     *
+     * @return A new destination.
+     *
+     * @throws IOException If the base cannot be converted to a Destination.
+     */
+    public static PDDestination create( COSBase base ) throws IOException
+    {
+        PDDestination retval = null;
+        if( base == null )
+        {
+            //this is ok, just return null.
+        }
+        else if( base instanceof COSArray && ((COSArray)base).size() > 0 )
+        {
+            COSArray array = (COSArray)base;
+            COSName type = (COSName)array.getObject( 1 );
+            String typeString = type.getName();
+            if( typeString.equals( PDPageFitDestination.TYPE ) ||
+                typeString.equals( PDPageFitDestination.TYPE_BOUNDED ))
+            {
+                retval = new PDPageFitDestination( array );
+            }
+            else if( typeString.equals( PDPageFitHeightDestination.TYPE ) ||
+                     typeString.equals( PDPageFitHeightDestination.TYPE_BOUNDED ))
+            {
+                retval = new PDPageFitHeightDestination( array );
+            }
+            else if( typeString.equals( PDPageFitRectangleDestination.TYPE ) )
+            {
+                retval = new PDPageFitRectangleDestination( array );
+            }
+            else if( typeString.equals( PDPageFitWidthDestination.TYPE ) ||
+                     typeString.equals( PDPageFitWidthDestination.TYPE_BOUNDED ))
+            {
+                retval = new PDPageFitWidthDestination( array );
+            }
+            else if( typeString.equals( PDPageXYZDestination.TYPE ) )
+            {
+                retval = new PDPageXYZDestination( array );
+            }
+            else
+            {
+                throw new IOException( "Unknown destination type:" + type );
+            }
+        }
+        else if( base instanceof COSString )
+        {
+            retval = new PDNamedDestination( (COSString)base );
+        }
+        else if( base instanceof COSName )
+        {
+            retval = new PDNamedDestination( (COSName)base );
+        }
+        else
+        {
+            throw new IOException( "Error: can't convert to Destination " + base );
+        }
+        return retval;
+    }
+
+    /**
+     * Return a string representation of this class.
+     *
+     * @return A human readable string.
+     */
+    public String toString()
+    {
+        return super.toString();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDNamedDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDNamedDestination.java
new file mode 100644 (file)
index 0000000..c6b1633
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This represents a destination to a page by referencing it with a name.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDNamedDestination extends PDDestination
+{
+    private COSBase namedDestination;
+
+    /**
+     * Constructor.
+     *
+     * @param dest The named destination.
+     */
+    public PDNamedDestination( COSString dest )
+    {
+        namedDestination = dest;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param dest The named destination.
+     */
+    public PDNamedDestination( COSName dest )
+    {
+        namedDestination = dest;
+    }
+
+    /**
+     * Default constructor.
+     */
+    public PDNamedDestination()
+    {
+        //default, so do nothing
+    }
+
+    /**
+     * Default constructor.
+     *
+     * @param dest The named destination.
+     */
+    public PDNamedDestination( String dest )
+    {
+        namedDestination = new COSString( dest );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return namedDestination;
+    }
+
+    /**
+     * This will get the name of the destination.
+     *
+     * @return The name of the destination.
+     */
+    public String getNamedDestination()
+    {
+        String retval = null;
+        if( namedDestination instanceof COSString )
+        {
+            retval = ((COSString)namedDestination).getString();
+        }
+        else if( namedDestination instanceof COSName )
+        {
+            retval = ((COSName)namedDestination).getName();
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the named destination.
+     *
+     * @param dest The new named destination.
+     *
+     * @throws IOException If there is an error setting the named destination.
+     */
+    public void setNamedDestination( String dest ) throws IOException
+    {
+        if( namedDestination instanceof COSString )
+        {
+            COSString string = ((COSString)namedDestination);
+            string.reset();
+            string.append( dest.getBytes("ISO-8859-1") );
+        }
+        else if( dest == null )
+        {
+            namedDestination = null;
+        }
+        else
+        {
+            namedDestination = new COSString( dest );
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageDestination.java
new file mode 100644 (file)
index 0000000..02ee542
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageNode;
+
+/**
+ * This represents a destination to a page, see subclasses for specific parameters.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public abstract class PDPageDestination extends PDDestination
+{
+    /**
+     * Storage for the page destination.
+     */
+    protected COSArray array;
+
+    /**
+     * Constructor to create empty page destination.
+     *
+     */
+    protected PDPageDestination()
+    {
+        array = new COSArray();
+    }
+
+    /**
+     * Constructor to create empty page destination.
+     *
+     * @param arr A page destination array.
+     */
+    protected PDPageDestination( COSArray arr )
+    {
+        array = arr;
+    }
+
+    /**
+     * This will get the page for this destination.  A page destination
+     * can either reference a page or a page number(when doing a remote destination to
+     * another PDF).  If this object is referencing by page number then this method will
+     * return null and getPageNumber should be used.
+     *
+     * @return The page for this destination.
+     */
+    public PDPage getPage()
+    {
+        PDPage retval = null;
+        if( array.size() > 0 )
+        {
+            COSBase page = array.getObject( 0 );
+            if( page instanceof COSDictionary )
+            {
+                retval = new PDPage( (COSDictionary)page );
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Set the page for this destination.
+     *
+     * @param page The page for the destination.
+     */
+    public void setPage( PDPage page )
+    {
+        array.set( 0, page );
+    }
+
+    /**
+     * This will get the page number for this destination.  A page destination
+     * can either reference a page or a page number(when doing a remote destination to
+     * another PDF).  If this object is referencing by page number then this method will
+     * return that number, otherwise -1 will be returned.
+     *
+     * @return The page number for this destination.
+     */
+    public int getPageNumber()
+    {
+        int retval = -1;
+        if( array.size() > 0 )
+        {
+            COSBase page = array.getObject( 0 );
+            if( page instanceof COSNumber )
+            {
+                retval = ((COSNumber)page).intValue();
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Returns the page number for this destination, regardless of whether
+     * this is a page number or a reference to a page.
+     *
+     * @since Apache PDFBox 1.0.0
+     * @see PDOutlineItem
+     * @return page number, or -1 if the destination type is unknown
+     */
+    public int findPageNumber() {
+        int retval = -1;
+        if( array.size() > 0 )
+        {
+            COSBase page = array.getObject( 0 );
+            if( page instanceof COSNumber )
+            {
+                retval = ((COSNumber)page).intValue();
+            }
+            else if (page instanceof COSDictionary)
+            {
+                COSBase parent = page;
+                while (((COSDictionary) parent).getDictionaryObject("Parent", "P") != null) {
+                    parent = ((COSDictionary) parent).getDictionaryObject( "Parent", "P" );
+                }
+                // now parent is the pages node
+                PDPageNode pages = new PDPageNode((COSDictionary) parent);
+                List<PDPage> allPages = new ArrayList<PDPage>();
+                pages.getAllKids(allPages);
+                retval = allPages.indexOf(new PDPage((COSDictionary) page)) + 1;
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Set the page number for this destination.
+     *
+     * @param pageNumber The page for the destination.
+     */
+    public void setPageNumber( int pageNumber )
+    {
+        array.set( 0, pageNumber );
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return array;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSArray getCOSArray()
+    {
+        return array;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitDestination.java
new file mode 100644 (file)
index 0000000..92d20c7
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+
+/**
+ * This represents a destination to a page and the page contents will be magnified to just
+ * fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitDestination extends PDPageDestination
+{
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE = "Fit";
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE_BOUNDED = "FitB";
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDPageFitDestination()
+    {
+        super();
+        array.growToSize(2);
+        array.setName( 1, TYPE );
+
+    }
+
+    /**
+     * Constructor from an existing destination array.
+     *
+     * @param arr The destination array.
+     */
+    public PDPageFitDestination( COSArray arr )
+    {
+        super( arr );
+    }
+
+    /**
+     * A flag indicating if this page destination should just fit bounding box of the PDF.
+     *
+     * @return true If the destination should fit just the bounding box.
+     */
+    public boolean fitBoundingBox()
+    {
+        return TYPE_BOUNDED.equals( array.getName( 1 ) );
+    }
+
+    /**
+     * Set if this page destination should just fit the bounding box.  The default is false.
+     *
+     * @param fitBoundingBox A flag indicating if this should fit the bounding box.
+     */
+    public void setFitBoundingBox( boolean fitBoundingBox )
+    {
+        array.growToSize( 2 );
+        if( fitBoundingBox )
+        {
+            array.setName( 1, TYPE_BOUNDED );
+        }
+        else
+        {
+            array.setName( 1, TYPE );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitHeightDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitHeightDestination.java
new file mode 100644 (file)
index 0000000..5cc6bcc
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at a x location and the height is magnified
+ * to just fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitHeightDestination extends PDPageDestination
+{
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE = "FitV";
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE_BOUNDED = "FitBV";
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDPageFitHeightDestination()
+    {
+        super();
+        array.growToSize(3);
+        array.setName( 1, TYPE );
+
+    }
+
+    /**
+     * Constructor from an existing destination array.
+     *
+     * @param arr The destination array.
+     */
+    public PDPageFitHeightDestination( COSArray arr )
+    {
+        super( arr );
+    }
+
+    /**
+     * Get the left x coordinate.  A return value of -1 implies that the current x-coordinate
+     * will be used.
+     *
+     * @return The left x coordinate.
+     */
+    public int getLeft()
+    {
+        return array.getInt( 2 );
+    }
+
+    /**
+     * Set the left x-coordinate, a value of -1 implies that the current x-coordinate
+     * will be used.
+     * @param x The left x coordinate.
+     */
+    public void setLeft( int x )
+    {
+        array.growToSize( 3 );
+        if( x == -1 )
+        {
+            array.set( 2, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 2, x );
+        }
+    }
+
+    /**
+     * A flag indicating if this page destination should just fit bounding box of the PDF.
+     *
+     * @return true If the destination should fit just the bounding box.
+     */
+    public boolean fitBoundingBox()
+    {
+        return TYPE_BOUNDED.equals( array.getName( 1 ) );
+    }
+
+    /**
+     * Set if this page destination should just fit the bounding box.  The default is false.
+     *
+     * @param fitBoundingBox A flag indicating if this should fit the bounding box.
+     */
+    public void setFitBoundingBox( boolean fitBoundingBox )
+    {
+        array.growToSize( 2 );
+        if( fitBoundingBox )
+        {
+            array.setName( 1, TYPE_BOUNDED );
+        }
+        else
+        {
+            array.setName( 1, TYPE );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitRectangleDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitRectangleDestination.java
new file mode 100644 (file)
index 0000000..718b1bf
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at a y location and the width is magnified
+ * to just fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitRectangleDestination extends PDPageDestination
+{
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE = "FitR";
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDPageFitRectangleDestination()
+    {
+        super();
+        array.growToSize(6);
+        array.setName( 1, TYPE );
+
+    }
+
+    /**
+     * Constructor from an existing destination array.
+     *
+     * @param arr The destination array.
+     */
+    public PDPageFitRectangleDestination( COSArray arr )
+    {
+        super( arr );
+    }
+
+    /**
+     * Get the left x coordinate.  A return value of -1 implies that the current x-coordinate
+     * will be used.
+     *
+     * @return The left x coordinate.
+     */
+    public int getLeft()
+    {
+        return array.getInt( 2 );
+    }
+
+    /**
+     * Set the left x-coordinate, a value of -1 implies that the current x-coordinate
+     * will be used.
+     * @param x The left x coordinate.
+     */
+    public void setLeft( int x )
+    {
+        array.growToSize( 3 );
+        if( x == -1 )
+        {
+            array.set( 2, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 2, x );
+        }
+    }
+
+    /**
+     * Get the bottom y coordinate.  A return value of -1 implies that the current y-coordinate
+     * will be used.
+     *
+     * @return The bottom y coordinate.
+     */
+    public int getBottom()
+    {
+        return array.getInt( 3 );
+    }
+
+    /**
+     * Set the bottom y-coordinate, a value of -1 implies that the current y-coordinate
+     * will be used.
+     * @param y The bottom y coordinate.
+     */
+    public void setBottom( int y )
+    {
+        array.growToSize( 6 );
+        if( y == -1 )
+        {
+            array.set( 3, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 3, y );
+        }
+    }
+
+    /**
+     * Get the right x coordinate.  A return value of -1 implies that the current x-coordinate
+     * will be used.
+     *
+     * @return The right x coordinate.
+     */
+    public int getRight()
+    {
+        return array.getInt( 4 );
+    }
+
+    /**
+     * Set the right x-coordinate, a value of -1 implies that the current x-coordinate
+     * will be used.
+     * @param x The right x coordinate.
+     */
+    public void setRight( int x )
+    {
+        array.growToSize( 6 );
+        if( x == -1 )
+        {
+            array.set( 4, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 4, x );
+        }
+    }
+
+
+    /**
+     * Get the top y coordinate.  A return value of -1 implies that the current y-coordinate
+     * will be used.
+     *
+     * @return The top y coordinate.
+     */
+    public int getTop()
+    {
+        return array.getInt( 5 );
+    }
+
+    /**
+     * Set the top y-coordinate, a value of -1 implies that the current y-coordinate
+     * will be used.
+     * @param y The top ycoordinate.
+     */
+    public void setTop( int y )
+    {
+        array.growToSize( 6 );
+        if( y == -1 )
+        {
+            array.set( 5, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 5, y );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitWidthDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageFitWidthDestination.java
new file mode 100644 (file)
index 0000000..4b9b23b
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at a y location and the width is magnified
+ * to just fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitWidthDestination extends PDPageDestination
+{
+
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE = "FitH";
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE_BOUNDED = "FitBH";
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDPageFitWidthDestination()
+    {
+        super();
+        array.growToSize(3);
+        array.setName( 1, TYPE );
+
+    }
+
+    /**
+     * Constructor from an existing destination array.
+     *
+     * @param arr The destination array.
+     */
+    public PDPageFitWidthDestination( COSArray arr )
+    {
+        super( arr );
+    }
+
+
+    /**
+     * Get the top y coordinate.  A return value of -1 implies that the current y-coordinate
+     * will be used.
+     *
+     * @return The top y coordinate.
+     */
+    public int getTop()
+    {
+        return array.getInt( 2 );
+    }
+
+    /**
+     * Set the top y-coordinate, a value of -1 implies that the current y-coordinate
+     * will be used.
+     * @param y The top ycoordinate.
+     */
+    public void setTop( int y )
+    {
+        array.growToSize( 3 );
+        if( y == -1 )
+        {
+            array.set( 2, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 2, y );
+        }
+    }
+
+    /**
+     * A flag indicating if this page destination should just fit bounding box of the PDF.
+     *
+     * @return true If the destination should fit just the bounding box.
+     */
+    public boolean fitBoundingBox()
+    {
+        return TYPE_BOUNDED.equals( array.getName( 1 ) );
+    }
+
+    /**
+     * Set if this page destination should just fit the bounding box.  The default is false.
+     *
+     * @param fitBoundingBox A flag indicating if this should fit the bounding box.
+     */
+    public void setFitBoundingBox( boolean fitBoundingBox )
+    {
+        array.growToSize( 2 );
+        if( fitBoundingBox )
+        {
+            array.setName( 1, TYPE_BOUNDED );
+        }
+        else
+        {
+            array.setName( 1, TYPE );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageXYZDestination.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageXYZDestination.java
new file mode 100644 (file)
index 0000000..c76368d
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at an x,y coordinate with a zoom setting.
+ * The default x,y,z will be whatever is the current value in the viewer application and
+ * are not required.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageXYZDestination extends PDPageDestination
+{
+    /**
+     * The type of this destination.
+     */
+    protected static final String TYPE = "XYZ";
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDPageXYZDestination()
+    {
+        super();
+        array.growToSize(5);
+        array.setName( 1, TYPE );
+
+    }
+
+    /**
+     * Constructor from an existing destination array.
+     *
+     * @param arr The destination array.
+     */
+    public PDPageXYZDestination( COSArray arr )
+    {
+        super( arr );
+    }
+
+    /**
+     * Get the left x coordinate.  A return value of -1 implies that the current x-coordinate
+     * will be used.
+     *
+     * @return The left x coordinate.
+     */
+    public int getLeft()
+    {
+        return array.getInt( 2 );
+    }
+
+    /**
+     * Set the left x-coordinate, a value of -1 implies that the current x-coordinate
+     * will be used.
+     * @param x The left x coordinate.
+     */
+    public void setLeft( int x )
+    {
+        array.growToSize( 3 );
+        if( x == -1 )
+        {
+            array.set( 2, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 2, x );
+        }
+    }
+
+    /**
+     * Get the top y coordinate.  A return value of -1 implies that the current y-coordinate
+     * will be used.
+     *
+     * @return The top y coordinate.
+     */
+    public int getTop()
+    {
+        return array.getInt( 3 );
+    }
+
+    /**
+     * Set the top y-coordinate, a value of -1 implies that the current y-coordinate
+     * will be used.
+     * @param y The top ycoordinate.
+     */
+    public void setTop( int y )
+    {
+        array.growToSize( 4 );
+        if( y == -1 )
+        {
+            array.set( 3, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 3, y );
+        }
+    }
+
+    /**
+     * Get the zoom value.  A return value of -1 implies that the current zoom
+     * will be used.
+     *
+     * @return The zoom value for the page.
+     */
+    public int getZoom()
+    {
+        return array.getInt( 4 );
+    }
+
+    /**
+     * Set the zoom value for the page, a value of -1 implies that the current zoom
+     * will be used.
+     * @param zoom The zoom value.
+     */
+    public void setZoom( int zoom )
+    {
+        array.growToSize( 5 );
+        if( zoom == -1 )
+        {
+            array.set( 4, (COSBase)null );
+        }
+        else
+        {
+            array.setInt( 4, zoom );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/destination/package.html
new file mode 100644 (file)
index 0000000..4de0c18
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The destination package allows destinations into a pdf document to be specified.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDDocumentOutline.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDDocumentOutline.java
new file mode 100644 (file)
index 0000000..550cfa1
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.outline;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This represents an outline in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDDocumentOutline extends PDOutlineNode
+{
+
+    /**
+     * Default Constructor.
+     */
+    public PDDocumentOutline()
+    {
+        super();
+        node.setName( "Type", "Outlines" );
+    }
+
+    /**
+     * Constructor for an existing document outline.
+     *
+     * @param dic The storage dictionary.
+     */
+    public PDDocumentOutline( COSDictionary dic )
+    {
+        super( dic );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineItem.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineItem.java
new file mode 100644 (file)
index 0000000..c52854f
--- /dev/null
@@ -0,0 +1,418 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.outline;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.exceptions.OutlineNotLocalException;
+import org.apache.pdfbox.pdmodel.PDDestinationNameTreeNode;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionGoTo;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDNamedDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+/**
+ * This represents an outline in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PDOutlineItem extends PDOutlineNode
+{
+
+    private static final int ITALIC_FLAG = 1;
+    private static final int BOLD_FLAG = 2;
+
+    /**
+     * Default Constructor.
+     */
+    public PDOutlineItem()
+    {
+        super();
+    }
+
+    /**
+     * Constructor for an existing outline item.
+     *
+     * @param dic The storage dictionary.
+     */
+    public PDOutlineItem( COSDictionary dic )
+    {
+        super( dic );
+    }
+
+    /**
+     * Insert a sibling after this node.
+     *
+     * @param item The item to insert.
+     */
+    public void insertSiblingAfter( PDOutlineItem item )
+    {
+        item.setParent( getParent() );
+        PDOutlineItem next = getNextSibling();
+        setNextSibling( item );
+        item.setPreviousSibling( this );
+        if( next != null )
+        {
+            item.setNextSibling( next );
+            next.setPreviousSibling( item );
+        }
+        updateParentOpenCount( 1 );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PDOutlineNode getParent()
+    {
+        return super.getParent();
+    }
+
+    /**
+     * Return the previous sibling or null if there is no sibling.
+     *
+     * @return The previous sibling.
+     */
+    public PDOutlineItem getPreviousSibling()
+    {
+        PDOutlineItem last = null;
+        COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( COSName.PREV );
+        if( lastDic != null )
+        {
+            last = new PDOutlineItem( lastDic );
+        }
+        return last;
+    }
+
+    /**
+     * Set the previous sibling, this will be maintained by this class.
+     *
+     * @param outlineNode The new previous sibling.
+     */
+    protected void setPreviousSibling( PDOutlineNode outlineNode )
+    {
+        node.setItem( COSName.PREV, outlineNode );
+    }
+
+    /**
+     * Return the next sibling or null if there is no next sibling.
+     *
+     * @return The next sibling.
+     */
+    public PDOutlineItem getNextSibling()
+    {
+        PDOutlineItem last = null;
+        COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( COSName.NEXT );
+        if( lastDic != null )
+        {
+            last = new PDOutlineItem( lastDic );
+        }
+        return last;
+    }
+
+    /**
+     * Set the next sibling, this will be maintained by this class.
+     *
+     * @param outlineNode The new next sibling.
+     */
+    protected void setNextSibling( PDOutlineNode outlineNode )
+    {
+        node.setItem( COSName.NEXT, outlineNode );
+    }
+
+    /**
+     * Get the title of this node.
+     *
+     * @return The title of this node.
+     */
+    public String getTitle()
+    {
+        return node.getString( COSName.TITLE );
+    }
+
+    /**
+     * Set the title for this node.
+     *
+     * @param title The new title for this node.
+     */
+    public void setTitle( String title )
+    {
+        node.setString( COSName.TITLE, title );
+    }
+
+    /**
+     * Get the page destination of this node.
+     *
+     * @return The page destination of this node.
+     * @throws IOException If there is an error creating the destination.
+     */
+    public PDDestination getDestination() throws IOException
+    {
+        return PDDestination.create( node.getDictionaryObject( COSName.DEST ) );
+    }
+
+    /**
+     * Set the page destination for this node.
+     *
+     * @param dest The new page destination for this node.
+     */
+    public void setDestination( PDDestination dest )
+    {
+        node.setItem( COSName.DEST, dest );
+    }
+
+    /**
+     * A convenience method that will create an XYZ destination using only the defaults.
+     *
+     * @param page The page to refer to.
+     */
+    public void setDestination( PDPage page )
+    {
+        PDPageXYZDestination dest = null;
+        if( page != null )
+        {
+            dest = new PDPageXYZDestination();
+            dest.setPage( page );
+        }
+        setDestination( dest );
+    }
+
+    /**
+     * This method will attempt to find the page in this PDF document that this outline points to.
+     * If the outline does not point to anything then this method will return null.  If the outline
+     * is an action that is not a GoTo action then this methods will throw the OutlineNotLocationException
+     *
+     * @param doc The document to get the page from.
+     *
+     * @return The page that this outline will go to when activated or null if it does not point to anything.
+     * @throws IOException If there is an error when trying to find the page.
+     */
+    public PDPage findDestinationPage( PDDocument doc ) throws IOException
+    {
+        PDPage page = null;
+        PDDestination rawDest = getDestination();
+        if( rawDest == null )
+        {
+            PDAction outlineAction = getAction();
+            if( outlineAction instanceof PDActionGoTo )
+            {
+                rawDest = ((PDActionGoTo)outlineAction).getDestination();
+            }
+            else if( outlineAction == null )
+            {
+                //if the outline action is null then this outline does not refer
+                //to anything and we will just return null.
+            }
+            else
+            {
+                throw new OutlineNotLocalException( "Error: Outline does not reference a local page." );
+            }
+        }
+
+        PDPageDestination pageDest = null;
+        if( rawDest instanceof PDNamedDestination )
+        {
+            //if we have a named destination we need to lookup the PDPageDestination
+            PDNamedDestination namedDest = (PDNamedDestination)rawDest;
+            PDDocumentNameDictionary namesDict = doc.getDocumentCatalog().getNames();
+            if( namesDict != null )
+            {
+                PDDestinationNameTreeNode destsTree = namesDict.getDests();
+                if( destsTree != null )
+                {
+                    pageDest = (PDPageDestination)destsTree.getValue( namedDest.getNamedDestination() );
+                }
+            }
+        }
+        else if( rawDest instanceof PDPageDestination)
+        {
+            pageDest = (PDPageDestination) rawDest;
+        }
+        else if( rawDest == null )
+        {
+            //if the destination is null then we will simply return a null page.
+        }
+        else
+        {
+            throw new IOException( "Error: Unknown destination type " + rawDest );
+        }
+
+        if( pageDest != null )
+        {
+            page = pageDest.getPage();
+            if( page == null )
+            {
+                int pageNumber = pageDest.getPageNumber();
+                if( pageNumber != -1 )
+                {
+                    List allPages = doc.getDocumentCatalog().getAllPages();
+                    page = (PDPage)allPages.get( pageNumber );
+                }
+            }
+        }
+
+        return page;
+    }
+
+    /**
+     * Get the action of this node.
+     *
+     * @return The action of this node.
+     */
+    public PDAction getAction()
+    {
+        return PDActionFactory.createAction( (COSDictionary)node.getDictionaryObject( COSName.A ) );
+    }
+
+    /**
+     * Set the action for this node.
+     *
+     * @param action The new action for this node.
+     */
+    public void setAction( PDAction action )
+    {
+        node.setItem( COSName.A, action );
+    }
+
+    /**
+     * Get the structure element of this node.
+     *
+     * @return The structure element of this node.
+     */
+    public PDStructureElement getStructureElement()
+    {
+        PDStructureElement se = null;
+        COSDictionary dic = (COSDictionary)node.getDictionaryObject( COSName.SE );
+        if( dic != null )
+        {
+            se = new PDStructureElement( dic );
+        }
+        return se;
+    }
+
+    /**
+     * Set the structure element for this node.
+     *
+     * @param structureElement The new structure element for this node.
+     */
+    public void setStructuredElement( PDStructureElement structureElement )
+    {
+        node.setItem( COSName.SE, structureElement );
+    }
+
+    /**
+     * Get the text color of this node.  Default is black and this method
+     * will never return null.
+     *
+     * @return The structure element of this node.
+     */
+    public PDColorState getTextColor()
+    {
+        PDColorState retval = null;
+        COSArray csValues = (COSArray)node.getDictionaryObject( COSName.C );
+        if( csValues == null )
+        {
+            csValues = new COSArray();
+            csValues.growToSize( 3, new COSFloat( 0 ) );
+            node.setItem( COSName.C, csValues );
+        }
+        retval = new PDColorState(csValues);
+        retval.setColorSpace( PDDeviceRGB.INSTANCE );
+        return retval;
+    }
+
+    /**
+     * Set the text color for this node.  The colorspace must be a PDDeviceRGB.
+     *
+     * @param textColor The text color for this node.
+     */
+    public void setTextColor( PDColorState textColor )
+    {
+        node.setItem( COSName.C, textColor.getCOSColorSpaceValue() );
+    }
+
+    /**
+     * Set the text color for this node.  The colorspace must be a PDDeviceRGB.
+     *
+     * @param textColor The text color for this node.
+     */
+    public void setTextColor( Color textColor )
+    {
+        COSArray array = new COSArray();
+        array.add( new COSFloat( textColor.getRed()/255f));
+        array.add( new COSFloat( textColor.getGreen()/255f));
+        array.add( new COSFloat( textColor.getBlue()/255f));
+        node.setItem( COSName.C, array );
+    }
+
+    /**
+     * A flag telling if the text should be italic.
+     *
+     * @return The italic flag.
+     */
+    public boolean isItalic()
+    {
+        return BitFlagHelper.getFlag( node, COSName.F, ITALIC_FLAG );
+    }
+
+    /**
+     * Set the italic property of the text.
+     *
+     * @param italic The new italic flag.
+     */
+    public void setItalic( boolean italic )
+    {
+        BitFlagHelper.setFlag( node, COSName.F, ITALIC_FLAG, italic );
+    }
+
+    /**
+     * A flag telling if the text should be bold.
+     *
+     * @return The bold flag.
+     */
+    public boolean isBold()
+    {
+        return BitFlagHelper.getFlag( node, COSName.F, BOLD_FLAG );
+    }
+
+    /**
+     * Set the bold property of the text.
+     *
+     * @param bold The new bold flag.
+     */
+    public void setBold( boolean bold )
+    {
+        BitFlagHelper.setFlag( node, COSName.F, BOLD_FLAG, bold );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineNode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineNode.java
new file mode 100644 (file)
index 0000000..d13ab33
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.documentnavigation.outline;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an node in an outline in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDOutlineNode implements COSObjectable
+{
+    /**
+     * The dictionary for this node.
+     */
+    protected COSDictionary node;
+
+    /**
+     * Default Constructor.
+     */
+    public PDOutlineNode()
+    {
+        node = new COSDictionary();
+    }
+
+    /**
+     * Default Constructor.
+     *
+     * @param dict The dictionary storage.
+     */
+    public PDOutlineNode( COSDictionary dict)
+    {
+        node = dict;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return node;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSDictionary getCOSDictionary()
+    {
+        return node;
+    }
+
+    /**
+     * Get the parent of this object.  This will either be a DocumentOutline or an OutlineItem.
+     *
+     * @return The parent of this object, or null if this is the document outline and there
+     * is no parent.
+     */
+    protected PDOutlineNode getParent()
+    {
+        PDOutlineNode retval = null;
+        COSDictionary parent = (COSDictionary)node.getDictionaryObject( "Parent", "P" );
+        if( parent != null )
+        {
+            if( parent.getDictionaryObject( "Parent", "P" ) == null )
+            {
+                retval = new PDDocumentOutline( parent );
+            }
+            else
+            {
+                retval = new PDOutlineItem( parent );
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the parent of this object, this is maintained by these objects and should not
+     * be called by any clients of PDFBox code.
+     *
+     * @param parent The parent of this object.
+     */
+    protected void setParent( PDOutlineNode parent )
+    {
+        node.setItem( "Parent", parent );
+    }
+
+    /**
+     * append a child node to this node.
+     *
+     * @param outlineNode The node to add.
+     */
+    public void appendChild( PDOutlineItem outlineNode )
+    {
+        outlineNode.setParent( this );
+        if( getFirstChild() == null )
+        {
+            int currentOpenCount = getOpenCount();
+            setFirstChild( outlineNode );
+            //1 for the the item we are adding;
+            int numberOfOpenNodesWeAreAdding = 1;
+            if( outlineNode.isNodeOpen() )
+            {
+                numberOfOpenNodesWeAreAdding += outlineNode.getOpenCount();
+            }
+            if( isNodeOpen() )
+            {
+                setOpenCount( currentOpenCount + numberOfOpenNodesWeAreAdding );
+            }
+            else
+            {
+                setOpenCount( currentOpenCount - numberOfOpenNodesWeAreAdding );
+            }
+            updateParentOpenCount( numberOfOpenNodesWeAreAdding );
+        }
+        else
+        {
+            PDOutlineItem previousLastChild = getLastChild();
+            previousLastChild.insertSiblingAfter( outlineNode );
+        }
+        
+        PDOutlineItem lastNode = outlineNode;
+        while(lastNode.getNextSibling() != null)
+        {
+            lastNode = lastNode.getNextSibling();
+        }
+        setLastChild( lastNode );
+    }
+
+    /**
+     * Return the first child or null if there is no child.
+     *
+     * @return The first child.
+     */
+    public PDOutlineItem getFirstChild()
+    {
+        PDOutlineItem last = null;
+        COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "First" );
+        if( lastDic != null )
+        {
+            last = new PDOutlineItem( lastDic );
+        }
+        return last;
+    }
+
+    /**
+     * Set the first child, this will be maintained by this class.
+     *
+     * @param outlineNode The new first child.
+     */
+    protected void setFirstChild( PDOutlineNode outlineNode )
+    {
+        node.setItem( "First", outlineNode );
+    }
+
+    /**
+     * Return the last child or null if there is no child.
+     *
+     * @return The last child.
+     */
+    public PDOutlineItem getLastChild()
+    {
+        PDOutlineItem last = null;
+        COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "Last" );
+        if( lastDic != null )
+        {
+            last = new PDOutlineItem( lastDic );
+        }
+        return last;
+    }
+
+    /**
+     * Set the last child, this will be maintained by this class.
+     *
+     * @param outlineNode The new last child.
+     */
+    protected void setLastChild( PDOutlineNode outlineNode )
+    {
+        node.setItem( "Last", outlineNode );
+    }
+
+    /**
+     * Get the number of open nodes.  Or a negative number if this node
+     * is closed.  See PDF Reference for more details.  This value
+     * is updated as you append children and siblings.
+     *
+     * @return The Count attribute of the outline dictionary.
+     */
+    public int getOpenCount()
+    {
+        return node.getInt( "Count", 0 );
+    }
+
+    /**
+     * Set the open count.  This number is automatically managed for you
+     * when you add items to the outline.
+     *
+     * @param openCount The new open cound.
+     */
+    protected void setOpenCount( int openCount )
+    {
+        node.setInt( "Count", openCount );
+    }
+
+    /**
+     * This will set this node to be open when it is shown in the viewer.  By default, when
+     * a new node is created it will be closed.
+     * This will do nothing if the node is already open.
+     */
+    public void openNode()
+    {
+        //if the node is already open then do nothing.
+        if( !isNodeOpen() )
+        {
+            int openChildrenCount = 0;
+            PDOutlineItem currentChild = getFirstChild();
+            while( currentChild != null )
+            {
+                //first increase by one for the current child
+                openChildrenCount++;
+                //then increase by the number of open nodes the child has
+                if( currentChild.isNodeOpen() )
+                {
+                    openChildrenCount += currentChild.getOpenCount();
+                }
+                currentChild = currentChild.getNextSibling();
+            }
+            setOpenCount( openChildrenCount );
+            updateParentOpenCount( openChildrenCount );
+        }
+    }
+
+    /**
+     * Close this node.
+     *
+     */
+    public void closeNode()
+    {
+        //if the node is already closed then do nothing.
+        if( isNodeOpen() )
+        {
+            int openCount = getOpenCount();
+            updateParentOpenCount( -openCount );
+            setOpenCount( -openCount );
+        }
+    }
+
+    /**
+     * Node is open if the open count is greater than zero.
+     * @return true if this node is open.
+     */
+    public boolean isNodeOpen()
+    {
+        return getOpenCount() > 0;
+    }
+
+    /**
+     * The count parameter needs to be updated when you add or remove elements to
+     * the outline.  When you add an element at a lower level then you need to
+     * increase all of the parents.
+     *
+     * @param amount The amount to update by.
+     */
+    protected void updateParentOpenCount( int amount )
+    {
+        PDOutlineNode parent = getParent();
+        if( parent != null )
+        {
+            int currentCount = parent.getOpenCount();
+            //if the currentCount is negative or it is absent then
+            //we will treat it as negative.  The default is to be negative.
+            boolean negative = currentCount < 0 ||
+                parent.getCOSDictionary().getDictionaryObject( "Count" ) == null;
+            currentCount = Math.abs( currentCount );
+            currentCount += amount;
+            if( negative )
+            {
+                currentCount = -currentCount;
+            }
+            parent.setOpenCount( currentCount );
+            //recursively call parent to update count, but the parents count is only
+            //updated if this is an open node
+            if( !negative )
+            {
+                parent.updateParentOpenCount( amount );
+            }
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/outline/package.html
new file mode 100644 (file)
index 0000000..4948b2d
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The outline package allows for a PDF outline(bookmarks) to be created.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/documentnavigation/package.html
new file mode 100644 (file)
index 0000000..973a29a
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+A package to allow access to document level navigation within a PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java
new file mode 100644 (file)
index 0000000..3982d85
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDResources;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.fdf.FDFDictionary;
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+import org.apache.pdfbox.pdmodel.fdf.FDFCatalog;
+import org.apache.pdfbox.pdmodel.fdf.FDFField;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class represents the acroform of a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class PDAcroForm implements COSObjectable
+{
+    private COSDictionary acroForm;
+    private PDDocument document;
+
+    private Map fieldCache;
+
+    /**
+     * Constructor.
+     *
+     * @param doc The document that this form is part of.
+     */
+    public PDAcroForm( PDDocument doc )
+    {
+        document = doc;
+        acroForm = new COSDictionary();
+        COSArray fields = new COSArray();
+        acroForm.setItem( COSName.getPDFName( "Fields" ), fields );
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param doc The document that this form is part of.
+     * @param form The existing acroForm.
+     */
+    public PDAcroForm( PDDocument doc, COSDictionary form )
+    {
+        document = doc;
+        acroForm = form;
+    }
+
+    /**
+     * This will get the document associated with this form.
+     *
+     * @return The PDF document.
+     */
+    public PDDocument getDocument()
+    {
+        return document;
+    }
+
+    /**
+     * This will get the dictionary that this form wraps.
+     *
+     * @return The dictionary for this form.
+     */
+    public COSDictionary getDictionary()
+    {
+        return acroForm;
+    }
+
+    /**
+     * This method will import an entire FDF document into the PDF document
+     * that this acroform is part of.
+     *
+     * @param fdf The FDF document to import.
+     *
+     * @throws IOException If there is an error doing the import.
+     */
+    public void importFDF( FDFDocument fdf ) throws IOException
+    {
+        List fields = fdf.getCatalog().getFDF().getFields();
+        if( fields != null )
+        {
+            for( int i=0; i<fields.size(); i++ )
+            {
+                FDFField fdfField = (FDFField)fields.get( i );
+                PDField docField = getField( fdfField.getPartialFieldName() );
+                if( docField != null )
+                {
+                    docField.importFDF( fdfField );
+                }
+            }
+        }
+    }
+
+    /**
+     * This will export all FDF form data.
+     *
+     * @return An FDF document used to export the document.
+     * @throws IOException If there is an error when exporting the document.
+     */
+    public FDFDocument exportFDF() throws IOException
+    {
+        FDFDocument fdf = new FDFDocument();
+        FDFCatalog catalog = fdf.getCatalog();
+        FDFDictionary fdfDict = new FDFDictionary();
+        catalog.setFDF( fdfDict );
+
+        List fdfFields = new ArrayList();
+        List fields = getFields();
+        Iterator fieldIter = fields.iterator();
+        while( fieldIter.hasNext() )
+        {
+            PDField docField = (PDField)fieldIter.next();
+            addFieldAndChildren( docField, fdfFields );
+        }
+        fdfDict.setID( document.getDocument().getDocumentID() );
+        if( fdfFields.size() > 0 )
+        {
+            fdfDict.setFields( fdfFields );
+        }
+        return fdf;
+    }
+
+    private void addFieldAndChildren( PDField docField, List fdfFields ) throws IOException
+    {
+        Object fieldValue = docField.getValue();
+        FDFField fdfField = new FDFField();
+        fdfField.setPartialFieldName( docField.getPartialName() );
+        fdfField.setValue( fieldValue );
+        List kids = docField.getKids();
+        List childFDFFields = new ArrayList();
+        if( kids != null )
+        {
+
+            for( int i=0; i<kids.size(); i++ )
+            {
+                addFieldAndChildren( (PDField)kids.get( i ), childFDFFields );
+            }
+            if( childFDFFields.size() > 0 )
+            {
+                fdfField.setKids( childFDFFields );
+            }
+        }
+        if( fieldValue != null || childFDFFields.size() > 0 )
+        {
+            fdfFields.add( fdfField );
+        }
+    }
+
+    /**
+     * This will return all of the fields in the document.  The type
+     * will be a org.apache.pdfbox.pdmodel.field.PDField.
+     *
+     * @return A list of all the fields.
+     * @throws IOException If there is an error while getting the list of fields.
+     */
+    public List getFields() throws IOException
+    {
+        List retval = null;
+        COSArray fields =
+            (COSArray) acroForm.getDictionaryObject(
+                COSName.getPDFName("Fields"));
+
+        if( fields != null )
+        {
+            List actuals = new ArrayList();
+            for (int i = 0; i < fields.size(); i++)
+            {
+                COSDictionary element = (COSDictionary) fields.getObject(i);
+                if (element != null)
+                {
+                    PDField field = PDFieldFactory.createField( this, element );
+                    if( field != null )
+                    {
+                        actuals.add(field);
+                    }
+                }
+            }
+            retval = new COSArrayList( actuals, fields );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the fields that are part of this AcroForm.
+     *
+     * @param fields The fields that are part of this form.
+     */
+    public void setFields( List fields )
+    {
+        acroForm.setItem( "Fields", COSArrayList.converterToCOSArray( fields ));
+    }
+
+    /**
+     * This will tell this form to cache the fields into a Map structure
+     * for fast access via the getField method.  The default is false.  You would
+     * want this to be false if you were changing the COSDictionary behind the scenes,
+     * otherwise setting this to true is acceptable.
+     *
+     * @param cache A boolean telling if we should cache the fields.
+     * @throws IOException If there is an error while caching the fields.
+     */
+    public void setCacheFields( boolean cache ) throws IOException
+    {
+        if( cache )
+        {
+            fieldCache = new HashMap();
+            List fields = getFields();
+            Iterator fieldIter = fields.iterator();
+            while( fieldIter.hasNext() )
+            {
+                PDField next = (PDField)fieldIter.next();
+                fieldCache.put( next.getFullyQualifiedName(), next );
+            }
+        }
+        else
+        {
+            fieldCache = null;
+        }
+    }
+
+    /**
+     * This will tell if this acro form is caching the fields.
+     *
+     * @return true if the fields are being cached.
+     */
+    public boolean isCachingFields()
+    {
+        return fieldCache != null;
+    }
+
+    /**
+     * This will get a field by name, possibly using the cache if setCache is true.
+     *
+     * @param name The name of the field to get.
+     *
+     * @return The field with that name of null if one was not found.
+     *
+     * @throws IOException If there is an error getting the field type.
+     */
+    public PDField getField( String name ) throws IOException
+    {
+        PDField retval = null;
+        if( fieldCache != null )
+        {
+            retval = (PDField)fieldCache.get( name );
+        }
+        else
+        {
+            String[] nameSubSection = name.split( "\\." );
+            COSArray fields =
+                (COSArray) acroForm.getDictionaryObject(
+                    COSName.getPDFName("Fields"));
+
+            for (int i = 0; i < fields.size() && retval == null; i++)
+            {
+                COSDictionary element = (COSDictionary) fields.getObject(i);
+                if( element != null )
+                {
+                    COSString fieldName =
+                        (COSString)element.getDictionaryObject( COSName.getPDFName( "T" ) );
+                    if( fieldName.getString().equals( name ) ||
+                        fieldName.getString().equals( nameSubSection[0] ) )
+                    {
+                        PDField root = PDFieldFactory.createField( this, element );
+
+                        if( nameSubSection.length > 1 )
+                        {
+                            PDField kid = root.findKid( nameSubSection, 1 );
+                            if( kid != null )
+                            {
+                                retval = kid;
+                            }
+                            else
+                            {
+                                retval = root;
+                            }
+                        }
+                        else
+                        {
+                            retval = root;
+                        }
+                    }
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will get the default resources for the acro form.
+     *
+     * @return The default resources.
+     */
+    public PDResources getDefaultResources()
+    {
+        PDResources retval = null;
+        COSDictionary dr = (COSDictionary)acroForm.getDictionaryObject( COSName.getPDFName( "DR" ) );
+        if( dr != null )
+        {
+            retval = new PDResources( dr );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the default resources for the acroform.
+     *
+     * @param dr The new default resources.
+     */
+    public void setDefaultResources( PDResources dr )
+    {
+        COSDictionary drDict = null;
+        if( dr != null )
+        {
+            drDict = dr.getCOSDictionary();
+        }
+        acroForm.setItem( COSName.getPDFName( "DR" ), drDict );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return acroForm;
+    }
+
+    /**
+     * Get the XFA resource, the XFA resource is only used for PDF 1.5+ forms.
+     *
+     * @return The xfa resource or null if it does not exist.
+     */
+    public PDXFA getXFA()
+    {
+        PDXFA xfa = null;
+        COSBase base = acroForm.getDictionaryObject( "XFA" );
+        if( base != null )
+        {
+            xfa = new PDXFA( base );
+        }
+        return xfa;
+    }
+
+    /**
+     * Set the XFA resource, this is only used for PDF 1.5+ forms.
+     *
+     * @param xfa The xfa resource.
+     */
+    public void setXFA( PDXFA xfa )
+    {
+        acroForm.setItem( "XFA", xfa );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDAppearance.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDAppearance.java
new file mode 100644 (file)
index 0000000..c365c54
--- /dev/null
@@ -0,0 +1,641 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+
+import org.apache.pdfbox.pdmodel.PDResources;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
+import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
+
+import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * This one took me a while, but i'm proud to say that it handles
+ * the appearance of a textbox. This allows you to apply a value to
+ * a field in the document and handle the appearance so that the
+ * value is actually visible too.
+ * The problem was described by Ben Litchfield, the author of the
+ * example: org.apache.pdfbox.examlpes.fdf.ImportFDF. So Ben, here is the
+ * solution.
+ *
+ * @author sug
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.20 $
+ */
+public class PDAppearance
+{
+    private PDVariableText parent;
+
+    private String value;
+    private COSString defaultAppearance;
+
+    private PDAcroForm acroForm;
+    private List<COSObjectable> widgets = new ArrayList<COSObjectable>();
+
+
+    /**
+     * Constructs a COSAppearnce from the given field.
+     *
+     * @param theAcroForm the acro form that this field is part of.
+     * @param field the field which you wish to control the appearance of
+     * @throws IOException If there is an error creating the appearance.
+     */
+    public PDAppearance( PDAcroForm theAcroForm, PDVariableText field ) throws IOException
+    {
+        acroForm = theAcroForm;
+        parent = field;
+
+        widgets = field.getKids();
+        if( widgets == null )
+        {
+            widgets = new ArrayList<COSObjectable>();
+            widgets.add( field.getWidget() );
+        }
+
+        defaultAppearance = getDefaultAppearance();
+
+
+    }
+
+    /**
+     * Returns the default apperance of a textbox. If the textbox
+     * does not have one, then it will be taken from the AcroForm.
+     * @return The DA element
+     */
+    private COSString getDefaultAppearance()
+    {
+
+        COSString dap = parent.getDefaultAppearance();
+        if (dap == null)
+        {
+            COSArray kids = (COSArray)parent.getDictionary().getDictionaryObject( COSName.KIDS );
+            if( kids != null && kids.size() > 0 )
+            {
+                COSDictionary firstKid = (COSDictionary)kids.getObject( 0 );
+                dap = (COSString)firstKid.getDictionaryObject( COSName.DA );
+            }
+            if( dap == null )
+            {
+                dap = (COSString) acroForm.getDictionary().getDictionaryObject(COSName.DA);
+            }
+        }
+        return dap;
+    }
+
+    private int getQ()
+    {
+        int q = parent.getQ();
+        if( parent.getDictionary().getDictionaryObject( COSName.Q ) == null )
+        {
+            COSArray kids = (COSArray)parent.getDictionary().getDictionaryObject( COSName.KIDS );
+            if( kids != null && kids.size() > 0 )
+            {
+                COSDictionary firstKid = (COSDictionary)kids.getObject( 0 );
+                COSNumber qNum = (COSNumber)firstKid.getDictionaryObject( COSName.Q );
+                if( qNum != null )
+                {
+                    q = qNum.intValue();
+                }
+            }
+        }
+        return q;
+    }
+
+    /**
+     * Extracts the original appearance stream into a list of tokens.
+     *
+     * @return The tokens in the original appearance stream
+     */
+    private List getStreamTokens( PDAppearanceStream appearanceStream ) throws IOException
+    {
+        List tokens = null;
+        if( appearanceStream != null )
+        {
+            tokens = getStreamTokens( appearanceStream.getStream() );
+        }
+        return tokens;
+    }
+
+    private List getStreamTokens( COSString string ) throws IOException
+    {
+        PDFStreamParser parser;
+
+        List tokens = null;
+        if( string != null )
+        {
+            ByteArrayInputStream stream = new ByteArrayInputStream( string.getBytes() );
+            parser = new PDFStreamParser( stream, acroForm.getDocument().getDocument().getScratchFile() );
+            parser.parse();
+            tokens = parser.getTokens();
+        }
+        return tokens;
+    }
+
+    private List getStreamTokens( COSStream stream ) throws IOException
+    {
+        PDFStreamParser parser;
+
+        List tokens = null;
+        if( stream != null )
+        {
+            parser = new PDFStreamParser( stream );
+            parser.parse();
+            tokens = parser.getTokens();
+        }
+        return tokens;
+    }
+
+    /**
+     * Tests if the apperance stream already contains content.
+     *
+     * @return true if it contains any content
+     */
+    private boolean containsMarkedContent( List stream )
+    {
+        return stream.contains( PDFOperator.getOperator( "BMC" ) );
+    }
+
+    /**
+     * This is the public method for setting the appearance stream.
+     *
+     * @param apValue the String value which the apperance shoud represent
+     *
+     * @throws IOException If there is an error creating the stream.
+     */
+    public void setAppearanceValue(String apValue) throws IOException
+    {
+        // MulitLine check and set
+        if ( parent.isMultiline() && apValue.indexOf('\n') != -1 )
+        {
+            apValue = convertToMultiLine( apValue );
+        }
+
+        value = apValue;
+        Iterator<COSObjectable> widgetIter = widgets.iterator();
+        while( widgetIter.hasNext() )
+        {
+            COSObjectable next = widgetIter.next();
+            PDField field = null;
+            PDAnnotationWidget widget = null;
+            if( next instanceof PDField )
+            {
+                field = (PDField)next;
+                widget = field.getWidget();
+            }
+            else
+            {
+                widget = (PDAnnotationWidget)next;
+            }
+            PDFormFieldAdditionalActions actions = null;
+            if( field != null )
+            {
+                actions = field.getActions();
+            }
+            if( actions != null &&
+                actions.getF() != null &&
+                widget.getDictionary().getDictionaryObject( COSName.AP ) ==null)
+            {
+                //do nothing because the field will be formatted by acrobat
+                //when it is opened.  See FreedomExpressions.pdf for an example of this.
+            }
+            else
+            {
+
+                PDAppearanceDictionary appearance = widget.getAppearance();
+                if( appearance == null )
+                {
+                    appearance = new PDAppearanceDictionary();
+                    widget.setAppearance( appearance );
+                }
+
+                Map normalAppearance = appearance.getNormalAppearance();
+                PDAppearanceStream appearanceStream = (PDAppearanceStream)normalAppearance.get( "default" );
+                if( appearanceStream == null )
+                {
+                    COSStream cosStream = new COSStream( acroForm.getDocument().getDocument().getScratchFile() );
+                    appearanceStream = new PDAppearanceStream( cosStream );
+                    appearanceStream.setBoundingBox( widget.getRectangle().createRetranslatedRectangle() );
+                    appearance.setNormalAppearance( appearanceStream );
+                }
+
+                List tokens = getStreamTokens( appearanceStream );
+                List daTokens = getStreamTokens( getDefaultAppearance() );
+                PDFont pdFont = getFontAndUpdateResources( tokens, appearanceStream );
+
+                if (!containsMarkedContent( tokens ))
+                {
+                    ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+                    //BJL 9/25/2004 Must prepend existing stream
+                    //because it might have operators to draw things like
+                    //rectangles and such
+                    ContentStreamWriter writer = new ContentStreamWriter( output );
+                    writer.writeTokens( tokens );
+
+                    output.write( " /Tx BMC\n".getBytes("ISO-8859-1") );
+                    insertGeneratedAppearance( widget, output, pdFont, tokens, appearanceStream );
+                    output.write( " EMC".getBytes("ISO-8859-1") );
+                    writeToStream( output.toByteArray(), appearanceStream );
+                }
+                else
+                {
+                    if( tokens != null )
+                    {
+                        if( daTokens != null )
+                        {
+                            int bmcIndex = tokens.indexOf( PDFOperator.getOperator( "BMC" ));
+                            int emcIndex = tokens.indexOf( PDFOperator.getOperator( "EMC" ));
+                            if( bmcIndex != -1 && emcIndex != -1 &&
+                                emcIndex == bmcIndex+1 )
+                            {
+                                //if the EMC immediately follows the BMC index then should
+                                //insert the daTokens inbetween the two markers.
+                                tokens.addAll( emcIndex, daTokens );
+                            }
+                        }
+                        ByteArrayOutputStream output = new ByteArrayOutputStream();
+                        ContentStreamWriter writer = new ContentStreamWriter( output );
+                        float fontSize = calculateFontSize( pdFont, appearanceStream.getBoundingBox(), tokens, null );
+                        boolean foundString = false;
+                        for( int i=0; i<tokens.size(); i++ )
+                        {
+                            if( tokens.get( i ) instanceof COSString )
+                            {
+                                foundString = true;
+                                COSString drawnString =((COSString)tokens.get(i));
+                                drawnString.reset();
+                                drawnString.append( apValue.getBytes("ISO-8859-1") );
+                            }
+                        }
+                        int setFontIndex = tokens.indexOf( PDFOperator.getOperator( "Tf" ));
+                        tokens.set( setFontIndex-1, new COSFloat( fontSize ) );
+                        if( foundString )
+                        {
+                            writer.writeTokens( tokens );
+                        }
+                        else
+                        {
+                            int bmcIndex = tokens.indexOf( PDFOperator.getOperator( "BMC" ) );
+                            int emcIndex = tokens.indexOf( PDFOperator.getOperator( "EMC" ) );
+
+                            if( bmcIndex != -1 )
+                            {
+                                writer.writeTokens( tokens, 0, bmcIndex+1 );
+                            }
+                            else
+                            {
+                                writer.writeTokens( tokens );
+                            }
+                            output.write( "\n".getBytes("ISO-8859-1") );
+                            insertGeneratedAppearance( widget, output,
+                                pdFont, tokens, appearanceStream );
+                            if( emcIndex != -1 )
+                            {
+                                writer.writeTokens( tokens, emcIndex, tokens.size() );
+                            }
+                        }
+                        writeToStream( output.toByteArray(), appearanceStream );
+                    }
+                    else
+                    {
+                        //hmm?
+                    }
+                }
+            }
+        }
+    }
+
+    private void insertGeneratedAppearance( PDAnnotationWidget fieldWidget, OutputStream output,
+        PDFont pdFont, List tokens, PDAppearanceStream appearanceStream ) throws IOException
+    {
+        PrintWriter printWriter = new PrintWriter( output, true );
+        float fontSize = 0.0f;
+        PDRectangle boundingBox = null;
+        boundingBox = appearanceStream.getBoundingBox();
+        if( boundingBox == null )
+        {
+            boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle();
+        }
+        printWriter.println( "BT" );
+        if( defaultAppearance != null )
+        {
+            String daString = defaultAppearance.getString();
+            PDFStreamParser daParser = new PDFStreamParser(new ByteArrayInputStream( daString.getBytes("ISO-8859-1") ), null );
+            daParser.parse();
+            List<Object> daTokens = daParser.getTokens();
+            fontSize = calculateFontSize( pdFont, boundingBox, tokens, daTokens );
+            int fontIndex = daTokens.indexOf( PDFOperator.getOperator( "Tf" ) );
+            if(fontIndex != -1 )
+            {
+                daTokens.set( fontIndex-1, new COSFloat( fontSize ) );
+            }
+            ContentStreamWriter daWriter = new ContentStreamWriter(output);
+            daWriter.writeTokens( daTokens );
+        }
+        printWriter.println( getTextPosition( boundingBox, pdFont, fontSize, tokens ) );
+        int q = getQ();
+        if( q == PDTextbox.QUADDING_LEFT )
+        {
+            //do nothing because left is default
+        }
+        else if( q == PDTextbox.QUADDING_CENTERED ||
+                 q == PDTextbox.QUADDING_RIGHT )
+        {
+            float fieldWidth = boundingBox.getWidth();
+            float stringWidth = (pdFont.getStringWidth( value )/1000)*fontSize;
+            float adjustAmount = fieldWidth - stringWidth - 4;
+
+            if( q == PDTextbox.QUADDING_CENTERED )
+            {
+                adjustAmount = adjustAmount/2.0f;
+            }
+
+            printWriter.println( adjustAmount + " 0 Td" );
+        }
+        else
+        {
+            throw new IOException( "Error: Unknown justification value:" + q );
+        }
+        printWriter.println("(" + value + ") Tj");
+        printWriter.println("ET" );
+        printWriter.flush();
+    }
+
+    private PDFont getFontAndUpdateResources( List tokens, PDAppearanceStream appearanceStream ) throws IOException
+    {
+
+        PDFont retval = null;
+        PDResources streamResources = appearanceStream.getResources();
+        PDResources formResources = acroForm.getDefaultResources();
+        if( formResources != null )
+        {
+            if( streamResources == null )
+            {
+                streamResources = new PDResources();
+                appearanceStream.setResources( streamResources );
+            }
+
+            COSString da = getDefaultAppearance();
+            if( da != null )
+            {
+                String data = da.getString();
+                PDFStreamParser streamParser = new PDFStreamParser(
+                        new ByteArrayInputStream( data.getBytes("ISO-8859-1") ), null );
+                streamParser.parse();
+                tokens = streamParser.getTokens();
+            }
+
+            int setFontIndex = tokens.indexOf( PDFOperator.getOperator( "Tf" ));
+            COSName cosFontName = (COSName)tokens.get( setFontIndex-2 );
+            String fontName = cosFontName.getName();
+            retval = (PDFont)streamResources.getFonts().get( fontName );
+            if( retval == null )
+            {
+                retval = (PDFont)formResources.getFonts().get( fontName );
+                streamResources.getFonts().put( fontName, retval );
+            }
+        }
+        return retval;
+    }
+
+    private String convertToMultiLine( String line )
+    {
+        int currIdx = 0;
+        int lastIdx = 0;
+        StringBuffer result = new StringBuffer(line.length() + 64);
+        while( (currIdx = line.indexOf('\n',lastIdx )) > -1 )
+        {
+            result.append(line.substring(lastIdx,currIdx));
+            result.append(" ) Tj\n0 -13 Td\n(");
+            lastIdx = currIdx + 1;
+        }
+        result.append(line.substring(lastIdx));
+        return result.toString();
+    }
+
+    /**
+     * Writes the stream to the actual stream in the COSStream.
+     *
+     * @throws IOException If there is an error writing to the stream
+     */
+    private void writeToStream( byte[] data, PDAppearanceStream appearanceStream ) throws IOException
+    {
+        OutputStream out = appearanceStream.getStream().createUnfilteredStream();
+        out.write( data );
+        out.flush();
+    }
+
+
+    /**
+     * w in an appearance stream represents the lineWidth.
+     * @return the linewidth
+     */
+    private float getLineWidth( List tokens )
+    {
+
+        float retval = 1;
+        if( tokens != null )
+        {
+            int btIndex = tokens.indexOf(PDFOperator.getOperator( "BT" ));
+            int wIndex = tokens.indexOf(PDFOperator.getOperator( "w" ));
+            //the w should only be used if it is before the first BT.
+            if( (wIndex > 0) && (wIndex < btIndex) )
+            {
+                retval = ((COSNumber)tokens.get(wIndex-1)).floatValue();
+            }
+        }
+        return retval;
+    }
+
+    private PDRectangle getSmallestDrawnRectangle( PDRectangle boundingBox, List tokens )
+    {
+        PDRectangle smallest = boundingBox;
+        for( int i=0; i<tokens.size(); i++ )
+        {
+            Object next = tokens.get( i );
+            if( next == PDFOperator.getOperator( "re" ) )
+            {
+                COSNumber x = (COSNumber)tokens.get( i-4 );
+                COSNumber y = (COSNumber)tokens.get( i-3 );
+                COSNumber width = (COSNumber)tokens.get( i-2 );
+                COSNumber height = (COSNumber)tokens.get( i-1 );
+                PDRectangle potentialSmallest = new PDRectangle();
+                potentialSmallest.setLowerLeftX( x.floatValue() );
+                potentialSmallest.setLowerLeftY( y.floatValue() );
+                potentialSmallest.setUpperRightX( x.floatValue() + width.floatValue() );
+                potentialSmallest.setUpperRightY( y.floatValue() + height.floatValue() );
+                if( smallest == null ||
+                    smallest.getLowerLeftX() < potentialSmallest.getLowerLeftX() ||
+                    smallest.getUpperRightY() > potentialSmallest.getUpperRightY() )
+                {
+                    smallest = potentialSmallest;
+                }
+
+            }
+        }
+        return smallest;
+    }
+
+    /**
+     * My "not so great" method for calculating the fontsize.
+     * It does not work superb, but it handles ok.
+     * @return the calculated font-size
+     *
+     * @throws IOException If there is an error getting the font height.
+     */
+    private float calculateFontSize( PDFont pdFont, PDRectangle boundingBox, List tokens, List daTokens )
+        throws IOException
+    {
+        float fontSize = 0;
+        if( daTokens != null )
+        {
+            //daString looks like   "BMC /Helv 3.4 Tf EMC"
+
+            int fontIndex = daTokens.indexOf( PDFOperator.getOperator( "Tf" ) );
+            if(fontIndex != -1 )
+            {
+                fontSize = ((COSNumber)daTokens.get(fontIndex-1)).floatValue();
+            }
+        }
+        
+        float widthBasedFontSize = Float.MAX_VALUE;
+        
+        if( parent.doNotScroll() )
+        {
+            //if we don't scroll then we will shrink the font to fit into the text area.
+            float widthAtFontSize1 = pdFont.getStringWidth( value )/1000.f;
+            float availableWidth = getAvailableWidth(boundingBox, getLineWidth(tokens));
+            widthBasedFontSize = availableWidth / widthAtFontSize1;
+        }
+        else if( fontSize == 0 )
+        {
+            float lineWidth = getLineWidth( tokens );
+            float stringWidth = pdFont.getStringWidth( value );
+            float height = 0;
+            if( pdFont instanceof PDSimpleFont )
+            {
+                height = ((PDSimpleFont)pdFont).getFontDescriptor().getFontBoundingBox().getHeight();
+            }
+            else
+            {
+                //now much we can do, so lets assume font is square and use width
+                //as the height
+                height = pdFont.getAverageFontWidth();
+            }
+            height = height/1000f;
+
+            float availHeight = getAvailableHeight( boundingBox, lineWidth );
+            fontSize = Math.min((availHeight/height), widthBasedFontSize);
+        }
+        return fontSize;
+    }
+
+    /**
+     * Calculates where to start putting the text in the box.
+     * The positioning is not quite as accurate as when Acrobat
+     * places the elements, but it works though.
+     *
+     * @return the sting for representing the start position of the text
+     *
+     * @throws IOException If there is an error calculating the text position.
+     */
+    private String getTextPosition( PDRectangle boundingBox, PDFont pdFont, float fontSize, List tokens )
+        throws IOException
+    {
+        float lineWidth = getLineWidth( tokens );
+        float pos = 0.0f;
+        if(parent.isMultiline())
+        {
+            int rows = (int) (getAvailableHeight( boundingBox, lineWidth ) / ((int) fontSize));
+            pos = ((rows)*fontSize)-fontSize;
+        }
+        else
+        {
+            if( pdFont instanceof PDSimpleFont )
+            {
+                //BJL 9/25/2004
+                //This algorithm is a little bit of black magic.  It does
+                //not appear to be documented anywhere.  Through examining a few
+                //PDF documents and the value that Acrobat places in there I
+                //have determined that the below method of computing the position
+                //is correct for certain documents, but maybe not all.  It does
+                //work f1040ez.pdf and Form_1.pdf
+                PDFontDescriptor fd = ((PDSimpleFont)pdFont).getFontDescriptor();
+                float bBoxHeight = boundingBox.getHeight();
+                float fontHeight = fd.getFontBoundingBox().getHeight() + 2 * fd.getDescent();
+                fontHeight = (fontHeight/1000) * fontSize;
+                pos = (bBoxHeight - fontHeight)/2;
+            }
+            else
+            {
+                throw new IOException( "Error: Don't know how to calculate the position for non-simple fonts" );
+            }
+        }
+        PDRectangle innerBox = getSmallestDrawnRectangle( boundingBox, tokens );
+        float xInset = 2+ 2*(boundingBox.getWidth() - innerBox.getWidth());
+        return Math.round(xInset) + " "+ pos + " Td";
+    }
+
+    /**
+     * calculates the available width of the box.
+     * @return the calculated available width of the box
+     */
+    private float getAvailableWidth( PDRectangle boundingBox, float lineWidth )
+    {
+        return boundingBox.getWidth() - 2 * lineWidth;
+    }
+
+    /**
+     * calculates the available height of the box.
+     * @return the calculated available height of the box
+     */
+    private float getAvailableHeight( PDRectangle boundingBox, float lineWidth )
+    {
+        return boundingBox.getHeight() - 2 * lineWidth;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDCheckbox.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDCheckbox.java
new file mode 100644 (file)
index 0000000..8c566d3
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import java.io.IOException;
+
+/**
+ * A class for handling the PDF field as a checkbox.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author sug
+ * @version $Revision: 1.11 $
+ */
+public class PDCheckbox extends PDChoiceButton
+{
+    private static final COSName KEY = COSName.getPDFName("AS");
+    private static final COSName OFF_VALUE = COSName.getPDFName("Off");
+
+    private COSName value;
+
+    /**
+     * @see PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The checkbox field dictionary
+     */
+    public PDCheckbox( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super( theAcroForm, field);
+        COSDictionary ap = (COSDictionary) field.getDictionaryObject(COSName.getPDFName("AP"));
+        if( ap != null )
+        {
+            COSBase n = ap.getDictionaryObject(COSName.getPDFName("N"));
+
+            if( n instanceof COSDictionary )
+            {
+                for( COSName name : ((COSDictionary)n).keySet() )
+                {
+                    if( !name.equals( OFF_VALUE ))
+                    {
+                        value = name;
+                    }
+                }
+
+            }
+        }
+        else
+        {
+            value = (COSName)getDictionary().getDictionaryObject( "V" );
+        }
+    }
+
+    /**
+     * This will tell if this radio button is currently checked or not.
+     *
+     * @return true If the radio button is checked.
+     */
+    public boolean isChecked()
+    {
+        boolean retval = false;
+        String onValue = getOnValue();
+        COSName radioValue = (COSName)getDictionary().getDictionaryObject( KEY );
+        if( radioValue != null && value != null && radioValue.getName().equals( onValue ) )
+        {
+            retval = true;
+        }
+
+        return retval;
+    }
+
+    /**
+     * Checks the radiobutton.
+     */
+    public void check()
+    {
+        getDictionary().setItem(KEY, value);
+    }
+
+    /**
+     * Unchecks the radiobutton.
+     */
+    public void unCheck()
+    {
+        getDictionary().setItem(KEY, OFF_VALUE);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setValue(String newValue)
+    {
+        getDictionary().setName( "V", newValue );
+        if( newValue == null )
+        {
+            getDictionary().setItem( KEY, OFF_VALUE );
+        }
+        else
+        {
+            getDictionary().setName( KEY, newValue );
+        }
+    }
+
+    /**
+     * This will get the value of the radio button.
+     *
+     * @return The value of the radio button.
+     */
+    public String getOffValue()
+    {
+        return OFF_VALUE.getName();
+    }
+
+    /**
+     * This will get the value of the radio button.
+     *
+     * @return The value of the radio button.
+     */
+    public String getOnValue()
+    {
+        String retval = null;
+        COSDictionary ap = (COSDictionary) getDictionary().getDictionaryObject(COSName.getPDFName("AP"));
+        COSBase n = ap.getDictionaryObject(COSName.getPDFName("N"));
+
+        //N can be a COSDictionary or a COSStream
+        if( n instanceof COSDictionary )
+        {
+            for( COSName key :((COSDictionary)n).keySet() )
+            {
+                if( !key.equals( OFF_VALUE) )
+                {
+                    retval = key.getName();
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * getValue gets the fields value to as a string.
+     *
+     * @return The string value of this field.
+     *
+     * @throws IOException If there is an error getting the value.
+     */
+    public String getValue() throws IOException
+    {
+        return getDictionary().getNameAsString( "V" );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDChoiceButton.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDChoiceButton.java
new file mode 100644 (file)
index 0000000..0bf5df6
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This holds common functionality for check boxes and radio buttons.
+ *
+ * @author sug
+ * @version $Revision: 1.4 $
+ */
+public abstract class PDChoiceButton extends PDField
+{
+
+    /**
+     * @see PDField#PDField(PDAcroForm,org.apache.pdfbox.cos.COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The field for this button.
+     */
+    public PDChoiceButton( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super(theAcroForm, field);
+    }
+
+    /**
+     * This will get the option values "Opt" entry of the pdf button.
+     *
+     * @return A list of java.lang.String values.
+     */
+    public List getOptions()
+    {
+        List retval = null;
+        COSArray array = (COSArray)getDictionary().getDictionaryObject( COSName.getPDFName( "Opt" ) );
+        if( array != null )
+        {
+            List strings = new ArrayList();
+            for( int i=0; i<array.size(); i++ )
+            {
+                strings.add( ((COSString)array.getObject( i )).getString() );
+            }
+            retval = new COSArrayList( strings, array );
+        }
+        return retval;
+    }
+
+    /**
+     * This will will set the list of options for this button.
+     *
+     * @param options The list of options for the button.
+     */
+    public void setOptions( List options )
+    {
+        getDictionary().setItem(
+            COSName.getPDFName( "Opt" ),
+            COSArrayList.converterToCOSArray( options ) );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDChoiceField.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDChoiceField.java
new file mode 100644 (file)
index 0000000..48fc2ad
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDVariableText;
+
+/**
+ * A class for handling the PDF field as a choicefield.
+ *
+ * @author sug
+ * @version $Revision: 1.7 $
+ */
+public class PDChoiceField extends PDVariableText
+{
+
+    /**
+     * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The field for this choice field.
+     */
+    public PDChoiceField( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super(theAcroForm, field);
+    }
+
+    /**
+     * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#setValue(java.lang.String)
+     *
+     * @param optionValue The new value for this text field.
+     *
+     * @throws IOException If there is an error calculating the appearance stream or the value in not one
+     *   of the existing options.
+     */
+    public void setValue(String optionValue) throws IOException
+    {
+        int indexSelected = -1;
+        COSArray options = (COSArray)getDictionary().getDictionaryObject( "Opt" );
+        if( options.size() == 0 )
+        {
+            throw new IOException( "Error: You cannot set a value for a choice field if there are no options." );
+        }
+        else
+        {
+            // YXJ: Changed the order of the loops. Acrobat produces PDF's
+            // where sometimes there is 1 string and the rest arrays.
+            // This code works either way.
+            for( int i=0; i<options.size() && indexSelected == -1; i++ ) {
+                COSBase option = options.getObject( i );
+                if( option instanceof COSArray )
+                {
+                    COSArray keyValuePair = (COSArray)option;
+                    COSString key = (COSString)keyValuePair.getObject( 0 );
+                    COSString value = (COSString)keyValuePair.getObject( 1 );
+                    if( optionValue.equals( key.getString() ) || optionValue.equals( value.getString() ) )
+                    {
+                        //have the parent draw the appearance stream with the value
+                        super.setValue( value.getString() );
+                        //but then use the key as the V entry
+                        getDictionary().setItem( COSName.getPDFName( "V" ), key );
+                        indexSelected = i;
+                    }
+                }
+                else
+                {
+                    COSString value = (COSString)option;
+                    if( optionValue.equals( value.getString() ) )
+                    {
+                        super.setValue( optionValue );
+                        indexSelected = i;
+                    }
+                }
+            }
+        }
+        if( indexSelected == -1 )
+        {
+            throw new IOException( "Error: '" + optionValue + "' was not an available option.");
+        }
+        else
+        {
+            COSArray indexArray = (COSArray)getDictionary().getDictionaryObject( "I" );
+            if( indexArray != null )
+            {
+                indexArray.clear();
+                indexArray.add( COSInteger.get( indexSelected ) );
+            }
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDField.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDField.java
new file mode 100644 (file)
index 0000000..8f8475e
--- /dev/null
@@ -0,0 +1,649 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFField;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is the superclass for a Field element in a PDF.
+ * Based on the COS object model from PDFBox.
+ *
+ * @author sug
+ * @version $Revision: 1.23 $
+ */
+public abstract class PDField implements COSObjectable
+{
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_READ_ONLY = 1;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_REQUIRED = 1 << 1;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_NO_EXPORT = 1 << 2;
+
+
+    private PDAcroForm acroForm;
+
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     *
+     * @param theAcroForm The form that this field is part of.
+     */
+    public PDField( PDAcroForm theAcroForm )
+    {
+        acroForm = theAcroForm;
+        dictionary = new COSDictionary();
+        //no required fields in base field class
+    }
+
+
+    /**
+     * Creates a COSField from a COSDictionary, expected to be
+     * a correct object definition for a field in PDF.
+     *
+     * @param theAcroForm The form that this field is part of.
+     * @param field the PDF objet to represent as a field.
+     */
+    public PDField(PDAcroForm theAcroForm, COSDictionary field)
+    {
+        acroForm = theAcroForm;
+        dictionary = field;
+    }
+
+    /**
+     * Returns the partial name of the field.
+     *
+     * @return the name of the field
+     */
+    public String getPartialName()
+    {
+        return getDictionary().getString( COSName.T );
+    }
+
+    /**
+     * This will set the partial name of the field.
+     *
+     * @param name The new name for the field.
+     */
+    public void setPartialName( String name )
+    {
+        getDictionary().setString( COSName.T, name );
+    }
+
+    /**
+     * Returns the fully qualified name of the field, which is a concatenation of
+     * the names of all the parents fields.
+     *
+     * @return the name of the field
+     *
+     * @throws IOException If there is an error generating the fully qualified name.
+     */
+    public String getFullyQualifiedName() throws IOException
+    {
+        PDField parent = getParent();
+        String parentName = null;
+        if( parent != null )
+        {
+            parentName = parent.getFullyQualifiedName();
+        }
+        String finalName = getPartialName();
+        if( parentName != null )
+        {
+            finalName = parentName + "." + finalName;
+        }
+        return finalName;
+    }
+
+    /**
+     * Gets the alternate name of the field.
+     * 
+     * @return the alternate name of the field
+     */
+    public String getAlternateFieldName()
+    {
+        return this.getDictionary().getString(COSName.TU);
+    }
+
+    /**
+     * This will set the alternate name of the field.
+     * 
+     * @param alternateFieldName the alternate name of the field
+     */
+    public void setAlternateFieldName(String alternateFieldName)
+    {
+        this.getDictionary().setString(COSName.TU, alternateFieldName);
+    }
+
+    /**
+     * Get the FT entry of the field.  This is a read only field and is set depending
+     * on the actual type.  The field type is an inheritable attribute.  This method will
+     * return only the direct value on this object.  Use the findFieldType for an upward
+     * recursive search.
+     *
+     * @return The Field type.
+     *
+     * @see PDField#findFieldType()
+     */
+    public String getFieldType()
+    {
+        return getDictionary().getNameAsString( COSName.FT );
+    }
+
+    /**
+     * Find the field type and optionally do a recursive upward search.  Sometimes the fieldtype
+     * will be specified on the parent instead of the direct object.  This will look at this
+     * object for the field type, if none is specified then it will look to the parent if there
+     * is a parent.  If there is no parent and no field type has been found then this
+     * will return null.
+     *
+     * @return The field type or null if none was found.
+     */
+    public String findFieldType()
+    {
+        return findFieldType( getDictionary() );
+    }
+
+    private String findFieldType( COSDictionary dic )
+    {
+        String retval = dic.getNameAsString( COSName.FT );
+        if( retval == null )
+        {
+            COSDictionary parent = (COSDictionary)dic.getDictionaryObject( COSName.PARENT, COSName.P );
+            if( parent != null )
+            {
+                retval = findFieldType( parent );
+            }
+        }
+        return retval;
+
+    }
+
+
+    /**
+     * setValue sets the fields value to a given string.
+     *
+     * @param value the string value
+     *
+     * @throws IOException If there is an error creating the appearance stream.
+     */
+    public abstract void setValue(String value) throws IOException;
+
+    /**
+     * getValue gets the fields value to as a string.
+     *
+     * @return The string value of this field.
+     *
+     * @throws IOException If there is an error getting the value.
+     */
+    public abstract String getValue() throws IOException;
+
+    /**
+     * sets the field to be read-only.
+     *
+     * @param readonly The new flag for readonly.
+     */
+    public void setReadonly(boolean readonly)
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_READ_ONLY, readonly );
+    }
+
+    /**
+     *
+     * @return true if the field is readonly
+     */
+    public boolean isReadonly()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_READ_ONLY );
+    }
+
+    /**
+     * sets the field to be required.
+     *
+     * @param required The new flag for required.
+     */
+    public void setRequired(boolean required)
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_REQUIRED, required );
+    }
+
+    /**
+     *
+     * @return true if the field is required
+     */
+    public boolean isRequired()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_REQUIRED );
+    }
+
+    /**
+     * sets the field to be not exported..
+     *
+     * @param noExport The new flag for noExport.
+     */
+    public void setNoExport(boolean noExport)
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_NO_EXPORT, noExport );
+    }
+
+    /**
+     *
+     * @return true if the field is not to be exported.
+     */
+    public boolean isNoExport()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_NO_EXPORT );
+    }
+
+    /**
+     * This will get the flags for this field.
+     *
+     * @return flags The set of flags.
+     */
+    public int getFieldFlags()
+    {
+        int retval = 0;
+        COSInteger ff = (COSInteger)getDictionary().getDictionaryObject( COSName.FF );
+        if( ff != null )
+        {
+            retval = ff.intValue();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the flags for this field.
+     *
+     * @param flags The new flags.
+     */
+    public void setFieldFlags( int flags )
+    {
+        getDictionary().setInt( COSName.FF, flags );
+    }
+
+    /**
+     * This will import a fdf field from a fdf document.
+     *
+     * @param fdfField The fdf field to import.
+     *
+     * @throws IOException If there is an error importing the data for this field.
+     */
+    public void importFDF( FDFField fdfField ) throws IOException
+    {
+        Object fieldValue = fdfField.getValue();
+        int fieldFlags = getFieldFlags();
+
+        if( fieldValue != null )
+        {
+            if( fieldValue instanceof String )
+            {
+                setValue( (String)fieldValue );
+            }
+            else if( fieldValue instanceof PDTextStream )
+            {
+                setValue( ((PDTextStream)fieldValue).getAsString() );
+            }
+            else
+            {
+                throw new IOException( "Unknown field type:" + fieldValue.getClass().getName() );
+            }
+        }
+        Integer ff = fdfField.getFieldFlags();
+        if( ff != null )
+        {
+            setFieldFlags( ff.intValue() );
+        }
+        else
+        {
+            //these are suppose to be ignored if the Ff is set.
+            Integer setFf = fdfField.getSetFieldFlags();
+
+            if( setFf != null )
+            {
+                int setFfInt = setFf.intValue();
+                fieldFlags = fieldFlags | setFfInt;
+                setFieldFlags( fieldFlags );
+            }
+
+            Integer clrFf = fdfField.getClearFieldFlags();
+            if( clrFf != null )
+            {
+                //we have to clear the bits of the document fields for every bit that is
+                //set in this field.
+                //
+                //Example:
+                //docFf = 1011
+                //clrFf = 1101
+                //clrFfValue = 0010;
+                //newValue = 1011 & 0010 which is 0010
+                int clrFfValue = clrFf.intValue();
+                clrFfValue ^= 0xFFFFFFFF;
+                fieldFlags = fieldFlags & clrFfValue;
+                setFieldFlags( fieldFlags );
+            }
+        }
+
+        PDAnnotationWidget widget = getWidget();
+        if( widget != null )
+        {
+            int annotFlags = widget.getAnnotationFlags();
+            Integer f = fdfField.getWidgetFieldFlags();
+            if( f != null && widget != null )
+            {
+                widget.setAnnotationFlags( f.intValue() );
+            }
+            else
+            {
+                //these are suppose to be ignored if the F is set.
+                Integer setF = fdfField.getSetWidgetFieldFlags();
+                if( setF != null )
+                {
+                    annotFlags = annotFlags | setF.intValue();
+                    widget.setAnnotationFlags( annotFlags );
+                }
+
+                Integer clrF = fdfField.getClearWidgetFieldFlags();
+                if( clrF != null )
+                {
+                    //we have to clear the bits of the document fields for every bit that is
+                    //set in this field.
+                    //
+                    //Example:
+                    //docF = 1011
+                    //clrF = 1101
+                    //clrFValue = 0010;
+                    //newValue = 1011 & 0010 which is 0010
+                    int clrFValue = clrF.intValue();
+                    clrFValue ^= 0xFFFFFFFFL;
+                    annotFlags = annotFlags & clrFValue;
+                    widget.setAnnotationFlags( annotFlags );
+                }
+            }
+        }
+        List<FDFField> fdfKids = fdfField.getKids();
+        List<COSObjectable> pdKids = getKids();
+        for( int i=0; fdfKids != null && i<fdfKids.size(); i++ )
+        {
+            FDFField fdfChild = fdfKids.get( i );
+            String fdfName = fdfChild.getPartialFieldName();
+            for( int j=0; j<pdKids.size(); j++ )
+            {
+                Object pdChildObj = pdKids.get( j );
+                if( pdChildObj instanceof PDField )
+                {
+                    PDField pdChild = (PDField)pdChildObj;
+                    if( fdfName != null && fdfName.equals( pdChild.getPartialName() ) )
+                    {
+                        pdChild.importFDF( fdfChild );
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * This will get the single associated widget that is part of this field.  This
+     * occurs when the Widget is embedded in the fields dictionary.  Sometimes there
+     * are multiple sub widgets associated with this field, in which case you want to
+     * use getKids().  If the kids entry is specified, then the first entry in that
+     * list will be returned.
+     *
+     * @return The widget that is associated with this field.
+     * @throws IOException If there is an error getting the widget object.
+     */
+    public PDAnnotationWidget getWidget() throws IOException
+    {
+        PDAnnotationWidget retval = null;
+        List<COSObjectable> kids = getKids();
+        if( kids == null )
+        {
+            retval = new PDAnnotationWidget( getDictionary() );
+        }
+        else if( kids.size() > 0 )
+        {
+            Object firstKid = kids.get( 0 );
+            if( firstKid instanceof PDAnnotationWidget )
+            {
+                retval = (PDAnnotationWidget)firstKid;
+            }
+            else
+            {
+                retval = ((PDField)firstKid).getWidget();
+            }
+        }
+        else
+        {
+            retval = null;
+        }
+        return retval;
+    }
+
+    /**
+     * Get the parent field to this field, or null if none exists.
+     *
+     * @return The parent field.
+     *
+     * @throws IOException If there is an error creating the parent field.
+     */
+    public PDField getParent() throws IOException
+    {
+        PDField parent = null;
+        COSDictionary parentDic = (COSDictionary)getDictionary().getDictionaryObject( COSName.PARENT, COSName.P );
+        if( parentDic != null )
+        {
+            parent = PDFieldFactory.createField( getAcroForm(), parentDic );
+        }
+        return parent;
+    }
+
+    /**
+     * Set the parent of this field.
+     *
+     * @param parent The parent to this field.
+     */
+    public void setParent( PDField parent )
+    {
+        getDictionary().setItem( "Parent", parent );
+    }
+
+    /**
+     * This will find one of the child elements.  The name array are the components
+     * of the name to search down the tree of names.  The nameIndex is where to
+     * start in that array.  This method is called recursively until it finds
+     * the end point based on the name array.
+     *
+     * @param name An array that picks the path to the field.
+     * @param nameIndex The index into the array.
+     * @return The field at the endpoint or null if none is found.
+     * @throws IOException If there is an error creating the field.
+     */
+    public PDField findKid( String[] name, int nameIndex ) throws IOException
+    {
+        PDField retval = null;
+        COSArray kids = (COSArray)getDictionary().getDictionaryObject( COSName.KIDS );
+        if( kids != null )
+        {
+            for (int i = 0; retval == null && i < kids.size(); i++)
+            {
+                COSDictionary kidDictionary = (COSDictionary)kids.getObject(i);
+                if( name[nameIndex].equals( kidDictionary.getString( "T" ) ) )
+                {
+                    retval = PDFieldFactory.createField( acroForm, kidDictionary );
+                    if( name.length > nameIndex+1 )
+                    {
+                        retval = retval.findKid( name, nameIndex+1 );
+                    }
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This will get all the kids of this field.  The values in the list
+     * will either be PDWidget or PDField.  Normally they will be PDWidget objects
+     * unless this is a non-terminal field and they will be child PDField objects.
+     *
+     * @return A list of either PDWidget or PDField objects.
+     * @throws IOException If there is an error retrieving the kids.
+     */
+    public List<COSObjectable> getKids() throws IOException
+    {
+        List<COSObjectable> retval = null;
+        COSArray kids = (COSArray)getDictionary().getDictionaryObject(COSName.KIDS);
+        if( kids != null )
+        {
+            List<COSObjectable> kidsList = new ArrayList<COSObjectable>();
+            for (int i = 0; i < kids.size(); i++)
+            {
+                COSDictionary kidDictionary = (COSDictionary)kids.getObject(i);
+                COSDictionary parent = (COSDictionary)kidDictionary.getDictionaryObject( COSName.PARENT, COSName.P );
+                if( kidDictionary.getDictionaryObject( COSName.FT ) != null ||
+                    (parent != null && parent.getDictionaryObject( COSName.FT ) != null ) )
+                {
+                    kidsList.add( PDFieldFactory.createField( acroForm, kidDictionary ));
+                }
+                else if( "Widget".equals( kidDictionary.getNameAsString( COSName.SUBTYPE ) ) )
+                {
+                    kidsList.add( new PDAnnotationWidget( kidDictionary ) );
+                }
+                else
+                {
+                    //
+                    kidsList.add( PDFieldFactory.createField( acroForm, kidDictionary ));
+                }
+            }
+            retval = new COSArrayList( kidsList, kids );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the list of kids.
+     *
+     * @param kids The list of child widgets.
+     */
+    public void setKids( List<COSObjectable> kids )
+    {
+        COSArray kidsArray = COSArrayList.converterToCOSArray( kids );
+        getDictionary().setItem( COSName.KIDS, kidsArray );
+    }
+
+    /**
+     * This will return a string representation of this field.
+     *
+     * @return A string representation of this field.
+     */
+    @Override
+    public String toString()
+    {
+        return "" + getDictionary().getDictionaryObject( COSName.V );
+    }
+
+    /**
+     * This will get the acroform that this field is part of.
+     *
+     * @return The form this field is on.
+     */
+    public PDAcroForm getAcroForm()
+    {
+        return acroForm;
+    }
+
+    /**
+     * This will set the form this field is on.
+     *
+     * @param value The new form to use.
+     */
+    public void setAcroForm(PDAcroForm value)
+    {
+        acroForm = value;
+    }
+
+    /**
+     * This will get the dictionary associated with this field.
+     *
+     * @return The dictionary that this class wraps.
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return dictionary;
+    }
+
+    /**
+     * Get the additional actions for this field.  This will return null
+     * if there are no additional actions for this field.
+     *
+     * @return The actions of the field.
+     */
+    public PDFormFieldAdditionalActions getActions()
+    {
+        COSDictionary aa = (COSDictionary)dictionary.getDictionaryObject( COSName.AA );
+        PDFormFieldAdditionalActions retval = null;
+        if( aa != null )
+        {
+            retval = new PDFormFieldAdditionalActions( aa );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the actions of the field.
+     *
+     * @param actions The field actions.
+     */
+    public void setActions( PDFormFieldAdditionalActions actions )
+    {
+        dictionary.setItem( COSName.AA, actions );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDFieldFactory.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDFieldFactory.java
new file mode 100644 (file)
index 0000000..9b7423c
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+
+import java.io.IOException;
+
+import java.util.List;
+
+/**
+ * This is the Factory for creating and returning the correct
+ * field elements.
+ *
+ * @author sug
+ * @version $Revision: 1.8 $
+ */
+public class PDFieldFactory
+{
+    private static final int RADIO_BITMASK = 32768;
+    private static final int PUSHBUTTON_BITMASK = 65536;
+    private static final int RADIOS_IN_UNISON_BITMASK = 33554432;
+
+    private static final String FIELD_TYPE_BTN = "Btn";
+    private static final String FIELD_TYPE_TX = "Tx";
+    private static final String FIELD_TYPE_CH = "Ch";
+    private static final String FIELD_TYPE_SIG = "Sig";
+
+    /**
+     * Utility class so no constructor.
+     */
+    private PDFieldFactory()
+    {
+        //do nothing.
+    }
+
+    /**
+     * This method creates a COSField subclass from the given field.
+     * The field is a PDF Dictionary object that must represent a
+     * field element. - othewise null is returned
+     *
+     * @param acroForm The form that the field will be part of.
+     * @param field The dictionary representing a field element
+     *
+     * @return a subclass to COSField according to the kind of field passed to createField
+     * @throws IOException If there is an error determining the field type.
+     */
+    public static PDField createField( PDAcroForm acroForm, COSDictionary field) throws IOException
+    {
+        PDField pdField = new PDUnknownField( acroForm, field );
+        if( isButton(pdField) )
+        {
+            int flags = pdField.getFieldFlags();
+            //BJL, I have found that the radio flag bit is not always set
+            //and that sometimes there is just a kids dictionary.
+            //so, if there is a kids dictionary then it must be a radio button
+            //group.
+            COSArray kids = (COSArray)field.getDictionaryObject( COSName.getPDFName( "Kids" ) );
+            if( kids != null || isRadio(flags) )
+            {
+                pdField = new PDRadioCollection( acroForm, field );
+            }
+            else if( isPushButton( flags ) )
+            {
+                pdField = new PDPushButton( acroForm, field );
+            }
+            else
+            {
+                pdField = new PDCheckbox( acroForm, field );
+            }
+
+        }
+        else if (isChoiceField(pdField))
+        {
+            pdField = new PDChoiceField( acroForm, field );
+        }
+        else if (isTextbox(pdField))
+        {
+            pdField = new PDTextbox( acroForm, field );
+        }
+        else if( isSignature( pdField ) )
+        {
+            pdField = new PDSignatureField( acroForm, field );
+        }
+        else
+        {
+            //do nothing and return an unknown field type.
+        }
+        return pdField;
+    }
+
+    /**
+     * This method determines if the given
+     * field is a radiobutton collection.
+     *
+     * @param flags The field flags.
+     *
+     * @return the result of the determination
+     */
+    private static boolean isRadio( int flags )
+    {
+        return (flags & RADIO_BITMASK) > 0;
+    }
+
+    /**
+     * This method determines if the given
+     * field is a pushbutton.
+     *
+     * @param flags The field flags.
+     *
+     * @return the result of the determination
+     */
+    private static boolean isPushButton( int flags )
+    {
+        return (flags & PUSHBUTTON_BITMASK) > 0;
+    }
+
+    /**
+     * This method determines if the given field is a choicefield
+     * Choicefields are either listboxes or comboboxes.
+     *
+     * @param field the field to determine
+     * @return the result of the determination
+     */
+    private static boolean isChoiceField(PDField field) throws IOException
+    {
+        return FIELD_TYPE_CH.equals(field.findFieldType());
+    }
+
+    /**
+     * This method determines if the given field is a button.
+     *
+     * @param field the field to determine
+     * @return the result of the determination
+     *
+     * @throws IOException If there is an error determining the field type.
+     */
+    private static boolean isButton(PDField field) throws IOException
+    {
+        String ft = field.findFieldType();
+        boolean retval = FIELD_TYPE_BTN.equals( ft );
+        List kids = field.getKids();
+        if( ft == null && kids != null && kids.size() > 0)
+        {
+            //sometimes if it is a button the type is only defined by one
+            //of the kids entries
+            Object obj = kids.get( 0 );
+            COSDictionary kidDict = null;
+            if( obj instanceof PDField )
+            {
+                kidDict = ((PDField)obj).getDictionary();
+            }
+            else if( obj instanceof PDAnnotationWidget )
+            {
+                kidDict = ((PDAnnotationWidget)obj).getDictionary();
+            }
+            else
+            {
+                throw new IOException( "Error:Unexpected type of kids field:" + obj );
+            }
+            retval = isButton( new PDUnknownField( field.getAcroForm(), kidDict ) );
+        }
+        return retval;
+    }
+
+   /**
+     * This method determines if the given field is a signature.
+     *
+     * @param field the field to determine
+     * @return the result of the determination
+     */
+    private static boolean isSignature(PDField field) throws IOException
+    {
+        return FIELD_TYPE_SIG.equals(field.findFieldType());
+    }
+
+    /**
+     * This method determines if the given field is a Textbox.
+     *
+     * @param field the field to determine
+     * @return the result of the determination
+     */
+    private static boolean isTextbox(PDField field) throws IOException
+    {
+        return FIELD_TYPE_TX.equals(field.findFieldType());
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDPushButton.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDPushButton.java
new file mode 100644 (file)
index 0000000..4b2f446
--- /dev/null
@@ -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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import java.io.IOException;
+
+/**
+ * A class for handling the PDF field as a PDPushButton.
+ *
+ * @author sug
+ * @version $Revision: 1.3 $
+ */
+public class PDPushButton extends PDField
+{
+
+    /**
+     * @see org.apache.pdfbox.pdmodel.field.PDField#COSField(org.apache.pdfbox.cos.COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The field for this push button.
+     */
+    public PDPushButton( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super(theAcroForm, field);
+    }
+
+    /**
+     * @see as.interactive.pdf.form.cos.COSField#setValue(java.lang.String)
+     *
+     * @param value The new value for the field.
+     *
+     * @throws IOException If there is an error creating the appearance stream.
+     */
+    public void setValue(String value) throws IOException
+    {
+        COSString fieldValue = new COSString(value);
+        getDictionary().setItem( COSName.getPDFName( "V" ), fieldValue );
+        getDictionary().setItem( COSName.getPDFName( "DV" ), fieldValue );
+    }
+
+    /**
+     * getValue gets the fields value to as a string.
+     *
+     * @return The string value of this field.
+     *
+     * @throws IOException If there is an error getting the value.
+     */
+    public String getValue() throws IOException
+    {
+        return getDictionary().getString( "V" );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDRadioCollection.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDRadioCollection.java
new file mode 100644 (file)
index 0000000..06ebe4c
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+/**
+ * A class for handling the PDF field as a Radio Collection.
+ * This class automatically keeps track of the child radio buttons
+ * in the collection.
+ *
+ * @see PDCheckbox
+ * @author sug
+ * @version $Revision: 1.13 $
+ */
+public class PDRadioCollection extends PDChoiceButton
+{
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_RADIOS_IN_UNISON = 1 << 25;
+
+    /**
+     * @param theAcroForm The acroForm for this field.
+     * @param field The field that makes up the radio collection.
+     *
+     * {@inheritDoc}
+     */
+    public PDRadioCollection( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super(theAcroForm,field);
+    }
+
+    /**
+     * From the PDF Spec <br/>
+     * If set, a group of radio buttons within a radio button field that
+     * use the same value for the on state will turn on and off in unison; that is if
+     * one is checked, they are all checked. If clear, the buttons are mutually exclusive
+     * (the same behavior as HTML radio buttons).
+     *
+     * @param radiosInUnison The new flag for radiosInUnison.
+     */
+    public void setRadiosInUnison(boolean radiosInUnison)
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_RADIOS_IN_UNISON, radiosInUnison );
+    }
+
+    /**
+     *
+     * @return true If the flag is set for radios in unison.
+     */
+    public boolean isRadiosInUnison()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_RADIOS_IN_UNISON );
+    }
+
+    /**
+     * This setValue method iterates the collection of radiobuttons
+     * and checks or unchecks each radiobutton according to the
+     * given value.
+     * If the value is not represented by any of the radiobuttons,
+     * then none will be checked.
+     *
+     * {@inheritDoc}
+     */
+    public void setValue(String value) throws IOException
+    {
+        getDictionary().setString( COSName.V, value );
+        List kids = getKids();
+        for (int i = 0; i < kids.size(); i++)
+        {
+            PDField field = (PDField)kids.get(i);
+            if ( field instanceof PDCheckbox )
+            {
+                PDCheckbox btn = (PDCheckbox)field;
+                if( btn.getOnValue().equals(value) )
+                {
+                    btn.check();
+                }
+                else
+                {
+                    btn.unCheck();
+                }
+            }
+        }
+    }
+
+    /**
+     * getValue gets the fields value to as a string.
+     *
+     * @return The string value of this field.
+     *
+     * @throws IOException If there is an error getting the value.
+     */
+    public String getValue()throws IOException
+    {
+        String retval = null;
+        List kids = getKids();
+        for (int i = 0; i < kids.size(); i++)
+        {
+            PDField kid = (PDField)kids.get(i);
+            if ( kid instanceof PDCheckbox )
+            {
+                PDCheckbox btn = (PDCheckbox)kid;
+                if( btn.isChecked() )
+                {
+                    retval = btn.getOnValue();
+                }
+            }
+        }
+        if( retval == null )
+        {
+            retval = getDictionary().getNameAsString( COSName.V );
+        }
+        return retval;
+    }
+
+
+    /**
+     * This will return a list of PDField objects that are part of this radio collection.
+     *
+     * @see PDField#getWidget()
+     * @return A list of PDWidget objects.
+     * @throws IOException if there is an error while creating the children objects.
+     */
+    @SuppressWarnings("unchecked")
+    public List getKids() throws IOException
+    {
+        List retval = null;
+        COSArray kids = (COSArray)getDictionary().getDictionaryObject(COSName.KIDS);
+        if( kids != null )
+        {
+            List kidsList = new ArrayList();
+            for (int i = 0; i < kids.size(); i++)
+            {
+                kidsList.add( PDFieldFactory.createField( getAcroForm(), (COSDictionary)kids.getObject(i) ) );
+            }
+            retval = new COSArrayList( kidsList, kids );
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDSignature.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDSignature.java
new file mode 100644 (file)
index 0000000..9549c00
--- /dev/null
@@ -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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import java.io.IOException;
+
+/**
+ * A class for handling the PDF field as a signature.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDSignature extends PDField
+{
+
+    /**
+     * @see PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The dictionary for the signature.
+     */
+    public PDSignature( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super(theAcroForm,field);
+    }
+
+    /**
+     * @see PDField#setValue(java.lang.String)
+     *
+     * @param value The new value for the field.
+     *
+     * @throws IOException If there is an error creating the appearance stream.
+     */
+    public void setValue(String value) throws IOException
+    {
+        throw new RuntimeException( "Not yet implemented" );
+    }
+
+    /**
+     * @see PDField#setValue(java.lang.String)
+     *
+     * @return The string value of this field.
+     *
+     * @throws IOException If there is an error creating the appearance stream.
+     */
+    public String getValue() throws IOException
+    {
+        throw new RuntimeException( "Not yet implemented" );
+    }
+
+    /**
+     * Return a string rep of this object.
+     *
+     * @return A string rep of this object.
+     */
+    public String toString()
+    {
+        return "PDSignature";
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDSignatureField.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDSignatureField.java
new file mode 100644 (file)
index 0000000..92abaac
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class for handling the PDF field as a signature.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Thomas Chojecki
+ * @version $Revision: 1.5 $
+ */
+public class PDSignatureField extends PDField
+{
+
+    /**
+     * @see PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The dictionary for the signature.
+     * @throws IOException If there is an error while resolving partital name for the signature field
+     */
+    public PDSignatureField( PDAcroForm theAcroForm, COSDictionary field) throws IOException
+    {
+        super(theAcroForm,field);
+        // dirty hack to avoid npe caused through getWidget() method
+        getDictionary().setName( COSName.TYPE, "Annot" );
+        getDictionary().setName( COSName.SUBTYPE, PDAnnotationWidget.SUB_TYPE);
+    }
+
+    /**
+     * @see PDField#PDField(PDAcroForm)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @throws IOException If there is an error while resolving partial name for the signature field
+     */
+    public PDSignatureField( PDAcroForm theAcroForm) throws IOException
+    {
+        super( theAcroForm );
+        getDictionary().setName("FT", "Sig");
+        getWidget().setLocked(true);
+        getWidget().setPrinted(true);
+        setPartialName(generatePartialName());
+        getDictionary().setName( COSName.TYPE, "Annot" );
+        getDictionary().setName( COSName.SUBTYPE, PDAnnotationWidget.SUB_TYPE);
+    }
+    
+    /**
+     * Generate a unique name for the signature
+     * @return
+     * @throws IOException
+     */
+    private String generatePartialName() throws IOException
+    {
+      PDAcroForm acroForm = getAcroForm();
+      List fields = acroForm.getFields();
+      
+      String fieldName = "Signature";
+      int i = 1;
+      
+      Set<String> sigNames = new HashSet<String>();
+      
+      for ( Object object : fields )
+      {
+        if(object instanceof PDSignatureField)
+        {
+          sigNames.add(((PDSignatureField)object).getPartialName());
+        }
+      }
+
+      while(sigNames.contains(fieldName+i))
+        ++i;
+      
+      return fieldName+i;
+    }
+    
+    /**
+     * @see PDField#setValue(java.lang.String)
+     *
+     * @param value The new value for the field.
+     *
+     * @throws IOException If there is an error creating the appearance stream.
+     * @deprecated use setSignature(PDSignature) instead
+     */
+    @Override
+    @Deprecated
+    public void setValue(String value) throws IOException
+    {
+        throw new RuntimeException( "Can't set signature as String, use setSignature(PDSignature) instead" );
+    }
+
+    /**
+     * @see PDField#setValue(java.lang.String)
+     *
+     * @return The string value of this field.
+     *
+     * @throws IOException If there is an error creating the appearance stream.
+     * @deprecated use getSignature() instead
+     */
+    @Override
+    @Deprecated
+    public String getValue() throws IOException
+    {
+      throw new RuntimeException( "Can't get signature as String, use getSignature() instead." );
+    }
+
+    /**
+     * Return a string rep of this object.
+     *
+     * @return A string rep of this object.
+     */
+    @Override
+    public String toString()
+    {
+        return "PDSignature";
+    }
+    
+    /**
+     * Add a signature dictionary to the signature field
+     * 
+     * @param value is the PDSignature 
+     */
+    public void setSignature(PDSignature value) 
+    {
+      getDictionary().setItem("V", value);
+    }
+    
+    /**
+     * Get the signature dictionary 
+     * 
+     * @return the signature dictionary
+     * 
+     */
+    public PDSignature getSignature()
+    {
+      COSBase dictionary = getDictionary().getDictionaryObject(COSName.V);
+      if (dictionary == null)
+        return null;
+      return new PDSignature((COSDictionary)dictionary);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDTextbox.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDTextbox.java
new file mode 100644 (file)
index 0000000..eff83c2
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A class for handling the PDF field as a textbox.
+ *
+ * @author sug
+ * @version $Revision: 1.9 $
+ */
+public class PDTextbox extends PDVariableText
+{
+
+    /**
+     * @see PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroform.
+     */
+    public PDTextbox( PDAcroForm theAcroForm )
+    {
+        super( theAcroForm );
+    }
+
+    /**
+     * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The field's dictionary.
+     */
+    public PDTextbox( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super( theAcroForm, field);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDUnknownField.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDUnknownField.java
new file mode 100644 (file)
index 0000000..c45b60b
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.interactive.form;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This class represents a form field with an unknown type.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDUnknownField extends PDField
+{
+    /**
+     * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm, COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The field's dictionary.
+     */
+    public PDUnknownField( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super( theAcroForm, field);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setValue(String value) throws IOException
+    {
+        //do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getValue() throws IOException
+    {
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDVariableText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDVariableText.java
new file mode 100644 (file)
index 0000000..7fa9107
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+import java.io.IOException;
+
+/**
+ * A class for handling PDF fields that display text.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public abstract class PDVariableText extends PDField
+{
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_MULTILINE = 1 << 12;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_PASSWORD = 1 << 13;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_FILE_SELECT = 1 << 20;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_DO_NOT_SPELL_CHECK = 1 << 22;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_DO_NOT_SCROLL = 1 << 23;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_COMB = 1 << 24;
+    /**
+     * A Ff flag.
+     */
+    public static final int FLAG_RICH_TEXT = 1 << 25;
+
+
+    /**
+     * DA    Default appearance.
+     */
+    private COSString da;
+
+    private PDAppearance appearance;
+
+
+    /**
+     * A Q value.
+     */
+    public static final int QUADDING_LEFT = 0;
+
+    /**
+     * A Q value.
+     */
+    public static final int QUADDING_CENTERED = 1;
+
+    /**
+     * A Q value.
+     */
+    public static final int QUADDING_RIGHT = 2;
+
+    /**
+     * @see PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroform.
+     */
+    public PDVariableText( PDAcroForm theAcroForm )
+    {
+        super( theAcroForm );
+    }
+
+    /**
+     * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm,COSDictionary)
+     *
+     * @param theAcroForm The acroForm for this field.
+     * @param field The field's dictionary.
+     */
+    public PDVariableText( PDAcroForm theAcroForm, COSDictionary field)
+    {
+        super( theAcroForm, field);
+        da = (COSString) field.getDictionaryObject(COSName.DA);
+    }
+
+    /**
+     * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#setValue(java.lang.String)
+     *
+     * @param value The new value for this text field.
+     *
+     * @throws IOException If there is an error calculating the appearance stream.
+     */
+    public void setValue(String value) throws IOException
+    {
+        COSString fieldValue = new COSString(value);
+        getDictionary().setItem( COSName.V, fieldValue );
+
+        //hmm, not sure what the case where the DV gets set to the field
+        //value, for now leave blank until we can come up with a case
+        //where it needs to be in there
+        //getDictionary().setItem( COSName.getPDFName( "DV" ), fieldValue );
+        if(appearance == null)
+        {
+            this.appearance = new PDAppearance( getAcroForm(), this );
+        }
+        appearance.setAppearanceValue(value);
+    }
+
+    /**
+     * getValue gets the fields value to as a string.
+     *
+     * @return The string value of this field.
+     *
+     * @throws IOException If there is an error getting the value.
+     */
+    public String getValue() throws IOException
+    {
+        return getDictionary().getString( COSName.V );
+    }
+
+    /**
+     * @return true if the field is multiline
+     */
+    public boolean isMultiline()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_MULTILINE );
+    }
+
+    /**
+     * Set the multiline bit.
+     *
+     * @param multiline The value for the multiline.
+     */
+    public void setMultiline( boolean multiline )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_MULTILINE, multiline );
+    }
+
+    /**
+     * @return true if the field is a password field.
+     */
+    public boolean isPassword()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_PASSWORD );
+    }
+
+    /**
+     * Set the password bit.
+     *
+     * @param password The value for the password.
+     */
+    public void setPassword( boolean password )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_PASSWORD, password );
+    }
+
+    /**
+     * @return true if the field is a file select field.
+     */
+    public boolean isFileSelect()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_FILE_SELECT );
+    }
+
+    /**
+     * Set the file select bit.
+     *
+     * @param fileSelect The value for the fileSelect.
+     */
+    public void setFileSelect( boolean fileSelect )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_FILE_SELECT, fileSelect );
+    }
+
+    /**
+     * @return true if the field is not suppose to spell check.
+     */
+    public boolean doNotSpellCheck()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SPELL_CHECK );
+    }
+
+    /**
+     * Set the doNotSpellCheck bit.
+     *
+     * @param doNotSpellCheck The value for the doNotSpellCheck.
+     */
+    public void setDoNotSpellCheck( boolean doNotSpellCheck )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SPELL_CHECK, doNotSpellCheck );
+    }
+
+    /**
+     * @return true if the field is not suppose to scroll.
+     */
+    public boolean doNotScroll()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SCROLL );
+    }
+
+    /**
+     * Set the doNotScroll bit.
+     *
+     * @param doNotScroll The value for the doNotScroll.
+     */
+    public void setDoNotScroll( boolean doNotScroll )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SCROLL, doNotScroll );
+    }
+
+    /**
+     * @return true if the field is not suppose to comb the text display.
+     */
+    public boolean shouldComb()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_COMB );
+    }
+
+    /**
+     * Set the comb bit.
+     *
+     * @param comb The value for the comb.
+     */
+    public void setComb( boolean comb )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_COMB, comb );
+    }
+
+    /**
+     * @return true if the field is a rich text field.
+     */
+    public boolean isRichText()
+    {
+        return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_RICH_TEXT );
+    }
+
+    /**
+     * Set the richText bit.
+     *
+     * @param richText The value for the richText.
+     */
+    public void setRichText( boolean richText )
+    {
+        BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_RICH_TEXT, richText );
+    }
+
+    /**
+     * @return the DA element of the dictionary object
+     */
+    protected COSString getDefaultAppearance()
+    {
+        return da;
+    }
+
+    /**
+     * This will get the 'quadding' or justification of the text to be displayed.
+     * 0 - Left(default)<br/>
+     * 1 - Centered<br />
+     * 2 - Right<br />
+     * Please see the QUADDING_CONSTANTS.
+     *
+     * @return The justification of the text strings.
+     */
+    public int getQ()
+    {
+        int retval = 0;
+        COSNumber number = (COSNumber)getDictionary().getDictionaryObject( COSName.Q );
+        if( number != null )
+        {
+            retval = number.intValue();
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the quadding/justification of the text.  See QUADDING constants.
+     *
+     * @param q The new text justification.
+     */
+    public void setQ( int q )
+    {
+        getDictionary().setInt( COSName.Q, q );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDXFA.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/PDXFA.java
new file mode 100644 (file)
index 0000000..2975f0c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.form;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents an XML Forms Architecture Data packet.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDXFA implements COSObjectable
+{
+    private COSBase xfa;
+
+    /**
+     * Constructor.
+     *
+     * @param xfaBase The xfa resource.
+     */
+    public PDXFA( COSBase xfaBase )
+    {
+        xfa = xfaBase;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return xfa;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/form/package.html
new file mode 100644 (file)
index 0000000..1e0ddb5
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The interactive package contains classes that deal with interactive annotations such as textfields and buttons.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDMeasureDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDMeasureDictionary.java
new file mode 100644 (file)
index 0000000..d943296
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.measurement;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents a measure dictionary.
+ * 
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDMeasureDictionary implements COSObjectable
+{
+    /**
+     * The type of the dictionary.
+     */
+    public static final String TYPE = "Measure";
+
+    private COSDictionary measureDictionary;
+
+    /**
+     * Constructor.
+     */
+    protected PDMeasureDictionary()
+    {
+        this.measureDictionary = new COSDictionary();
+        this.getDictionary().setName(COSName.TYPE, TYPE);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param dictionary the corresponding dictionary
+     */
+    public PDMeasureDictionary(COSDictionary dictionary)
+    {
+        this.measureDictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.measureDictionary;
+    }
+
+    /**
+     * This will return the corresponding dictionary.
+     * 
+     * @return the measure dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return this.measureDictionary;
+    }
+
+    /**
+     * This will return the type of the measure dictionary.
+     * It must be "Measure"
+     * 
+     * @return the type
+     */
+    public String getType()
+    {
+        return TYPE;
+    }
+
+    /**
+     * returns the subtype of the measure dictionary.
+     * @return the subtype of the measure data dictionary
+     */
+
+    public String getSubtype()
+    {
+        return this.getDictionary().getNameAsString(COSName.SUBTYPE,
+                PDRectlinearMeasureDictionary.SUBTYPE);
+    }
+
+    /**
+     * This will set the subtype of the measure dictionary.
+     * @param subtype the subtype of the measure dictionary
+     */
+    protected void setSubtype(String subtype)
+    {
+        this.getDictionary().setName(COSName.SUBTYPE, subtype);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDNumberFormatDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDNumberFormatDictionary.java
new file mode 100644 (file)
index 0000000..f0eb010
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.measurement;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents a number format dictionary.
+ * 
+ * @version $Revision: 1.0$
+ *
+ */
+public class PDNumberFormatDictionary implements COSObjectable
+{
+
+    /**
+     * The type of the dictionary.
+     */
+    public static final String TYPE = "NumberFormat";
+
+    /**
+     * Constant indicating that the label specified by U is a suffix to the value.
+     */
+    public static final String LABEL_SUFFIX_TO_VALUE = "S";
+    /**
+     * Constant indicating that the label specified by U is a postfix to the value.
+     */
+    public static final String LABEL_PREFIX_TO_VALUE = "P";
+
+    /**
+     * Constant for showing a fractional value as decimal to the precision specified by the D entry.
+     */
+    public static final String FRACTIONAL_DISPLAY_DECIMAL = "D";
+    /**
+     * Constant for showing a fractional value as a fraction with denominator specified by the D entry.
+     */
+    public static final String FRACTIONAL_DISPLAY_FRACTION = "F";
+    /**
+     * Constant for showing a fractional value without fractional part; round to the nearest whole unit.
+     */
+    public static final String FRACTIONAL_DISPLAY_ROUND = "R";
+    /**
+     * Constant for showing a fractional value without fractional part; truncate to achieve whole units.
+     */
+    public static final String FRACTIONAL_DISPLAY_TRUNCATE = "T";
+
+    private COSDictionary numberFormatDictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDNumberFormatDictionary()
+    {
+        this.numberFormatDictionary = new COSDictionary();
+        this.numberFormatDictionary.setName(COSName.TYPE, TYPE);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param dictionary the corresponding dictionary
+     */
+    public PDNumberFormatDictionary(COSDictionary dictionary)
+    {
+        this.numberFormatDictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public COSBase getCOSObject()
+    {
+        return this.numberFormatDictionary;
+    }
+
+    /**
+     * This will return the dictionary.
+     * 
+     * @return the number format dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return this.numberFormatDictionary;
+    }
+
+    /**
+     * This will return the type of the number format dictionary.
+     * It must be "NumberFormat"
+     * 
+     * @return the type
+     */
+    public String getType()
+    {
+        return TYPE;
+    }
+
+    /**
+     * This will return the label for the units.
+     * 
+     * @return the label for the units
+     */
+    public String getUnits()
+    {
+        return this.getDictionary().getString("U");
+    }
+
+    /**
+     * This will set the label for the units.
+     * 
+     * @param units the label for the units
+     */
+    public void setUnits(String units)
+    {
+        this.getDictionary().setString("U", units);
+    }
+
+    /**
+     * This will return the conversion factor.
+     * 
+     * @return the conversion factor
+     */
+    public float getConversionFactor()
+    {
+        return this.getDictionary().getFloat("C");
+    }
+
+    /**
+     * This will set the conversion factor.
+     * 
+     * @param conversionFactor the conversion factor
+     */
+    public void setConversionFactor(float conversionFactor)
+    {
+        this.getDictionary().setFloat("C", conversionFactor);
+    }
+
+    /** 
+     * This will return the value for the manner to display a fractional value.
+     *  
+     * @return the manner to display a fractional value
+     */
+    public String getFractionalDisplay()
+    {
+        return this.getDictionary().getString("F", FRACTIONAL_DISPLAY_DECIMAL);
+    }
+
+    /** 
+     * This will set the value for the manner to display a fractional value.
+     * Allowed values are "D", "F", "R" and "T"
+     * @param fractionalDisplay the manner to display a fractional value
+     */
+    public void setFractionalDisplay(String fractionalDisplay)
+    {
+        if ((fractionalDisplay == null)
+            || FRACTIONAL_DISPLAY_DECIMAL.equals(fractionalDisplay)
+            || FRACTIONAL_DISPLAY_FRACTION.equals(fractionalDisplay)
+            || FRACTIONAL_DISPLAY_ROUND.equals(fractionalDisplay)
+            || FRACTIONAL_DISPLAY_TRUNCATE.equals(fractionalDisplay))
+        {
+            this.getDictionary().setString("F", fractionalDisplay);
+        }
+        else
+        {
+            throw new IllegalArgumentException("Value must be \"D\", \"F\", \"R\", or \"T\", (or null).");
+        }
+    }
+
+    /**
+     * This will return the precision or denominator of a fractional amount.
+     * 
+     * @return the precision or denominator
+     */
+    public int getDenominator()
+    {
+        return this.getDictionary().getInt("D");
+    }
+
+    /**
+     * This will set the precision or denominator of a fractional amount.
+     * 
+     * @param denominator the precision or denominator
+     */
+    public void setDenominator(int denominator)
+    {
+        this.getDictionary().setInt("D", denominator);
+    }
+
+    /**
+     * This will return the value indication if the denominator of the fractional value is reduced/truncated .
+     * 
+     * @return fd
+     */
+    public boolean isFD()
+    {
+        return this.getDictionary().getBoolean("FD", false);
+    }
+
+    /**
+     * This will set the value indication if the denominator of the fractional value is reduced/truncated .
+     * The denominator may not be reduced/truncated if true
+     * @param fd fd
+     */
+    public void setFD(boolean fd)
+    {
+        this.getDictionary().setBoolean("FD", fd);
+    }
+
+    /**
+     * This will return the text to be used between orders of thousands in display of numerical values.
+     * 
+     * @return thousands separator
+     */
+    public String getThousandsSeparator()
+    {
+        return this.getDictionary().getString("RT", ",");
+    }
+
+    /**
+     * This will set the text to be used between orders of thousands in display of numerical values.
+     * 
+     * @param thousandsSeparator thousands separator
+     */
+    public void setThousandsSeparator(String thousandsSeparator)
+    {
+        this.getDictionary().setString("RT", thousandsSeparator);
+    }
+
+    /**
+     * This will return the text to be used as the decimal point in displaying numerical values.
+     * 
+     * @return decimal separator
+     */
+    public String getDecimalSeparator()
+    {
+        return this.getDictionary().getString("RD", ".");
+    }
+
+    /**
+     * This will set the text to be used as the decimal point in displaying numerical values.
+     * 
+     * @param decimalSeparator decimal separator
+     */
+    public void setDecimalSeparator(String decimalSeparator)
+    {
+        this.getDictionary().setString("RD", decimalSeparator);
+    }
+
+    /**
+     * This will return the text to be concatenated to the left of the label specified by U.
+     * @return label prefix
+     */
+    public String getLabelPrefixString()
+    {
+        return this.getDictionary().getString("PS", " ");
+    }
+
+    /**
+     * This will set the text to be concatenated to the left of the label specified by U.
+     * @param labelPrefixString label prefix
+     */
+    public void setLabelPrefixString(String labelPrefixString)
+    {
+        this.getDictionary().setString("PS", labelPrefixString);
+    }
+
+    /**
+     * This will return the text to be concatenated after the label specified by U.
+     * 
+     * @return label suffix
+     */
+    public String getLabelSuffixString()
+    {
+        return this.getDictionary().getString("SS", " ");
+    }
+
+    /**
+     * This will set the text to be concatenated after the label specified by U.
+     * 
+     * @param labelSuffixString label suffix
+     */
+    public void setLabelSuffixString(String labelSuffixString)
+    {
+        this.getDictionary().setString("SS", labelSuffixString);
+    }
+
+    /**
+     * This will return a value indicating the ordering of the label specified by U to the calculated unit value.
+     * 
+     * @return label position 
+     */
+    public String getLabelPositionToValue()
+    {
+        return this.getDictionary().getString("O", LABEL_SUFFIX_TO_VALUE);
+    }
+
+    /**
+     * This will set the value indicating the ordering of the label specified by U to the calculated unit value.
+     * Possible values are "S" and "P"
+     * 
+     * @param labelPositionToValue label position 
+     */
+    public void setLabelPositionToValue(String labelPositionToValue)
+    {
+        if ((labelPositionToValue == null)
+            || LABEL_PREFIX_TO_VALUE.equals(labelPositionToValue)
+            || LABEL_SUFFIX_TO_VALUE.equals(labelPositionToValue))
+        {
+            this.getDictionary().setString("O", labelPositionToValue);
+        }
+        else
+        {
+            throw new IllegalArgumentException("Value must be \"S\", or \"P\" (or null).");
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDRectlinearMeasureDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDRectlinearMeasureDictionary.java
new file mode 100644 (file)
index 0000000..1c6ecfb
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.measurement;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class represents a rectlinear measure dictionary.
+ * 
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDRectlinearMeasureDictionary extends PDMeasureDictionary
+{
+
+    /**
+     * The subtype of the rectlinear measure dictionary.
+     */
+    public static final String SUBTYPE = "RL";
+
+    /**
+     * Constructor.
+     */
+    public PDRectlinearMeasureDictionary()
+    {
+        this.setSubtype(SUBTYPE);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param dictionary the corresponding dictionary
+     */
+    public PDRectlinearMeasureDictionary(COSDictionary dictionary)
+    {
+        super(dictionary);
+    }
+
+    /**
+     * This will return the scale ration.
+     * 
+     * @return the scale ratio.
+     */
+    public String getScaleRatio()
+    {
+        return this.getDictionary().getString(COSName.R);
+    }
+
+    /**
+     * This will set the scale ration.
+     * 
+     * @param scaleRatio the scale ratio.
+     */
+    public void setScaleRatio(String scaleRatio)
+    {
+        this.getDictionary().setString(COSName.R, scaleRatio);
+    }
+
+    /**
+     * This will return the changes along the x-axis.
+     * 
+     * @return changes along the x-axis
+     */
+    public PDNumberFormatDictionary[] getChangeXs()
+    {
+        COSArray x = (COSArray)this.getDictionary().getDictionaryObject("X");
+        if (x != null)
+        {
+            PDNumberFormatDictionary[] retval =
+                new PDNumberFormatDictionary[x.size()];
+            for (int i = 0; i < x.size(); i++)
+            {
+                COSDictionary dic = (COSDictionary) x.get(i);
+                retval[i] = new PDNumberFormatDictionary(dic);
+            }
+            return retval;
+        }
+        return null;
+    }
+
+    /**
+     * This will set the changes along the x-axis.
+     * 
+     * @param changeXs changes along the x-axis
+     */
+    public void setChangeXs(PDNumberFormatDictionary[] changeXs)
+    {
+        COSArray array = new COSArray();
+        for (int i = 0; i < changeXs.length; i++)
+        {
+            array.add(changeXs[i]);
+        }
+        this.getDictionary().setItem("X", array);
+    }
+
+    /**
+     * This will return the changes along the y-axis.
+     * 
+     * @return changes along the y-axis
+     */
+    public PDNumberFormatDictionary[] getChangeYs()
+    {
+        COSArray y = (COSArray)this.getDictionary().getDictionaryObject("Y");
+        if (y != null)
+        {
+            PDNumberFormatDictionary[] retval =
+                new PDNumberFormatDictionary[y.size()];
+            for (int i = 0; i < y.size(); i++)
+            {
+                COSDictionary dic = (COSDictionary) y.get(i);
+                retval[i] = new PDNumberFormatDictionary(dic);
+            }
+            return retval;
+        }
+        return null;
+    }
+
+    /**
+     * This will set the changes along the y-axis.
+     * 
+     * @param changeYs changes along the y-axis
+     */
+    public void setChangeYs(PDNumberFormatDictionary[] changeYs)
+    {
+        COSArray array = new COSArray();
+        for (int i = 0; i < changeYs.length; i++)
+        {
+            array.add(changeYs[i]);
+        }
+        this.getDictionary().setItem("Y", array);
+    }
+
+    /**
+     * This will return the distances.
+     * 
+     * @return distances
+     */
+    public PDNumberFormatDictionary[] getDistances()
+    {
+        COSArray d = (COSArray)this.getDictionary().getDictionaryObject("D");
+        if (d != null)
+        {
+            PDNumberFormatDictionary[] retval =
+                new PDNumberFormatDictionary[d.size()];
+            for (int i = 0; i < d.size(); i++)
+            {
+                COSDictionary dic = (COSDictionary) d.get(i);
+                retval[i] = new PDNumberFormatDictionary(dic);
+            }
+            return retval;
+        }
+        return null;
+    }
+
+    /**
+     * This will set the distances.
+     * 
+     * @param distances distances
+     */
+    public void setDistances(PDNumberFormatDictionary[] distances)
+    {
+        COSArray array = new COSArray();
+        for (int i = 0; i < distances.length; i++)
+        {
+            array.add(distances[i]);
+        }
+        this.getDictionary().setItem("D", array);
+    }
+
+    /**
+     * This will return the areas.
+     * 
+     * @return areas
+     */
+    public PDNumberFormatDictionary[] getAreas()
+    {
+        COSArray a = (COSArray)this.getDictionary().getDictionaryObject(COSName.A);
+        if (a != null)
+        {
+            PDNumberFormatDictionary[] retval =
+                new PDNumberFormatDictionary[a.size()];
+            for (int i = 0; i < a.size(); i++)
+            {
+                COSDictionary dic = (COSDictionary) a.get(i);
+                retval[i] = new PDNumberFormatDictionary(dic);
+            }
+            return retval;
+        }
+        return null;
+    }
+
+    /**
+     * This will set the areas.
+     * 
+     * @param areas areas
+     */
+    public void setAreas(PDNumberFormatDictionary[] areas)
+    {
+        COSArray array = new COSArray();
+        for (int i = 0; i < areas.length; i++)
+        {
+            array.add(areas[i]);
+        }
+        this.getDictionary().setItem(COSName.A, array);
+    }
+
+    /**
+     * This will return the angles.
+     * 
+     * @return angles
+     */
+    public PDNumberFormatDictionary[] getAngles()
+    {
+        COSArray t = (COSArray)this.getDictionary().getDictionaryObject("T");
+        if (t != null)
+        {
+            PDNumberFormatDictionary[] retval =
+                new PDNumberFormatDictionary[t.size()];
+            for (int i = 0; i < t.size(); i++)
+            {
+                COSDictionary dic = (COSDictionary) t.get(i);
+                retval[i] = new PDNumberFormatDictionary(dic);
+            }
+            return retval;
+        }
+        return null;
+    }
+
+    /**
+     * This will set the angles.
+     * 
+     * @param angles angles
+     */
+    public void setAngles(PDNumberFormatDictionary[] angles)
+    {
+        COSArray array = new COSArray();
+        for (int i = 0; i < angles.length; i++)
+        {
+            array.add(angles[i]);
+        }
+        this.getDictionary().setItem("T", array);
+    }
+
+    /**
+     * This will return the sloaps of a line.
+     * 
+     * @return the sloaps of a line
+     */
+    public PDNumberFormatDictionary[] getLineSloaps()
+    {
+        COSArray s = (COSArray)this.getDictionary().getDictionaryObject("S");
+        if (s != null)
+        {
+            PDNumberFormatDictionary[] retval =
+                new PDNumberFormatDictionary[s.size()];
+            for (int i = 0; i < s.size(); i++)
+            {
+                COSDictionary dic = (COSDictionary) s.get(i);
+                retval[i] = new PDNumberFormatDictionary(dic);
+            }
+            return retval;
+        }
+        return null;
+    }
+
+    /**
+     * This will set the sloaps of a line.
+     * 
+     * @param lineSloaps the sloaps of a line
+     */
+    public void setLineSloaps(PDNumberFormatDictionary[] lineSloaps)
+    {
+        COSArray array = new COSArray();
+        for (int i = 0; i < lineSloaps.length; i++)
+        {
+            array.add(lineSloaps[i]);
+        }
+        this.getDictionary().setItem("S", array);
+    }
+
+    /**
+     * This will return the origin of the coordinate system.
+     * 
+     * @return the origin
+     */
+    public float[] getCoordSystemOrigin()
+    {
+        COSArray o = (COSArray)this.getDictionary().getDictionaryObject("O");
+        if (o != null)
+        {
+            return o.toFloatArray();
+        }
+        return null;
+    }
+
+    /**
+     * This will set the origin of the coordinate system.
+     * 
+     * @param coordSystemOrigin the origin
+     */
+    public void setCoordSystemOrigin(float[] coordSystemOrigin)
+    {
+        COSArray array = new COSArray();
+        array.setFloatArray(coordSystemOrigin);
+        this.getDictionary().setItem("O", array);
+    }
+
+    /**
+     * This will return the CYX factor.
+     * 
+     * @return CYX factor
+     */
+    public float getCYX()
+    {
+        return this.getDictionary().getFloat("CYX");
+    }
+
+    /**
+     * This will set the CYX factor.
+     * 
+     * @param cyx CYX factor
+     */
+    public void setCYX(float cyx)
+    {
+        this.getDictionary().setFloat("CYX", cyx);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDViewportDictionary.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/PDViewportDictionary.java
new file mode 100644 (file)
index 0000000..6243128
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.measurement;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This class represents a viewport dictionary.
+ * 
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDViewportDictionary implements COSObjectable
+{
+
+    /**
+     * The type of this annotation.
+     */
+    public static final String TYPE = "Viewport";
+    
+    private COSDictionary viewportDictionary;
+
+    /**
+     * Constructor.
+     */
+    public PDViewportDictionary()
+    {
+        this.viewportDictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param dictionary the dictionary
+     */
+    public PDViewportDictionary(COSDictionary dictionary)
+    {
+        this.viewportDictionary = dictionary;
+    }
+
+    /**
+     * {@inheritDoc} 
+     */
+    public COSBase getCOSObject()
+    {
+        return this.viewportDictionary;
+    }
+
+    /**
+     * This will return the corresponding dictionary.
+     * 
+     * @return the viewport dictionary
+     */
+    public COSDictionary getDictionary()
+    {
+        return this.viewportDictionary;
+    }
+
+    /**
+     * Returns the type of the viewport dictionary.
+     * It must be "Viewport"
+     * @return the type of the external data dictionary
+     */
+
+    public String getType()
+    {
+        return TYPE;
+    }
+
+    /**
+     * This will retrieve the rectangle specifying the location of the viewport.
+     * 
+     * @return the location
+     */
+    public PDRectangle getBBox()
+    {
+        COSArray bbox = (COSArray)this.getDictionary().getDictionaryObject("BBox");
+        if (bbox != null)
+        {
+            return new PDRectangle(bbox);
+        }
+        return null;
+    }
+
+    /**
+     * This will set the rectangle specifying the location of the viewport.
+     * 
+     * @param rectangle the rectangle specifying the location.
+     */
+    public void setBBox(PDRectangle rectangle)
+    {
+        this.getDictionary().setItem("BBox", rectangle);
+    }
+
+    /**
+     * This will retrieve the name of the viewport.
+     * 
+     * @return the name of the viewport
+     */
+    public String getName()
+    {
+        return this.getDictionary().getNameAsString(COSName.NAME);
+    }
+
+   /**
+    * This will set the name of the viewport.
+    *  
+    * @param name the name of the viewport
+    */
+    public void setName(String name)
+    {
+        this.getDictionary().setName(COSName.NAME, name);
+    }
+
+    /**
+     * This will retrieve the measure dictionary.
+     * 
+     * @return the measure dictionary
+     */
+    public PDMeasureDictionary getMeasure()
+    {
+        COSDictionary measure = (COSDictionary)this.getDictionary().getDictionaryObject("Measure");
+        if (measure != null)
+        {
+            return new PDMeasureDictionary(measure);
+        }
+        return null;
+    }
+
+    /**
+     * This will set the measure dictionary.
+     * 
+     * @param measure the measure dictionary
+     */
+    public void setMeasure(PDMeasureDictionary measure)
+    {
+        this.getDictionary().setItem("Measure", measure);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/measurement/package.html
new file mode 100644 (file)
index 0000000..1b91e60
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The measurement package contains classes that work with elements specifying measure properties.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/PDThread.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/PDThread.java
new file mode 100644 (file)
index 0000000..fd8a845
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.pagenavigation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This a single thread in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDThread implements COSObjectable
+{
+
+
+    private COSDictionary thread;
+
+    /**
+     * Constructor that is used for a preexisting dictionary.
+     *
+     * @param t The underlying dictionary.
+     */
+    public PDThread( COSDictionary t )
+    {
+        thread = t;
+    }
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDThread()
+    {
+        thread = new COSDictionary();
+        thread.setName( "Type", "Thread" );
+    }
+
+    /**
+     * This will get the underlying dictionary that this object wraps.
+     *
+     * @return The underlying info dictionary.
+     */
+    public COSDictionary getDictionary()
+    {
+        return thread;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return thread;
+    }
+
+    /**
+     * Get info about the thread, or null if there is nothing.
+     *
+     * @return The thread information.
+     */
+    public PDDocumentInformation getThreadInfo()
+    {
+        PDDocumentInformation retval = null;
+        COSDictionary info = (COSDictionary)thread.getDictionaryObject( "I" );
+        if( info != null )
+        {
+            retval = new PDDocumentInformation( info );
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the thread info, can be null.
+     *
+     * @param info The info dictionary about this thread.
+     */
+    public void setThreadInfo( PDDocumentInformation info )
+    {
+        thread.setItem( "I", info );
+    }
+
+    /**
+     * Get the first bead in the thread, or null if it has not been set yet.  This
+     * is a required field for this object.
+     *
+     * @return The first bead in the thread.
+     */
+    public PDThreadBead getFirstBead()
+    {
+        PDThreadBead retval = null;
+        COSDictionary bead = (COSDictionary)thread.getDictionaryObject( "F" );
+        if( bead != null )
+        {
+            retval = new PDThreadBead( bead );
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will set the first bead in the thread.  When this is set it will
+     * also set the thread property of the bead object.
+     *
+     * @param bead The first bead in the thread.
+     */
+    public void setFirstBead( PDThreadBead bead )
+    {
+        if( bead != null )
+        {
+            bead.setThread( this );
+        }
+        thread.setItem( "F", bead );
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/PDThreadBead.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/PDThreadBead.java
new file mode 100644 (file)
index 0000000..ff72e6d
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.pagenavigation;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This a single bead in a thread in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDThreadBead implements COSObjectable
+{
+
+
+    private COSDictionary bead;
+
+    /**
+     * Constructor that is used for a preexisting dictionary.
+     *
+     * @param b The underlying dictionary.
+     */
+    public PDThreadBead( COSDictionary b )
+    {
+        bead = b;
+    }
+
+    /**
+     * Default constructor.
+     *
+     */
+    public PDThreadBead()
+    {
+        bead = new COSDictionary();
+        bead.setName( "Type", "Bead" );
+        setNextBead( this );
+        setPreviousBead( this );
+    }
+
+    /**
+     * This will get the underlying dictionary that this object wraps.
+     *
+     * @return The underlying info dictionary.
+     */
+    public COSDictionary getDictionary()
+    {
+        return bead;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return bead;
+    }
+
+    /**
+     * This will get the thread that this bead is part of.  This is only required
+     * for the first bead in a thread, so other beads 'may' return null.
+     *
+     * @return The thread that this bead is part of.
+     */
+    public PDThread getThread()
+    {
+        PDThread retval = null;
+        COSDictionary dic = (COSDictionary)bead.getDictionaryObject( "T" );
+        if( dic != null )
+        {
+            retval = new PDThread( dic );
+        }
+        return retval;
+    }
+
+    /**
+     * Set the thread that this bead is part of.  This is only required for the
+     * first bead in a thread.  Note: This property is set for you by the PDThread.setFirstBead() method.
+     *
+     * @param thread The thread that this bead is part of.
+     */
+    public void setThread( PDThread thread )
+    {
+        bead.setItem( "T", thread );
+    }
+
+    /**
+     * This will get the next bead.  If this bead is the last bead in the list then this
+     * will return the first bead.
+     *
+     * @return The next bead in the list or the first bead if this is the last bead.
+     */
+    public PDThreadBead getNextBead()
+    {
+        return new PDThreadBead( (COSDictionary) bead.getDictionaryObject( "N" ) );
+    }
+
+    /**
+     * Set the next bead in the thread.
+     *
+     * @param next The next bead.
+     */
+    protected void setNextBead( PDThreadBead next )
+    {
+        bead.setItem( "N", next );
+    }
+
+    /**
+     * This will get the previous bead.  If this bead is the first bead in the list then this
+     * will return the last bead.
+     *
+     * @return The previous bead in the list or the last bead if this is the first bead.
+     */
+    public PDThreadBead getPreviousBead()
+    {
+        return new PDThreadBead( (COSDictionary) bead.getDictionaryObject( "V" ) );
+    }
+
+    /**
+     * Set the previous bead in the thread.
+     *
+     * @param previous The previous bead.
+     */
+    protected void setPreviousBead( PDThreadBead previous )
+    {
+        bead.setItem( "V", previous );
+    }
+
+    /**
+     * Append a bead after this bead.  This will correctly set the next/previous beads in the
+     * linked list.
+     *
+     * @param append The bead to insert.
+     */
+    public void appendBead( PDThreadBead append )
+    {
+        PDThreadBead nextBead = getNextBead();
+        nextBead.setPreviousBead( append );
+        append.setNextBead( nextBead );
+        setNextBead( append );
+        append.setPreviousBead( this );
+    }
+
+    /**
+     * Get the page that this bead is part of.
+     *
+     * @return The page that this bead is part of.
+     */
+    public PDPage getPage()
+    {
+        PDPage page = null;
+        COSDictionary dic = (COSDictionary)bead.getDictionaryObject( "P" );
+        if( dic != null )
+        {
+            page = new PDPage( dic );
+        }
+        return page;
+    }
+
+    /**
+     * Set the page that this bead is part of.  This is a required property and must be
+     * set when creating a new bead.  The PDPage object also has a list of beads in the natural
+     * reading order.  It is recommended that you add this object to that list as well.
+     *
+     * @param page The page that this bead is on.
+     */
+    public void setPage( PDPage page )
+    {
+        bead.setItem( "P", page );
+    }
+
+    /**
+     * The rectangle on the page that this bead is part of.
+     *
+     * @return The part of the page that this bead covers.
+     */
+    public PDRectangle getRectangle()
+    {
+        PDRectangle rect = null;
+        COSArray array = (COSArray)bead.getDictionaryObject( COSName.R );
+        if( array != null )
+        {
+            rect = new PDRectangle( array );
+        }
+        return rect;
+    }
+
+    /**
+     * Set the rectangle on the page that this bead covers.
+     *
+     * @param rect The portion of the page that this bead covers.
+     */
+    public void setRectangle( PDRectangle rect )
+    {
+        bead.setItem( COSName.R, rect );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/pagenavigation/package.html
new file mode 100644 (file)
index 0000000..8f299ac
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+A package to allow provide access to PDF page navigation functionality.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/viewerpreferences/PDViewerPreferences.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/viewerpreferences/PDViewerPreferences.java
new file mode 100644 (file)
index 0000000..e7e2319
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ * 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.pdfbox.pdmodel.interactive.viewerpreferences;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This is the document viewing preferences.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDViewerPreferences implements COSObjectable
+{
+    /**
+     * From PDF Reference: "Neither document outline nor thumbnail images visible".
+     */
+    public static final String NON_FULL_SCREEN_PAGE_MODE_USE_NONE = "UseNone";
+    /**
+     * From PDF Reference: "Document outline visible".
+     */
+    public static final String NON_FULL_SCREEN_PAGE_MODE_USE_OUTLINES = "UseOutlines";
+    /**
+     * From PDF Reference: "Thumbnail images visible".
+     */
+    public static final String NON_FULL_SCREEN_PAGE_MODE_USE_THUMBS = "UseThumbs";
+    /**
+     * From PDF Reference: "Optional content group panel visible".
+     */
+    public static final String NON_FULL_SCREEN_PAGE_MODE_USE_OPTIONAL_CONTENT = "UseOC";
+
+    /**
+     * Reading direction.
+     */
+    public static final String READING_DIRECTION_L2R = "L2R";
+    /**
+     * Reading direction.
+     */
+    public static final String READING_DIRECTION_R2L = "R2L";
+
+    /**
+     * Boundary constant.
+     */
+    public static final String BOUNDARY_MEDIA_BOX = "MediaBox";
+    /**
+     * Boundary constant.
+     */
+    public static final String BOUNDARY_CROP_BOX = "CropBox";
+    /**
+     * Boundary constant.
+     */
+    public static final String BOUNDARY_BLEED_BOX = "BleedBox";
+    /**
+     * Boundary constant.
+     */
+    public static final String BOUNDARY_TRIM_BOX = "TrimBox";
+    /**
+     * Boundary constant.
+     */
+    public static final String BOUNDARY_ART_BOX = "ArtBox";
+
+
+    private COSDictionary prefs;
+
+    /**
+     * Constructor that is used for a preexisting dictionary.
+     *
+     * @param dic The underlying dictionary.
+     */
+    public PDViewerPreferences( COSDictionary dic )
+    {
+        prefs = dic;
+    }
+
+    /**
+     * This will get the underlying dictionary that this object wraps.
+     *
+     * @return The underlying info dictionary.
+     */
+    public COSDictionary getDictionary()
+    {
+        return prefs;
+    }
+
+    /**
+     * Convert this standard java object to a COS object.
+     *
+     * @return The cos object that matches this Java object.
+     */
+    public COSBase getCOSObject()
+    {
+        return prefs;
+    }
+
+    /**
+     * Get the toolbar preference.
+     *
+     * @return the toolbar preference.
+     */
+    public boolean hideToolbar()
+    {
+        return prefs.getBoolean( "HideToolbar", false );
+    }
+
+    /**
+     * Set the toolbar preference.
+     *
+     * @param value Set the toolbar preference.
+     */
+    public void setHideToolbar( boolean value )
+    {
+        prefs.setBoolean( "HideToolbar", value );
+    }
+
+    /**
+     * Get the menubar preference.
+     *
+     * @return the menubar preference.
+     */
+    public boolean hideMenubar()
+    {
+        return prefs.getBoolean( "HideMenubar", false );
+    }
+
+    /**
+     * Set the menubar preference.
+     *
+     * @param value Set the menubar preference.
+     */
+    public void setHideMenubar( boolean value )
+    {
+        prefs.setBoolean( "HideMenubar", value );
+    }
+
+    /**
+     * Get the window UI preference.
+     *
+     * @return the window UI preference.
+     */
+    public boolean hideWindowUI()
+    {
+        return prefs.getBoolean( "HideWindowUI", false );
+    }
+
+    /**
+     * Set the window UI preference.
+     *
+     * @param value Set the window UI preference.
+     */
+    public void setHideWindowUI( boolean value )
+    {
+        prefs.setBoolean( "HideWindowUI", value );
+    }
+
+    /**
+     * Get the fit window preference.
+     *
+     * @return the fit window preference.
+     */
+    public boolean fitWindow()
+    {
+        return prefs.getBoolean( "FitWindow", false );
+    }
+
+    /**
+     * Set the fit window preference.
+     *
+     * @param value Set the fit window preference.
+     */
+    public void setFitWindow( boolean value )
+    {
+        prefs.setBoolean( "FitWindow", value );
+    }
+
+    /**
+     * Get the center window preference.
+     *
+     * @return the center window preference.
+     */
+    public boolean centerWindow()
+    {
+        return prefs.getBoolean( "CenterWindow", false );
+    }
+
+    /**
+     * Set the center window preference.
+     *
+     * @param value Set the center window preference.
+     */
+    public void setCenterWindow( boolean value )
+    {
+        prefs.setBoolean( "CenterWindow", value );
+    }
+
+    /**
+     * Get the display doc title preference.
+     *
+     * @return the display doc title preference.
+     */
+    public boolean displayDocTitle()
+    {
+        return prefs.getBoolean( "DisplayDocTitle", false );
+    }
+
+    /**
+     * Set the display doc title preference.
+     *
+     * @param value Set the display doc title preference.
+     */
+    public void setDisplayDocTitle( boolean value )
+    {
+        prefs.setBoolean( "DisplayDocTitle", value );
+    }
+
+    /**
+     * Get the non full screen page mode preference.
+     *
+     * @return the non full screen page mode preference.
+     */
+    public String getNonFullScreenPageMode()
+    {
+        return prefs.getNameAsString( "NonFullScreenPageMode", NON_FULL_SCREEN_PAGE_MODE_USE_NONE);
+    }
+
+    /**
+     * Set the non full screen page mode preference.
+     *
+     * @param value Set the non full screen page mode preference.
+     */
+    public void setNonFullScreenPageMode( String value )
+    {
+        prefs.setName( "NonFullScreenPageMode", value );
+    }
+
+    /**
+     * Get the reading direction preference.
+     *
+     * @return the reading direction preference.
+     */
+    public String getReadingDirection()
+    {
+        return prefs.getNameAsString( "Direction", READING_DIRECTION_L2R);
+    }
+
+    /**
+     * Set the reading direction preference.
+     *
+     * @param value Set the reading direction preference.
+     */
+    public void setReadingDirection( String value )
+    {
+        prefs.setName( "Direction", value );
+    }
+
+    /**
+     * Get the ViewArea preference.  See BOUNDARY_XXX constants.
+     *
+     * @return the ViewArea preference.
+     */
+    public String getViewArea()
+    {
+        return prefs.getNameAsString( "ViewArea", BOUNDARY_CROP_BOX);
+    }
+
+    /**
+     * Set the ViewArea preference.  See BOUNDARY_XXX constants.
+     *
+     * @param value Set the ViewArea preference.
+     */
+    public void setViewArea( String value )
+    {
+        prefs.setName( "ViewArea", value );
+    }
+
+    /**
+     * Get the ViewClip preference.  See BOUNDARY_XXX constants.
+     *
+     * @return the ViewClip preference.
+     */
+    public String getViewClip()
+    {
+        return prefs.getNameAsString( "ViewClip", BOUNDARY_CROP_BOX);
+    }
+
+    /**
+     * Set the ViewClip preference.  See BOUNDARY_XXX constants.
+     *
+     * @param value Set the ViewClip preference.
+     */
+    public void setViewClip( String value )
+    {
+        prefs.setName( "ViewClip", value );
+    }
+
+    /**
+     * Get the PrintArea preference.  See BOUNDARY_XXX constants.
+     *
+     * @return the PrintArea preference.
+     */
+    public String getPrintArea()
+    {
+        return prefs.getNameAsString( "PrintArea", BOUNDARY_CROP_BOX);
+    }
+
+    /**
+     * Set the PrintArea preference.  See BOUNDARY_XXX constants.
+     *
+     * @param value Set the PrintArea preference.
+     */
+    public void setPrintArea( String value )
+    {
+        prefs.setName( "PrintArea", value );
+    }
+
+    /**
+     * Get the PrintClip preference.  See BOUNDARY_XXX constants.
+     *
+     * @return the PrintClip preference.
+     */
+    public String getPrintClip()
+    {
+        return prefs.getNameAsString( "PrintClip", BOUNDARY_CROP_BOX);
+    }
+
+    /**
+     * Set the PrintClip preference.  See BOUNDARY_XXX constants.
+     *
+     * @param value Set the PrintClip preference.
+     */
+    public void setPrintClip( String value )
+    {
+        prefs.setName( "PrintClip", value );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/viewerpreferences/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/interactive/viewerpreferences/package.html
new file mode 100644 (file)
index 0000000..a677443
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+A package to allow access to document viewing preferences.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/markedcontent/PDPropertyList.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/markedcontent/PDPropertyList.java
new file mode 100644 (file)
index 0000000..62a0198
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.pdmodel.markedcontent;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
+
+/**
+ * This class represents a property list used for the marked content feature to map a resource name
+ * to a dictionary.
+ *
+ * @since PDF 1.2
+ * @version $Revision$
+ */
+public class PDPropertyList implements COSObjectable
+{
+
+    private COSDictionary props;
+
+    /**
+     * Creates a new property list.
+     */
+    public PDPropertyList()
+    {
+        this.props = new COSDictionary();
+    }
+
+    /**
+     * Creates a new instance based on a given {@link COSDictionary}.
+     * @param dict the dictionary
+     */
+    public PDPropertyList(COSDictionary dict)
+    {
+        this.props = dict;
+    }
+
+    /** {@inheritDoc} */
+    public COSBase getCOSObject()
+    {
+        return this.props;
+    }
+
+    /**
+     * Returns the optional content group belonging to the given resource name.
+     * @param name the resource name
+     * @return the optional content group or null if the group was not found
+     */
+    public PDOptionalContentGroup getOptionalContentGroup(COSName name)
+    {
+        COSDictionary dict = (COSDictionary)props.getDictionaryObject(name);
+        if (dict != null)
+        {
+            if (COSName.OCG.equals(dict.getItem(COSName.TYPE)))
+            {
+                return new PDOptionalContentGroup(dict);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Puts a mapping from a resource name to an optional content group.
+     * @param name the resource name
+     * @param ocg the optional content group
+     */
+    public void putMapping(COSName name, PDOptionalContentGroup ocg)
+    {
+        putMapping(name, (COSDictionary)ocg.getCOSObject());
+    }
+
+    private void putMapping(COSName name, COSDictionary dict)
+    {
+        props.setItem(name, dict);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/package.html
new file mode 100644 (file)
index 0000000..7ea3c42
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel package represents a high level API for creating and manipulating PDF documents.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/text/PDTextState.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/text/PDTextState.java
new file mode 100644 (file)
index 0000000..0933385
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * 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.pdfbox.pdmodel.text;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+
+/**
+ * This class will hold the current state of the text parameters when executing a
+ * content stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDTextState implements Cloneable
+{
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_FILL_TEXT = 0;
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_STROKE_TEXT = 1;
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_FILL_THEN_STROKE_TEXT = 2;
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_NEITHER_FILL_NOR_STROKE_TEXT = 3;
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_FILL_TEXT_AND_ADD_TO_PATH_FOR_CLIPPING = 4;
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_STROKE_TEXT_AND_ADD_TO_PATH_FOR_CLIPPING = 5;
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_FILL_THEN_STROKE_TEXT_AND_ADD_TO_PATH_FOR_CLIPPING = 6;
+    /**
+     * See PDF Reference 1.5 Table 5.3.
+     */
+    public static final int RENDERING_MODE_ADD_TEXT_TO_PATH_FOR_CLIPPING = 7;
+
+
+    //these are set default according to PDF Reference 1.5 section 5.2
+    private float characterSpacing = 0;
+    private float wordSpacing = 0;
+    private float horizontalScaling = 100;
+    private float leading = 0;
+    private PDFont font;
+    private float fontSize;
+    private int renderingMode = 0;
+    private float rise = 0;
+    private boolean knockout = true;
+
+    /**
+     * Get the value of the characterSpacing.
+     *
+     * @return The current characterSpacing.
+     */
+    public float getCharacterSpacing()
+    {
+        return characterSpacing;
+    }
+
+    /**
+     * Set the value of the characterSpacing.
+     *
+     * @param value The characterSpacing.
+     */
+    public void setCharacterSpacing(float value)
+    {
+        characterSpacing = value;
+    }
+
+    /**
+     * Get the value of the wordSpacing.
+     *
+     * @return The wordSpacing.
+     */
+    public float getWordSpacing()
+    {
+        return wordSpacing;
+    }
+
+    /**
+     * Set the value of the wordSpacing.
+     *
+     * @param value The wordSpacing.
+     */
+    public void setWordSpacing(float value)
+    {
+        wordSpacing = value;
+    }
+
+    /**
+     * Get the value of the horizontalScaling.  The default is 100.  This value
+     * is the percentage value 0-100 and not 0-1.  So for mathematical operations
+     * you will probably need to divide by 100 first.
+     *
+     * @return The horizontalScaling.
+     */
+    public float getHorizontalScalingPercent()
+    {
+        return horizontalScaling;
+    }
+
+    /**
+     * Set the value of the horizontalScaling.
+     *
+     * @param value The horizontalScaling.
+     */
+    public void setHorizontalScalingPercent(float value)
+    {
+        horizontalScaling = value;
+    }
+
+    /**
+     * Get the value of the leading.
+     *
+     * @return The leading.
+     */
+    public float getLeading()
+    {
+        return leading;
+    }
+
+    /**
+     * Set the value of the leading.
+     *
+     * @param value The leading.
+     */
+    public void setLeading(float value)
+    {
+        leading = value;
+    }
+
+    /**
+     * Get the value of the font.
+     *
+     * @return The font.
+     */
+    public PDFont getFont()
+    {
+        return font;
+    }
+
+    /**
+     * Set the value of the font.
+     *
+     * @param value The font.
+     */
+    public void setFont(PDFont value)
+    {
+        font = value;
+    }
+
+    /**
+     * Get the value of the fontSize.
+     *
+     * @return The fontSize.
+     */
+    public float getFontSize()
+    {
+        return fontSize;
+    }
+
+    /**
+     * Set the value of the fontSize.
+     *
+     * @param value The fontSize.
+     */
+    public void setFontSize(float value)
+    {
+        fontSize = value;
+    }
+
+    /**
+     * Get the value of the renderingMode.
+     *
+     * @return The renderingMode.
+     */
+    public int getRenderingMode()
+    {
+        return renderingMode;
+    }
+
+    /**
+     * Set the value of the renderingMode.
+     *
+     * @param value The renderingMode.
+     */
+    public void setRenderingMode(int value)
+    {
+        renderingMode = value;
+    }
+
+    /**
+     * Get the value of the rise.
+     *
+     * @return The rise.
+     */
+    public float getRise()
+    {
+        return rise;
+    }
+
+    /**
+     * Set the value of the rise.
+     *
+     * @param value The rise.
+     */
+    public void setRise(float value)
+    {
+        rise = value;
+    }
+
+    /**
+     * Get the value of the knockout.
+     *
+     * @return The knockout.
+     */
+    public boolean getKnockoutFlag()
+    {
+        return knockout;
+    }
+
+    /**
+     * Set the value of the knockout.
+     *
+     * @param value The knockout.
+     */
+    public void setKnockoutFlag(boolean value)
+    {
+        knockout = value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object clone()
+    {
+        try
+        {
+            return super.clone();
+        }
+        catch (CloneNotSupportedException ignore)
+        {
+            //ignore
+        }
+        return null;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/text/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/pdmodel/text/package.html
new file mode 100644 (file)
index 0000000..5f1fba5
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel text package deals with text states, operations, and parameters within the PDF document.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/COSHEXTable.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/COSHEXTable.java
new file mode 100644 (file)
index 0000000..49f40dc
--- /dev/null
@@ -0,0 +1,557 @@
+/*
+ * 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.pdfbox.persistence.util;
+
+import org.apache.pdfbox.util.StringUtil;
+
+/**
+ * helper type for faster mapping of bytes to their hex equivalent.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.4 $
+ */
+public final class COSHEXTable
+{
+    private COSHEXTable()
+    {
+    }
+    /**
+     * ASCII byte values for the hex strings.
+     */
+    public static final byte[][] TABLE =
+        {
+            StringUtil.getBytes("00"),
+            StringUtil.getBytes("01"),
+            StringUtil.getBytes("02"),
+            StringUtil.getBytes("03"),
+            StringUtil.getBytes("04"),
+            StringUtil.getBytes("05"),
+            StringUtil.getBytes("06"),
+            StringUtil.getBytes("07"),
+            StringUtil.getBytes("08"),
+            StringUtil.getBytes("09"),
+            StringUtil.getBytes("0A"),
+            StringUtil.getBytes("0B"),
+            StringUtil.getBytes("0C"),
+            StringUtil.getBytes("0D"),
+            StringUtil.getBytes("0E"),
+            StringUtil.getBytes("0F"),
+            StringUtil.getBytes("10"),
+            StringUtil.getBytes("11"),
+            StringUtil.getBytes("12"),
+            StringUtil.getBytes("13"),
+            StringUtil.getBytes("14"),
+            StringUtil.getBytes("15"),
+            StringUtil.getBytes("16"),
+            StringUtil.getBytes("17"),
+            StringUtil.getBytes("18"),
+            StringUtil.getBytes("19"),
+            StringUtil.getBytes("1A"),
+            StringUtil.getBytes("1B"),
+            StringUtil.getBytes("1C"),
+            StringUtil.getBytes("1D"),
+            StringUtil.getBytes("1E"),
+            StringUtil.getBytes("1F"),
+            StringUtil.getBytes("20"),
+            StringUtil.getBytes("21"),
+            StringUtil.getBytes("22"),
+            StringUtil.getBytes("23"),
+            StringUtil.getBytes("24"),
+            StringUtil.getBytes("25"),
+            StringUtil.getBytes("26"),
+            StringUtil.getBytes("27"),
+            StringUtil.getBytes("28"),
+            StringUtil.getBytes("29"),
+            StringUtil.getBytes("2A"),
+            StringUtil.getBytes("2B"),
+            StringUtil.getBytes("2C"),
+            StringUtil.getBytes("2D"),
+            StringUtil.getBytes("2E"),
+            StringUtil.getBytes("2F"),
+            StringUtil.getBytes("30"),
+            StringUtil.getBytes("31"),
+            StringUtil.getBytes("32"),
+            StringUtil.getBytes("33"),
+            StringUtil.getBytes("34"),
+            StringUtil.getBytes("35"),
+            StringUtil.getBytes("36"),
+            StringUtil.getBytes("37"),
+            StringUtil.getBytes("38"),
+            StringUtil.getBytes("39"),
+            StringUtil.getBytes("3A"),
+            StringUtil.getBytes("3B"),
+            StringUtil.getBytes("3C"),
+            StringUtil.getBytes("3D"),
+            StringUtil.getBytes("3E"),
+            StringUtil.getBytes("3F"),
+            StringUtil.getBytes("40"),
+            StringUtil.getBytes("41"),
+            StringUtil.getBytes("42"),
+            StringUtil.getBytes("43"),
+            StringUtil.getBytes("44"),
+            StringUtil.getBytes("45"),
+            StringUtil.getBytes("46"),
+            StringUtil.getBytes("47"),
+            StringUtil.getBytes("48"),
+            StringUtil.getBytes("49"),
+            StringUtil.getBytes("4A"),
+            StringUtil.getBytes("4B"),
+            StringUtil.getBytes("4C"),
+            StringUtil.getBytes("4D"),
+            StringUtil.getBytes("4E"),
+            StringUtil.getBytes("4F"),
+            StringUtil.getBytes("50"),
+            StringUtil.getBytes("51"),
+            StringUtil.getBytes("52"),
+            StringUtil.getBytes("53"),
+            StringUtil.getBytes("54"),
+            StringUtil.getBytes("55"),
+            StringUtil.getBytes("56"),
+            StringUtil.getBytes("57"),
+            StringUtil.getBytes("58"),
+            StringUtil.getBytes("59"),
+            StringUtil.getBytes("5A"),
+            StringUtil.getBytes("5B"),
+            StringUtil.getBytes("5C"),
+            StringUtil.getBytes("5D"),
+            StringUtil.getBytes("5E"),
+            StringUtil.getBytes("5F"),
+            StringUtil.getBytes("60"),
+            StringUtil.getBytes("61"),
+            StringUtil.getBytes("62"),
+            StringUtil.getBytes("63"),
+            StringUtil.getBytes("64"),
+            StringUtil.getBytes("65"),
+            StringUtil.getBytes("66"),
+            StringUtil.getBytes("67"),
+            StringUtil.getBytes("68"),
+            StringUtil.getBytes("69"),
+            StringUtil.getBytes("6A"),
+            StringUtil.getBytes("6B"),
+            StringUtil.getBytes("6C"),
+            StringUtil.getBytes("6D"),
+            StringUtil.getBytes("6E"),
+            StringUtil.getBytes("6F"),
+            StringUtil.getBytes("70"),
+            StringUtil.getBytes("71"),
+            StringUtil.getBytes("72"),
+            StringUtil.getBytes("73"),
+            StringUtil.getBytes("74"),
+            StringUtil.getBytes("75"),
+            StringUtil.getBytes("76"),
+            StringUtil.getBytes("77"),
+            StringUtil.getBytes("78"),
+            StringUtil.getBytes("79"),
+            StringUtil.getBytes("7A"),
+            StringUtil.getBytes("7B"),
+            StringUtil.getBytes("7C"),
+            StringUtil.getBytes("7D"),
+            StringUtil.getBytes("7E"),
+            StringUtil.getBytes("7F"),
+            StringUtil.getBytes("80"),
+            StringUtil.getBytes("81"),
+            StringUtil.getBytes("82"),
+            StringUtil.getBytes("83"),
+            StringUtil.getBytes("84"),
+            StringUtil.getBytes("85"),
+            StringUtil.getBytes("86"),
+            StringUtil.getBytes("87"),
+            StringUtil.getBytes("88"),
+            StringUtil.getBytes("89"),
+            StringUtil.getBytes("8A"),
+            StringUtil.getBytes("8B"),
+            StringUtil.getBytes("8C"),
+            StringUtil.getBytes("8D"),
+            StringUtil.getBytes("8E"),
+            StringUtil.getBytes("8F"),
+            StringUtil.getBytes("90"),
+            StringUtil.getBytes("91"),
+            StringUtil.getBytes("92"),
+            StringUtil.getBytes("93"),
+            StringUtil.getBytes("94"),
+            StringUtil.getBytes("95"),
+            StringUtil.getBytes("96"),
+            StringUtil.getBytes("97"),
+            StringUtil.getBytes("98"),
+            StringUtil.getBytes("99"),
+            StringUtil.getBytes("9A"),
+            StringUtil.getBytes("9B"),
+            StringUtil.getBytes("9C"),
+            StringUtil.getBytes("9D"),
+            StringUtil.getBytes("9E"),
+            StringUtil.getBytes("9F"),
+            StringUtil.getBytes("A0"),
+            StringUtil.getBytes("A1"),
+            StringUtil.getBytes("A2"),
+            StringUtil.getBytes("A3"),
+            StringUtil.getBytes("A4"),
+            StringUtil.getBytes("A5"),
+            StringUtil.getBytes("A6"),
+            StringUtil.getBytes("A7"),
+            StringUtil.getBytes("A8"),
+            StringUtil.getBytes("A9"),
+            StringUtil.getBytes("AA"),
+            StringUtil.getBytes("AB"),
+            StringUtil.getBytes("AC"),
+            StringUtil.getBytes("AD"),
+            StringUtil.getBytes("AE"),
+            StringUtil.getBytes("AF"),
+            StringUtil.getBytes("B0"),
+            StringUtil.getBytes("B1"),
+            StringUtil.getBytes("B2"),
+            StringUtil.getBytes("B3"),
+            StringUtil.getBytes("B4"),
+            StringUtil.getBytes("B5"),
+            StringUtil.getBytes("B6"),
+            StringUtil.getBytes("B7"),
+            StringUtil.getBytes("B8"),
+            StringUtil.getBytes("B9"),
+            StringUtil.getBytes("BA"),
+            StringUtil.getBytes("BB"),
+            StringUtil.getBytes("BC"),
+            StringUtil.getBytes("BD"),
+            StringUtil.getBytes("BE"),
+            StringUtil.getBytes("BF"),
+            StringUtil.getBytes("C0"),
+            StringUtil.getBytes("C1"),
+            StringUtil.getBytes("C2"),
+            StringUtil.getBytes("C3"),
+            StringUtil.getBytes("C4"),
+            StringUtil.getBytes("C5"),
+            StringUtil.getBytes("C6"),
+            StringUtil.getBytes("C7"),
+            StringUtil.getBytes("C8"),
+            StringUtil.getBytes("C9"),
+            StringUtil.getBytes("CA"),
+            StringUtil.getBytes("CB"),
+            StringUtil.getBytes("CC"),
+            StringUtil.getBytes("CD"),
+            StringUtil.getBytes("CE"),
+            StringUtil.getBytes("CF"),
+            StringUtil.getBytes("D0"),
+            StringUtil.getBytes("D1"),
+            StringUtil.getBytes("D2"),
+            StringUtil.getBytes("D3"),
+            StringUtil.getBytes("D4"),
+            StringUtil.getBytes("D5"),
+            StringUtil.getBytes("D6"),
+            StringUtil.getBytes("D7"),
+            StringUtil.getBytes("D8"),
+            StringUtil.getBytes("D9"),
+            StringUtil.getBytes("DA"),
+            StringUtil.getBytes("DB"),
+            StringUtil.getBytes("DC"),
+            StringUtil.getBytes("DD"),
+            StringUtil.getBytes("DE"),
+            StringUtil.getBytes("DF"),
+            StringUtil.getBytes("E0"),
+            StringUtil.getBytes("E1"),
+            StringUtil.getBytes("E2"),
+            StringUtil.getBytes("E3"),
+            StringUtil.getBytes("E4"),
+            StringUtil.getBytes("E5"),
+            StringUtil.getBytes("E6"),
+            StringUtil.getBytes("E7"),
+            StringUtil.getBytes("E8"),
+            StringUtil.getBytes("E9"),
+            StringUtil.getBytes("EA"),
+            StringUtil.getBytes("EB"),
+            StringUtil.getBytes("EC"),
+            StringUtil.getBytes("ED"),
+            StringUtil.getBytes("EE"),
+            StringUtil.getBytes("EF"),
+            StringUtil.getBytes("F0"),
+            StringUtil.getBytes("F1"),
+            StringUtil.getBytes("F2"),
+            StringUtil.getBytes("F3"),
+            StringUtil.getBytes("F4"),
+            StringUtil.getBytes("F5"),
+            StringUtil.getBytes("F6"),
+            StringUtil.getBytes("F7"),
+            StringUtil.getBytes("F8"),
+            StringUtil.getBytes("F9"),
+            StringUtil.getBytes("FA"),
+            StringUtil.getBytes("FB"),
+            StringUtil.getBytes("FC"),
+            StringUtil.getBytes("FD"),
+            StringUtil.getBytes("FE"),
+            StringUtil.getBytes("FF")
+            };
+
+    /**
+     * ASCII byte values for the hex strings.
+     */
+    public static final String[] HEX_TABLE =
+        {
+            "00",
+            "01",
+            "02",
+            "03",
+            "04",
+            "05",
+            "06",
+            "07",
+            "08",
+            "09",
+            "0A",
+            "0B",
+            "0C",
+            "0D",
+            "0E",
+            "0F",
+            "10",
+            "11",
+            "12",
+            "13",
+            "14",
+            "15",
+            "16",
+            "17",
+            "18",
+            "19",
+            "1A",
+            "1B",
+            "1C",
+            "1D",
+            "1E",
+            "1F",
+            "20",
+            "21",
+            "22",
+            "23",
+            "24",
+            "25",
+            "26",
+            "27",
+            "28",
+            "29",
+            "2A",
+            "2B",
+            "2C",
+            "2D",
+            "2E",
+            "2F",
+            "30",
+            "31",
+            "32",
+            "33",
+            "34",
+            "35",
+            "36",
+            "37",
+            "38",
+            "39",
+            "3A",
+            "3B",
+            "3C",
+            "3D",
+            "3E",
+            "3F",
+            "40",
+            "41",
+            "42",
+            "43",
+            "44",
+            "45",
+            "46",
+            "47",
+            "48",
+            "49",
+            "4A",
+            "4B",
+            "4C",
+            "4D",
+            "4E",
+            "4F",
+            "50",
+            "51",
+            "52",
+            "53",
+            "54",
+            "55",
+            "56",
+            "57",
+            "58",
+            "59",
+            "5A",
+            "5B",
+            "5C",
+            "5D",
+            "5E",
+            "5F",
+            "60",
+            "61",
+            "62",
+            "63",
+            "64",
+            "65",
+            "66",
+            "67",
+            "68",
+            "69",
+            "6A",
+            "6B",
+            "6C",
+            "6D",
+            "6E",
+            "6F",
+            "70",
+            "71",
+            "72",
+            "73",
+            "74",
+            "75",
+            "76",
+            "77",
+            "78",
+            "79",
+            "7A",
+            "7B",
+            "7C",
+            "7D",
+            "7E",
+            "7F",
+            "80",
+            "81",
+            "82",
+            "83",
+            "84",
+            "85",
+            "86",
+            "87",
+            "88",
+            "89",
+            "8A",
+            "8B",
+            "8C",
+            "8D",
+            "8E",
+            "8F",
+            "90",
+            "91",
+            "92",
+            "93",
+            "94",
+            "95",
+            "96",
+            "97",
+            "98",
+            "99",
+            "9A",
+            "9B",
+            "9C",
+            "9D",
+            "9E",
+            "9F",
+            "A0",
+            "A1",
+            "A2",
+            "A3",
+            "A4",
+            "A5",
+            "A6",
+            "A7",
+            "A8",
+            "A9",
+            "AA",
+            "AB",
+            "AC",
+            "AD",
+            "AE",
+            "AF",
+            "B0",
+            "B1",
+            "B2",
+            "B3",
+            "B4",
+            "B5",
+            "B6",
+            "B7",
+            "B8",
+            "B9",
+            "BA",
+            "BB",
+            "BC",
+            "BD",
+            "BE",
+            "BF",
+            "C0",
+            "C1",
+            "C2",
+            "C3",
+            "C4",
+            "C5",
+            "C6",
+            "C7",
+            "C8",
+            "C9",
+            "CA",
+            "CB",
+            "CC",
+            "CD",
+            "CE",
+            "CF",
+            "D0",
+            "D1",
+            "D2",
+            "D3",
+            "D4",
+            "D5",
+            "D6",
+            "D7",
+            "D8",
+            "D9",
+            "DA",
+            "DB",
+            "DC",
+            "DD",
+            "DE",
+            "DF",
+            "E0",
+            "E1",
+            "E2",
+            "E3",
+            "E4",
+            "E5",
+            "E6",
+            "E7",
+            "E8",
+            "E9",
+            "EA",
+            "EB",
+            "EC",
+            "ED",
+            "EE",
+            "EF",
+            "F0",
+            "F1",
+            "F2",
+            "F3",
+            "F4",
+            "F5",
+            "F6",
+            "F7",
+            "F8",
+            "F9",
+            "FA",
+            "FB",
+            "FC",
+            "FD",
+            "FE",
+            "FF"
+            };
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/COSObjectKey.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/COSObjectKey.java
new file mode 100644 (file)
index 0000000..a5b45bd
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.persistence.util;
+
+import org.apache.pdfbox.cos.COSObject;
+
+/**
+ * Object representing the physical reference to an indirect pdf object.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.5 $
+ */
+public class COSObjectKey implements Comparable<COSObjectKey>
+{
+    private long number;
+    private long generation;
+
+    /**
+     * PDFObjectKey constructor comment.
+     *
+     * @param object The object that this key will represent.
+     */
+    public COSObjectKey(COSObject object)
+    {
+        this( object.getObjectNumber().longValue(), object.getGenerationNumber().longValue() );
+    }
+
+    /**
+     * PDFObjectKey constructor comment.
+     *
+     * @param num The object number.
+     * @param gen The object generation number.
+     */
+    public COSObjectKey(long num, long gen)
+    {
+        setNumber(num);
+        setGeneration(gen);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj)
+    {
+        return (obj instanceof COSObjectKey) &&
+               ((COSObjectKey)obj).getNumber() == getNumber() &&
+               ((COSObjectKey)obj).getGeneration() == getGeneration();
+    }
+
+    /**
+     * This will get the generation number.
+     *
+     * @return The objects generation number.
+     */
+    public long getGeneration()
+    {
+        return generation;
+    }
+    /**
+     * This will get the objects id.
+     *
+     * @return The object's id.
+     */
+    public long getNumber()
+    {
+        return number;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+        return (int)(number + generation);
+    }
+    /**
+     * This will set the objects generation number.
+     *
+     * @param newGeneration The objects generation number.
+     */
+    public void setGeneration(long newGeneration)
+    {
+        generation = newGeneration;
+    }
+    /**
+     * This will set the objects id.
+     *
+     * @param newNumber The objects number.
+     */
+    public void setNumber(long newNumber)
+    {
+        number = newNumber;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return "" + getNumber() + " " + getGeneration() + " R";
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(COSObjectKey other)
+    {
+        if (getNumber() < other.getNumber())
+        {
+            return -1;
+        }
+        else if (getNumber() > other.getNumber())
+        {
+            return 1;
+        }
+        else
+        {
+            if (getGeneration() < other.getGeneration())
+            {
+                return -1;
+            }
+            else if (getGeneration() > other.getGeneration())
+            {
+                return 1;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/persistence/util/package.html
new file mode 100644 (file)
index 0000000..a58603d
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These are utilities used by the persistence layer.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/BitFlagHelper.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/BitFlagHelper.java
new file mode 100644 (file)
index 0000000..b2f840e
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.pdfbox.util;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class will be used for bit flag operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class BitFlagHelper
+{
+    private BitFlagHelper()
+    {
+        //helper class should not be constructed
+    }
+
+    /**
+     * Sets the given boolean value at bitPos in the flags.
+     *
+     * @param dic The dictionary to set the value into.
+     * @param field The name of the field to set the value into.
+     * @param bitFlag the bit position to set the value in.
+     * @param value the value the bit position should have.
+     * 
+     * @deprecated  use {@link #setFlag(COSDictionary, COSName, int, boolean)} using COSName constants instead
+     */
+    public static final void setFlag( COSDictionary dic, String field, int bitFlag, boolean value )
+    {
+       setFlag(dic, COSName.getPDFName(field), bitFlag, value);
+    }
+    
+    /**
+     * Sets the given boolean value at bitPos in the flags.
+     *
+     * @param dic The dictionary to set the value into.
+     * @param field The COSName of the field to set the value into.
+     * @param bitFlag the bit position to set the value in.
+     * @param value the value the bit position should have.
+     */
+    public static final void setFlag( COSDictionary dic, COSName field, int bitFlag, boolean value )
+    {
+        int currentFlags = dic.getInt( field, 0 );
+        if( value )
+        {
+            currentFlags = currentFlags | bitFlag;
+        }
+        else
+        {
+            currentFlags = currentFlags &= ~bitFlag;
+        }
+        dic.setInt( field, currentFlags );
+    }
+
+    /**
+     * Gets the boolean value from the flags at the given bit
+     * position.
+     *
+     * @param dic The dictionary to get the field from.
+     * @param field The name of the field to get the flag from.
+     * @param bitFlag the bitPosition to get the value from.
+     *
+     * @return true if the number at bitPos is '1'
+     *
+     * @deprecated  use {@link #getFlag(COSDictionary, COSName, boolean)} using COSName constants instead
+     */
+    public static final boolean getFlag(COSDictionary dic, String field, int bitFlag)
+    {
+        return getFlag(dic, COSName.getPDFName(field), bitFlag);
+    }
+    
+    /**
+     * Gets the boolean value from the flags at the given bit
+     * position.
+     *
+     * @param dic The dictionary to get the field from.
+     * @param field The COSName of the field to get the flag from.
+     * @param bitFlag the bitPosition to get the value from.
+     *
+     * @return true if the number at bitPos is '1'
+     */
+    public static final boolean getFlag(COSDictionary dic, COSName field, int bitFlag)
+    {
+        int ff = dic.getInt( field, 0 );
+        return (ff & bitFlag) == bitFlag;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/DateConverter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/DateConverter.java
new file mode 100644 (file)
index 0000000..9fc7a99
--- /dev/null
@@ -0,0 +1,359 @@
+/*
+ * 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.pdfbox.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import java.io.IOException;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This class is used to convert dates to strings and back using the PDF
+ * date standards.  Date are described in PDFReference1.4 section 3.8.2
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class DateConverter
+{
+    //The Date format is supposed to be the PDF_DATE_FORMAT, but not all PDF documents
+    //will use that date, so I have added a couple other potential formats
+    //to try if the original one does not work.
+    private static final SimpleDateFormat[] POTENTIAL_FORMATS = new SimpleDateFormat[] {
+        new SimpleDateFormat("EEEE, dd MMM yyyy hh:mm:ss a", Locale.ENGLISH),
+        new SimpleDateFormat("EEEE, MMM dd, yyyy hh:mm:ss a", Locale.ENGLISH),
+        new SimpleDateFormat("MM/dd/yyyy hh:mm:ss", Locale.ENGLISH),
+        new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH),
+        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH), 
+        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz", Locale.ENGLISH),
+        new SimpleDateFormat("EEEE, MMM dd, yyyy", Locale.ENGLISH), // Acrobat Distiller 1.0.2 for Macintosh
+        new SimpleDateFormat("EEEE MMM dd, yyyy HH:mm:ss", Locale.ENGLISH), // ECMP5
+        new SimpleDateFormat("EEEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH), // GNU Ghostscript 7.0.7
+        new SimpleDateFormat("EEEE, MMM dd, yyyy 'at' hh:mma", Locale.ENGLISH), // Acrobat Net Distiller 1.0 for Windows
+        new SimpleDateFormat("d/MM/yyyy hh:mm:ss", Locale.ENGLISH), // PDFBOX-164
+        new SimpleDateFormat("dd/MM/yyyy hh:mm:ss", Locale.ENGLISH), // PDFBOX-170
+        new SimpleDateFormat("EEEEEEEEEE, MMMMMMMMMMMM dd, yyyy", Locale.ENGLISH),  // PDFBOX-465 
+        new SimpleDateFormat("dd MMM yyyy hh:mm:ss", Locale.ENGLISH),  // for 26 May 2000 11:25:00          
+        new SimpleDateFormat("dd MMM yyyy hh:mm", Locale.ENGLISH),  // for 26 May 2000 11:25
+               new SimpleDateFormat("M/dd/yyyy hh:mm:ss", Locale.ENGLISH),             
+               new SimpleDateFormat("MM/d/yyyy hh:mm:ss", Locale.ENGLISH),
+               new SimpleDateFormat("M/dd/yyyy", Locale.ENGLISH),
+               new SimpleDateFormat("MM/d/yyyy", Locale.ENGLISH),
+               new SimpleDateFormat("M/d/yyyy hh:mm:ss", Locale.ENGLISH),
+               new SimpleDateFormat("M/d/yyyy", Locale.ENGLISH),
+               new SimpleDateFormat("M/d/yy hh:mm:ss", Locale.ENGLISH),
+               new SimpleDateFormat("M/d/yy", Locale.ENGLISH),
+        new SimpleDateFormat("yyyymmdd hh:mm:ss Z"), // 
+        new SimpleDateFormat("yyyymmdd hh:mm:ss"),      // 
+        new SimpleDateFormat("yyyymmdd'+00''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+01''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+02''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+03''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+04''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+05''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+06''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+07''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+08''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+09''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+10''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+11''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'+12''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-01''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-02''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-03''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-04''00'''"), //
+        new SimpleDateFormat("yyyymmdd'-05''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-06''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-07''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-08''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-09''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-10''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-11''00'''"), // 
+        new SimpleDateFormat("yyyymmdd'-12''00'''"), // 
+        new SimpleDateFormat("yyyymmdd"), // for 20090401+0200
+    };
+
+    private DateConverter()
+    {
+        //utility class should not be constructed.
+    }
+
+    /**
+     * This will convert the calendar to a string.
+     *
+     * @param date The date to convert to a string.
+     *
+     * @return The date as a String to be used in a PDF document.
+     */
+    public static String toString( Calendar date )
+    {
+        String retval = null;
+        if( date != null )
+        {
+            StringBuffer buffer = new StringBuffer();
+            TimeZone zone = date.getTimeZone();
+            long offsetInMinutes = zone.getOffset( date.getTimeInMillis() )/1000/60;
+            long hours = Math.abs( offsetInMinutes/60 );
+            long minutes = Math.abs( offsetInMinutes%60 );
+            buffer.append( "D:" );
+            // PDFBOX-402 , SimpleDateFormat is not thread safe, created it when you use it.
+            buffer.append( new SimpleDateFormat( "yyyyMMddHHmmss" , Locale.ENGLISH).format( date.getTime() ) );
+            if( offsetInMinutes == 0 )
+            {
+                buffer.append( "Z" );
+            }
+            else if( offsetInMinutes < 0 )
+            {
+                buffer.append( "-" );
+            }
+            else
+            {
+                buffer.append( "+" );
+            }
+            if( hours < 10 )
+            {
+                buffer.append( "0" );
+            }
+            buffer.append( hours );
+            buffer.append( "'" );
+            if( minutes < 10 )
+            {
+                buffer.append( "0" );
+            }
+            buffer.append( minutes );
+            buffer.append( "'" );
+            retval = buffer.toString();
+
+        }
+        return retval;
+    }
+
+    /**
+     * This will convert a string to a calendar.
+     *
+     * @param date The string representation of the calendar.
+     *
+     * @return The calendar that this string represents.
+     *
+     * @throws IOException If the date string is not in the correct format.
+     */
+    public static Calendar toCalendar( COSString date ) throws IOException
+    {
+        Calendar retval = null;
+        if( date != null )
+        {
+            retval = toCalendar( date.getString() );
+        }
+
+        return retval;
+    }
+
+    /**
+     * This will convert a string to a calendar.
+     *
+     * @param date The string representation of the calendar.
+     *
+     * @return The calendar that this string represents.
+     *
+     * @throws IOException If the date string is not in the correct format.
+     */
+    public static Calendar toCalendar( String date ) throws IOException
+    {
+        Calendar retval = null;
+        if( date != null && date.trim().length() > 0 )
+        {
+            //these are the default values
+            int year = 0;
+            int month = 1;
+            int day = 1;
+            int hour = 0;
+            int minute = 0;
+            int second = 0;
+            //first string off the prefix if it exists
+            try
+            {
+                SimpleTimeZone zone = null;
+                if( date.startsWith( "D:" ) )
+                {
+                    date = date.substring( 2, date.length() );
+                }
+                if( date.length() < 4 )
+                {
+                    throw new IOException( "Error: Invalid date format '" + date + "'" );
+                }
+                year = Integer.parseInt( date.substring( 0, 4 ) );
+                if( date.length() >= 6 )
+                {
+                    month = Integer.parseInt( date.substring( 4, 6 ) );
+                }
+                if( date.length() >= 8 )
+                {
+                    day = Integer.parseInt( date.substring( 6, 8 ) );
+                }
+                if( date.length() >= 10 )
+                {
+                    hour = Integer.parseInt( date.substring( 8, 10 ) );
+                }
+                if( date.length() >= 12 )
+                {
+                    minute = Integer.parseInt( date.substring( 10, 12 ) );
+                }
+                if( date.length() >= 14 )
+                {
+                    second = Integer.parseInt( date.substring( 12, 14 ) );
+                }
+
+                if( date.length() >= 15 )
+                {
+                    char sign = date.charAt( 14 );
+                    if( sign == 'Z' )
+                    {
+                        zone = new SimpleTimeZone(0,"Unknown");
+                    }
+                    else
+                    {
+                        int hours = 0;
+                        int minutes = 0;
+                        if( date.length() >= 17 )
+                        {
+                            if( sign == '+' )
+                            {
+                                //parseInt cannot handle the + sign
+                                hours = Integer.parseInt( date.substring( 15, 17 ) );
+                            }
+                            else if (sign == '-') 
+                            { 
+                                hours = -Integer.parseInt(date.substring(15,17)); 
+                            } 
+                            else
+                            {
+                                hours = -Integer.parseInt( date.substring( 14, 16 ) );
+                            }
+                        }
+                        if( date.length() > 20 )
+                        {
+                            minutes = Integer.parseInt( date.substring( 18, 20 ) );
+                        }
+                        zone = new SimpleTimeZone( hours*60*60*1000 + minutes*60*1000, "Unknown" );
+                    }
+                }
+                if( zone != null )
+                {
+                    retval = new GregorianCalendar( zone );
+                }
+                else
+                {
+                    retval = new GregorianCalendar();
+                }
+
+                retval.set(year, month-1, day, hour, minute, second );
+                // PDFBOX-598: PDF dates are only accurate up to a second
+                retval.set(Calendar.MILLISECOND, 0);
+            }
+            catch( NumberFormatException e )
+            {
+                for( int i=0; retval == null && i<POTENTIAL_FORMATS.length; i++ )
+                {
+                    try
+                    {
+                        Date utilDate = POTENTIAL_FORMATS[i].parse( date );
+                        retval = new GregorianCalendar();
+                        retval.setTime( utilDate );
+                    }
+                    catch( ParseException pe )
+                    {
+                        //ignore and move to next potential format
+                    }
+                }
+                if( retval == null )
+                {
+                    //we didn't find a valid date format so throw an exception
+                    throw new IOException( "Error converting date:" + date );
+                }
+            }
+        }
+        return retval;
+    }
+
+    private static final void zeroAppend( StringBuffer out, int number )
+    {
+        if( number < 10 )
+        {
+            out.append( "0" );
+        }
+        out.append( number );
+    }
+
+    /**
+     * Convert the date to iso 8601 string format.
+     *
+     * @param cal The date to convert.
+     * @return The date represented as an ISO 8601 string.
+     */
+    public static String toISO8601( Calendar cal )
+    {
+        StringBuffer retval = new StringBuffer();
+
+        retval.append( cal.get( Calendar.YEAR ) );
+        retval.append( "-" );
+        zeroAppend( retval, cal.get( Calendar.MONTH )+1 );
+        retval.append( "-" );
+        zeroAppend( retval, cal.get( Calendar.DAY_OF_MONTH ) );
+        retval.append( "T" );
+        zeroAppend( retval, cal.get( Calendar.HOUR_OF_DAY ));
+        retval.append( ":" );
+        zeroAppend( retval, cal.get( Calendar.MINUTE ));
+        retval.append( ":" );
+        zeroAppend( retval, cal.get( Calendar.SECOND ));
+
+        int timeZone = cal.get( Calendar.ZONE_OFFSET ) + cal.get(Calendar.DST_OFFSET );
+        if( timeZone < 0 )
+        {
+            retval.append( "-" );
+        }
+        else
+        {
+            retval.append( "+" );
+        }
+        timeZone = Math.abs( timeZone );
+        //milliseconds/1000 = seconds = seconds / 60 = minutes = minutes/60 = hours
+        int hours = timeZone/1000/60/60;
+        int minutes = (timeZone - (hours*1000*60*60))/1000/1000;
+        if( hours < 10 )
+        {
+            retval.append( "0" );
+        }
+        retval.append( Integer.toString( hours ) );
+        retval.append( ":" );
+        if( minutes < 10 )
+        {
+            retval.append( "0" );
+        }
+        retval.append( Integer.toString( minutes ) );
+
+        return retval.toString();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ErrorLogger.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ErrorLogger.java
new file mode 100644 (file)
index 0000000..2612ca3
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util;
+
+/**
+ * This class deals with some logging that is not handled by the log4j replacement.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ErrorLogger
+{
+    /**
+     * Utility class, should not be instantiated.
+     *
+     */
+    private ErrorLogger()
+    {
+    }
+
+    /**
+     * Log an error message.  This is only used for log4j replacement and
+     * should never be used when writing code.
+     *
+     * @param errorMessage The error message.
+     */
+    public static void log( String errorMessage )
+    {
+        System.err.println( errorMessage );
+    }
+
+    /**
+     * Log an error message.  This is only used for log4j replacement and
+     * should never be used when writing code.
+     *
+     * @param errorMessage The error message.
+     * @param t The exception.
+     */
+    public static void log( String errorMessage, Throwable t )
+    {
+        System.err.println( errorMessage );
+        t.printStackTrace();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ExtensionFileFilter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ExtensionFileFilter.java
new file mode 100644 (file)
index 0000000..c8f4071
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+
+/**
+ * A FileFilter that will only accept files of a certain extension.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ExtensionFileFilter extends FileFilter
+{
+    private String[] extensions = null;
+    private String desc;
+
+    /**
+     * Constructor.
+     *
+     * @param ext A list of filename extensions, ie new String[] { "PDF"}.
+     * @param description A description of the files.
+     */
+    public ExtensionFileFilter( String[] ext, String description )
+    {
+        extensions = ext;
+        desc = description;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean accept(File pathname)
+    {
+        if (pathname.isDirectory())
+        {
+            return true;
+        }
+        boolean acceptable = false;
+        String name = pathname.getName().toUpperCase();
+        for( int i=0; !acceptable && i<extensions.length; i++ )
+        {
+            if( name.endsWith( extensions[i].toUpperCase() ) )
+            {
+                acceptable = true;
+            }
+        }
+        return acceptable;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getDescription()
+    {
+        return desc;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ICU4JImpl.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ICU4JImpl.java
new file mode 100644 (file)
index 0000000..54f1c39
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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.pdfbox.util;
+
+import com.ibm.icu.text.Bidi;
+import com.ibm.icu.text.Normalizer;
+
+/**
+ * This class is an implementation the the ICU4J class. TextNormalize 
+ * will call this only if the ICU4J library exists in the classpath.
+ * @author <a href="mailto:carrier@digital-evidence.org">Brian Carrier</a>
+ * @version $Revision: 1.0 $
+ */
+public class ICU4JImpl 
+{
+    Bidi bidi;
+
+    /**
+     * Constructor.
+     */
+    public ICU4JImpl() 
+    {
+        bidi = new Bidi();
+
+        /* We do not use bidi.setInverse() because that uses
+         * Bidi.REORDER_INVERSE_NUMBERS_AS_L, which caused problems
+         * in some test files. For example, a file had a line of:
+         * 0 1 / ARABIC
+         * and the 0 and 1 were reversed in the end result.  
+         * REORDER_INVERSE_LIKE_DIRECT is the inverse Bidi mode 
+         * that more closely reflects the Unicode spec.
+         */
+        bidi.setReorderingMode(Bidi.REORDER_INVERSE_LIKE_DIRECT);
+    }
+
+    /**
+     * Takes a line of text in presentation order and converts it to logical order.
+     * @see TextNormalize.makeLineLogicalOrder(String, boolean)     
+     *  
+     * @param str String to convert
+     * @param isRtlDominant RTL (right-to-left) will be the dominant text direction
+     * @return The converted string
+     */
+    public String makeLineLogicalOrder(String str, boolean isRtlDominant) 
+    {    
+        bidi.setPara(str, isRtlDominant?Bidi.RTL:Bidi.LTR, null);
+
+        /* Set the mirror flag so that parentheses and other mirror symbols
+         * are properly reversed, when needed.  With this removed, lines
+         * such as (CBA) in the PDF file will come out like )ABC( in logical
+         * order.
+         */
+        return bidi.writeReordered(Bidi.DO_MIRRORING);
+    }
+
+    /**
+     * Normalize presentation forms of characters to the separate parts. 
+     * @see TextNormalize.normalizePres(String)
+     * 
+     * @param str String to normalize
+     * @return Normalized form
+     */
+    public String normalizePres(String str)
+    {
+        StringBuilder builder = null;
+        int p = 0;
+        int q = 0;
+        int strLength = str.length();
+        for (; q < strLength; q++) 
+        {
+            // We only normalize if the codepoint is in a given range.
+            // Otherwise, NFKC converts too many things that would cause
+            // confusion. For example, it converts the micro symbol in
+            // extended Latin to the value in the Greek script. We normalize
+            // the Unicode Alphabetic and Arabic A&B Presentation forms.
+            char c = str.charAt(q);
+            if ((0xFB00 <= c && c <= 0xFDFF) || (0xFE70 <= c && c <= 0xFEFF))
+            {
+                if (builder == null) {
+                    builder = new StringBuilder(strLength * 2);
+                }
+                builder.append(str.substring(p, q));
+                // Some fonts map U+FDF2 differently than the Unicode spec.
+                // They add an extra U+0627 character to compensate.
+                // This removes the extra character for those fonts. 
+                if(c == 0xFDF2 && q > 0 && (str.charAt(q-1) == 0x0627 || str.charAt(q-1) == 0xFE8D))
+                {
+                    builder.append("\u0644\u0644\u0647");
+                }
+                else
+                {
+                    // Trim because some decompositions have an extra space,
+                    // such as U+FC5E
+                    builder.append(
+                            Normalizer.normalize(c, Normalizer.NFKC).trim());
+                }
+                p = q + 1;
+            }
+        }
+        if (builder == null) {
+            return str;
+        } else {
+            builder.append(str.substring(p, q));
+            return builder.toString();
+        }
+    }
+    
+    /**
+     * Decomposes Diacritic characters to their combining forms.
+     * 
+     * @param str String to be Normalized
+     * @return A Normalized String
+     */      
+    public String normalizeDiac(String str)
+    {
+        StringBuilder retStr = new StringBuilder();
+        int strLength = str.length();
+        for (int i = 0; i < strLength; i++) 
+        {
+            char c = str.charAt(i);
+            if(Character.getType(c) == Character.NON_SPACING_MARK 
+                    || Character.getType(c) == Character.MODIFIER_SYMBOL
+                    || Character.getType(c) == Character.MODIFIER_LETTER)
+            {
+                /*
+                 * Trim because some decompositions have an extra space, such as
+                 * U+00B4
+                 */
+                retStr.append(Normalizer.normalize(c, Normalizer.NFKC).trim());
+            }
+            else
+            {
+                retStr.append(str.charAt(i));
+            }
+        }
+        return retStr.toString();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ImageParameters.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ImageParameters.java
new file mode 100644 (file)
index 0000000..5ae6f32
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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.pdfbox.util;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This contains all of the image parameters for in inlined image.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class ImageParameters
+{
+    private COSDictionary dictionary;
+
+    /**
+     * Constructor.
+     */
+    public ImageParameters()
+    {
+        dictionary = new COSDictionary();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param params The image parameters.
+     */
+    public ImageParameters( COSDictionary params )
+    {
+        dictionary = params;
+    }
+
+    /**
+     * This will get the dictionary that stores the image parameters.
+     *
+     * @return The COS dictionary that stores the image parameters.
+     */
+    public COSDictionary getDictionary()
+    {
+        return dictionary;
+    }
+
+    private COSBase getCOSObject( COSName abbreviatedName, COSName name )
+    {
+        COSBase retval = dictionary.getDictionaryObject( abbreviatedName );
+        if( retval == null )
+        {
+            retval = dictionary.getDictionaryObject( name );
+        }
+        return retval;
+    }
+
+    private int getNumberOrNegativeOne( COSName abbreviatedName, COSName name )
+    {
+        int retval = -1;
+        COSNumber number = (COSNumber)getCOSObject( abbreviatedName, name );
+        if( number != null )
+        {
+            retval = number.intValue();
+        }
+        return retval;
+    }
+
+    /**
+     * The bits per component of this image.  This will return -1 if one has not
+     * been set.
+     *
+     * @return The number of bits per component.
+     */
+    public int getBitsPerComponent()
+    {
+        return getNumberOrNegativeOne( COSName.BPC, COSName.BITS_PER_COMPONENT );
+    }
+
+    /**
+     * Set the number of bits per component.
+     *
+     * @param bpc The number of bits per component.
+     */
+    public void setBitsPerComponent( int bpc )
+    {
+        dictionary.setInt( COSName.BPC, bpc );
+    }
+
+
+    /**
+     * This will get the color space or null if none exists.
+     *
+     * @return The color space for this image.
+     *
+     * @throws IOException If there is an error getting the colorspace.
+     */
+    public PDColorSpace getColorSpace() throws IOException
+    {
+        return getColorSpace( null );
+    }
+    
+    /**
+     * This will get the color space or null if none exists.
+     *
+     * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+     *
+     * @return The color space for this image.
+     *
+     * @throws IOException If there is an error getting the colorspace.
+     */
+    public PDColorSpace getColorSpace( Map colorSpaces ) throws IOException
+    {
+        COSBase cs = getCOSObject( COSName.CS, COSName.COLORSPACE );
+        PDColorSpace retval = null;
+        if( cs != null )
+        {
+            retval = PDColorSpaceFactory.createColorSpace( cs, colorSpaces );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the color space for this image.
+     *
+     * @param cs The color space for this image.
+     */
+    public void setColorSpace( PDColorSpace cs )
+    {
+        COSBase base = null;
+        if( cs != null )
+        {
+            base = cs.getCOSObject();
+        }
+        dictionary.setItem( COSName.CS, base );
+    }
+
+    /**
+     * The height of this image.  This will return -1 if one has not
+     * been set.
+     *
+     * @return The height.
+     */
+    public int getHeight()
+    {
+        return getNumberOrNegativeOne( COSName.H, COSName.HEIGHT );
+    }
+
+    /**
+     * Set the height of the image.
+     *
+     * @param h The height of the image.
+     */
+    public void setHeight( int h )
+    {
+        dictionary.setInt( COSName.H, h );
+    }
+
+    /**
+     * The width of this image.  This will return -1 if one has not
+     * been set.
+     *
+     * @return The width.
+     */
+    public int getWidth()
+    {
+        return getNumberOrNegativeOne( COSName.W, COSName.WIDTH );
+    }
+
+    /**
+     * Set the width of the image.
+     *
+     * @param w The width of the image.
+     */
+    public void setWidth( int w )
+    {
+        dictionary.setInt( COSName.W, w );
+    }
+
+    /**
+     * This will get the list of filters that are associated with this stream.  Or
+     * null if there are none.
+     * @return A list of all encoding filters to apply to this stream.
+     */
+    public List getFilters()
+    {
+        List retval = null;
+        COSBase filters = dictionary.getDictionaryObject( new String[] {"Filter", "F"} );
+        if( filters instanceof COSName )
+        {
+            COSName name = (COSName)filters;
+            retval = new COSArrayList( name.getName(), name, dictionary, COSName.FILTER );
+        }
+        else if( filters instanceof COSArray )
+        {
+            retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters );
+        }
+        return retval;
+    }
+
+    /**
+     * This will set the filters that are part of this stream.
+     *
+     * @param filters The filters that are part of this stream.
+     */
+    public void setFilters( List filters )
+    {
+        COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters );
+        dictionary.setItem( "Filter", obj );
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/LayerUtility.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/LayerUtility.java
new file mode 100644 (file)
index 0000000..2e6031f
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * 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.pdfbox.util;
+
+import java.awt.geom.AffineTransform;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fontbox.util.BoundingBox;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.pdmodel.markedcontent.PDPropertyList;
+
+/**
+ * This class allows to import pages as Form XObjects into a PDF file and use them to create
+ * layers (optional content groups).
+ *
+ * @version $Revision$
+ */
+public class LayerUtility
+{
+    private static final boolean DEBUG = true;
+
+    private PDDocument targetDoc;
+    private PDFCloneUtility cloner;
+
+    /**
+     * Creates a new instance.
+     * @param document the PDF document to modify
+     */
+    public LayerUtility(PDDocument document)
+    {
+        this.targetDoc = document;
+        this.cloner = new PDFCloneUtility(document);
+    }
+
+    /**
+     * Returns the PDF document we work on.
+     * @return the PDF document
+     */
+    public PDDocument getDocument()
+    {
+        return this.targetDoc;
+    }
+
+    /**
+     * Some applications may not wrap their page content in a save/restore (q/Q) pair which can
+     * lead to problems with coordinate system transformations when content is appended. This
+     * method lets you add a q/Q pair around the existing page's content.
+     * @param page the page
+     * @throws IOException if an I/O error occurs
+     */
+    public void wrapInSaveRestore(PDPage page) throws IOException
+    {
+        COSDictionary saveGraphicsStateDic = new COSDictionary();
+        COSStream saveGraphicsStateStream = new COSStream(saveGraphicsStateDic,
+                getDocument().getDocument().getScratchFile());
+        OutputStream saveStream = saveGraphicsStateStream.createUnfilteredStream();
+        saveStream.write("q\n".getBytes("ISO-8859-1"));
+        saveStream.flush();
+
+        COSStream restoreGraphicsStateStream = new COSStream(saveGraphicsStateDic,
+                getDocument().getDocument().getScratchFile());
+        OutputStream restoreStream = restoreGraphicsStateStream.createUnfilteredStream();
+        restoreStream.write("Q\n".getBytes("ISO-8859-1"));
+        restoreStream.flush();
+
+        //Wrap the existing page's content in a save/restore pair (q/Q) to have a controlled
+        //environment to add additional content.
+        COSDictionary pageDictionary = page.getCOSDictionary();
+        COSBase contents = pageDictionary.getDictionaryObject(COSName.CONTENTS);
+        if (contents instanceof COSStream)
+        {
+            COSStream contentsStream = (COSStream)contents;
+
+            COSArray array = new COSArray();
+            array.add(saveGraphicsStateStream);
+            array.add(contentsStream);
+            array.add(restoreGraphicsStateStream);
+
+            pageDictionary.setItem(COSName.CONTENTS, array);
+        }
+        else if( contents instanceof COSArray )
+        {
+            COSArray contentsArray = (COSArray)contents;
+
+            contentsArray.add(0, saveGraphicsStateStream);
+            contentsArray.add(restoreGraphicsStateStream);
+        }
+        else
+        {
+            throw new IOException("Contents are unknown type: " + contents.getClass().getName());
+        }
+    }
+
+    /**
+     * Imports a page from some PDF file as a Form XObject so it can be placed on another page
+     * in the target document.
+     * @param sourceDoc the source PDF document that contains the page to be copied
+     * @param pageNumber the page number of the page to be copied
+     * @return a Form XObject containing the original page's content
+     * @throws IOException if an I/O error occurs
+     */
+    public PDXObjectForm importPageAsForm(PDDocument sourceDoc, int pageNumber) throws IOException
+    {
+        PDPage page = (PDPage)sourceDoc.getDocumentCatalog().getAllPages().get(pageNumber);
+        return importPageAsForm(sourceDoc, page);
+    }
+
+    private static final Set<String> PAGE_TO_FORM_FILTER = new java.util.HashSet<String>(
+            Arrays.asList(new String[] {"Group", "LastModified", "Metadata"}));
+
+    /**
+     * Imports a page from some PDF file as a Form XObject so it can be placed on another page
+     * in the target document.
+     * @param sourceDoc the source PDF document that contains the page to be copied
+     * @param page the page in the source PDF document to be copied
+     * @return a Form XObject containing the original page's content
+     * @throws IOException if an I/O error occurs
+     */
+    public PDXObjectForm importPageAsForm(PDDocument sourceDoc, PDPage page) throws IOException
+    {
+        COSStream pageStream = (COSStream)page.getContents().getCOSObject();
+        PDStream newStream = new PDStream(targetDoc,
+                pageStream.getUnfilteredStream(), false);
+        PDXObjectForm form = new PDXObjectForm(newStream);
+
+        //Copy resources
+        PDResources pageRes = page.findResources();
+        PDResources formRes = new PDResources();
+        cloner.cloneMerge(pageRes, formRes);
+        form.setResources(formRes);
+
+        //Transfer some values from page to form
+        transferDict(page.getCOSDictionary(), form.getCOSStream(), PAGE_TO_FORM_FILTER, true);
+
+        Matrix matrix = form.getMatrix();
+        AffineTransform at = matrix != null ? matrix.createAffineTransform() : new AffineTransform();
+        PDRectangle mediaBox = page.findMediaBox();
+        PDRectangle cropBox = page.findCropBox();
+        PDRectangle viewBox = (cropBox != null ? cropBox : mediaBox);
+
+        //Handle the /Rotation entry on the page dict
+        int rotation = getNormalizedRotation(page);
+
+        //Transform to FOP's user space
+        //at.scale(1 / viewBox.getWidth(), 1 / viewBox.getHeight());
+        at.translate(mediaBox.getLowerLeftX() - viewBox.getLowerLeftX(),
+                mediaBox.getLowerLeftY() - viewBox.getLowerLeftY());
+        switch (rotation)
+        {
+        case 90:
+            at.scale(viewBox.getWidth() / viewBox.getHeight(), viewBox.getHeight() / viewBox.getWidth());
+            at.translate(0, viewBox.getWidth());
+            at.rotate(-Math.PI / 2.0);
+            break;
+        case 180:
+            at.translate(viewBox.getWidth(), viewBox.getHeight());
+            at.rotate(-Math.PI);
+            break;
+        case 270:
+            at.scale(viewBox.getWidth() / viewBox.getHeight(), viewBox.getHeight() / viewBox.getWidth());
+            at.translate(viewBox.getHeight(), 0);
+            at.rotate(-Math.PI * 1.5);
+        default:
+            //no additional transformations necessary
+        }
+        //Compensate for Crop Boxes not starting at 0,0
+        at.translate(-viewBox.getLowerLeftX(), -viewBox.getLowerLeftY());
+        if (!at.isIdentity())
+        {
+            form.setMatrix(at);
+        }
+
+        BoundingBox bbox = new BoundingBox();
+        bbox.setLowerLeftX(viewBox.getLowerLeftX());
+        bbox.setLowerLeftY(viewBox.getLowerLeftY());
+        bbox.setUpperRightX(viewBox.getUpperRightX());
+        bbox.setUpperRightY(viewBox.getUpperRightY());
+        form.setBBox(new PDRectangle(bbox));
+
+        return form;
+    }
+
+    /**
+     * Places the given form over the existing content of the indicated page (like an overlay).
+     * The form is enveloped in a marked content section to indicate that it's part of an
+     * optional content group (OCG), here used as a layer. This optional group is returned and
+     * can be enabled and disabled through methods on {@link PDOptionalContentProperties}.
+     * @param targetPage the target page
+     * @param form the form to place
+     * @param transform the transformation matrix that controls the placement
+     * @param layerName the name for the layer/OCG to produce
+     * @return the optional content group that was generated for the form usage
+     * @throws IOException if an I/O error occurs
+     */
+    public PDOptionalContentGroup appendFormAsLayer(PDPage targetPage,
+            PDXObjectForm form, AffineTransform transform,
+            String layerName) throws IOException
+    {
+        PDDocumentCatalog catalog = targetDoc.getDocumentCatalog();
+        PDOptionalContentProperties ocprops = catalog.getOCProperties();
+        if (ocprops == null)
+        {
+            ocprops = new PDOptionalContentProperties();
+            catalog.setOCProperties(ocprops);
+        }
+        if (ocprops.hasGroup(layerName))
+        {
+            throw new IllegalArgumentException("Optional group (layer) already exists: " + layerName);
+        }
+
+        PDOptionalContentGroup layer = new PDOptionalContentGroup(layerName);
+        ocprops.addGroup(layer);
+
+        PDResources resources = targetPage.findResources();
+        PDPropertyList props = resources.getProperties();
+        if (props == null)
+        {
+            props = new PDPropertyList();
+            resources.setProperties(props);
+        }
+
+        //Find first free resource name with the pattern "MC<index>"
+        int index = 0;
+        PDOptionalContentGroup ocg;
+        COSName resourceName;
+        do
+        {
+            resourceName = COSName.getPDFName("MC" + index);
+            ocg = props.getOptionalContentGroup(resourceName);
+            index++;
+        } while (ocg != null);
+        //Put mapping for our new layer/OCG
+        props.putMapping(resourceName, layer);
+
+        PDPageContentStream contentStream = new PDPageContentStream(
+                targetDoc, targetPage, true, !DEBUG);
+        contentStream.beginMarkedContentSequence(COSName.OC, resourceName);
+        contentStream.drawXObject(form, transform);
+        contentStream.endMarkedContentSequence();
+        contentStream.close();
+
+        return layer;
+    }
+
+    private void transferDict(COSDictionary orgDict, COSDictionary targetDict,
+            Set<String> filter, boolean inclusive) throws IOException
+    {
+        for (Map.Entry<COSName, COSBase> entry : orgDict.entrySet())
+        {
+            COSName key = entry.getKey();
+            if (inclusive && !filter.contains(key.getName()))
+            {
+                continue;
+            }
+            else if (!inclusive && filter.contains(key.getName()))
+            {
+                continue;
+            }
+            targetDict.setItem(key,
+                    cloner.cloneForNewDocument(entry.getValue()));
+        }
+    }
+
+    private static int getNormalizedRotation(PDPage page)
+    {
+        //Handle the /Rotation entry on the page dict
+        int rotation = page.findRotation();
+        while (rotation >= 360)
+        {
+            rotation -= 360;
+        }
+        if (rotation < 0)
+        {
+            rotation = 0;
+        }
+        switch (rotation)
+        {
+        case 90:
+        case 180:
+        case 270:
+            return rotation;
+        default:
+            return 0;
+        }
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/MapUtil.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/MapUtil.java
new file mode 100644 (file)
index 0000000..1103c17
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.pdfbox.util;
+
+import java.util.Map;
+
+
+/**
+ * This class with handle some simple Map operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class MapUtil
+{
+    private MapUtil()
+    {
+        //utility class
+    }
+
+    /**
+     * Generate a unique key for the map based on a prefix.
+     *
+     * @param map The map to look for existing keys.
+     * @param prefix The prefix to use when generating the key.
+     * @return The new unique key that does not currently exist in the map.
+     */
+    public static final String getNextUniqueKey( Map map, String prefix )
+    {
+        int counter = 0;
+        while( map.get( prefix+counter ) != null )
+        {
+            counter++;
+        }
+        return prefix+counter;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/Matrix.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/Matrix.java
new file mode 100644 (file)
index 0000000..abba601
--- /dev/null
@@ -0,0 +1,397 @@
+/*
+ * 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.pdfbox.util;
+
+import java.awt.geom.AffineTransform;
+
+/**
+ * This class will be used for matrix manipulation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class Matrix implements Cloneable
+{
+    static final float[] default_single =
+    {
+        1,0,0,
+        0,1,0,
+        0,0,1
+    };
+
+    private float[] single;
+
+    /**
+     * Constructor.
+     */
+    public Matrix()
+    {
+       single = new float[default_single.length];
+       reset();
+    }
+    
+    /**
+     * This method resets the numbers in this Matrix to the original values, which are
+     * the values that a newly constructed Matrix would have.
+     */
+    public void reset()
+    {
+       System.arraycopy(default_single, 0, single, 0, default_single.length);
+    }
+
+    /**
+     * Create an affine transform from this matrix's values.
+     *
+     * @return An affine transform with this matrix's values.
+     */
+    public AffineTransform createAffineTransform()
+    {
+        AffineTransform retval = new AffineTransform(
+            single[0], single[1],
+            single[3], single[4],
+            single[6], single[7] );
+        return retval;
+    }
+
+    /**
+     * Set the values of the matrix from the AffineTransform.
+     *
+     * @param af The transform to get the values from.
+     */
+    public void setFromAffineTransform( AffineTransform af )
+    {
+        single[0] = (float)af.getScaleX();
+        single[1] = (float)af.getShearY();
+        single[3] = (float)af.getShearX();
+        single[4] = (float)af.getScaleY();
+        single[6] = (float)af.getTranslateX();
+        single[7] = (float)af.getTranslateY();
+    }
+
+    /**
+     * This will get a matrix value at some point.
+     *
+     * @param row The row to get the value from.
+     * @param column The column to get the value from.
+     *
+     * @return The value at the row/column position.
+     */
+    public float getValue( int row, int column )
+    {
+        return single[row*3+column];
+    }
+
+    /**
+     * This will set a value at a position.
+     *
+     * @param row The row to set the value at.
+     * @param column the column to set the value at.
+     * @param value The value to set at the position.
+     */
+    public void setValue( int row, int column, float value )
+    {
+        single[row*3+column] = value;
+    }
+
+    /**
+     * Return a single dimension array of all values in the matrix.
+     *
+     * @return The values ot this matrix.
+     */
+    public float[][] getValues()
+    {
+        float[][] retval = new float[3][3];
+        retval[0][0] = single[0];
+        retval[0][1] = single[1];
+        retval[0][2] = single[2];
+        retval[1][0] = single[3];
+        retval[1][1] = single[4];
+        retval[1][2] = single[5];
+        retval[2][0] = single[6];
+        retval[2][1] = single[7];
+        retval[2][2] = single[8];
+        return retval;
+    }
+
+    /**
+     * Return a single dimension array of all values in the matrix.
+     *
+     * @return The values ot this matrix.
+     */
+    public double[][] getValuesAsDouble()
+    {
+        double[][] retval = new double[3][3];
+        retval[0][0] = single[0];
+        retval[0][1] = single[1];
+        retval[0][2] = single[2];
+        retval[1][0] = single[3];
+        retval[1][1] = single[4];
+        retval[1][2] = single[5];
+        retval[2][0] = single[6];
+        retval[2][1] = single[7];
+        retval[2][2] = single[8];
+        return retval;
+    }
+
+    /**
+     * This will take the current matrix and multipy it with a matrix that is passed in.
+     *
+     * @param b The matrix to multiply by.
+     *
+     * @return The result of the two multiplied matrices.
+     */
+    public Matrix multiply( Matrix b )
+    {
+        return this.multiply(b, new Matrix());
+    }
+
+    /**
+     * This method multiplies this Matrix with the specified other Matrix, storing the product in the specified
+     * result Matrix. By reusing Matrix instances like this, multiplication chains can be executed without having
+     * to create many temporary Matrix objects.
+     * <p/>
+     * It is allowed to have (other == this) or (result == this) or indeed (other == result) but if this is done,
+     * the backing float[] matrix values may be copied in order to ensure a correct product.
+     * 
+     * @param other the second operand Matrix in the multiplication
+     * @param result the Matrix instance into which the result should be stored. If result is null, a new Matrix
+     *               instance is created.
+     * @return the product of the two matrices.
+     */
+    public Matrix multiply( Matrix other, Matrix result )
+    {
+       if (result == null)
+       {
+               result = new Matrix();
+       }
+       
+        if (other != null && other.single != null) 
+        {
+               // the operands
+               float[] thisOperand = this.single;
+               float[] otherOperand = other.single;
+               
+               // We're multiplying 2 sets of floats together to produce a third, but we allow
+               // any of these float[] instances to be the same objects.
+               // There is the possibility then to overwrite one of the operands with result values
+               // and therefore corrupt the result.
+               
+               // If either of these operands are the same float[] instance as the result, then
+               // they need to be copied.
+               
+               if (this == result)
+               {
+                       final float[] thisOrigVals = new float[this.single.length];
+                       System.arraycopy(this.single, 0, thisOrigVals, 0, this.single.length);
+                       
+                       thisOperand = thisOrigVals;
+               }
+               if (other == result)
+               {
+                       final float[] otherOrigVals = new float[other.single.length];
+                       System.arraycopy(other.single, 0, otherOrigVals, 0, other.single.length);
+                       
+                       otherOperand = otherOrigVals;
+               }
+               
+            result.single[0] = thisOperand[0] * otherOperand[0] + thisOperand[1] * otherOperand[3] + thisOperand[2] * otherOperand[6];
+            result.single[1] = thisOperand[0] * otherOperand[1] + thisOperand[1] * otherOperand[4] + thisOperand[2] * otherOperand[7];
+            result.single[2] = thisOperand[0] * otherOperand[2] + thisOperand[1] * otherOperand[5] + thisOperand[2] * otherOperand[8];
+            result.single[3] = thisOperand[3] * otherOperand[0] + thisOperand[4] * otherOperand[3] + thisOperand[5] * otherOperand[6];
+            result.single[4] = thisOperand[3] * otherOperand[1] + thisOperand[4] * otherOperand[4] + thisOperand[5] * otherOperand[7];
+            result.single[5] = thisOperand[3] * otherOperand[2] + thisOperand[4] * otherOperand[5] + thisOperand[5] * otherOperand[8];
+            result.single[6] = thisOperand[6] * otherOperand[0] + thisOperand[7] * otherOperand[3] + thisOperand[8] * otherOperand[6];
+            result.single[7] = thisOperand[6] * otherOperand[1] + thisOperand[7] * otherOperand[4] + thisOperand[8] * otherOperand[7];
+            result.single[8] = thisOperand[6] * otherOperand[2] + thisOperand[7] * otherOperand[5] + thisOperand[8] * otherOperand[8];
+        }
+        
+        return result;
+    }
+
+    /**
+     * Create a new matrix with just the scaling operators.
+     *
+     * @return A new matrix with just the scaling operators.
+     */
+    public Matrix extractScaling()
+    {
+        Matrix retval = new Matrix();
+
+        retval.single[0] = this.single[0];
+        retval.single[4] = this.single[4];
+
+        return retval;
+    }
+
+    /**
+     * Convenience method to create a scaled instance.
+     *
+     * @param x The xscale operator.
+     * @param y The yscale operator.
+     * @return A new matrix with just the x/y scaling
+     */
+    public static Matrix getScaleInstance( float x, float y)
+    {
+        Matrix retval = new Matrix();
+
+        retval.single[0] = x;
+        retval.single[4] = y;
+
+        return retval;
+    }
+
+    /**
+     * Create a new matrix with just the translating operators.
+     *
+     * @return A new matrix with just the translating operators.
+     */
+    public Matrix extractTranslating()
+    {
+        Matrix retval = new Matrix();
+
+        retval.single[6] = this.single[6];
+        retval.single[7] = this.single[7];
+
+        return retval;
+    }
+
+    /**
+     * Convenience method to create a translating instance.
+     *
+     * @param x The x translating operator.
+     * @param y The y translating operator.
+     * @return A new matrix with just the x/y translating.
+     */
+    public static Matrix getTranslatingInstance( float x, float y)
+    {
+        Matrix retval = new Matrix();
+
+        retval.single[6] = x;
+        retval.single[7] = y;
+
+        return retval;
+    }
+
+    /**
+     * Clones this object.
+     * @return cloned matrix as an object.
+     */
+    public Object clone()
+    {
+        Matrix clone = new Matrix();
+        System.arraycopy( single, 0, clone.single, 0, 9 );
+        return clone;
+    }
+
+    /**
+     * This will copy the text matrix data.
+     *
+     * @return a matrix that matches this one.
+     */
+    public Matrix copy()
+    {
+        return (Matrix) clone();
+    }
+
+    /**
+     * This will return a string representation of the matrix.
+     *
+     * @return The matrix as a string.
+     */
+    public String toString()
+    {
+        StringBuffer result = new StringBuffer( "" );
+        result.append( "[[" );
+        result.append( single[0] + "," );
+        result.append( single[1] + "," );
+        result.append( single[2] + "][");
+        result.append( single[3] + "," );
+        result.append( single[4] + "," );
+        result.append( single[5] + "][");
+        result.append( single[6] + "," );
+        result.append( single[7] + "," );
+        result.append( single[8] + "]]");
+
+        return result.toString();
+    }
+
+    /**
+     * Get the xscaling factor of this matrix.
+     * @return The x-scale.
+     */
+    public float getXScale()
+    {
+        float xScale = single[0];
+
+        /**
+         * BM: if the trm is rotated, the calculation is a little more complicated
+         *
+         * The rotation matrix multiplied with the scaling matrix is:
+         * (   x   0   0)    ( cos  sin  0)    ( x*cos x*sin   0)
+         * (   0   y   0) *  (-sin  cos  0)  = (-y*sin y*cos   0)
+         * (   0   0   1)    (   0    0  1)    (     0     0   1)
+         *
+         * So, if you want to deduce x from the matrix you take
+         * M(0,0) = x*cos and M(0,1) = x*sin and use the theorem of Pythagoras
+         *
+         * sqrt(M(0,0)^2+M(0,1)^2) =
+         * sqrt(x2*cos2+x2*sin2) =
+         * sqrt(x2*(cos2+sin2)) = <- here is the trick cos2+sin2 is one
+         * sqrt(x2) =
+         * abs(x)
+         */
+        if( !(single[1]==0.0f && single[3]==0.0f) )
+        {
+            xScale = (float)Math.sqrt(Math.pow(single[0], 2)+
+                                      Math.pow(single[1], 2));
+        }
+        return xScale;
+    }
+
+    /**
+     * Get the y scaling factor of this matrix.
+     * @return The y-scale factor.
+     */
+    public float getYScale()
+    {
+        float yScale = single[4];
+        if( !(single[1]==0.0f && single[3]==0.0f) )
+        {
+            yScale = (float)Math.sqrt(Math.pow(single[3], 2)+
+                                      Math.pow(single[4], 2));
+        }
+        return yScale;
+    }
+
+    /**
+     * Get the x position in the matrix.
+     * @return The x-position.
+     */
+    public float getXPosition()
+    {
+        return single[6];
+    }
+
+    /**
+     * Get the y position.
+     * @return The y position.
+     */
+    public float getYPosition()
+    {
+        return single[7];
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFCloneUtility.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFCloneUtility.java
new file mode 100644 (file)
index 0000000..498450a
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * Utility class used to clone PDF objects. It keeps track of objects it has already cloned.
+ *
+ * @version $Revision$
+ */
+public class PDFCloneUtility
+{
+
+    private PDDocument destination;
+    private Map<Object,COSBase> clonedVersion = new HashMap<Object,COSBase>();
+
+    /**
+     * Creates a new instance for the given target document.
+     * @param dest the destination PDF document that will receive the clones
+     */
+    public PDFCloneUtility(PDDocument dest)
+    {
+        this.destination = dest;
+    }
+
+    /**
+     * Returns the destination PDF document this cloner instance is set up for.
+     * @return the destination PDF document
+     */
+    public PDDocument getDestination()
+    {
+        return this.destination;
+    }
+
+    /**
+     * Deep-clones the given object for inclusion into a different PDF document identified by
+     * the destination parameter.
+     * @param base the initial object as the root of the deep-clone operation
+     * @return the cloned instance of the base object
+     * @throws IOException if an I/O error occurs
+     */
+      public COSBase cloneForNewDocument( Object base ) throws IOException
+      {
+          if( base == null )
+          {
+              return null;
+          }
+          COSBase retval = (COSBase)clonedVersion.get( base );
+          if( retval != null )
+          {
+              //we are done, it has already been converted.
+          }
+          else if( base instanceof List )
+          {
+              COSArray array = new COSArray();
+              List list = (List)base;
+              for( int i=0; i<list.size(); i++ )
+              {
+                  array.add( cloneForNewDocument( list.get( i ) ) );
+              }
+              retval = array;
+          }
+          else if( base instanceof COSObjectable && !(base instanceof COSBase) )
+          {
+              retval = cloneForNewDocument( ((COSObjectable)base).getCOSObject() );
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSObject )
+          {
+              COSObject object = (COSObject)base;
+              retval = cloneForNewDocument( object.getObject() );
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSArray )
+          {
+              COSArray newArray = new COSArray();
+              COSArray array = (COSArray)base;
+              for( int i=0; i<array.size(); i++ )
+              {
+                  newArray.add( cloneForNewDocument( array.get( i ) ) );
+              }
+              retval = newArray;
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSStream )
+          {
+              COSStream originalStream = (COSStream)base;
+              PDStream stream = new PDStream( destination, originalStream.getFilteredStream(), true );
+              clonedVersion.put( base, stream.getStream() );
+              for( Map.Entry<COSName, COSBase> entry :  originalStream.entrySet() )
+              {
+                  stream.getStream().setItem(
+                          entry.getKey(),
+                          cloneForNewDocument(entry.getValue()));
+              }
+              retval = stream.getStream();
+          }
+          else if( base instanceof COSDictionary )
+          {
+              COSDictionary dic = (COSDictionary)base;
+              retval = new COSDictionary();
+              clonedVersion.put( base, retval );
+              for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+              {
+                  ((COSDictionary)retval).setItem(
+                          entry.getKey(),
+                          cloneForNewDocument(entry.getValue()));
+              }
+          }
+          else
+          {
+              retval = (COSBase)base;
+          }
+          clonedVersion.put( base, retval );
+          return retval;
+      }
+
+
+      /**
+       * Merges two objects of the same type by deep-cloning its members.
+       * <br/>
+       * Base and target must be instances of the same class.
+       * @param base the base object to be cloned
+       * @param target the merge target
+       * @throws IOException if an I/O error occurs
+       */
+      public void cloneMerge( COSObjectable base, COSObjectable target) throws IOException
+      {
+          if( base == null )
+          {
+              return;
+          }
+          COSBase retval = (COSBase)clonedVersion.get( base );
+          if( retval != null )
+          {
+              return;
+              //we are done, it has already been converted. // ### Is that correct for cloneMerge???
+          }
+          else if( base instanceof List )
+          {
+              COSArray array = new COSArray();
+              List list = (List)base;
+              for( int i = 0; i < list.size(); i++ )
+              {
+                  array.add( cloneForNewDocument( list.get( i ) ) );
+              }
+              ((List)target).add(array);
+          }
+          else if( base instanceof COSObjectable && !(base instanceof COSBase) )
+          {
+              cloneMerge(((COSObjectable)base).getCOSObject(), ((COSObjectable)target).getCOSObject() );
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSObject )
+          {
+              if(target instanceof COSObject)
+              {
+                  cloneMerge(((COSObject) base).getObject(),((COSObject) target).getObject() );
+              }
+              else if(target instanceof COSDictionary)
+              {
+                  cloneMerge(((COSObject)base).getObject(), ((COSDictionary)target));
+              }
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSArray )
+          {
+              COSArray array = (COSArray)base;
+              for( int i=0; i<array.size(); i++ )
+              {
+                  ((COSArray)target).add( cloneForNewDocument( array.get( i ) ) );
+              }
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSStream )
+          {
+            // does that make sense???
+              COSStream originalStream = (COSStream)base;
+              PDStream stream = new PDStream( destination, originalStream.getFilteredStream(), true );
+              clonedVersion.put( base, stream.getStream() );
+              for( Map.Entry<COSName, COSBase> entry : originalStream.entrySet() )
+              {
+                  stream.getStream().setItem(
+                          entry.getKey(),
+                          cloneForNewDocument(entry.getValue()));
+              }
+              retval = stream.getStream();
+              target = retval;
+          }
+          else if( base instanceof COSDictionary )
+          {
+              COSDictionary dic = (COSDictionary)base;
+              clonedVersion.put( base, retval );
+              for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+              {
+                  COSName key = entry.getKey();
+                  COSBase value = entry.getValue();
+                  if (((COSDictionary)target).getItem(key) != null)
+                  {
+                      cloneMerge(value, ((COSDictionary)target).getItem(key));
+                  }
+                  else
+                  {
+                      ((COSDictionary)target).setItem( key, cloneForNewDocument(value));
+                  }
+              }
+          }
+          else
+          {
+              retval = (COSBase)base;
+          }
+          clonedVersion.put( base, retval );
+
+      }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFHighlighter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFHighlighter.java
new file mode 100644 (file)
index 0000000..956cd77
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+
+/**
+ * Highlighting of words in a PDF document with an XML file.
+ *
+ * @author slagraulet (slagraulet@cardiweb.com)
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ *
+ * @see <a href="http://partners.adobe.com/public/developer/en/pdf/HighlightFileFormat.pdf">
+ *      Adobe Highlight File Format</a>
+ */
+public class PDFHighlighter extends PDFTextStripper
+{
+    private Writer highlighterOutput = null;
+    //private Color highlightColor = Color.YELLOW;
+
+    private String[] searchedWords;
+    private ByteArrayOutputStream textOS = null;
+    private Writer textWriter = null;
+    private static final String ENCODING = "UTF-16";
+
+    /**
+     * Default constructor.
+     *
+     * @throws IOException If there is an error constructing this class.
+     */
+    public PDFHighlighter() throws IOException
+    {
+        super(ENCODING);
+        super.setLineSeparator( "" );
+        super.setPageSeparator( "" );
+        super.setWordSeparator( "" );
+        super.setShouldSeparateByBeads( false );
+        super.setSuppressDuplicateOverlappingText( false );
+    }
+
+    /**
+     * Generate an XML highlight string based on the PDF.
+     *
+     * @param pdDocument The PDF to find words in.
+     * @param highlightWord The word to search for.
+     * @param xmlOutput The resulting output xml file.
+     *
+     * @throws IOException If there is an error reading from the PDF, or writing to the XML.
+     */
+    public void generateXMLHighlight(PDDocument pdDocument, String highlightWord, Writer xmlOutput ) throws IOException
+    {
+        generateXMLHighlight( pdDocument, new String[] { highlightWord }, xmlOutput );
+    }
+
+    /**
+     * Generate an XML highlight string based on the PDF.
+     *
+     * @param pdDocument The PDF to find words in.
+     * @param sWords The words to search for.
+     * @param xmlOutput The resulting output xml file.
+     *
+     * @throws IOException If there is an error reading from the PDF, or writing to the XML.
+     */
+    public void generateXMLHighlight(PDDocument pdDocument, String[] sWords, Writer xmlOutput ) throws IOException
+    {
+        highlighterOutput = xmlOutput;
+        searchedWords = sWords;
+        highlighterOutput.write("<XML>\n<Body units=characters " +
+                                //color and mode are not implemented by the highlight spec
+                                //so don't include them for now
+                                //" color=#" + getHighlightColorAsString() +
+                                //" mode=active " + */
+                                " version=2>\n<Highlight>\n");
+        textOS = new ByteArrayOutputStream();
+        textWriter = new OutputStreamWriter( textOS, ENCODING);
+        writeText(pdDocument, textWriter);
+        highlighterOutput.write("</Highlight>\n</Body>\n</XML>");
+        highlighterOutput.flush();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void endPage( PDPage pdPage ) throws IOException
+    {
+        textWriter.flush();
+
+        String page = new String( textOS.toByteArray(), ENCODING );
+        textOS.reset();
+        //page = page.replaceAll( "\n", "" );
+        //page = page.replaceAll( "\r", "" );
+        //page = CCRStringUtil.stripChar(page, '\n');
+        //page = CCRStringUtil.stripChar(page, '\r');
+
+        // Traitement des listes � puces (caract�res sp�ciaux)
+        if (page.indexOf("a") != -1)
+        {
+            page = page.replaceAll("a[0-9]{1,3}", ".");
+        }
+
+        for (int i = 0; i < searchedWords.length; i++)
+        {
+            Pattern pattern = Pattern.compile(searchedWords[i], Pattern.CASE_INSENSITIVE);
+            Matcher matcher = pattern.matcher(page);
+            while( matcher.find() )
+            {
+                int begin = matcher.start();
+                int end = matcher.end();
+                highlighterOutput.write("    <loc " +
+                        "pg=" + (getCurrentPageNo()-1)
+                        + " pos=" + begin
+                        + " len="+ (end - begin)
+                        + ">\n");
+            }
+        }
+    }
+
+    /**
+     * Command line application.
+     *
+     * @param args The command line arguments to the application.
+     *
+     * @throws IOException If there is an error generating the highlight file.
+     */
+    public static void main(String[] args) throws IOException
+    {
+        PDFHighlighter xmlExtractor = new PDFHighlighter();
+        PDDocument doc = null;
+        try
+        {
+            if( args.length < 2 )
+            {
+                usage();
+            }
+            String[] highlightStrings = new String[ args.length - 1];
+            System.arraycopy( args, 1, highlightStrings, 0, highlightStrings.length );
+            doc = PDDocument.load( args[0] );
+
+            xmlExtractor.generateXMLHighlight(
+                doc,
+                highlightStrings,
+                new OutputStreamWriter( System.out ) );
+        }
+        finally
+        {
+            if( doc != null )
+            {
+                doc.close();
+            }
+        }
+    }
+
+    private static void usage()
+    {
+        System.err.println( "usage: java " + PDFHighlighter.class.getName() + " <pdf file> word1 word2 word3 ..." );
+        System.exit( 1 );
+    }
+
+
+    /**
+     * Get the color to highlight the strings with.  Default is Color.YELLOW.
+     *
+     * @return The color to highlight strings with.
+     */
+    /*public Color getHighlightColor()
+    {
+        return highlightColor;
+    }**/
+
+    /**
+     * Get the color to highlight the strings with.  Default is Color.YELLOW.
+     *
+     * @param color The color to highlight strings with.
+     */
+    /*public void setHighlightColor(Color color)
+    {
+        this.highlightColor = color;
+    }**/
+
+    /**
+     * Set the highlight color using HTML like rgb string.  The string must be 6 characters long.
+     *
+     * @param color The color to use for highlighting.  Should be in the format of "FF0000".
+     */
+    /*public void setHighlightColor( String color )
+    {
+        highlightColor = Color.decode( color );
+    }**/
+
+    /**
+     * Get the highlight color as an HTML like string.  This will return a string of six characters.
+     *
+     * @return The current highlight color.  For example FF0000
+     */
+    /*public String getHighlightColorAsString()
+    {
+        //BJL: kudos to anyone that has a cleaner way of doing this!
+        String red = Integer.toHexString( highlightColor.getRed() );
+        String green = Integer.toHexString( highlightColor.getGreen() );
+        String blue = Integer.toHexString( highlightColor.getBlue() );
+
+        return (red.length() < 2 ? "0" + red : red) +
+               (green.length() < 2 ? "0" + green : green) +
+               (blue.length() < 2 ? "0" + blue : blue);
+    }**/
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFImageWriter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFImageWriter.java
new file mode 100644 (file)
index 0000000..67782f0
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * 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.pdfbox.util;
+
+import java.awt.HeadlessException;
+import java.awt.Toolkit;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.imageio.IIOException;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageOutputStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This class will take a PDF document and strip out all of the text and ignore the
+ * formatting and such.  Please note; it is up to clients of this class to verify that
+ * a specific user has the correct permissions to extract text from the
+ * PDF document.
+ * <p>
+ * Patterned after PDFTextStripper.
+ *
+ * @author <a href="mailto:DanielWilson@Users.SourceForge.net">Daniel Wilson</a>
+ * @version $Revision: 1.1 $
+ */
+public class PDFImageWriter extends PDFStreamEngine
+{
+
+    /**
+     * Instantiate a new PDFImageWriter object.
+     */
+    public PDFImageWriter()
+    {
+    }
+
+    /**
+     * Instantiate a new PDFImageWriter object.  Loading all of the operator mappings
+     * from the properties object that is passed in.
+     *
+     * @param props The properties containing the mapping of operators to PDFOperator
+     * classes.
+     *
+     * @throws IOException If there is an error reading the properties.
+     */
+    public PDFImageWriter( Properties props ) throws IOException
+    {
+        super( props );
+    }
+
+    /**
+     * Converts a given page range of a PDF document to bitmap images.
+     * @param document the PDF document
+     * @param imageType the target format (ex. "png")
+     * @param password the password (needed if the PDF is encrypted)
+     * @param startPage the start page (1 is the first page)
+     * @param endPage the end page (set to Integer.MAX_VALUE for all pages)
+     * @param outputPrefix used to construct the filename for the individual images
+     * @return true if the images were produced, false if there was an error
+     * @throws IOException if an I/O error occurs
+     */
+    public boolean writeImage(PDDocument document, String imageType, String password,
+            int startPage, int endPage, String outputPrefix)
+    throws IOException
+    {
+        int resolution;
+        try
+        {
+            resolution = Toolkit.getDefaultToolkit().getScreenResolution();
+        }
+        catch( HeadlessException e )
+        {
+            resolution = 96;
+        }
+        return writeImage(document, imageType, password, startPage, endPage, outputPrefix,
+                8, resolution);
+    }
+
+    /**
+     * Converts a given page range of a PDF document to bitmap images.
+     * @param document the PDF document
+     * @param imageFormat the target format (ex. "png")
+     * @param password the password (needed if the PDF is encrypted)
+     * @param startPage the start page (1 is the first page)
+     * @param endPage the end page (set to Integer.MAX_VALUE for all pages)
+     * @param outputPrefix used to construct the filename for the individual images
+     * @param imageType the image type (see {@link BufferedImage}.TYPE_*)
+     * @param resolution the resolution in dpi (dots per inch)
+     * @return true if the images were produced, false if there was an error
+     * @throws IOException if an I/O error occurs
+     */
+    public boolean writeImage(PDDocument document, String imageFormat, String password,
+            int startPage, int endPage, String outputPrefix, int imageType, int resolution)
+    throws IOException
+    {
+        boolean bSuccess = true;
+        List pages = document.getDocumentCatalog().getAllPages();
+        for( int i = startPage - 1; i < endPage && i < pages.size(); i++ )
+        {
+            ImageOutputStream output = null;
+            ImageWriter imageWriter = null;
+            try
+            {
+                PDPage page = (PDPage)pages.get( i );
+                BufferedImage image = page.convertToImage(imageType, resolution);
+                String fileName = outputPrefix + (i + 1) + "." + imageFormat;
+                System.out.println( "Writing: " + fileName );
+                output = ImageIO.createImageOutputStream( new File( fileName ) );
+
+                boolean foundWriter = false;
+                Iterator writerIter = ImageIO.getImageWritersByFormatName( imageFormat );
+                while( writerIter.hasNext() && !foundWriter )
+                {
+                    try
+                    {
+                        imageWriter = (ImageWriter)writerIter.next();
+                        ImageWriteParam writerParams = imageWriter.getDefaultWriteParam();
+                        if( writerParams.canWriteCompressed() )
+                        {
+                            writerParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+                            writerParams.setCompressionQuality(1.0f);
+                        }
+                        IIOMetadata meta = createMetadata( image, imageWriter, writerParams, resolution);
+                        imageWriter.setOutput( output );
+                        imageWriter.write( null, new IIOImage( image, null, meta ), writerParams );
+                        foundWriter = true;
+                    }
+                    catch( IIOException io )
+                    {
+                        throw new IOException( io.getMessage() );
+                    }
+                    finally
+                    {
+                        if( imageWriter != null )
+                        {
+                            imageWriter.dispose();
+                        }
+                    }
+                }
+                if( !foundWriter )
+                {
+                    bSuccess = false;
+                }
+            }
+            finally
+            {
+                if( output != null )
+                {
+                    output.flush();
+                    output.close();
+                }
+            }
+        }
+        return bSuccess;
+    }
+
+    private IIOMetadata createMetadata(RenderedImage image, ImageWriter imageWriter,
+            ImageWriteParam writerParams, int resolution)
+    {
+        ImageTypeSpecifier type;
+        if (writerParams.getDestinationType() != null)
+        {
+            type = writerParams.getDestinationType();
+        }
+        else
+        {
+            type = ImageTypeSpecifier.createFromRenderedImage( image );
+        }
+        IIOMetadata meta = imageWriter.getDefaultImageMetadata( type, writerParams );
+        return (addResolution(meta, resolution) ? meta : null);
+    }
+
+    private static final String STANDARD_METADATA_FORMAT = "javax_imageio_1.0";
+
+    private boolean addResolution(IIOMetadata meta, int resolution)
+    {
+        if (meta.isStandardMetadataFormatSupported())
+        {
+            IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(STANDARD_METADATA_FORMAT);
+            IIOMetadataNode dim = getChildNode(root, "Dimension");
+            IIOMetadataNode child;
+            child = getChildNode(dim, "HorizontalPixelSize");
+            if (child == null)
+            {
+                child = new IIOMetadataNode("HorizontalPixelSize");
+                dim.appendChild(child);
+            }
+            child.setAttribute("value",
+                    Double.toString(resolution / 25.4));
+            child = getChildNode(dim, "VerticalPixelSize");
+            if (child == null)
+            {
+                child = new IIOMetadataNode("VerticalPixelSize");
+                dim.appendChild(child);
+            }
+            child.setAttribute("value",
+                    Double.toString(resolution / 25.4));
+            try
+            {
+                meta.mergeTree(STANDARD_METADATA_FORMAT, root);
+            }
+            catch (IIOInvalidTreeException e)
+            {
+                throw new RuntimeException("Cannot update image metadata: "
+                        + e.getMessage());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private static IIOMetadataNode getChildNode(Node n, String name)
+    {
+        NodeList nodes = n.getChildNodes();
+        for (int i = 0; i < nodes.getLength(); i++)
+        {
+            Node child = nodes.item(i);
+            if (name.equals(child.getNodeName()))
+            {
+                return (IIOMetadataNode)child;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFMarkedContentExtractor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFMarkedContentExtractor.java
new file mode 100644 (file)
index 0000000..42f68f9
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Stack;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+
+/**
+ * This is an stream engine to extract the marked content of a pdf.
+ * @author koch
+ * @version $Revision$
+ */
+public class PDFMarkedContentExtractor extends PDFStreamEngine
+{
+    private boolean suppressDuplicateOverlappingText = true;
+    private List<PDMarkedContent> markedContents = new ArrayList<PDMarkedContent>();
+    private Stack<PDMarkedContent> currentMarkedContents = new Stack<PDMarkedContent>();
+
+    private Map<String, List<TextPosition>> characterListMapping =
+        new HashMap<String, List<TextPosition>>();
+
+    /**
+     * encoding that text will be written in (or null).
+     */
+    protected String outputEncoding; 
+
+    /**
+     * The normalizer is used to remove text ligatures/presentation forms
+     * and to correct the direction of right to left text, such as Arabic and Hebrew.
+     */
+    private TextNormalize normalize = null;
+
+    /**
+     * Instantiate a new PDFTextStripper object. This object will load
+     * properties from PDFMarkedContentExtractor.properties and will not
+     * do anything special to convert the text to a more encoding-specific
+     * output.
+     *
+     * @throws IOException If there is an error loading the properties.
+     */
+    public PDFMarkedContentExtractor() throws IOException
+    {
+        super( ResourceLoader.loadProperties(
+                "org/apache/pdfbox/resources/PDFMarkedContentExtractor.properties", true ) );
+        this.outputEncoding = null;
+        this.normalize = new TextNormalize(this.outputEncoding);
+    }
+
+
+    /**
+     * Instantiate a new PDFTextStripper object.  Loading all of the operator mappings
+     * from the properties object that is passed in.  Does not convert the text
+     * to more encoding-specific output.
+     *
+     * @param props The properties containing the mapping of operators to PDFOperator
+     * classes.
+     *
+     * @throws IOException If there is an error reading the properties.
+     */
+    public PDFMarkedContentExtractor( Properties props ) throws IOException
+    {
+        super( props );
+        this.outputEncoding = null;
+        this.normalize = new TextNormalize(this.outputEncoding);
+    }
+
+    /**
+     * Instantiate a new PDFTextStripper object. This object will load
+     * properties from PDFMarkedContentExtractor.properties and will apply
+     * encoding-specific conversions to the output text.
+     *
+     * @param encoding The encoding that the output will be written in.
+     * @throws IOException If there is an error reading the properties.
+     */
+    public PDFMarkedContentExtractor( String encoding ) throws IOException
+    {
+        super( ResourceLoader.loadProperties(
+                "org/apache/pdfbox/resources/PDFMarkedContentExtractor.properties", true ));
+        this.outputEncoding = encoding;
+        this.normalize = new TextNormalize(this.outputEncoding);
+    }
+
+
+    /**
+     * This will determine of two floating point numbers are within a specified variance.
+     *
+     * @param first The first number to compare to.
+     * @param second The second number to compare to.
+     * @param variance The allowed variance.
+     */
+    private boolean within( float first, float second, float variance )
+    {
+        return second > first - variance && second < first + variance;
+    }
+
+
+    public void beginMarkedContentSequence(COSName tag, COSDictionary properties)
+    {
+        PDMarkedContent markedContent = PDMarkedContent.create(tag, properties);
+        if (this.currentMarkedContents.isEmpty())
+        {
+            this.markedContents.add(markedContent);
+        }
+        else
+        {
+            PDMarkedContent currentMarkedContent =
+                this.currentMarkedContents.peek();
+            if (currentMarkedContent != null)
+            {
+                currentMarkedContent.addMarkedContent(markedContent);
+            }
+        }
+        this.currentMarkedContents.push(markedContent);
+    }
+
+    public void endMarkedContentSequence()
+    {
+        if (!this.currentMarkedContents.isEmpty())
+        {
+            this.currentMarkedContents.pop();
+        }
+    }
+
+    public void xobject(PDXObject xobject)
+    {
+        if (!this.currentMarkedContents.isEmpty())
+        {
+            this.currentMarkedContents.peek().addXObject(xobject);
+        }
+    }
+
+
+    /**
+     * This will process a TextPosition object and add the
+     * text to the list of characters on a page.  It takes care of
+     * overlapping text.
+     *
+     * @param text The text to process.
+     */
+    protected void processTextPosition( TextPosition text )
+    {
+        boolean showCharacter = true;
+        if( this.suppressDuplicateOverlappingText )
+        {
+            showCharacter = false;
+            String textCharacter = text.getCharacter();
+            float textX = text.getX();
+            float textY = text.getY();
+            List<TextPosition> sameTextCharacters = this.characterListMapping.get( textCharacter );
+            if( sameTextCharacters == null )
+            {
+                sameTextCharacters = new ArrayList<TextPosition>();
+                this.characterListMapping.put( textCharacter, sameTextCharacters );
+            }
+
+            // RDD - Here we compute the value that represents the end of the rendered
+            // text.  This value is used to determine whether subsequent text rendered
+            // on the same line overwrites the current text.
+            //
+            // We subtract any positive padding to handle cases where extreme amounts
+            // of padding are applied, then backed off (not sure why this is done, but there
+            // are cases where the padding is on the order of 10x the character width, and
+            // the TJ just backs up to compensate after each character).  Also, we subtract
+            // an amount to allow for kerning (a percentage of the width of the last
+            // character).
+            //
+            boolean suppressCharacter = false;
+            float tolerance = (text.getWidth()/textCharacter.length())/3.0f;
+            for( int i=0; i<sameTextCharacters.size() && textCharacter != null; i++ )
+            {
+                TextPosition character = (TextPosition)sameTextCharacters.get( i );
+                String charCharacter = character.getCharacter();
+                float charX = character.getX();
+                float charY = character.getY();
+                //only want to suppress
+
+                if( charCharacter != null &&
+                        //charCharacter.equals( textCharacter ) &&
+                        within( charX, textX, tolerance ) &&
+                        within( charY,
+                                textY,
+                                tolerance ) )
+                {
+                    suppressCharacter = true;
+                }
+            }
+            if( !suppressCharacter )
+            {
+                sameTextCharacters.add( text );
+                showCharacter = true;
+            }
+        }
+
+        if( showCharacter )
+        {
+            List<TextPosition> textList = new ArrayList<TextPosition>();
+
+            /* In the wild, some PDF encoded documents put diacritics (accents on
+             * top of characters) into a separate Tj element.  When displaying them
+             * graphically, the two chunks get overlayed.  With text output though,
+             * we need to do the overlay. This code recombines the diacritic with
+             * its associated character if the two are consecutive.
+             */ 
+            if(textList.isEmpty())
+            {
+                textList.add(text);
+            }
+            else
+            {
+                /* test if we overlap the previous entry.  
+                 * Note that we are making an assumption that we need to only look back
+                 * one TextPosition to find what we are overlapping.  
+                 * This may not always be true. */
+                TextPosition previousTextPosition = (TextPosition)textList.get(textList.size()-1);
+                if(text.isDiacritic() && previousTextPosition.contains(text))
+                {
+                    previousTextPosition.mergeDiacritic(text, this.normalize);
+                }
+                /* If the previous TextPosition was the diacritic, merge it into this
+                 * one and remove it from the list. */
+                else if(previousTextPosition.isDiacritic() && text.contains(previousTextPosition))
+                {
+                    text.mergeDiacritic(previousTextPosition, this.normalize);
+                    textList.remove(textList.size()-1);
+                    textList.add(text);
+                }
+                else
+                {
+                    textList.add(text);
+                }
+            }
+            if (!this.currentMarkedContents.isEmpty())
+            {
+                this.currentMarkedContents.peek().addText(text);
+            }
+        }
+    }
+
+
+    public List<PDMarkedContent> getMarkedContents()
+    {
+        return this.markedContents;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFMergerUtility.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFMergerUtility.java
new file mode 100644 (file)
index 0000000..d3d974e
--- /dev/null
@@ -0,0 +1,442 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+import org.apache.pdfbox.pdmodel.interactive.form.PDFieldFactory;
+
+/**
+ * This class will take a list of pdf documents and merge them, saving the result
+ * in a new document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDFMergerUtility
+{
+
+    private List<InputStream> sources;
+    private String destinationFileName;
+    private OutputStream destinationStream;
+    private boolean ignoreAcroFormErrors = false;
+
+    /**
+     * Instantiate a new PDFMergerUtility.
+     */
+    public PDFMergerUtility()
+    {
+        sources = new ArrayList<InputStream>();
+    }
+
+    /**
+     * Get the name of the destination file.
+     * @return Returns the destination.
+     */
+    public String getDestinationFileName()
+    {
+        return destinationFileName;
+    }
+
+    /**
+     * Set the name of the destination file.
+     * @param destination
+     *            The destination to set.
+     */
+    public void setDestinationFileName(String destination)
+    {
+        this.destinationFileName = destination;
+    }
+
+    /**
+     * Get the destination OutputStream.
+     * @return Returns the destination OutputStream.
+     */
+    public OutputStream getDestinationStream()
+    {
+        return destinationStream;
+    }
+
+    /**
+     * Set the destination OutputStream.
+     * @param destination
+     *            The destination to set.
+     */
+    public void setDestinationStream(OutputStream destinationStream)
+    {
+        this.destinationStream = destinationStream;
+    }
+
+    /**
+     * Add a source file to the list of files to merge.
+     *
+     * @param source Full path and file name of source document.
+     */
+    public void addSource(String source)
+    {
+        try
+        {
+            sources.add(new FileInputStream(new File(source)));
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Add a source file to the list of files to merge.
+     *
+     * @param source File representing source document
+     */
+    public void addSource(File source)
+    {
+        try
+        {
+            sources.add(new FileInputStream(source));
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Add a source to the list of documents to merge.
+     *
+     * @param source InputStream representing source document
+     */
+    public void addSource(InputStream source)
+    {
+        sources.add(source);
+    }
+
+    /**
+     * Add a list of sources to the list of documents to merge.
+     *
+     * @param source List of InputStream objects representing source documents
+     */
+    public void addSources(List<InputStream> sources)
+    {
+        this.sources.addAll(sources);
+    }
+
+    /**
+     * Merge the list of source documents, saving the result in the destination file.
+     *
+     * @throws IOException If there is an error saving the document.
+     * @throws COSVisitorException If an error occurs while saving the destination file.
+     */
+    public void mergeDocuments() throws IOException, COSVisitorException
+    {
+        PDDocument destination = null;
+        InputStream sourceFile;
+        PDDocument source;
+        if (sources != null && sources.size() > 0)
+        {
+            java.util.Vector<PDDocument> tobeclosed = new java.util.Vector<PDDocument>();
+
+            try
+            {
+                Iterator<InputStream> sit = sources.iterator();
+                sourceFile = sit.next();
+                destination = PDDocument.load(sourceFile);
+
+                while (sit.hasNext())
+                {
+                    sourceFile = sit.next();
+                    source = PDDocument.load(sourceFile);
+                    tobeclosed.add(source);
+                    appendDocument(destination, source);
+                }
+                if(destinationStream == null)
+                {
+                    destination.save(destinationFileName);
+                }
+                else
+                {
+                    destination.save(destinationStream);
+                }
+            }
+            finally
+            {
+                if (destination != null)
+                {
+                    destination.close();
+                }
+                for (PDDocument doc : tobeclosed)
+                {
+                    doc.close();
+                }
+            }
+        }
+    }
+
+
+    /**
+     * append all pages from source to destination.
+     *
+     * @param destination the document to receive the pages
+     * @param source the document originating the new pages
+     *
+     * @throws IOException If there is an error accessing data from either document.
+     */
+    public void appendDocument(PDDocument destination, PDDocument source) throws IOException
+    {
+        if( destination.isEncrypted() )
+        {
+            throw new IOException( "Error: destination PDF is encrypted, can't append encrypted PDF documents." );
+        }
+        if( source.isEncrypted() )
+        {
+            throw new IOException( "Error: source PDF is encrypted, can't append encrypted PDF documents." );
+        }
+        PDDocumentInformation destInfo = destination.getDocumentInformation();
+        PDDocumentInformation srcInfo = source.getDocumentInformation();
+        destInfo.getDictionary().mergeInto( srcInfo.getDictionary() );
+
+        PDDocumentCatalog destCatalog = destination.getDocumentCatalog();
+        PDDocumentCatalog srcCatalog = source.getDocumentCatalog();
+
+        if( destCatalog.getOpenAction() == null )
+        {
+            destCatalog.setOpenAction( srcCatalog.getOpenAction() );
+        }
+
+        PDFCloneUtility cloner = new PDFCloneUtility(destination);
+
+        try
+        {
+            PDAcroForm destAcroForm = destCatalog.getAcroForm();
+            PDAcroForm srcAcroForm = srcCatalog.getAcroForm();
+            if( destAcroForm == null )
+            {
+                cloner.cloneForNewDocument( srcAcroForm );
+                destCatalog.setAcroForm( srcAcroForm );
+            }
+            else
+            {
+                if( srcAcroForm != null )
+                {
+                    mergeAcroForm(cloner, destAcroForm, srcAcroForm);
+                }
+            }
+        }
+        catch(Exception e)
+        {
+            // if we are not ignoring exceptions, we'll re-throw this
+            if(!ignoreAcroFormErrors)
+            {
+                throw (IOException)e;
+            }
+        }
+
+        COSArray destThreads = (COSArray)destCatalog.getCOSDictionary().getDictionaryObject(
+                COSName.THREADS);
+        COSArray srcThreads = (COSArray)cloner.cloneForNewDocument(
+                destCatalog.getCOSDictionary().getDictionaryObject( COSName.THREADS ));
+        if( destThreads == null )
+        {
+            destCatalog.getCOSDictionary().setItem( COSName.THREADS, srcThreads );
+        }
+        else
+        {
+            destThreads.addAll( srcThreads );
+        }
+
+        PDDocumentNameDictionary destNames = destCatalog.getNames();
+        PDDocumentNameDictionary srcNames = srcCatalog.getNames();
+        if( srcNames != null )
+        {
+            if( destNames == null )
+            {
+                destCatalog.getCOSDictionary().setItem( COSName.NAMES,
+                        cloner.cloneForNewDocument( srcNames ) );
+            }
+            else
+            {
+                cloner.cloneMerge(srcNames, destNames);
+            }
+
+        }
+
+        PDDocumentOutline destOutline = destCatalog.getDocumentOutline();
+        PDDocumentOutline srcOutline = srcCatalog.getDocumentOutline();
+        if( srcOutline != null )
+        {
+            if( destOutline == null )
+            {
+                PDDocumentOutline cloned =
+                    new PDDocumentOutline( (COSDictionary)cloner.cloneForNewDocument( srcOutline ) );
+                destCatalog.setDocumentOutline( cloned );
+            }
+            else
+            {
+                PDOutlineItem first = srcOutline.getFirstChild();
+                if(first != null)
+                {
+                    PDOutlineItem clonedFirst = new PDOutlineItem(
+                            (COSDictionary)cloner.cloneForNewDocument( first ));
+                    destOutline.appendChild( clonedFirst );
+                }
+            }
+        }
+
+        String destPageMode = destCatalog.getPageMode();
+        String srcPageMode = srcCatalog.getPageMode();
+        if( destPageMode == null )
+        {
+            destCatalog.setPageMode( srcPageMode );
+        }
+
+        COSDictionary destLabels = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject(
+                COSName.PAGE_LABELS);
+        COSDictionary srcLabels = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject(
+                COSName.PAGE_LABELS);
+        if( srcLabels != null )
+        {
+            int destPageCount = destination.getNumberOfPages();
+            COSArray destNums = null;
+            if( destLabels == null )
+            {
+                destLabels = new COSDictionary();
+                destNums = new COSArray();
+                destLabels.setItem( COSName.NUMS, destNums );
+                destCatalog.getCOSDictionary().setItem( COSName.PAGE_LABELS, destLabels );
+            }
+            else
+            {
+                destNums = (COSArray)destLabels.getDictionaryObject( COSName.NUMS );
+            }
+            COSArray srcNums = (COSArray)srcLabels.getDictionaryObject( COSName.NUMS );
+            if (srcNums != null)
+            {
+                for( int i=0; i<srcNums.size(); i+=2 )
+                {
+                    COSNumber labelIndex = (COSNumber)srcNums.getObject( i );
+                    long labelIndexValue = labelIndex.intValue();
+                    destNums.add( COSInteger.get( labelIndexValue + destPageCount ) );
+                    destNums.add( cloner.cloneForNewDocument( srcNums.getObject( i+1 ) ) );
+                }
+            }
+        }
+
+        COSStream destMetadata = (COSStream)destCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
+        COSStream srcMetadata = (COSStream)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
+        if( destMetadata == null && srcMetadata != null )
+        {
+            PDStream newStream = new PDStream( destination, srcMetadata.getUnfilteredStream(), false );
+            newStream.getStream().mergeInto( srcMetadata );
+            newStream.addCompression();
+            destCatalog.getCOSDictionary().setItem( COSName.METADATA, newStream );
+        }
+
+        //finally append the pages
+        List<PDPage> pages = source.getDocumentCatalog().getAllPages();
+        Iterator<PDPage> pageIter = pages.iterator();
+        while( pageIter.hasNext() )
+        {
+            PDPage page = pageIter.next();
+            PDPage newPage =
+                new PDPage( (COSDictionary)cloner.cloneForNewDocument( page.getCOSDictionary() ) );
+            newPage.setCropBox( page.findCropBox() );
+            newPage.setMediaBox( page.findMediaBox() );
+            newPage.setRotation( page.findRotation() );
+            destination.addPage( newPage );
+        }
+    }
+
+
+    private int nextFieldNum = 1;
+
+    /**
+     * Merge the contents of the source form into the destination form
+     * for the destination file.
+     *
+     * @param cloner the object cloner for the destination document
+     * @param destAcroForm the destination form
+     * @param srcAcroForm the source form
+     * @throws IOException If an error occurs while adding the field.
+     */
+    private void mergeAcroForm(PDFCloneUtility cloner, PDAcroForm destAcroForm, PDAcroForm srcAcroForm)
+        throws IOException
+    {
+        List destFields = destAcroForm.getFields();
+        List srcFields = srcAcroForm.getFields();
+        if( srcFields != null )
+        {
+            if( destFields == null )
+            {
+                destFields = new COSArrayList();
+                destAcroForm.setFields( destFields );
+            }
+            Iterator srcFieldsIterator = srcFields.iterator();
+            while (srcFieldsIterator.hasNext())
+            {
+                PDField srcField = (PDField)srcFieldsIterator.next();
+                PDField destField =
+                    PDFieldFactory.createField(
+                        destAcroForm,
+                        (COSDictionary)cloner.cloneForNewDocument(srcField.getDictionary() ));
+                // if the form already has a field with this name then we need to rename this field
+                // to prevent merge conflicts.
+                if ( destAcroForm.getField(destField.getFullyQualifiedName()) != null )
+                {
+                    destField.setPartialName("dummyFieldName"+(nextFieldNum++));
+                }
+                destFields.add(destField);
+            }
+        }
+    }
+
+    public boolean isIgnoreAcroFormErrors()
+    {
+        return ignoreAcroFormErrors;
+    }
+
+    public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrors)
+    {
+        this.ignoreAcroFormErrors = ignoreAcroFormErrors;
+    }
+
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFOperator.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFOperator.java
new file mode 100644 (file)
index 0000000..7dd100a
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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.pdfbox.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class represents an Operator in the content stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class PDFOperator
+{
+    private String theOperator;
+    private byte[] imageData;
+    private ImageParameters imageParameters;
+
+    private static Map operators = Collections.synchronizedMap( new HashMap() );
+
+    /**
+     * Constructor.
+     *
+     * @param aOperator The operator that this object will represent.
+     */
+    private PDFOperator( String aOperator )
+    {
+        theOperator = aOperator;
+        if( aOperator.startsWith( "/" ) )
+        {
+            throw new RuntimeException( "Operators are not allowed to start with / '" + aOperator + "'" );
+        }
+    }
+
+    /**
+     * This is used to create/cache operators in the system.
+     *
+     * @param operator The operator for the system.
+     *
+     * @return The operator that matches the operator keyword.
+     */
+    public static PDFOperator getOperator( String operator )
+    {
+        PDFOperator operation = null;
+        if( operator.equals( "ID" ) || operator.equals( "BI" ) )
+        {
+            //we can't cache the ID operators.
+            operation = new PDFOperator( operator );
+        }
+        else
+        {
+            operation = (PDFOperator)operators.get( operator );
+            if( operation == null )
+            {
+                operation = new PDFOperator( operator );
+                operators.put( operator, operation );
+            }
+        }
+
+        return operation;
+    }
+
+    /**
+     * This will get the operation that this operator represents.
+     *
+     * @return The string representation of the operation.
+     */
+    public String getOperation()
+    {
+        return theOperator;
+    }
+
+    /**
+     * This will print a string rep of this class.
+     *
+     * @return A string rep of this class.
+     */
+    public String toString()
+    {
+        return "PDFOperator{" + theOperator + "}";
+    }
+
+    /**
+     * This is the special case for the ID operator where there are just random
+     * bytes inlined the stream.
+     *
+     * @return Value of property imageData.
+     */
+    public byte[] getImageData()
+    {
+        return this.imageData;
+    }
+
+    /**
+     * This will set the image data, this is only used for the ID operator.
+     *
+     * @param imageDataArray New value of property imageData.
+     */
+    public void setImageData(byte[] imageDataArray)
+    {
+        imageData = imageDataArray;
+    }
+
+    /**
+     * This will get the image parameters, this is only valid for BI operators.
+     *
+     * @return The image parameters.
+     */
+    public ImageParameters getImageParameters()
+    {
+        return imageParameters;
+    }
+
+    /**
+     * This will set the image parameters, this is only valid for BI operators.
+     *
+     * @param params The image parameters.
+     */
+    public void setImageParameters( ImageParameters params)
+    {
+        imageParameters = params;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFStreamEngine.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFStreamEngine.java
new file mode 100644 (file)
index 0000000..a1018ff
--- /dev/null
@@ -0,0 +1,713 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType3Font;
+
+import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * This class will run through a PDF content stream and execute certain operations
+ * and provide a callback interface for clients that want to do things with the stream.
+ * See the PDFTextStripper class for an example of how to use this class.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.38 $
+ */
+public class PDFStreamEngine
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(PDFStreamEngine.class);
+
+    /**
+     * The PDF operators that are ignored by this engine.
+     */
+    private final Set<String> unsupportedOperators = new HashSet<String>();
+    
+    private static final byte[] SPACE_BYTES = { (byte)32 };
+
+    private PDGraphicsState graphicsState = null;
+
+    private Matrix textMatrix = null;
+    private Matrix textLineMatrix = null;
+    private Stack<PDGraphicsState> graphicsStack = new Stack<PDGraphicsState>();
+
+    private Map<String,OperatorProcessor> operators = new HashMap<String,OperatorProcessor>();
+
+    private Stack<StreamResources> streamResourcesStack = new Stack<StreamResources>();
+
+    private PDPage page;
+
+    private Map<String,PDFont> documentFontCache = new HashMap<String,PDFont>();
+    
+    private int validCharCnt;
+    private int totalCharCnt;
+
+    /**
+     * Flag to skip malformed or otherwise unparseable input where possible.
+     */
+    private boolean forceParsing = false;
+
+    /**
+     * This is a simple internal class used by the Stream engine to handle the
+     * resources stack.
+     */
+    private static class StreamResources
+    {
+        private Map<String,PDFont> fonts;
+        private Map<String,PDColorSpace> colorSpaces;
+        private Map<String,PDXObject> xobjects;
+        private Map<String,PDExtendedGraphicsState> graphicsStates;
+        private PDResources resources;
+        
+        private StreamResources()
+        {};
+    }
+
+    /**
+     * Constructor.
+     */
+    public PDFStreamEngine()
+    {
+        //default constructor
+        validCharCnt = 0;
+        totalCharCnt = 0;
+        
+    }
+
+    /**
+     * Constructor with engine properties.  The property keys are all
+     * PDF operators, the values are class names used to execute those
+     * operators. An empty value means that the operator will be silently
+     * ignored.
+     *
+     * @param properties The engine properties.
+     *
+     * @throws IOException If there is an error setting the engine properties.
+     */
+    public PDFStreamEngine( Properties properties ) throws IOException
+    {
+        if( properties == null ) 
+        {
+            throw new NullPointerException( "properties cannot be null" );
+        }
+        Enumeration<?> names = properties.propertyNames();
+        for ( Object name : Collections.list( names ) )
+        {
+            String operator = name.toString();
+            String processorClassName = properties.getProperty( operator );
+            if( "".equals( processorClassName ) )
+            {
+                unsupportedOperators.add( operator );
+            }
+            else
+            {
+                try
+                {
+                    Class<?> klass = Class.forName( processorClassName );
+                    OperatorProcessor processor =
+                        (OperatorProcessor) klass.newInstance();
+                    registerOperatorProcessor( operator, processor );
+                }
+                catch( Exception e )
+                {
+                    throw new WrappedIOException(
+                            "OperatorProcessor class " + processorClassName
+                            + " could not be instantiated", e );
+                }
+            }
+        }
+        validCharCnt = 0;
+        totalCharCnt = 0;
+    }
+
+    public boolean isForceParsing() {
+        return forceParsing;
+    }
+
+    public void setForceParsing(boolean forceParsing) {
+        this.forceParsing = forceParsing;
+    }
+
+    /**
+     * Register a custom operator processor with the engine.
+     *
+     * @param operator The operator as a string.
+     * @param op Processor instance.
+     */
+    public void registerOperatorProcessor( String operator, OperatorProcessor op )
+    {
+        op.setContext( this );
+        operators.put( operator, op );
+    }
+
+    /**
+     * This method must be called between processing documents.  The
+     * PDFStreamEngine caches information for the document between pages
+     * and this will release the cached information.  This only needs
+     * to be called if processing a new document.
+     *
+     */
+    public void resetEngine()
+    {
+        documentFontCache.clear();
+        validCharCnt = 0;
+        totalCharCnt = 0;
+    }
+
+    /**
+     * This will process the contents of the stream.
+     *
+     * @param aPage The page.
+     * @param resources The location to retrieve resources.
+     * @param cosStream the Stream to execute.
+     *
+     *
+     * @throws IOException if there is an error accessing the stream.
+     */
+    public void processStream( PDPage aPage, PDResources resources, COSStream cosStream ) throws IOException
+    {
+        graphicsState = new PDGraphicsState(aPage.findCropBox());
+        textMatrix = null;
+        textLineMatrix = null;
+        graphicsStack.clear();
+        streamResourcesStack.clear();
+
+        processSubStream( aPage, resources, cosStream );
+    }
+
+    /**
+     * Process a sub stream of the current stream.
+     *
+     * @param aPage The page used for drawing.
+     * @param resources The resources used when processing the stream.
+     * @param cosStream The stream to process.
+     *
+     * @throws IOException If there is an exception while processing the stream.
+     */
+    public void processSubStream(
+            PDPage aPage, PDResources resources, COSStream cosStream)
+            throws IOException {
+        page = aPage;
+        if (resources != null) {
+            StreamResources sr = new StreamResources();
+            sr.fonts = resources.getFonts( documentFontCache );
+            sr.colorSpaces = resources.getColorSpaces();
+            sr.xobjects = resources.getXObjects();
+            sr.graphicsStates = resources.getGraphicsStates();
+            sr.resources = resources;
+
+            streamResourcesStack.push(sr);
+            try {
+                processSubStream(cosStream);
+            } finally {
+                streamResourcesStack.pop();
+            }
+        } else {
+            processSubStream(cosStream);
+        }
+    }
+
+    private void processSubStream(COSStream cosStream) throws IOException {
+        List<COSBase> arguments = new ArrayList<COSBase>();
+        PDFStreamParser parser = new PDFStreamParser(cosStream, forceParsing);
+        try {
+            Iterator<Object> iter = parser.getTokenIterator();
+
+            while (iter.hasNext()) {
+                Object next = iter.next();
+                if (log.isDebugEnabled()) {
+                    log.debug("processing substream token: " + next);
+                }
+                if (next instanceof COSObject) {
+                    arguments.add(((COSObject) next).getObject());
+                } else if (next instanceof PDFOperator) {
+                    processOperator((PDFOperator) next, arguments);
+                    arguments = new ArrayList<COSBase>();
+                } else {
+                    arguments.add((COSBase) next);
+                }
+            }
+        } finally {
+            parser.close();
+        }
+    }
+
+    
+    /**
+     * A method provided as an event interface to allow a subclass to perform
+     * some specific functionality when text needs to be processed.
+     *
+     * @param text The text to be processed.
+     */
+    protected void processTextPosition( TextPosition text )
+    {
+        //subclasses can override to provide specific functionality.
+    }
+
+    /**
+     * A method provided as an event interface to allow a subclass to perform
+     * some specific functionality on the string encoded by a glyph.
+     *
+     * @param str The string to be processed.
+     */
+    protected String inspectFontEncoding( String str )
+    {
+        return str;
+    }
+
+    /**
+     * Process encoded text from the PDF Stream. 
+     * You should override this method if you want to perform an action when 
+     * encoded text is being processed.
+     *
+     * @param string The encoded text
+     *
+     * @throws IOException If there is an error processing the string
+     */
+    public void processEncodedText( byte[] string ) throws IOException
+    {
+        /* Note on variable names.  There are three different units being used
+         * in this code.  Character sizes are given in glyph units, text locations
+         * are initially given in text units, and we want to save the data in 
+         * display units. The variable names should end with Text or Disp to 
+         * represent if the values are in text or disp units (no glyph units are saved).
+         */
+        final float fontSizeText = graphicsState.getTextState().getFontSize();
+        final float horizontalScalingText = graphicsState.getTextState().getHorizontalScalingPercent()/100f;
+        //float verticalScalingText = horizontalScaling;//not sure if this is right but what else to do???
+        final float riseText = graphicsState.getTextState().getRise();
+        final float wordSpacingText = graphicsState.getTextState().getWordSpacing();
+        final float characterSpacingText = graphicsState.getTextState().getCharacterSpacing();
+        
+        //We won't know the actual number of characters until
+        //we process the byte data(could be two bytes each) but
+        //it won't ever be more than string.length*2(there are some cases
+        //were a single byte will result in two output characters "fi"
+        
+        final PDFont font = graphicsState.getTextState().getFont();
+        // all fonts are providing the width/height of a character in thousandths of a unit of text space
+        float fontMatrixXScaling = 1/1000f;
+        float fontMatrixYScaling = 1/1000f;
+        float glyphSpaceToTextSpaceFactor = 1/1000f;
+        // expect Type3 fonts, those are providing the width of a character in glyph space units
+        if (font instanceof PDType3Font)
+        {
+            PDMatrix fontMatrix = font.getFontMatrix();
+            fontMatrixXScaling = fontMatrix.getValue(0, 0);
+            fontMatrixYScaling = fontMatrix.getValue(1, 1);
+            //This will typically be 1000 but in the case of a type3 font
+            //this might be a different number
+            glyphSpaceToTextSpaceFactor = 1f/fontMatrix.getValue( 0, 0 );
+        }
+        float spaceWidthText=0;
+        try
+        {   
+            // to avoid crash as described in PDFBOX-614
+            // lets see what the space displacement should be
+            spaceWidthText = (font.getFontWidth( SPACE_BYTES, 0, 1 )*glyphSpaceToTextSpaceFactor);
+        }
+        catch (Throwable exception)
+        {
+            log.warn( exception, exception);
+        }
+        
+        if( spaceWidthText == 0 )
+        {
+            spaceWidthText = (font.getAverageFontWidth()*glyphSpaceToTextSpaceFactor);
+            //The average space width appears to be higher than necessary
+            //so lets make it a little bit smaller.
+            spaceWidthText *= .80f;
+        }
+        
+        float maxVerticalDisplacementText = 0;
+
+        Matrix textStateParameters = new Matrix();
+        textStateParameters.setValue(0,0, fontSizeText*horizontalScalingText);
+        textStateParameters.setValue(1,1, fontSizeText);
+        textStateParameters.setValue(2,1, riseText);
+
+        int pageRotation = page.findRotation();
+        float pageHeight = page.findMediaBox().getHeight();
+        float pageWidth = page.findMediaBox().getWidth();
+
+        Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
+        Matrix textXctm = new Matrix();
+        Matrix textMatrixEnd = new Matrix();
+        Matrix td = new Matrix();
+        Matrix tempMatrix = new Matrix();
+
+        int codeLength = 1;
+        for( int i=0; i<string.length; i+=codeLength)
+        {
+            // Decode the value to a Unicode character
+            codeLength = 1;
+            String c = font.encode( string, i, codeLength );
+            if( c == null && i+1<string.length)
+            {
+                //maybe a multibyte encoding
+                codeLength++;
+                c = font.encode( string, i, codeLength );
+            }
+
+            // the space width has to be transformed into display units
+            float spaceWidthDisp = spaceWidthText * fontSizeText * horizontalScalingText * textMatrix.getValue(0, 0) * ctm.getValue(0, 0);
+
+            //todo, handle horizontal displacement
+            // get the width and height of this character in text units 
+            float characterHorizontalDisplacementText = font.getFontWidth( string, i, codeLength );
+            float characterVerticalDisplacementText = font.getFontHeight( string, i, codeLength );
+
+            // multiply the width/height with the scaling factor
+            characterHorizontalDisplacementText = characterHorizontalDisplacementText * fontMatrixXScaling;
+            characterVerticalDisplacementText = characterVerticalDisplacementText * fontMatrixYScaling;
+
+            maxVerticalDisplacementText = 
+                Math.max( 
+                    maxVerticalDisplacementText, 
+                    characterVerticalDisplacementText);
+
+            // PDF Spec - 5.5.2 Word Spacing
+            //
+            // Word spacing works the same was as character spacing, but applies
+            // only to the space character, code 32.
+            //
+            // Note: Word spacing is applied to every occurrence of the single-byte
+            // character code 32 in a string.  This can occur when using a simple
+            // font or a composite font that defines code 32 as a single-byte code.
+            // It does not apply to occurrences of the byte value 32 in multiple-byte
+            // codes.
+            //
+            // RDD - My interpretation of this is that only character code 32's that
+            // encode to spaces should have word spacing applied.  Cases have been
+            // observed where a font has a space character with a character code
+            // other than 32, and where word spacing (Tw) was used.  In these cases,
+            // applying word spacing to either the non-32 space or to the character
+            // code 32 non-space resulted in errors consistent with this interpretation.
+            //
+            float spacingText = 0;
+            if( (string[i] == 0x20) && codeLength == 1 )
+            {
+                spacingText += wordSpacingText;
+            }
+            textXctm = textMatrix.multiply(ctm, textXctm);
+            // Convert textMatrix to display units
+            // We need to instantiate a new Matrix instance here as it is passed to the TextPosition constructor below.
+            Matrix textMatrixStart = textStateParameters.multiply(textXctm);
+            
+            // TODO : tx should be set for horizontal text and ty for vertical text
+            // which seems to be specified in the font (not the direction in the matrix).
+            float tx = ((characterHorizontalDisplacementText)*fontSizeText)*horizontalScalingText;
+            float ty = 0;
+            // reset the matrix instead of creating a new one
+            td.reset();
+            td.setValue( 2, 0, tx );
+            td.setValue( 2, 1, ty );
+
+            // The text matrix gets updated after each glyph is placed.  The updated
+            // version will have the X and Y coordinates for the next glyph.
+            // textMatrixEnd contains the coordinates of the end of the last glyph without 
+            // taking characterSpacingText and spacintText into account, otherwise it'll be
+            // impossible to detect new words within text extraction
+            tempMatrix = textStateParameters.multiply(td, tempMatrix);
+            textMatrixEnd = tempMatrix.multiply(textXctm, textMatrixEnd);
+            final float endXPosition = textMatrixEnd.getXPosition();
+            final float endYPosition = textMatrixEnd.getYPosition();
+
+            // add some spacing to the text matrix (see comment above)
+            tx = ((characterHorizontalDisplacementText)*fontSizeText+characterSpacingText+spacingText)*horizontalScalingText;
+            td.setValue( 2, 0, tx );
+            textMatrix = td.multiply(textMatrix, textMatrix );
+            
+            // determine the width of this character
+            // XXX: Note that if we handled vertical text, we should be using Y here
+            float startXPosition = textMatrixStart.getXPosition();
+            float widthText = endXPosition - startXPosition;
+
+            //there are several cases where one character code will
+            //output multiple characters.  For example "fi" or a
+            //glyphname that has no mapping like "visiblespace"
+            if( c != null )
+            {
+                validCharCnt++;
+            }
+            else 
+            {
+                // PDFBOX-373: Replace a null entry with "?" so it is
+                // not printed as "(null)"
+                c = "?";
+            }
+            totalCharCnt++;
+
+            float totalVerticalDisplacementDisp = maxVerticalDisplacementText * fontSizeText * textMatrix.getYScale();
+
+            // process the decoded text
+            processTextPosition(
+                    new TextPosition(
+                            pageRotation,
+                            pageWidth,
+                            pageHeight,
+                            textMatrixStart,
+                            endXPosition,
+                            endYPosition,
+                            totalVerticalDisplacementDisp,
+                            widthText,
+                            spaceWidthDisp,
+                            c,
+                            font,
+                            fontSizeText,
+                            (int)(fontSizeText * textMatrix.getXScale())
+                            ));
+        }
+    }
+
+    /**
+     * This is used to handle an operation.
+     *
+     * @param operation The operation to perform.
+     * @param arguments The list of arguments.
+     *
+     * @throws IOException If there is an error processing the operation.
+     */
+    public void processOperator( String operation, List<COSBase> arguments ) throws IOException
+    {
+        try
+        {
+            PDFOperator oper = PDFOperator.getOperator( operation );
+            processOperator( oper, arguments );
+        }
+        catch (IOException e)
+        {
+            log.warn(e, e);
+        }
+    }
+
+    /**
+     * This is used to handle an operation.
+     *
+     * @param operator The operation to perform.
+     * @param arguments The list of arguments.
+     *
+     * @throws IOException If there is an error processing the operation.
+     */
+    protected void processOperator( PDFOperator operator, List<COSBase> arguments ) throws IOException
+    {
+        try
+        {
+            String operation = operator.getOperation();
+            OperatorProcessor processor = (OperatorProcessor)operators.get( operation );
+            if( processor != null )
+            {
+                processor.setContext(this);
+                processor.process( operator, arguments );
+            }
+            else
+            {
+                if (!unsupportedOperators.contains(operation)) 
+                {
+                    log.info("unsupported/disabled operation: " + operation);
+                    unsupportedOperators.add(operation);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            log.warn(e, e);
+        }
+    }
+
+    /**
+     * @return Returns the colorSpaces.
+     */
+    public Map<String,PDColorSpace> getColorSpaces()
+    {
+        return streamResourcesStack.peek().colorSpaces;
+    }
+
+    /**
+     * @return Returns the colorSpaces.
+     */
+    public Map<String,PDXObject> getXObjects()
+    {
+        return streamResourcesStack.peek().xobjects;
+    }
+
+    /**
+     * @param value The colorSpaces to set.
+     */
+    public void setColorSpaces(Map<String,PDColorSpace> value)
+    {
+        streamResourcesStack.peek().colorSpaces = value;
+    }
+    /**
+     * @return Returns the fonts.
+     */
+    public Map<String,PDFont> getFonts()
+    {
+        return streamResourcesStack.peek().fonts;
+    }
+    /**
+     * @param value The fonts to set.
+     */
+    public void setFonts(Map<String,PDFont> value)
+    {
+        streamResourcesStack.peek().fonts = value;
+    }
+    /**
+     * @return Returns the graphicsStack.
+     */
+    public Stack<PDGraphicsState> getGraphicsStack()
+    {
+        return graphicsStack;
+    }
+    /**
+     * @param value The graphicsStack to set.
+     */
+    public void setGraphicsStack(Stack<PDGraphicsState> value)
+    {
+        graphicsStack = value;
+    }
+    /**
+     * @return Returns the graphicsState.
+     */
+    public PDGraphicsState getGraphicsState()
+    {
+        return graphicsState;
+    }
+    /**
+     * @param value The graphicsState to set.
+     */
+    public void setGraphicsState(PDGraphicsState value)
+    {
+        graphicsState = value;
+    }
+    /**
+     * @return Returns the graphicsStates.
+     */
+    public Map<String,PDExtendedGraphicsState> getGraphicsStates()
+    {
+        return streamResourcesStack.peek().graphicsStates;
+    }
+    /**
+     * @param value The graphicsStates to set.
+     */
+    public void setGraphicsStates(Map<String,PDExtendedGraphicsState> value)
+    {
+        ((StreamResources) streamResourcesStack.peek()).graphicsStates = value;
+    }
+    /**
+     * @return Returns the textLineMatrix.
+     */
+    public Matrix getTextLineMatrix()
+    {
+        return textLineMatrix;
+    }
+    /**
+     * @param value The textLineMatrix to set.
+     */
+    public void setTextLineMatrix(Matrix value)
+    {
+        textLineMatrix = value;
+    }
+    /**
+     * @return Returns the textMatrix.
+     */
+    public Matrix getTextMatrix()
+    {
+        return textMatrix;
+    }
+    /**
+     * @param value The textMatrix to set.
+     */
+    public void setTextMatrix(Matrix value)
+    {
+        textMatrix = value;
+    }
+    /**
+     * @return Returns the resources.
+     */
+    public PDResources getResources()
+    {
+        return streamResourcesStack.peek().resources;
+    }
+
+    /**
+     * Get the current page that is being processed.
+     *
+     * @return The page being processed.
+     */
+    public PDPage getCurrentPage()
+    {
+        return page;
+    }
+    
+    /** 
+     * Get the total number of valid characters in the doc 
+     * that could be decoded in processEncodedText(). 
+     * @return The number of valid characters. 
+     */
+    public int getValidCharCnt()
+    {
+        return validCharCnt;
+    }
+
+    /**
+     * Get the total number of characters in the doc
+     * (including ones that could not be mapped).  
+     * @return The number of characters. 
+     */
+    public int getTotalCharCnt()
+    {
+        return totalCharCnt;
+    }
+    
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFText2HTML.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFText2HTML.java
new file mode 100644 (file)
index 0000000..4dd60a4
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * Wrap stripped text in simple HTML, trying to form HTML paragraphs. Paragraphs
+ * broken by pages, columns, or figures are not mended.
+ * 
+ * 
+ * @author jjb - http://www.johnjbarton.com
+ * @version $Revision: 1.3 $
+ */
+public class PDFText2HTML extends PDFTextStripper 
+{
+    private static final int INITIAL_PDF_TO_HTML_BYTES = 8192;
+
+    private boolean onFirstPage = true;
+
+    /**
+     * Constructor.
+     * @param encoding The encoding to be used
+     * @throws IOException If there is an error during initialization.
+     */
+    public PDFText2HTML(String encoding) throws IOException 
+    {
+        super(encoding);
+        setLineSeparator(systemLineSeparator);
+        setParagraphStart("<p>");
+        setParagraphEnd("</p>"+systemLineSeparator);
+        setPageStart("<div style=\"page-break-before:always; page-break-after:always\">");
+        setPageEnd("</div>"+systemLineSeparator);
+        setArticleStart(systemLineSeparator);
+        setArticleEnd(systemLineSeparator);
+    }
+
+    /**
+     * Write the header to the output document. Now also writes the tag defining
+     * the character encoding.
+     * 
+     * @throws IOException
+     *             If there is a problem writing out the header to the document.
+     */
+    protected void writeHeader() throws IOException 
+    {
+        StringBuffer buf = new StringBuffer(INITIAL_PDF_TO_HTML_BYTES);
+        buf.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"" + "\n" 
+                + "\"http://www.w3.org/TR/html4/loose.dtd\">\n");
+        buf.append("<html><head>");
+        buf.append("<title>" + escape(getTitle()) + "</title>\n");
+        if(outputEncoding != null)
+        {
+            buf.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" 
+                    + this.outputEncoding + "\">\n");
+        }
+        buf.append("</head>\n");
+        buf.append("<body>\n");
+        super.writeString(buf.toString());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void writePage() throws IOException 
+    {
+        if (onFirstPage) 
+        {
+            writeHeader();
+            onFirstPage = false;
+        }
+        super.writePage();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void endDocument(PDDocument pdf) throws IOException 
+    {
+        super.writeString("</body></html>");
+    }
+
+    /**
+     * This method will attempt to guess the title of the document using
+     * either the document properties or the first lines of text.
+     * 
+     * @return returns the title.
+     */
+    protected String getTitle() 
+    {
+        String titleGuess = document.getDocumentInformation().getTitle();
+        if(titleGuess != null && titleGuess.length() > 0)
+        {
+            return titleGuess;
+        }
+        else 
+        {
+            Iterator<List<TextPosition>> textIter = getCharactersByArticle().iterator();
+            float lastFontSize = -1.0f;
+
+            StringBuffer titleText = new StringBuffer();
+            while (textIter.hasNext()) 
+            {
+                Iterator<TextPosition> textByArticle = textIter.next().iterator();
+                while (textByArticle.hasNext()) 
+                {
+                    TextPosition position = textByArticle.next();
+
+                    float currentFontSize = position.getFontSize();
+                    //If we're past 64 chars we will assume that we're past the title
+                    //64 is arbitrary 
+                    if (currentFontSize != lastFontSize || titleText.length() > 64) 
+                    {
+                        if (titleText.length() > 0) 
+                        {
+                            return titleText.toString();
+                        }
+                        lastFontSize = currentFontSize;
+                    }
+                    if (currentFontSize > 13.0f) 
+                    { // most body text is 12pt
+                        titleText.append(position.getCharacter());
+                    }
+                }
+            }
+        }
+        return "";
+    }
+
+
+    /**
+     * Write out the article separator (div tag) with proper text direction
+     * information.
+     * 
+     * @param isltr true if direction of text is left to right
+     * @throws IOException
+     *             If there is an error writing to the stream.
+     */
+    protected void startArticle(boolean isltr) throws IOException 
+    {
+        if (isltr) 
+        {
+            super.writeString("<div>");
+        } 
+        else 
+        {
+            super.writeString("<div dir=\"RTL\">");
+        }
+    }
+
+    /**
+     * Write out the article separator.
+     * 
+     * @throws IOException
+     *             If there is an error writing to the stream.
+     */
+    protected void endArticle() throws IOException 
+    {
+        super.endArticle();
+        super.writeString("</div>");
+    }
+
+    /**
+     * Write a string to the output stream and escape some HTML characters.
+     *
+     * @param chars String to be written to the stream
+     * @throws IOException
+     *             If there is an error writing to the stream.
+     */
+    protected void writeString(String chars) throws IOException 
+    {
+        super.writeString(escape(chars));
+    }
+    
+    /**
+     * Escape some HTML characters.
+     *
+     * @param chars String to be escaped
+     * @return returns escaped String.
+     */
+    private String escape(String chars)
+    {
+       StringBuilder builder = new StringBuilder(chars.length());
+        for (int i = 0; i < chars.length(); i++) 
+        {
+            char c = chars.charAt(i);
+            // write non-ASCII as named entities
+            if ((c < 32) || (c > 126)) 
+            {
+                int charAsInt = c;
+                builder.append("&#").append(charAsInt).append(";");
+            } 
+            else 
+            {
+                switch (c) 
+                {
+                case 34:
+                    builder.append("&quot;");
+                    break;
+                case 38:
+                    builder.append("&amp;");
+                    break;
+                case 60:
+                    builder.append("&lt;");
+                    break;
+                case 62:
+                    builder.append("&gt;");
+                    break;
+                default:
+                    builder.append(String.valueOf(c));
+                }
+            }
+        }
+        return builder.toString();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFTextStripper.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFTextStripper.java
new file mode 100644 (file)
index 0000000..4cb3ec5
--- /dev/null
@@ -0,0 +1,1882 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+import org.apache.pdfbox.pdmodel.interactive.pagenavigation.PDThreadBead;
+import org.apache.pdfbox.util.TextPosition;
+
+
+/**
+ * This class will take a pdf document and strip out all of the text and ignore the
+ * formatting and such.  Please note; it is up to clients of this class to verify that
+ * a specific user has the correct permissions to extract text from the
+ * PDF document.
+ * 
+ * The basic flow of this process is that we get a document and use a series of 
+ * processXXX() functions that work on smaller and smaller chunks of the page.  
+ * Eventually, we fully process each page and then print it. 
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.70 $
+ */
+public class PDFTextStripper extends PDFStreamEngine
+{
+
+    private static final String thisClassName = PDFTextStripper.class.getSimpleName().toLowerCase();
+
+    private static float DEFAULT_INDENT_THRESHOLD = 2.0f;
+    private static float DEFAULT_DROP_THRESHOLD = 2.5f;
+
+    //enable the ability to set the default indent/drop thresholds
+    //with -D system properties:
+    //    pdftextstripper.indent
+    //    pdftextstripper.drop
+    static
+    {
+        String prop = thisClassName+".indent";
+        String s = System.getProperty(prop);
+        if(s!=null && s.length()>0)
+        {
+            try
+            {
+                float f = Float.parseFloat(s);
+                DEFAULT_INDENT_THRESHOLD = f;
+            }
+            catch(NumberFormatException nfe)
+            {
+                        //ignore and use default
+            }
+        }
+        prop = thisClassName+".drop";
+        s = System.getProperty(prop);
+        if(s!=null && s.length()>0)
+        {
+            try
+            {
+                float f = Float.parseFloat(s);
+                DEFAULT_DROP_THRESHOLD = f;
+            }
+            catch(NumberFormatException nfe){
+                        //ignore and use default
+            }
+        }
+    }
+
+    /**
+     * The platforms line separator.
+     */
+    protected final String systemLineSeparator = System.getProperty("line.separator"); 
+
+    private String lineSeparator = systemLineSeparator;
+    private String pageSeparator = systemLineSeparator;
+    private String wordSeparator = " ";
+    private String paragraphStart = "";
+    private String paragraphEnd = "";
+    private String pageStart = "";
+    private String pageEnd = pageSeparator;
+    private String articleStart = "";
+    private String articleEnd = "";
+
+
+    private int currentPageNo = 0;
+    private int startPage = 1;
+    private int endPage = Integer.MAX_VALUE;
+    private PDOutlineItem startBookmark = null;
+    private int startBookmarkPageNumber = -1;
+    private PDOutlineItem endBookmark = null;
+    private int endBookmarkPageNumber = -1;
+    private boolean suppressDuplicateOverlappingText = true;
+    private boolean shouldSeparateByBeads = true;
+    private boolean sortByPosition = false;
+    private boolean addMoreFormatting = false;
+    
+    private float indentThreshold = DEFAULT_INDENT_THRESHOLD;
+    private float dropThreshold = DEFAULT_DROP_THRESHOLD;
+
+    // We will need to estimate where to add spaces.  
+    // These are used to help guess. 
+    private float spacingTolerance = .5f;
+    private float averageCharTolerance = .3f;
+
+    private List<PDThreadBead> pageArticles = null;
+    /**
+     * The charactersByArticle is used to extract text by article divisions.  For example
+     * a PDF that has two columns like a newspaper, we want to extract the first column and
+     * then the second column.  In this example the PDF would have 2 beads(or articles), one for
+     * each column.  The size of the charactersByArticle would be 5, because not all text on the
+     * screen will fall into one of the articles.  The five divisions are shown below
+     *
+     * Text before first article
+     * first article text
+     * text between first article and second article
+     * second article text
+     * text after second article
+     *
+     * Most PDFs won't have any beads, so charactersByArticle will contain a single entry.
+     */
+    protected Vector<List<TextPosition>> charactersByArticle = new Vector<List<TextPosition>>();
+
+    private Map<String, List<TextPosition>> characterListMapping = new HashMap<String, List<TextPosition>>();
+
+    /**
+     * encoding that text will be written in (or null).
+     */
+    protected String outputEncoding; 
+
+    /**
+     * The document to read.
+     */
+    protected PDDocument document;
+    /**
+     * The stream to write the output to.
+     */
+    protected Writer output;
+
+    /**
+     * The normalizer is used to remove text ligatures/presentation forms
+     * and to correct the direction of right to left text, such as Arabic and Hebrew.
+     */
+    private TextNormalize normalize = null;
+
+    /**
+     * Instantiate a new PDFTextStripper object. This object will load
+     * properties from PDFTextStripper.properties and will not do
+     * anything special to convert the text to a more encoding-specific
+     * output.
+     *
+     * @throws IOException If there is an error loading the properties.
+     */
+    public PDFTextStripper() throws IOException
+    {
+        super( ResourceLoader.loadProperties(
+                "org/apache/pdfbox/resources/PDFTextStripper.properties", true ) );
+        this.outputEncoding = null;
+        normalize = new TextNormalize(this.outputEncoding);
+    }
+
+
+    /**
+     * Instantiate a new PDFTextStripper object.  Loading all of the operator mappings
+     * from the properties object that is passed in.  Does not convert the text
+     * to more encoding-specific output.
+     *
+     * @param props The properties containing the mapping of operators to PDFOperator
+     * classes.
+     *
+     * @throws IOException If there is an error reading the properties.
+     */
+    public PDFTextStripper( Properties props ) throws IOException
+    {
+        super( props );
+        this.outputEncoding = null;
+        normalize = new TextNormalize(this.outputEncoding);
+    }
+    /**
+     * Instantiate a new PDFTextStripper object. This object will load
+     * properties from PDFTextStripper.properties and will apply
+     * encoding-specific conversions to the output text.
+     *
+     * @param encoding The encoding that the output will be written in.
+     * @throws IOException If there is an error reading the properties.
+     */
+    public PDFTextStripper( String encoding ) throws IOException
+    {
+        super( ResourceLoader.loadProperties(
+                "org/apache/pdfbox/resources/PDFTextStripper.properties", true ));
+        this.outputEncoding = encoding;
+        normalize = new TextNormalize(this.outputEncoding);
+    }
+
+    /**
+     * This will return the text of a document.  See writeText. <br />
+     * NOTE: The document must not be encrypted when coming into this method.
+     *
+     * @param doc The document to get the text from.
+     *
+     * @return The text of the PDF document.
+     *
+     * @throws IOException if the doc state is invalid or it is encrypted.
+     */
+    public String getText( PDDocument doc ) throws IOException
+    {
+        StringWriter outputStream = new StringWriter();
+        writeText( doc, outputStream );
+        return outputStream.toString();
+    }
+
+    /**
+     * @deprecated
+     * @see PDFTextStripper#getText( PDDocument )
+     * @param doc The document to extract the text from.
+     * @return The document text.
+     * @throws IOException If there is an error extracting the text.
+     */
+    public String getText( COSDocument doc ) throws IOException
+    {
+        return getText( new PDDocument( doc ) );
+    }
+
+    /**
+     * @deprecated
+     * @see PDFTextStripper#writeText( PDDocument, Writer )
+     * @param doc The document to extract the text.
+     * @param outputStream The stream to write the text to.
+     * @throws IOException If there is an error extracting the text.
+     */
+    public void writeText( COSDocument doc, Writer outputStream ) throws IOException
+    {
+        writeText( new PDDocument( doc ), outputStream );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void resetEngine()
+    {
+        super.resetEngine();
+        currentPageNo = 0;
+    }
+    
+    /**
+     * This will take a PDDocument and write the text of that document to the print writer.
+     *
+     * @param doc The document to get the data from.
+     * @param outputStream The location to put the text.
+     *
+     * @throws IOException If the doc is in an invalid state.
+     */
+    public void writeText( PDDocument doc, Writer outputStream ) throws IOException
+    {
+        resetEngine();
+        document = doc;
+        output = outputStream;
+        if (getAddMoreFormatting()) {
+            paragraphEnd = lineSeparator;
+            pageStart = lineSeparator;
+            articleStart = lineSeparator;
+            articleEnd = lineSeparator;
+        }
+        startDocument(document);
+
+        if( document.isEncrypted() )
+        {
+            // We are expecting non-encrypted documents here, but it is common
+            // for users to pass in a document that is encrypted with an empty
+            // password (such a document appears to not be encrypted by
+            // someone viewing the document, thus the confusion).  We will
+            // attempt to decrypt with the empty password to handle this case.
+            //
+            try
+            {
+                document.decrypt("");
+            }
+            catch (CryptographyException e)
+            {
+                throw new WrappedIOException("Error decrypting document, details: ", e);
+            }
+            catch (InvalidPasswordException e)
+            {
+                throw new WrappedIOException("Error: document is encrypted", e);
+            }
+        }
+
+        processPages( document.getDocumentCatalog().getAllPages() );
+        endDocument(document);
+    }
+
+    /**
+     * This will process all of the pages and the text that is in them.
+     *
+     * @param pages The pages object in the document.
+     *
+     * @throws IOException If there is an error parsing the text.
+     */
+    protected void processPages( List<COSObjectable> pages ) throws IOException
+    {
+        if( startBookmark != null )
+        {
+            startBookmarkPageNumber = getPageNumber( startBookmark, pages );
+        }
+
+        if( endBookmark != null )
+        {
+            endBookmarkPageNumber = getPageNumber( endBookmark, pages );
+        }
+
+        if( startBookmarkPageNumber == -1 && startBookmark != null &&
+                endBookmarkPageNumber == -1 && endBookmark != null &&
+                startBookmark.getCOSObject() == endBookmark.getCOSObject() )
+        {
+            //this is a special case where both the start and end bookmark
+            //are the same but point to nothing.  In this case
+            //we will not extract any text.
+            startBookmarkPageNumber = 0;
+            endBookmarkPageNumber = 0;
+        }
+
+
+        Iterator<COSObjectable> pageIter = pages.iterator();
+        while( pageIter.hasNext() )
+        {
+            PDPage nextPage = (PDPage)pageIter.next();
+            PDStream contentStream = nextPage.getContents();
+            currentPageNo++;
+            if( contentStream != null )
+            {
+                COSStream contents = contentStream.getStream();
+                processPage( nextPage, contents );
+            }
+        }
+    }
+
+    private int getPageNumber( PDOutlineItem bookmark, List<COSObjectable> allPages ) throws IOException
+    {
+        int pageNumber = -1;
+        PDPage page = bookmark.findDestinationPage( document );
+        if( page != null )
+        {
+            pageNumber = allPages.indexOf( page )+1;//use one based indexing
+        }
+        return pageNumber;
+    }
+
+    /**
+     * This method is available for subclasses of this class.  It will be called before processing
+     * of the document start.
+     *
+     * @param pdf The PDF document that is being processed.
+     * @throws IOException If an IO error occurs.
+     */
+    protected void startDocument(PDDocument pdf) throws IOException
+    {
+        // no default implementation, but available for subclasses
+    }
+
+    /**
+     * This method is available for subclasses of this class.  It will be called after processing
+     * of the document finishes.
+     *
+     * @param pdf The PDF document that is being processed.
+     * @throws IOException If an IO error occurs.
+     */
+    protected void endDocument(PDDocument pdf ) throws IOException
+    {
+        // no default implementation, but available for subclasses
+    }
+
+    /**
+     * This will process the contents of a page.
+     *
+     * @param page The page to process.
+     * @param content The contents of the page.
+     *
+     * @throws IOException If there is an error processing the page.
+     */
+    protected void processPage( PDPage page, COSStream content ) throws IOException
+    {
+        if( currentPageNo >= startPage && currentPageNo <= endPage &&
+                (startBookmarkPageNumber == -1 || currentPageNo >= startBookmarkPageNumber ) &&
+                (endBookmarkPageNumber == -1 || currentPageNo <= endBookmarkPageNumber ))
+        {
+            startPage( page );
+            pageArticles = page.getThreadBeads();
+            int numberOfArticleSections = 1 + pageArticles.size() * 2;
+            if( !shouldSeparateByBeads )
+            {
+                numberOfArticleSections = 1;
+            }
+            int originalSize = charactersByArticle.size();
+            charactersByArticle.setSize( numberOfArticleSections );
+            for( int i=0; i<numberOfArticleSections; i++ )
+            {
+                if( numberOfArticleSections < originalSize )
+                {
+                    ((List<TextPosition>)charactersByArticle.get( i )).clear();
+                }
+                else
+                {
+                    charactersByArticle.set( i, new ArrayList<TextPosition>() );
+                }
+            }
+
+            characterListMapping.clear();
+            processStream( page, page.findResources(), content );
+            writePage();
+            endPage( page );
+        }
+
+    }
+
+    /**
+     * Start a new article, which is typically defined as a column
+     * on a single page (also referred to as a bead).  This assumes
+     * that the primary direction of text is left to right.  
+     * Default implementation is to do nothing.  Subclasses
+     * may provide additional information.
+     *
+     * @throws IOException If there is any error writing to the stream.
+     */
+    protected void startArticle() throws IOException
+    {
+        startArticle(true);
+    }
+
+    /**
+     * Start a new article, which is typically defined as a column
+     * on a single page (also referred to as a bead).  
+     * Default implementation is to do nothing.  Subclasses
+     * may provide additional information.
+     *
+     * @param isltr true if primary direction of text is left to right.
+     * @throws IOException If there is any error writing to the stream.
+     */
+    protected void startArticle(boolean isltr) throws IOException
+    {
+        output.write(getArticleStart());
+    }
+
+    /**
+     * End an article.  Default implementation is to do nothing.  Subclasses
+     * may provide additional information.
+     *
+     * @throws IOException If there is any error writing to the stream.
+     */
+    protected void endArticle() throws IOException
+    {
+        output.write(getArticleEnd());
+    }
+
+    /**
+     * Start a new page.  Default implementation is to do nothing.  Subclasses
+     * may provide additional information.
+     *
+     * @param page The page we are about to process.
+     *
+     * @throws IOException If there is any error writing to the stream.
+     */
+    protected void startPage( PDPage page ) throws IOException
+    {
+        //default is to do nothing.
+    }
+
+    /**
+     * End a page.  Default implementation is to do nothing.  Subclasses
+     * may provide additional information.
+     *
+     * @param page The page we are about to process.
+     *
+     * @throws IOException If there is any error writing to the stream.
+     */
+    protected void endPage( PDPage page ) throws IOException
+    {
+        //default is to do nothing
+    }
+
+    private static final float ENDOFLASTTEXTX_RESET_VALUE = -1;
+    private static final float MAXYFORLINE_RESET_VALUE = -Float.MAX_VALUE;
+    private static final float EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE = -Float.MAX_VALUE;
+    private static final float MAXHEIGHTFORLINE_RESET_VALUE = -1;
+    private static final float MINYTOPFORLINE_RESET_VALUE = Float.MAX_VALUE;
+    private static final float LASTWORDSPACING_RESET_VALUE = -1;
+
+    /**
+     * This will print the text of the processed page to "output".
+     * It will estimate, based on the coordinates of the text, where
+     * newlines and word spacings should be placed. The text will be
+     * sorted only if that feature was enabled. 
+     *
+     * @throws IOException If there is an error writing the text.
+     */
+    protected void writePage() throws IOException
+    {
+        float maxYForLine = MAXYFORLINE_RESET_VALUE;
+        float minYTopForLine = MINYTOPFORLINE_RESET_VALUE;
+        float endOfLastTextX = ENDOFLASTTEXTX_RESET_VALUE;
+        float lastWordSpacing = LASTWORDSPACING_RESET_VALUE;
+        float maxHeightForLine = MAXHEIGHTFORLINE_RESET_VALUE;
+        PositionWrapper lastPosition = null;
+        PositionWrapper lastLineStartPosition = null;
+
+        boolean startOfPage = true;//flag to indicate start of page
+        boolean startOfArticle = true;
+        if(charactersByArticle.size() > 0) { 
+            writePageStart();
+        }
+
+        for( int i = 0; i < charactersByArticle.size(); i++)
+        {
+            List<TextPosition> textList = charactersByArticle.get( i );
+            if( getSortByPosition() )
+            {
+                TextPositionComparator comparator = new TextPositionComparator();
+                Collections.sort( textList, comparator );
+            }
+
+            Iterator<TextPosition> textIter = textList.iterator();
+
+            /* Before we can display the text, we need to do some normalizing.
+             * Arabic and Hebrew text is right to left and is typically stored
+             * in its logical format, which means that the rightmost character is
+             * stored first, followed by the second character from the right etc.
+             * However, PDF stores the text in presentation form, which is left to
+             * right.  We need to do some normalization to convert the PDF data to
+             * the proper logical output format.
+             *
+             * Note that if we did not sort the text, then the output of reversing the
+             * text is undefined and can sometimes produce worse output then not trying
+             * to reverse the order.  Sorting should be done for these languages.
+             * */
+
+            /* First step is to determine if we have any right to left text, and
+             * if so, is it dominant. */
+            int ltrCnt = 0;
+            int rtlCnt = 0;
+
+            while( textIter.hasNext() )
+            {
+                TextPosition position = (TextPosition)textIter.next();
+                String stringValue = position.getCharacter();
+                for (int a = 0; a < stringValue.length(); a++)
+                {
+                    byte dir = Character.getDirectionality(stringValue.charAt(a));
+                    if ((dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT ) ||
+                            (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING) ||
+                            (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE ))
+                    {
+                        ltrCnt++;
+                    }
+                    else if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT ) ||
+                            (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) ||
+                            (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING) ||
+                            (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE ))
+                    {
+                        rtlCnt++;
+                    }
+                }
+            }
+
+            // choose the dominant direction
+            boolean isRtlDominant = rtlCnt > ltrCnt;
+
+            startArticle(!isRtlDominant);
+            startOfArticle = true;
+            // we will later use this to skip reordering
+            boolean hasRtl = rtlCnt > 0;
+
+            /* Now cycle through to print the text.
+             * We queue up a line at a time before we print so that we can convert
+             * the line from presentation form to logical form (if needed). 
+             */
+            List<TextPosition> line = new ArrayList<TextPosition>();
+
+            textIter = textList.iterator();    // start from the beginning again
+            /* PDF files don't always store spaces. We will need to guess where we should add
+             * spaces based on the distances between TextPositions. Historically, this was done
+             * based on the size of the space character provided by the font. In general, this worked
+             * but there were cases where it did not work. Calculating the average character width
+             * and using that as a metric works better in some cases but fails in some cases where the
+             * spacing worked. So we use both. NOTE: Adobe reader also fails on some of these examples.
+             */
+            //Keeps track of the previous average character width
+            float previousAveCharWidth = -1;
+            while( textIter.hasNext() )
+            {
+                TextPosition position = (TextPosition)textIter.next();
+                PositionWrapper current = new PositionWrapper(position);
+                String characterValue = position.getCharacter();
+
+                //Resets the average character width when we see a change in font
+                // or a change in the font size
+                if(lastPosition != null && ((position.getFont() != lastPosition.getTextPosition().getFont())
+                        || (position.getFontSize() != lastPosition.getTextPosition().getFontSize())))
+                {
+                    previousAveCharWidth = -1;
+                }
+
+                float positionX;
+                float positionY;
+                float positionWidth;
+                float positionHeight;
+
+                /* If we are sorting, then we need to use the text direction
+                 * adjusted coordinates, because they were used in the sorting. */
+                if (getSortByPosition())
+                {
+                    positionX = position.getXDirAdj();
+                    positionY = position.getYDirAdj();
+                    positionWidth = position.getWidthDirAdj();
+                    positionHeight = position.getHeightDir();
+                }
+                else
+                {
+                    positionX = position.getX();
+                    positionY = position.getY();
+                    positionWidth = position.getWidth();
+                    positionHeight = position.getHeight();
+                }
+
+                //The current amount of characters in a word
+                int wordCharCount = position.getIndividualWidths().length;
+
+                /* Estimate the expected width of the space based on the
+                 * space character with some margin. */
+                float wordSpacing = position.getWidthOfSpace();
+                float deltaSpace = 0;
+                if ((wordSpacing == 0) || (wordSpacing == Float.NaN))
+                {
+                    deltaSpace = Float.MAX_VALUE;
+                }
+                else
+                {
+                    if( lastWordSpacing < 0 )
+                    {
+                        deltaSpace = (wordSpacing * getSpacingTolerance());
+                    }
+                    else
+                    {
+                        deltaSpace = (((wordSpacing+lastWordSpacing)/2f)* getSpacingTolerance());
+                    }
+                }
+
+                /* Estimate the expected width of the space based on the
+                 * average character width with some margin. This calculation does not
+                 * make a true average (average of averages) but we found that it gave the
+                 * best results after numerous experiments. Based on experiments we also found that
+                 * .3 worked well. */
+                float averageCharWidth = -1;
+                if(previousAveCharWidth < 0)
+                {
+                    averageCharWidth = (positionWidth/wordCharCount);
+                }
+                else
+                {
+                    averageCharWidth = (previousAveCharWidth + (positionWidth/wordCharCount))/2f;
+                }
+                float deltaCharWidth = (averageCharWidth * getAverageCharTolerance());
+
+                //Compares the values obtained by the average method and the wordSpacing method and picks
+                //the smaller number.
+                float expectedStartOfNextWordX = EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE;
+                if(endOfLastTextX != ENDOFLASTTEXTX_RESET_VALUE)
+                {
+                    if(deltaCharWidth > deltaSpace)
+                    {
+                        expectedStartOfNextWordX = endOfLastTextX + deltaSpace;
+                    }
+                    else
+                    {
+                        expectedStartOfNextWordX = endOfLastTextX + deltaCharWidth;
+                    }
+                }
+
+                if( lastPosition != null )
+                {
+                    if(startOfArticle){
+                        lastPosition.setArticleStart();
+                        startOfArticle = false;
+                    }
+                    // RDD - Here we determine whether this text object is on the current
+                    // line.  We use the lastBaselineFontSize to handle the superscript
+                    // case, and the size of the current font to handle the subscript case.
+                    // Text must overlap with the last rendered baseline text by at least
+                    // a small amount in order to be considered as being on the same line.
+
+                    /* XXX BC: In theory, this check should really check if the next char is in full range
+                     * seen in this line. This is what I tried to do with minYTopForLine, but this caused a lot
+                     * of regression test failures.  So, I'm leaving it be for now. */
+                    if(!overlap(positionY, positionHeight, maxYForLine, maxHeightForLine))
+                    {
+                        writeLine(normalize(line,isRtlDominant,hasRtl),isRtlDominant);
+                        line.clear();
+
+                        lastLineStartPosition = handleLineSeparation(current, lastPosition, lastLineStartPosition, maxHeightForLine);
+
+                        endOfLastTextX = ENDOFLASTTEXTX_RESET_VALUE;
+                        expectedStartOfNextWordX = EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE;
+                        maxYForLine = MAXYFORLINE_RESET_VALUE;
+                        maxHeightForLine = MAXHEIGHTFORLINE_RESET_VALUE;
+                        minYTopForLine = MINYTOPFORLINE_RESET_VALUE;
+                    }
+
+                    //Test if our TextPosition starts after a new word would be expected to start.
+                    if (expectedStartOfNextWordX != EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE && expectedStartOfNextWordX < positionX &&
+                            //only bother adding a space if the last character was not a space
+                             lastPosition.getTextPosition().getCharacter() != null &&
+                            !lastPosition.getTextPosition().getCharacter().endsWith( " " ) )
+                    {
+                        line.add(WordSeparator.getSeparator());
+                    }
+                }
+
+                if (positionY >= maxYForLine)
+                {
+                    maxYForLine = positionY;
+                }
+
+                // RDD - endX is what PDF considers to be the x coordinate of the
+                // end position of the text.  We use it in computing our metrics below.
+                endOfLastTextX = positionX + positionWidth;
+
+                // add it to the list
+                if (characterValue != null)
+                {
+                    if(startOfPage && lastPosition==null){
+                        writeParagraphStart();//not sure this is correct for RTL?
+                    }
+                    line.add(position);
+                }
+                maxHeightForLine = Math.max( maxHeightForLine, positionHeight );
+                minYTopForLine = Math.min(minYTopForLine,positionY - positionHeight);
+                lastPosition = current;
+                if(startOfPage){
+                    lastPosition.setParagraphStart();
+                    lastPosition.setLineStart();
+                    lastLineStartPosition = lastPosition;
+                    startOfPage=false;
+                }
+                lastWordSpacing = wordSpacing;
+                previousAveCharWidth = averageCharWidth;
+            }
+
+            // print the final line
+            if (line.size() > 0)
+            {
+                writeLine(normalize(line,isRtlDominant,hasRtl),isRtlDominant);
+                writeParagraphEnd();
+            }
+
+            endArticle();
+        }
+        writePageEnd();
+    }
+
+    private boolean overlap( float y1, float height1, float y2, float height2 )
+    {
+        return within( y1, y2, .1f) || (y2 <= y1 && y2 >= y1-height1) ||
+        (y1 <= y2 && y1 >= y2-height2);
+    }
+
+    /**
+     * Write the page separator value to the output stream.
+     * @throws IOException
+     *             If there is a problem writing out the pageseparator to the document.
+     */
+    protected void writePageSeperator() throws IOException
+    {
+        // RDD - newline at end of flush - required for end of page (so that the top
+        // of the next page starts on its own line.
+        //
+        output.write(getPageSeparator());
+        output.flush();
+    }
+
+    /**
+     * Write the line separator value to the output stream.
+     * @throws IOException
+     *             If there is a problem writing out the lineseparator to the document.
+     */
+    protected void writeLineSeparator( ) throws IOException
+    {
+        output.write(getLineSeparator());
+    }
+
+
+    /**
+     * Write the word separator value to the output stream.
+     * @throws IOException
+     *             If there is a problem writing out the wordseparator to the document.
+     */
+    protected void writeWordSeparator() throws IOException
+    {
+        output.write(getWordSeparator());
+    }
+
+    /**
+     * Write the string in TextPosition to the output stream.
+     *
+     * @param text The text to write to the stream.
+     * @throws IOException If there is an error when writing the text.
+     */
+    protected void writeCharacters( TextPosition text ) throws IOException
+    {
+        output.write( text.getCharacter() );
+    }
+
+    /**
+     * Write a Java string to the output stream.
+     *
+     * @param text The text to write to the stream.
+     * @throws IOException If there is an error when writing the text.
+     */
+    protected void writeString( String text ) throws IOException
+    {
+        output.write( text );
+    }
+
+    /**
+     * This will determine of two floating point numbers are within a specified variance.
+     *
+     * @param first The first number to compare to.
+     * @param second The second number to compare to.
+     * @param variance The allowed variance.
+     */
+    private boolean within( float first, float second, float variance )
+    {
+        return second < first + variance && second > first - variance;
+    }
+
+    /**
+     * This will process a TextPosition object and add the
+     * text to the list of characters on a page.  It takes care of
+     * overlapping text.
+     *
+     * @param text The text to process.
+     */
+    protected void processTextPosition( TextPosition text )
+    {
+        boolean showCharacter = true;
+        if( suppressDuplicateOverlappingText )
+        {
+            showCharacter = false;
+            String textCharacter = text.getCharacter();
+            float textX = text.getX();
+            float textY = text.getY();
+            List<TextPosition> sameTextCharacters = (List<TextPosition>)characterListMapping.get( textCharacter );
+            if( sameTextCharacters == null )
+            {
+                sameTextCharacters = new ArrayList<TextPosition>();
+                characterListMapping.put( textCharacter, sameTextCharacters );
+            }
+
+            // RDD - Here we compute the value that represents the end of the rendered
+            // text.  This value is used to determine whether subsequent text rendered
+            // on the same line overwrites the current text.
+            //
+            // We subtract any positive padding to handle cases where extreme amounts
+            // of padding are applied, then backed off (not sure why this is done, but there
+            // are cases where the padding is on the order of 10x the character width, and
+            // the TJ just backs up to compensate after each character).  Also, we subtract
+            // an amount to allow for kerning (a percentage of the width of the last
+            // character).
+            //
+            boolean suppressCharacter = false;
+            float tolerance = (text.getWidth()/textCharacter.length())/3.0f;
+            for( int i=0; i<sameTextCharacters.size() && textCharacter != null; i++ )
+            {
+                TextPosition character = sameTextCharacters.get( i );
+                String charCharacter = character.getCharacter();
+                float charX = character.getX();
+                float charY = character.getY();
+                //only want to suppress
+
+                if( charCharacter != null &&
+                        //charCharacter.equals( textCharacter ) &&
+                        within( charX, textX, tolerance ) &&
+                        within( charY,
+                                textY,
+                                tolerance ) )
+                {
+                    suppressCharacter = true;
+                }
+            }
+            if( !suppressCharacter )
+            {
+                sameTextCharacters.add( text );
+                showCharacter = true;
+            }
+        }
+
+        if( showCharacter )
+        {
+            //if we are showing the character then we need to determine which
+            //article it belongs to.
+            int foundArticleDivisionIndex = -1;
+            int notFoundButFirstLeftAndAboveArticleDivisionIndex = -1;
+            int notFoundButFirstLeftArticleDivisionIndex = -1;
+            int notFoundButFirstAboveArticleDivisionIndex = -1;
+            float x = text.getX();
+            float y = text.getY();
+            if( shouldSeparateByBeads )
+            {
+                for( int i=0; i<pageArticles.size() && foundArticleDivisionIndex == -1; i++ )
+                {
+                    PDThreadBead bead = (PDThreadBead)pageArticles.get( i );
+                    if( bead != null )
+                    {
+                        PDRectangle rect = bead.getRectangle();
+                        if( rect.contains( x, y ) )
+                        {
+                            foundArticleDivisionIndex = i*2+1;
+                        }
+                        else if( (x < rect.getLowerLeftX() ||
+                                y < rect.getUpperRightY()) &&
+                                notFoundButFirstLeftAndAboveArticleDivisionIndex == -1)
+                        {
+                            notFoundButFirstLeftAndAboveArticleDivisionIndex = i*2;
+                        }
+                        else if( x < rect.getLowerLeftX() &&
+                                notFoundButFirstLeftArticleDivisionIndex == -1)
+                        {
+                            notFoundButFirstLeftArticleDivisionIndex = i*2;
+                        }
+                        else if( y < rect.getUpperRightY() &&
+                                notFoundButFirstAboveArticleDivisionIndex == -1)
+                        {
+                            notFoundButFirstAboveArticleDivisionIndex = i*2;
+                        }
+                    }
+                    else
+                    {
+                        foundArticleDivisionIndex = 0;
+                    }
+                }
+            }
+            else
+            {
+                foundArticleDivisionIndex = 0;
+            }
+            int articleDivisionIndex = -1;
+            if( foundArticleDivisionIndex != -1 )
+            {
+                articleDivisionIndex = foundArticleDivisionIndex;
+            }
+            else if( notFoundButFirstLeftAndAboveArticleDivisionIndex != -1 )
+            {
+                articleDivisionIndex = notFoundButFirstLeftAndAboveArticleDivisionIndex;
+            }
+            else if( notFoundButFirstLeftArticleDivisionIndex != -1 )
+            {
+                articleDivisionIndex = notFoundButFirstLeftArticleDivisionIndex;
+            }
+            else if( notFoundButFirstAboveArticleDivisionIndex != -1 )
+            {
+                articleDivisionIndex = notFoundButFirstAboveArticleDivisionIndex;
+            }
+            else
+            {
+                articleDivisionIndex = charactersByArticle.size()-1;
+            }
+
+            List<TextPosition> textList = (List<TextPosition>) charactersByArticle.get( articleDivisionIndex );
+
+            /* In the wild, some PDF encoded documents put diacritics (accents on
+             * top of characters) into a separate Tj element.  When displaying them
+             * graphically, the two chunks get overlayed.  With text output though,
+             * we need to do the overlay. This code recombines the diacritic with
+             * its associated character if the two are consecutive.
+             */ 
+            if(textList.isEmpty())
+            {
+                textList.add(text);
+            }
+            else
+            {
+                /* test if we overlap the previous entry.  
+                 * Note that we are making an assumption that we need to only look back
+                 * one TextPosition to find what we are overlapping.  
+                 * This may not always be true. */
+                TextPosition previousTextPosition = (TextPosition)textList.get(textList.size()-1);
+                if(text.isDiacritic() && previousTextPosition.contains(text))
+                {
+                    previousTextPosition.mergeDiacritic(text, normalize);
+                }
+                /* If the previous TextPosition was the diacritic, merge it into this
+                 * one and remove it from the list. */
+                else if(previousTextPosition.isDiacritic() && text.contains(previousTextPosition))
+                {
+                    text.mergeDiacritic(previousTextPosition, normalize);
+                    textList.remove(textList.size()-1);
+                    textList.add(text);
+                }
+                else
+                {
+                    textList.add(text);
+                }
+            }
+        }
+    }
+
+    /**
+     * This is the page that the text extraction will start on.  The pages start
+     * at page 1.  For example in a 5 page PDF document, if the start page is 1
+     * then all pages will be extracted.  If the start page is 4 then pages 4 and 5
+     * will be extracted.  The default value is 1.
+     *
+     * @return Value of property startPage.
+     */
+    public int getStartPage()
+    {
+        return startPage;
+    }
+
+    /**
+     * This will set the first page to be extracted by this class.
+     *
+     * @param startPageValue New value of property startPage.
+     */
+    public void setStartPage(int startPageValue)
+    {
+        startPage = startPageValue;
+    }
+
+    /**
+     * This will get the last page that will be extracted.  This is inclusive,
+     * for example if a 5 page PDF an endPage value of 5 would extract the
+     * entire document, an end page of 2 would extract pages 1 and 2.  This defaults
+     * to Integer.MAX_VALUE such that all pages of the pdf will be extracted.
+     *
+     * @return Value of property endPage.
+     */
+    public int getEndPage()
+    {
+        return endPage;
+    }
+
+    /**
+     * This will set the last page to be extracted by this class.
+     *
+     * @param endPageValue New value of property endPage.
+     */
+    public void setEndPage(int endPageValue)
+    {
+        endPage = endPageValue;
+    }
+
+    /**
+     * Set the desired line separator for output text.  The line.separator
+     * system property is used if the line separator preference is not set
+     * explicitly using this method.
+     *
+     * @param separator The desired line separator string.
+     */
+    public void setLineSeparator(String separator)
+    {
+        lineSeparator = separator;
+    }
+
+    /**
+     * This will get the line separator.
+     *
+     * @return The desired line separator string.
+     */
+    public String getLineSeparator()
+    {
+        return lineSeparator;
+    }
+
+    /**
+     * Set the desired page separator for output text.  The line.separator
+     * system property is used if the page separator preference is not set
+     * explicitly using this method.
+     *
+     * @param separator The desired page separator string.
+     */
+    public void setPageSeparator(String separator)
+    {
+        pageSeparator = separator;
+    }
+
+    /**
+     * This will get the word separator.
+     *
+     * @return The desired word separator string.
+     */
+    public String getWordSeparator()
+    {
+        return wordSeparator;
+    }
+
+    /**
+     * Set the desired word separator for output text.  The PDFBox text extraction
+     * algorithm will output a space character if there is enough space between
+     * two words.  By default a space character is used.  If you need and accurate
+     * count of characters that are found in a PDF document then you might want to
+     * set the word separator to the empty string.
+     *
+     * @param separator The desired page separator string.
+     */
+    public void setWordSeparator(String separator)
+    {
+        wordSeparator = separator;
+    }
+
+    /**
+     * This will get the page separator.
+     *
+     * @return The page separator string.
+     */
+    public String getPageSeparator()
+    {
+        return pageSeparator;
+    }
+    /**
+     * @return Returns the suppressDuplicateOverlappingText.
+     */
+    public boolean getSuppressDuplicateOverlappingText()
+    {
+        return suppressDuplicateOverlappingText;
+    }
+
+    /**
+     * Get the current page number that is being processed.
+     *
+     * @return A 1 based number representing the current page.
+     */
+    protected int getCurrentPageNo()
+    {
+        return currentPageNo;
+    }
+
+    /**
+     * The output stream that is being written to.
+     *
+     * @return The stream that output is being written to.
+     */
+    protected Writer getOutput()
+    {
+        return output;
+    }
+
+    /**
+     * Character strings are grouped by articles.  It is quite common that there
+     * will only be a single article.  This returns a List that contains List objects,
+     * the inner lists will contain TextPosition objects.
+     *
+     * @return A double List of TextPositions for all text strings on the page.
+     */
+    protected Vector<List<TextPosition>> getCharactersByArticle()
+    {
+        return charactersByArticle;
+    }
+
+    /**
+     * By default the text stripper will attempt to remove text that overlapps each other.
+     * Word paints the same character several times in order to make it look bold.  By setting
+     * this to false all text will be extracted, which means that certain sections will be
+     * duplicated, but better performance will be noticed.
+     *
+     * @param suppressDuplicateOverlappingTextValue The suppressDuplicateOverlappingText to set.
+     */
+    public void setSuppressDuplicateOverlappingText(
+            boolean suppressDuplicateOverlappingTextValue)
+    {
+        this.suppressDuplicateOverlappingText = suppressDuplicateOverlappingTextValue;
+    }
+
+    /**
+     * This will tell if the text stripper should separate by beads.
+     *
+     * @return If the text will be grouped by beads.
+     */
+    public boolean getSeparateByBeads()
+    {
+        return shouldSeparateByBeads;
+    }
+
+    /**
+     * Set if the text stripper should group the text output by a list of beads.  The default value is true!
+     *
+     * @param aShouldSeparateByBeads The new grouping of beads.
+     */
+    public void setShouldSeparateByBeads(boolean aShouldSeparateByBeads)
+    {
+        this.shouldSeparateByBeads = aShouldSeparateByBeads;
+    }
+
+    /**
+     * Get the bookmark where text extraction should end, inclusive.  Default is null.
+     *
+     * @return The ending bookmark.
+     */
+    public PDOutlineItem getEndBookmark()
+    {
+        return endBookmark;
+    }
+
+    /**
+     * Set the bookmark where the text extraction should stop.
+     *
+     * @param aEndBookmark The ending bookmark.
+     */
+    public void setEndBookmark(PDOutlineItem aEndBookmark)
+    {
+        endBookmark = aEndBookmark;
+    }
+
+    /**
+     * Get the bookmark where text extraction should start, inclusive.  Default is null.
+     *
+     * @return The starting bookmark.
+     */
+    public PDOutlineItem getStartBookmark()
+    {
+        return startBookmark;
+    }
+
+    /**
+     * Set the bookmark where text extraction should start, inclusive.
+     *
+     * @param aStartBookmark The starting bookmark.
+     */
+    public void setStartBookmark(PDOutlineItem aStartBookmark)
+    {
+        startBookmark = aStartBookmark;
+    }
+
+    /**
+     * This will tell if the text stripper should add some more text formatting.
+     * @return true if some more text formatting will be added
+     */
+    public boolean getAddMoreFormatting()
+    {
+        return addMoreFormatting;
+    }
+    
+    /**
+     * There will some additional text formatting be added if addMoreFormatting
+     * is set to true. Default is false. 
+     * @param newAddMoreFormatting Tell PDFBox to add some more text formatting
+     */
+    public void setAddMoreFormatting(boolean newAddMoreFormatting)
+    {
+        addMoreFormatting = newAddMoreFormatting;
+    }
+
+    /**
+     * This will tell if the text stripper should sort the text tokens
+     * before writing to the stream.
+     *
+     * @return true If the text tokens will be sorted before being written.
+     */
+    public boolean getSortByPosition()
+    {
+        return sortByPosition;
+    }
+
+    /**
+     * The order of the text tokens in a PDF file may not be in the same
+     * as they appear visually on the screen.  For example, a PDF writer may
+     * write out all text by font, so all bold or larger text, then make a second
+     * pass and write out the normal text.<br/>
+     * The default is to <b>not</b> sort by position.<br/>
+     * <br/>
+     * A PDF writer could choose to write each character in a different order.  By
+     * default PDFBox does <b>not</b> sort the text tokens before processing them due to
+     * performance reasons.
+     *
+     * @param newSortByPosition Tell PDFBox to sort the text positions.
+     */
+    public void setSortByPosition(boolean newSortByPosition)
+    {
+        sortByPosition = newSortByPosition;
+    }
+
+    /**
+     * Get the current space width-based tolerance value that is being used
+     * to estimate where spaces in text should be added.  Note that the
+     * default value for this has been determined from trial and error. 
+     * 
+     * @return The current tolerance / scaling factor
+     */
+    public float getSpacingTolerance() 
+    {
+        return spacingTolerance;
+    }
+
+    /**
+     * Set the space width-based tolerance value that is used
+     * to estimate where spaces in text should be added.  Note that the
+     * default value for this has been determined from trial and error.
+     * Setting this value larger will reduce the number of spaces added. 
+     * 
+     * @param spacingToleranceValue tolerance / scaling factor to use
+     */
+    public void setSpacingTolerance(float spacingToleranceValue)
+    {
+        this.spacingTolerance = spacingToleranceValue;
+    }
+
+    /**
+     * Get the current character width-based tolerance value that is being used
+     * to estimate where spaces in text should be added.  Note that the
+     * default value for this has been determined from trial and error.
+     * 
+     * @return The current tolerance / scaling factor
+     */
+    public float getAverageCharTolerance() 
+    {
+        return averageCharTolerance;
+    }
+
+    /**
+     * Set the character width-based tolerance value that is used
+     * to estimate where spaces in text should be added.  Note that the
+     * default value for this has been determined from trial and error.
+     * Setting this value larger will reduce the number of spaces added. 
+     * 
+     * @param averageCharToleranceValue average tolerance / scaling factor to use
+     */
+    public void setAverageCharTolerance(float averageCharToleranceValue) 
+    {
+        this.averageCharTolerance = averageCharToleranceValue;
+    }
+
+
+    /**
+     * returns the multiple of whitespace character widths
+     * for the current text which the current
+     * line start can be indented from the previous line start
+     * beyond which the current line start is considered
+     * to be a paragraph start.
+     * @return the number of whitespace character widths to use
+     * when detecting paragraph indents.
+     */
+    public float getIndentThreshold() 
+    {
+        return indentThreshold;
+    }
+
+    /**
+     * sets the multiple of whitespace character widths
+     * for the current text which the current
+     * line start can be indented from the previous line start
+     * beyond which the current line start is considered
+     * to be a paragraph start.  The default value is 2.0.
+     *
+     * @param indentThreshold the number of whitespace character widths to use
+     * when detecting paragraph indents.
+     */
+    public void setIndentThreshold(float indentThreshold) 
+    {
+        this.indentThreshold = indentThreshold;
+    }
+
+    /**
+     * the minimum whitespace, as a multiple
+     * of the max height of the current characters
+     * beyond which the current line start is considered
+     * to be a paragraph start.
+     * @return the character height multiple for
+     * max allowed whitespace between lines in
+     * the same paragraph.
+     */
+    public float getDropThreshold() 
+    {
+        return dropThreshold;
+    }
+
+    /**
+     * sets the minimum whitespace, as a multiple
+     * of the max height of the current characters
+     * beyond which the current line start is considered
+     * to be a paragraph start.  The default value is 2.5.
+     *
+     * @param dropThreshold the character height multiple for
+     * max allowed whitespace between lines in
+     * the same paragraph.
+     */
+    public void setDropThreshold(float dropThreshold) 
+    {
+        this.dropThreshold = dropThreshold;
+    }
+
+    /**
+     * Returns the string which will be used at the beginning of a paragraph.
+     * @return the paragraph start string
+     */
+    public String getParagraphStart()
+    {
+        return paragraphStart;
+    }
+
+    /**
+     * Sets the string which will be used at the beginning of a paragraph.
+     * @param s the paragraph start string
+     */
+    public void setParagraphStart(String s)
+    {
+        this.paragraphStart = s;
+    }
+
+    /**
+     * Returns the string which will be used at the end of a paragraph.
+     * @return the paragraph end string
+     */
+    public String getParagraphEnd()
+    {
+        return paragraphEnd;
+    }
+
+    /**
+     * Sets the string which will be used at the end of a paragraph.
+     * @param s the paragraph end string
+     */
+    public void setParagraphEnd(String s)
+    {
+        this.paragraphEnd = s;
+    }
+
+
+    /**
+     * Returns the string which will be used at the beginning of a page.
+     * @return the page start string
+     */
+    public String getPageStart() 
+    {
+        return pageStart;
+    }
+
+    /**
+     * Sets the string which will be used at the beginning of a page.
+     * @param s the page start string
+     */
+    public void setPageStart(String pageStart) 
+    {
+        this.pageStart = pageStart;
+    }
+
+    /**
+     * Returns the string which will be used at the end of a page.
+     * @return the page end string
+     */
+    public String getPageEnd() 
+    {
+        return pageEnd;
+    }
+
+    /**
+     * Sets the string which will be used at the end of a page.
+     * @param s the page end string
+     */
+    public void setPageEnd(String pageEnd) 
+    {
+        this.pageEnd = pageEnd;
+    }
+
+    /**
+     * Returns the string which will be used at the beginning of an article.
+     * @return the article start string
+     */
+    public String getArticleStart() {
+        return articleStart;
+    }
+
+    /**
+     * Sets the string which will be used at the beginning of an article.
+     * @param s the article start string
+     */
+    public void setArticleStart(String articleStart) {
+        this.articleStart = articleStart;
+    }
+
+    /**
+     * Returns the string which will be used at the end of an article.
+     * @return the article end string
+     */
+    public String getArticleEnd(){
+        return articleEnd;
+    }
+
+    /**
+     * Sets the string which will be used at the end of an article.
+     * @param s the article end string
+     */
+    public void setArticleEnd(String articleEnd){
+        this.articleEnd = articleEnd;
+    }
+
+
+    /**
+     * Reverse characters of a compound Arabic glyph.
+     * When getSortByPosition() is true, inspect the sequence encoded
+     * by one glyph. If the glyph encodes two or more Arabic characters,
+     * reverse these characters from a logical order to a visual order.
+     * This ensures that the bidirectional algorithm that runs later will
+     * convert them back to a logical order.
+     * 
+     * @param str a string obtained from font.encoding()
+     */
+    public String inspectFontEncoding(String str)
+    {
+        if (!sortByPosition || str == null || str.length() < 2)
+            return str;
+
+        for (int i = 0; i < str.length(); ++i)
+        {
+            if (Character.getDirectionality(str.charAt(i))
+                    != Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+                return str;
+        }
+
+        StringBuilder reversed = new StringBuilder(str.length());
+        for (int i = str.length() - 1; i >= 0; --i)
+        {
+            reversed.append(str.charAt(i));
+        }
+        return reversed.toString();
+    }
+
+    /**
+     * handles the line separator for a new line given
+     * the specified current and previous TextPositions.
+     * @param position the current text position
+     * @param lastPosition the previous text position
+     * @param lastLineStartPosition the last text position that followed a line
+     *        separator.
+     * @param maxHeightForLine max height for positions since lastLineStartPosition
+     * @throws IOException
+     */
+    protected PositionWrapper handleLineSeparation(PositionWrapper current,
+            PositionWrapper lastPosition, PositionWrapper lastLineStartPosition, float maxHeightForLine)
+            throws IOException {
+        current.setLineStart();
+        isParagraphSeparation(current, lastPosition, lastLineStartPosition, maxHeightForLine);
+        lastLineStartPosition = current;
+        if (current.isParagraphStart())  {
+            if(lastPosition.isArticleStart()) {
+                writeParagraphStart();
+            } else {
+                writeLineSeparator();
+                writeParagraphSeparator();
+            }
+        } else {
+            writeLineSeparator();
+        }
+        return lastLineStartPosition;
+    }
+    
+    /**
+     * tests the relationship between the last text position, the current text
+     * position and the last text position that followed a line separator to
+     * decide if the gap represents a paragraph separation. This should
+     * <i>only</i> be called for consecutive text positions that first pass the
+     * line separation test.
+     * <p>
+     * This base implementation tests to see if the lastLineStartPosition is
+     * null OR if the current vertical position has dropped below the last text
+     * vertical position by at least 2.5 times the current text height OR if the
+     * current horizontal position is indented by at least 2 times the current
+     * width of a space character.</p>
+     * <p>
+     * This also attempts to identify text that is indented under a hanging indent.</p>
+     * <p>
+     * This method sets the isParagraphStart and isHangingIndent flags on the current
+     * position object.</p>
+     *
+     * @param position the current text position.  This may have its isParagraphStart
+     * or isHangingIndent flags set upon return.
+     * @param lastPosition the previous text position (should not be null).
+     * @param lastLineStartPosition the last text position that followed a line
+     *            separator. May be null.
+     * @param maxHeightForLine max height for text positions since lasLineStartPosition.
+     */
+    protected void isParagraphSeparation(PositionWrapper position,  
+            PositionWrapper lastPosition, PositionWrapper lastLineStartPosition, float maxHeightForLine){
+        boolean result = false;
+        if(lastLineStartPosition == null) {
+            result = true;
+        }else{
+            float yGap = Math.abs(position.getTextPosition().getYDirAdj()-
+                    lastPosition.getTextPosition().getYDirAdj());
+            float xGap = (position.getTextPosition().getXDirAdj()-
+                    lastLineStartPosition.getTextPosition().getXDirAdj());//do we need to flip this for rtl?
+            if(yGap > (getDropThreshold()*maxHeightForLine)){
+                        result = true;
+            }else if(xGap > (getIndentThreshold()*position.getTextPosition().getWidthOfSpace())){
+                //text is indented, but try to screen for hanging indent
+                if(!lastLineStartPosition.isParagraphStart()){
+                     result = true;
+                }else{
+                     position.setHangingIndent();
+                }
+            }else if(xGap < -position.getTextPosition().getWidthOfSpace()){
+                //text is left of previous line. Was it a hanging indent?
+                if(!lastLineStartPosition.isParagraphStart()){
+                            result = true;
+                }
+            }else if(Math.abs(xGap) < (0.25 * position.getTextPosition().getWidth())){
+                //current horizontal position is within 1/4 a char of the last
+                //linestart.  We'll treat them as lined up.
+                if(lastLineStartPosition.isHangingIndent()){
+                    position.setHangingIndent();
+                }else if(lastLineStartPosition.isParagraphStart()){
+                    //check to see if the previous line looks like
+                    //any of a number of standard list item formats
+                    Pattern liPattern = matchListItemPattern(lastLineStartPosition);
+                    if(liPattern!=null){
+                        Pattern currentPattern = matchListItemPattern(position);
+                        if(liPattern == currentPattern){
+                                    result = true;
+                        }
+                    }
+               }
+           }
+        }
+        if(result){
+            position.setParagraphStart();
+        }
+    }
+
+    /**
+     * writes the paragraph separator string to the output.
+     * @throws IOException
+     */
+    protected void writeParagraphSeparator()throws IOException{
+        writeParagraphEnd();
+        writeParagraphStart();
+    }
+
+    /**
+     * Write something (if defined) at the start of a paragraph.
+     * @throws IOException if something went wrong
+     */
+    protected void writeParagraphStart() throws IOException{
+        output.write(getParagraphStart());
+    }
+
+    /**
+     * Write something (if defined) at the end of a paragraph.
+     * @throws IOException if something went wrong
+     */
+    protected void writeParagraphEnd() throws IOException{
+        output.write(getParagraphEnd());
+    }
+
+    /**
+     * Write something (if defined) at the start of a page.
+     * @throws IOException if something went wrong
+     */
+    protected void writePageStart()throws IOException{
+        output.write(getPageStart());
+    }
+
+    /**
+     * Write something (if defined) at the end of a page.
+     * @throws IOException if something went wrong
+     */
+    protected void writePageEnd()throws IOException{
+        output.write(getPageEnd());
+    }
+
+    /**
+     * returns the list item Pattern object that matches
+     * the text at the specified PositionWrapper or null
+     * if the text does not match such a pattern.  The list
+     * of Patterns tested against is given by the
+     * {@link #getListItemPatterns()} method.  To add to
+     * the list, simply override that method (if sub-classing)
+     * or explicitly supply your own list using
+     * {@link #setListItemPatterns(List)}.
+     * @param pw
+     * @return
+     */
+    protected Pattern matchListItemPattern(PositionWrapper pw) {
+        TextPosition tp = pw.getTextPosition();
+        String txt = tp.getCharacter();
+        Pattern p = matchPattern(txt,getListItemPatterns());
+        return p;
+    }
+
+    /**
+     * a list of regular expressions that match commonly used
+     * list item formats, i.e. bullets, numbers, letters,
+     * Roman numerals, etc.  Not meant to be
+     * comprehensive.
+     */
+    private static final String[] LIST_ITEM_EXPRESSIONS = {
+            "\\.",
+            "\\d+\\.",
+            "\\[\\d+\\]",
+            "\\d+\\)",
+            "[A-Z]\\.",
+            "[a-z]\\.",
+            "[A-Z]\\)",
+            "[a-z]\\)",
+            "[IVXL]+\\.",
+            "[ivxl]+\\.",
+
+    };
+
+    private List<Pattern> liPatterns = null;
+    /**
+     * use to supply a different set of regular expression
+     * patterns for matching list item starts.
+     *
+     * @param patterns
+     */
+    protected void setListItemPatterns(List<Pattern> patterns){
+            liPatterns = patterns;
+    }
+
+
+    /**
+     * returns a list of regular expression Patterns representing
+     * different common list item formats.  For example
+     * numbered items of form:
+     * <ol>
+     * <li>some text</li>
+     * <li>more text</li>
+     * </ol>
+     * or
+     * <ul>
+     * <li>some text</li>
+     * <li>more text</li>
+     * </ul>
+     * etc., all begin with some character pattern. The pattern "\\d+\." (matches "1.", "2.", ...)
+     * or "\[\\d+\]" (matches "[1]", "[2]", ...).
+     * <p>
+     * This method returns a list of such regular expression Patterns.
+     * @return a list of Pattern objects.
+     */
+    protected List<Pattern> getListItemPatterns(){
+        if(liPatterns == null){
+            liPatterns = new ArrayList<Pattern>();
+            for(String expression : LIST_ITEM_EXPRESSIONS){
+                Pattern p = Pattern.compile(expression);
+                liPatterns.add(p);
+            }
+        }
+        return liPatterns;
+    }
+
+    /**
+     * iterates over the specified list of Patterns until
+     * it finds one that matches the specified string.  Then
+     * returns the Pattern.
+     * <p>
+     * Order of the supplied list of patterns is important as
+     * most common patterns should come first.  Patterns
+     * should be strict in general, and all will be
+     * used with case sensitivity on.
+     * </p>
+     * @param s
+     * @param patterns
+     * @return
+     */
+    protected static final Pattern matchPattern(String s, List<Pattern> patterns){
+        Pattern matchedPattern = null;
+        for(Pattern p : patterns){
+            if(p.matcher(s).matches()){
+                return p;
+            }
+        }
+        return matchedPattern;
+    }
+
+    /**
+     * Write a list of string containing a whole line of a document.
+     * @param line a list with the words of the given line
+     * @param isRtlDominant determines if rtl or ltl is dominant
+     * @throws IOException if something went wrong
+     */
+    private void writeLine(List<String> line, boolean isRtlDominant)throws IOException{
+        int numberOfStrings = line.size();
+        if (isRtlDominant) {
+            for(int i=numberOfStrings-1; i>=0; i--){
+                if (i < numberOfStrings-1)
+                    writeWordSeparator();
+                writeString(line.get(i));
+            }
+        }
+        else {
+            for(int i=0; i<numberOfStrings; i++){
+                writeString(line.get(i));
+                if (!isRtlDominant && i < numberOfStrings-1)
+                    writeWordSeparator();
+            }
+        }
+    }
+
+    /**
+     * Normalize the given list of TextPositions.
+     * @param line list of TextPositions
+     * @param isRtlDominant determines if rtl or ltl is dominant 
+     * @param hasRtl determines if lines contains rtl formatted text(parts)
+     * @return a list of strings, one string for every word
+     */
+    private List<String> normalize(List<TextPosition> line, boolean isRtlDominant, boolean hasRtl){
+        LinkedList<String> normalized = new LinkedList<String>();
+        StringBuilder lineBuilder = new StringBuilder();
+        for(TextPosition text : line){
+            if (text instanceof WordSeparator) {
+                String lineStr = lineBuilder.toString();
+                if (hasRtl) {
+                    lineStr = normalize.makeLineLogicalOrder(lineStr,isRtlDominant);
+                }
+                lineStr = normalize.normalizePres(lineStr);
+                normalized.add(lineStr);
+                lineBuilder = new StringBuilder();
+            }
+            else {
+                lineBuilder.append(text.getCharacter());
+            }
+        }
+        if (lineBuilder.length() > 0) {
+            String lineStr = lineBuilder.toString();
+            if (hasRtl) {
+                lineStr = normalize.makeLineLogicalOrder(lineStr,isRtlDominant);
+            }
+            lineStr = normalize.normalizePres(lineStr);
+            normalized.add(lineStr);
+        }
+        return normalized;
+    }
+
+    /**
+     * internal marker class.  Used as a place holder in
+     * a line of TextPositions.
+     * @author ME21969
+     *
+     */
+    private static final class WordSeparator extends TextPosition{
+        private static final WordSeparator separator = new WordSeparator();
+        
+        private WordSeparator(){
+        }
+
+        public static final WordSeparator getSeparator(){
+            return separator;
+        }
+
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFTextStripperByArea.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PDFTextStripperByArea.java
new file mode 100644 (file)
index 0000000..00d1efd
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * 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.pdfbox.util;
+
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This will extract text from a specified region in the PDF.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDFTextStripperByArea extends PDFTextStripper
+{
+    private List<String> regions = new ArrayList<String>();
+    private Map<String,Rectangle2D> regionArea = new HashMap<String,Rectangle2D>();
+    private Map<String,Vector<ArrayList<TextPosition>>> regionCharacterList = 
+        new HashMap<String,Vector<ArrayList<TextPosition>>>();
+    private Map<String,StringWriter> regionText = new HashMap<String,StringWriter>();
+
+    /**
+     * Constructor.
+     * @throws IOException If there is an error loading properties.
+     */
+    public PDFTextStripperByArea() throws IOException
+    {
+        super();
+        setPageSeparator( "" );
+    }
+
+        
+    /**
+     * Instantiate a new PDFTextStripperArea object. Loading all of the operator
+     * mappings from the properties object that is passed in. Does not convert
+     * the text to more encoding-specific output.
+     * 
+     * @param props
+     *            The properties containing the mapping of operators to
+     *            PDFOperator classes.
+     * 
+     * @throws IOException
+     *             If there is an error reading the properties.
+     */
+    public PDFTextStripperByArea(Properties props) throws IOException
+    {
+        super(props);
+        setPageSeparator("");
+    }
+
+    /**
+     * Instantiate a new PDFTextStripperArea object. This object will load
+     * properties from PDFTextStripper.properties and will apply
+     * encoding-specific conversions to the output text.
+     * 
+     * @param encoding
+     *            The encoding that the output will be written in.
+     * @throws IOException
+     *             If there is an error reading the properties.
+     */
+    public PDFTextStripperByArea(String encoding) throws IOException
+    {
+        super(encoding);
+        setPageSeparator("");
+    }
+    
+   /**
+     * Add a new region to group text by.
+     *
+     * @param regionName The name of the region.
+     * @param rect The rectangle area to retrieve the text from.
+     */
+    public void addRegion( String regionName, Rectangle2D rect )
+    {
+        regions.add( regionName );
+        regionArea.put( regionName, rect );
+    }
+
+    /**
+     * Get the list of regions that have been setup.
+     *
+     * @return A list of java.lang.String objects to identify the region names.
+     */
+    public List<String> getRegions()
+    {
+        return regions;
+    }
+
+    /**
+     * Get the text for the region, this should be called after extractRegions().
+     *
+     * @param regionName The name of the region to get the text from.
+     * @return The text that was identified in that region.
+     */
+    public String getTextForRegion( String regionName )
+    {
+        StringWriter text = regionText.get( regionName );
+        return text.toString();
+    }
+
+    /**
+     * Process the page to extract the region text.
+     *
+     * @param page The page to extract the regions from.
+     * @throws IOException If there is an error while extracting text.
+     */
+    public void extractRegions( PDPage page ) throws IOException
+    {
+        Iterator<String> regionIter = regions.iterator();
+        while( regionIter.hasNext() )
+        {
+            setStartPage(getCurrentPageNo());
+            setEndPage(getCurrentPageNo());
+            //reset the stored text for the region so this class
+            //can be reused.
+            String regionName = regionIter.next();
+            Vector<ArrayList<TextPosition>> regionCharactersByArticle = new Vector<ArrayList<TextPosition>>();
+            regionCharactersByArticle.add( new ArrayList<TextPosition>() );
+            regionCharacterList.put( regionName, regionCharactersByArticle );
+            regionText.put( regionName, new StringWriter() );
+        }
+
+        PDStream contentStream = page.getContents();
+        if( contentStream != null )
+        {
+            COSStream contents = contentStream.getStream();
+            processPage( page, contents );
+        }
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    protected void processTextPosition( TextPosition text )
+    {
+        Iterator<String> regionIter = regionArea.keySet().iterator();
+        while( regionIter.hasNext() )
+        {
+            String region = regionIter.next();
+            Rectangle2D rect = regionArea.get( region );
+            if( rect.contains( text.getX(), text.getY() ) )
+            {
+                charactersByArticle = (Vector)regionCharacterList.get( region );
+                super.processTextPosition( text );
+            }
+        }
+    }
+
+    
+    /**
+     * This will print the processed page text to the output stream.
+     *
+     * @throws IOException If there is an error writing the text.
+     */
+    protected void writePage() throws IOException
+    {
+        Iterator<String> regionIter = regionArea.keySet().iterator();
+        while( regionIter.hasNext() )
+        {
+            String region = regionIter.next();
+            charactersByArticle = (Vector)regionCharacterList.get( region );
+            output = regionText.get( region );
+            super.writePage();
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PageExtractor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PageExtractor.java
new file mode 100644 (file)
index 0000000..3acd0d6
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+/**
+ * This class will extract one or more sequential pages and create a new document.
+ * @author Adam Nichols (adam@apache.org)
+ */
+public class PageExtractor {
+    protected PDDocument sourceDocument;
+    protected int startPage = 1; // first page to extract is page 1 (by default)
+    protected int endPage = 0;
+    
+    /** 
+     * Creates a new instance of PageExtractor
+     * @param document The document to split.
+     */
+    public PageExtractor(PDDocument sourceDocument) {
+        this.sourceDocument = sourceDocument;
+        endPage = sourceDocument.getNumberOfPages();
+    }
+    
+    /** 
+     * Creates a new instance of PageExtractor
+     * @param document The document to split.
+     * @param startPage The first page you want extracted (inclusive)
+     * @param endPage The last page you want extracted (inclusive)
+     */
+    public PageExtractor(PDDocument sourceDocument, int startPage, int endPage) {
+        this(sourceDocument);
+        this.startPage = startPage;
+        this.endPage = endPage;
+    }
+    
+    /**
+     * This will take a document and extract the desired pages into a new 
+     * document.  Both startPage and endPage are included in the extracted 
+     * document.  If the endPage is greater than the number of pages in the 
+     * source document, it will go to the end of the document.  If startPage is
+     * less than 1, it'll start with page 1.  If startPage is greater than 
+     * endPage or greater than the number of pages in the source document, a 
+     * blank document will be returned.
+     * 
+     * @return The extracted document
+     * @throws IOException If there is an IOError
+     */
+    public PDDocument extract() throws IOException {
+        PDDocument extractedDocument = new PDDocument();
+        extractedDocument.setDocumentInformation(sourceDocument.getDocumentInformation());
+        extractedDocument.getDocumentCatalog().setViewerPreferences(
+                sourceDocument.getDocumentCatalog().getViewerPreferences());
+        
+        List<PDPage> pages = (List<PDPage>)sourceDocument.getDocumentCatalog().getAllPages();
+        int pageCounter = 1;
+        for(PDPage page : pages) {
+            if(pageCounter >= startPage && pageCounter <= endPage) {
+                PDPage imported = extractedDocument.importPage(page);
+                imported.setCropBox(page.findCropBox());
+                imported.setMediaBox(page.findMediaBox());
+                imported.setResources(page.findResources());
+                imported.setRotation(page.findRotation());
+            }
+            pageCounter++;
+        }
+            
+        return extractedDocument;
+    }
+
+    /**
+     * Gets the first page number to be extracted.
+     * @return the first page number which should be extracted
+     */
+    public int getStartPage() {
+        return startPage;
+    }
+
+    /**
+     * Sets the first page number to be extracted.
+     * @param startPage the first page number which should be extracted
+     */
+    public void setStartPage(int startPage) {
+        this.startPage = startPage;
+    }
+
+    /**
+     * Gets the last page number (inclusive) to be extracted.
+     * @return the last page number which should be extracted
+     */
+    public int getEndPage() {
+        return endPage;
+    }
+
+    /**
+     * Sets the last page number to be extracted.
+     * @param endPage the last page number which should be extracted
+     */
+    public void setEndPage(int endPage) {
+        this.endPage = endPage;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PositionWrapper.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/PositionWrapper.java
new file mode 100644 (file)
index 0000000..d79b657
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util;
+
+
+/**
+ * wrapper of TextPosition that adds flags to track
+ * status as linestart and paragraph start positions.
+ * <p>
+ * This is implemented as a wrapper since the TextPosition
+ * class doesn't provide complete access to its
+ * state fields to subclasses.  Also, conceptually TextPosition is
+ * immutable while these flags need to be set post-creation so
+ * it makes sense to put these flags in this separate class.
+ * </p>
+ * @author m.martinez@ll.mit.edu
+ *
+ */
+public class PositionWrapper{
+       
+       private boolean isLineStart = false;
+       private boolean isParagraphStart = false;
+       private boolean isPageBreak = false;
+       private boolean isHangingIndent = false;
+       private boolean isArticleStart = false;
+       
+       private TextPosition position = null;
+       
+       /**
+        * returns the underlying TextPosition object
+        * @return
+        */
+       protected TextPosition getTextPosition(){
+               return position;
+       }
+       
+
+    public boolean isLineStart() {
+        return isLineStart;
+    }
+
+
+    /**
+     * sets the isLineStart() flag to true
+     */
+    public void setLineStart() {
+        this.isLineStart = true;
+    }
+
+
+    public boolean isParagraphStart() {
+        return isParagraphStart;
+    }
+
+
+    /**
+     * sets the isParagraphStart() flag to true.
+     */
+    public void setParagraphStart() {
+        this.isParagraphStart = true;
+    }
+
+
+       public boolean isArticleStart() {
+               return isArticleStart;
+       }
+
+
+       /**
+        * sets the isArticleStart() flag to true.
+        */
+       public void setArticleStart() {
+               this.isArticleStart = true;
+       }
+
+
+       public boolean isPageBreak() {
+               return isPageBreak;
+       }
+
+    
+    /**
+     * sets the isPageBreak() flag to true
+     */
+    public void setPageBreak() {
+        this.isPageBreak = true;
+    }
+
+
+    public boolean isHangingIndent() {
+        return isHangingIndent;
+    }
+
+
+    /**
+     * sets the isHangingIndent() flag to true
+     */
+    public void setHangingIndent() {
+        this.isHangingIndent = true;
+    }
+
+
+    /**
+     * constructs a PositionWrapper around the specified TextPosition object.
+     * @param position
+     */
+    public PositionWrapper(TextPosition position){
+        this.position = position;
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ResourceLoader.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/ResourceLoader.java
new file mode 100644 (file)
index 0000000..f2761dd
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+import java.util.Properties;
+
+/**
+ * This class will handle loading resource files(AFM/CMAP).
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class ResourceLoader
+{
+
+    /**
+     * private constructor for utility class.
+     */
+    private ResourceLoader()
+    {
+        //private utility class
+    }
+
+    /**
+     * This will attempt to load the resource given the resource name.
+     *
+     * @param resourceName The resource to try and load.
+     *
+     * @return The resource as a stream or null if it could not be found.
+     *
+     * @throws IOException If there is an error while attempting to load the resource.
+     */
+    public static InputStream loadResource( String resourceName ) throws IOException
+    {
+        ClassLoader loader = ResourceLoader.class.getClassLoader();
+
+        InputStream is = null;
+
+        if( loader != null )
+        {
+            is = loader.getResourceAsStream( resourceName );
+        }
+
+        //see sourceforge bug 863053, this is a fix for a user that
+        //needed to have PDFBox loaded by the bootstrap classloader
+        if( is == null )
+        {
+            loader = ClassLoader.getSystemClassLoader();
+            if( loader != null )
+            {
+                is = loader.getResourceAsStream( resourceName );
+            }
+        }
+
+        if( is == null )
+        {
+            File f = new File( resourceName );
+            if( f.exists() )
+            {
+                is = new FileInputStream( f );
+            }
+        }
+
+        return is;
+    }
+
+    /**
+     * This will attempt to load the resource given the resource name.
+     *
+     * @param resourceName The resource to try and load.
+     * @param failIfNotFound Throw an error message if the properties were not found.
+     *
+     * @return The resource as a stream or null if it could not be found.
+     *
+     * @throws IOException If there is an error loading the properties.
+     */
+    public static Properties loadProperties( String resourceName, boolean failIfNotFound ) throws IOException
+    {
+        Properties properties = null;
+        InputStream is = null;
+        try
+        {
+            is = loadResource( resourceName );
+            if( is != null )
+            {
+                properties = new Properties();
+                properties.load( is );
+            }
+            else
+            {
+                if( failIfNotFound )
+                {
+                    throw new IOException( "Error: could not find resource '" + resourceName + "' on classpath." );
+                }
+            }
+        }
+        finally
+        {
+            if( is != null )
+            {
+                is.close();
+            }
+        }
+        return properties;
+    }
+
+    /**
+     * This will attempt to load the resource given the resource name.
+     *
+     * @param resourceName The resource to try and load.
+     * @param defaults A stream of default properties.
+     *
+     * @return The resource as a stream or null if it could not be found.
+     *
+     * @throws IOException If there is an error loading the properties.
+     */
+    public static Properties loadProperties( String resourceName, Properties defaults ) throws IOException
+    {
+        InputStream is = null;
+        try
+        {
+            is = loadResource( resourceName );
+            if( is != null )
+            {
+                defaults.load( is );
+            }
+        }
+        finally
+        {
+            if( is != null )
+            {
+                is.close();
+            }
+        }
+        return defaults;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/Splitter.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/Splitter.java
new file mode 100644 (file)
index 0000000..325f5ef
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * 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.pdfbox.util;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Split a document into several other documents.
+ *
+ * @author Mario Ivankovits (mario@ops.co.at)
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class Splitter
+{
+
+    /**
+     * The source PDF document.
+     */
+    protected PDDocument pdfDocument;
+
+    /**
+     * The current PDF document that contains the splitted page.
+     */
+    protected PDDocument currentDocument = null;
+
+    private int splitAtPage=1;
+    private List<PDDocument> newDocuments = null;
+
+    /**
+     * The current page number that we are processing, zero based.
+     */
+    protected int pageNumber = 0;
+
+    /**
+     * This will take a document and split into several other documents.
+     *
+     * @param document The document to split.
+     *
+     * @return A list of all the split documents.
+     *
+     * @throws IOException If there is an IOError
+     */
+    public List<PDDocument> split( PDDocument document ) throws IOException
+    {
+        newDocuments = new ArrayList<PDDocument>();
+        pdfDocument = document;
+
+        List pages = pdfDocument.getDocumentCatalog().getAllPages();
+        processPages(pages);
+        return newDocuments;
+    }
+
+    /**
+     * This will tell the splitting algorithm where to split the pages.  The default
+     * is 1, so every page will become a new document.  If it was to then each document would
+     * contain 2 pages.  So it the source document had 5 pages it would split into
+     * 3 new documents, 2 documents containing 2 pages and 1 document containing one
+     * page.
+     *
+     * @param split The number of pages each split document should contain.
+     */
+    public void setSplitAtPage( int split )
+    {
+        if( split <= 0 )
+        {
+            throw new RuntimeException( "Error split must be at least one page." );
+        }
+        splitAtPage = split;
+    }
+
+    /**
+     * This will return how many pages each split document will contain.
+     *
+     * @return The split parameter.
+     */
+    public int getSplitAtPage()
+    {
+        return splitAtPage;
+    }
+
+    /**
+     * Interface method to handle the start of the page processing.
+     *
+     * @param pages The list of pages from the source document.
+     *
+     * @throws IOException If an IO error occurs.
+     */
+    protected void processPages(List pages) throws IOException
+    {
+        Iterator iter = pages.iterator();
+        while( iter.hasNext() )
+        {
+            PDPage page = (PDPage)iter.next();
+            processNextPage( page );
+        }
+    }
+
+    /**
+     * Interface method, you can control where a document gets split by implementing
+     * this method.  By default a split occurs at every page.  If you wanted to split
+     * based on some complex logic then you could override this method.  For example.
+     * <code>
+     * protected void createNewDocumentIfNecessary()
+     * {
+     *     if( isPrime( pageNumber ) )
+     *     {
+     *         super.createNewDocumentIfNecessary();
+     *     }
+     * }
+     * </code>
+     *
+     * @throws IOException If there is an error creating the new document.
+     */
+    protected void createNewDocumentIfNecessary() throws IOException
+    {
+        if (isNewDocNecessary())
+        {
+            createNewDocument();
+        }
+    }
+
+    /**
+     * Check if it is necessary to create a new document.
+     *
+     * @return true If a new document should be created.
+     */
+    protected boolean isNewDocNecessary()
+    {
+        return pageNumber % splitAtPage == 0 || currentDocument == null;
+    }
+
+    /**
+     * Create a new document to write the splitted contents to.
+     *
+     * @throws IOException If there is an problem creating the new document.
+     */
+    protected void createNewDocument() throws IOException
+    {
+        currentDocument = new PDDocument();
+        currentDocument.setDocumentInformation(pdfDocument.getDocumentInformation());
+        currentDocument.getDocumentCatalog().setViewerPreferences(
+        pdfDocument.getDocumentCatalog().getViewerPreferences());
+        newDocuments.add(currentDocument);
+    }
+
+
+
+    /**
+     * Interface to start processing a new page.
+     *
+     * @param page The page that is about to get processed.
+     *
+     * @throws IOException If there is an error creating the new document.
+     */
+    protected void processNextPage( PDPage page ) throws IOException
+    {
+        createNewDocumentIfNecessary();
+        PDPage imported = currentDocument.importPage( page );
+        imported.setCropBox( page.findCropBox() );
+        imported.setMediaBox( page.findMediaBox() );
+        // only the resources of the page will be copied
+        imported.setResources( page.getResources() );
+        imported.setRotation( page.findRotation() );
+        pageNumber++;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/StringUtil.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/StringUtil.java
new file mode 100644 (file)
index 0000000..a781c56
--- /dev/null
@@ -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.pdfbox.util;
+
+import java.io.UnsupportedEncodingException;
+
+public class StringUtil
+{
+    /**
+     * Converts a string to it ISO-8859-1 byte sequence
+     *
+     * It is an workaround for variable initialisations outside of functions.
+     */ 
+    public static byte[] getBytes(String s)
+    {
+        try
+            {
+                return s.getBytes("ISO-8859-1");
+            }
+        catch(UnsupportedEncodingException e)
+            {
+                throw new RuntimeException("Unsupported Encoding", e);
+            }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextNormalize.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextNormalize.java
new file mode 100644 (file)
index 0000000..3994df1
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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.pdfbox.util;
+
+import java.util.HashMap;
+
+/**
+ * This class allows a caller to normalize text in various ways.
+ * It will load the ICU4J jar file if it is defined on the classpath.
+ * 
+ * @author <a href="mailto:carrier@digital-evidence.org">Brian Carrier</a>
+ * @version $Revision: 1.0 $
+ */
+public class TextNormalize 
+{
+    private ICU4JImpl icu4j = null;
+    private static final HashMap DIACHASH = new HashMap();
+    private String outputEncoding;
+
+    /**
+     * 
+     * @param encoding The Encoding that the text will eventually be written as (or null)
+     */
+    public TextNormalize(String encoding) 
+    {
+        findICU4J();
+        populateDiacHash();
+        this.outputEncoding = encoding;
+    }
+
+    private void findICU4J() 
+    {
+        // see if we can load the icu4j classes from the classpath
+        try 
+        {
+            this.getClass().getClassLoader().loadClass("com.ibm.icu.text.Bidi");
+            this.getClass().getClassLoader().loadClass("com.ibm.icu.text.Normalizer");
+            icu4j = new ICU4JImpl();
+        } 
+        catch (ClassNotFoundException e) 
+        {
+            icu4j = null;
+        }
+    }
+    /*
+     * Adds non-decomposing diacritics to the hash with their related
+     * combining character. These are values that the unicode spec claims
+     * are equivalent but are not mapped in the form NFKC normalization method.
+     * Determined by going through the Combining Diacritical Marks section of 
+     * the Unicode spec and identifying which characters are not mapped to by 
+     * the normalization. 
+     */
+    private void populateDiacHash()
+    {
+        DIACHASH.put(new Integer(0x0060), "\u0300");
+        DIACHASH.put(new Integer(0x02CB), "\u0300");
+        DIACHASH.put(new Integer(0x0027), "\u0301");
+        DIACHASH.put(new Integer(0x02B9), "\u0301");
+        DIACHASH.put(new Integer(0x02CA), "\u0301");
+        DIACHASH.put(new Integer(0x005e), "\u0302");
+        DIACHASH.put(new Integer(0x02C6), "\u0302");
+        DIACHASH.put(new Integer(0x007E), "\u0303");
+        DIACHASH.put(new Integer(0x02C9), "\u0304");
+        DIACHASH.put(new Integer(0x00B0), "\u030A");
+        DIACHASH.put(new Integer(0x02BA), "\u030B");
+        DIACHASH.put(new Integer(0x02C7), "\u030C");
+        DIACHASH.put(new Integer(0x02C8), "\u030D");
+        DIACHASH.put(new Integer(0x0022), "\u030E");
+        DIACHASH.put(new Integer(0x02BB), "\u0312");
+        DIACHASH.put(new Integer(0x02BC), "\u0313");
+        DIACHASH.put(new Integer(0x0486), "\u0313");
+        DIACHASH.put(new Integer(0x055A), "\u0313");
+        DIACHASH.put(new Integer(0x02BD), "\u0314");
+        DIACHASH.put(new Integer(0x0485), "\u0314");
+        DIACHASH.put(new Integer(0x0559), "\u0314");
+        DIACHASH.put(new Integer(0x02D4), "\u031D");
+        DIACHASH.put(new Integer(0x02D5), "\u031E");
+        DIACHASH.put(new Integer(0x02D6), "\u031F");
+        DIACHASH.put(new Integer(0x02D7), "\u0320");
+        DIACHASH.put(new Integer(0x02B2), "\u0321");
+        DIACHASH.put(new Integer(0x02CC), "\u0329");
+        DIACHASH.put(new Integer(0x02B7), "\u032B");
+        DIACHASH.put(new Integer(0x02CD), "\u0331");
+        DIACHASH.put(new Integer(0x005F), "\u0332");
+        DIACHASH.put(new Integer(0x204E), "\u0359");
+    }
+
+    /**
+     * Takes a line of text in presentation order and converts it to logical order.
+     * For most text other than Arabic and Hebrew, the presentation and logical
+     * orders are the same. However, for Arabic and Hebrew, they are different and
+     * if the text involves both RTL and LTR text then the Unicode BIDI algorithm
+     * must be used to determine how to map between them.  
+     * 
+     * @param str Presentation form of line to convert (i.e. left most char is first char)
+     * @param isRtlDominant true if the PAGE has a dominant right to left ordering
+     * @return Logical form of string (or original string if ICU4J library is not on classpath)
+     */
+    public String makeLineLogicalOrder(String str, boolean isRtlDominant) 
+    {
+        if (icu4j != null) 
+        {
+            return icu4j.makeLineLogicalOrder(str, isRtlDominant);
+        }
+        else 
+        {
+            return str;
+        }
+    }
+
+    /**
+     * Normalize the presentation forms of characters in the string.
+     * For example, convert the single "fi" ligature to "f" and "i".
+     * 
+     * @param str String to normalize
+     * @return Normalized string (or original string if ICU4J library is not on classpath)
+     */
+    public String normalizePres(String str) 
+    {
+        if (icu4j != null) 
+        {
+            return icu4j.normalizePres(str);
+        }
+        else 
+        {
+            return str;
+        }
+    }
+    
+    /**
+     * Normalize the diacritic, for example, 
+     * convert non-combining diacritic characters to their combining
+     * counterparts. 
+     * 
+     * @param str String to normalize 
+     * @return Normalized string (or original string if ICU4J library is not on classpath)
+     */
+    public String normalizeDiac(String str)
+    {
+        /*
+         * Unicode contains special combining forms of the diacritic characters
+         * and we want to use these. 
+         */
+        if(outputEncoding != null && outputEncoding.toUpperCase().startsWith("UTF"))
+        {
+            Integer c = new Integer(str.charAt(0));
+            // convert the characters not defined in the Unicode spec
+            if(DIACHASH.containsKey(c))
+            {
+                return (String)DIACHASH.get(c);
+            }
+            else if (icu4j != null) 
+            {
+                return icu4j.normalizeDiac(str);
+            }
+            else 
+            {
+                return str;
+            }
+        }
+        else
+        {
+            return str;
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextPosition.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextPosition.java
new file mode 100644 (file)
index 0000000..91c5dfa
--- /dev/null
@@ -0,0 +1,745 @@
+/*
+ * 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.pdfbox.util;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+
+/**
+ * This represents a string and a position on the screen of those characters.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class TextPosition
+{
+    /* TextMatrix for the start of the text object.  Coordinates
+     * are in display units and have not been adjusted. */
+    private Matrix textPos;
+
+    // ending X and Y coordinates in display units
+    private float endX;
+    private float endY;
+
+    private float maxTextHeight; // maximum height of text, in display units
+    private int rot; // 0, 90, 180, 270 degrees of page rotation
+    private float x = Float.NEGATIVE_INFINITY;
+    private float y = Float.NEGATIVE_INFINITY;
+    private float pageHeight;
+    private float pageWidth;
+    private float[] widths;
+    private float widthOfSpace; // width of a space, in display units
+    private String str; 
+    private PDFont font;
+    private float fontSize;
+    private int fontSizePt;
+    // TODO remove unused value
+    private float wordSpacing;  // word spacing value, in display units
+
+    /**
+     *  Constructor.
+     */
+    protected TextPosition()
+    {
+
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param page Page that the text is located in
+     * @param textPositionSt TextMatrix for start of text (in display units)
+     * @param textPositionEnd TextMatrix for end of text (in display units)
+     * @param maxFontH Maximum height of text (in display units)
+     * @param individualWidths The width of each individual character. (in ? units)
+     * @param spaceWidth The width of the space character. (in display units)
+     * @param string The character to be displayed.
+     * @param currentFont The current for for this text position.
+     * @param fontSizeValue The new font size.
+     * @param fontSizeInPt The font size in pt units.
+     * @param ws The word spacing parameter (in display units)
+     */
+    public TextPosition(
+            PDPage page,
+            Matrix textPositionSt,
+            Matrix textPositionEnd,
+            float maxFontH,
+            float[] individualWidths,
+            float spaceWidth,
+            String string,
+            PDFont currentFont,
+            float fontSizeValue,
+            int fontSizeInPt,
+            float ws
+    )
+    {
+        this.textPos = textPositionSt;
+
+        this.endX = textPositionEnd.getXPosition();
+        this.endY = textPositionEnd.getYPosition();
+
+        this.rot = page.findRotation();
+        // make sure it is 0 to 270 and no negative numbers
+        if(this.rot < 0)
+        {
+            rot += 360;
+        }
+
+        this.maxTextHeight = maxFontH;
+        this.pageHeight = page.findMediaBox().getHeight();
+        this.pageWidth = page.findMediaBox().getWidth();
+
+        this.widths = individualWidths;
+        this.widthOfSpace = spaceWidth;
+        this.str = string;
+        this.font = currentFont;
+        this.fontSize = fontSizeValue;
+        this.fontSizePt = fontSizeInPt;
+        this.wordSpacing = ws;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param pageRotation rotation of the page that the text is located in
+     * @param pageWidth rotation of the page that the text is located in
+     * @param pageHeight rotation of the page that the text is located in
+     * @param textPositionSt TextMatrix for start of text (in display units)
+     * @param textPositionEnd TextMatrix for end of text (in display units)
+     * @param maxFontH Maximum height of text (in display units)
+     * @param individualWidth The width of the given character/string. (in ? units)
+     * @param spaceWidth The width of the space character. (in display units)
+     * @param string The character to be displayed.
+     * @param currentFont The current for for this text position.
+     * @param fontSizeValue The new font size.
+     * @param fontSizeInPt The font size in pt units.
+     * 
+     * @deprecated Use {@link TextPosition(int, float, float, Matrix, float, float, float, float, float, String, PDFont, float, int)} instead.
+     */
+    public TextPosition(
+            int pageRotation,
+            float pageWidth,
+            float pageHeight,
+            Matrix textPositionSt,
+            Matrix textPositionEnd,
+            float maxFontH,
+            float individualWidth,
+            float spaceWidth,
+            String string,
+            PDFont currentFont,
+            float fontSizeValue,
+            int fontSizeInPt
+    )
+    {
+       this(pageRotation, pageWidth, pageHeight, textPositionSt,
+                       textPositionEnd.getXPosition(), textPositionEnd.getYPosition(),
+                       maxFontH, individualWidth, spaceWidth, string, currentFont, fontSizeValue, fontSizeInPt);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param pageRotation rotation of the page that the text is located in
+     * @param pageWidth rotation of the page that the text is located in
+     * @param pageHeight rotation of the page that the text is located in
+     * @param textPositionSt TextMatrix for start of text (in display units)
+     * @param endX
+     * @param endY
+     * @param maxFontH Maximum height of text (in display units)
+     * @param individualWidth The width of the given character/string. (in ? units)
+     * @param spaceWidth The width of the space character. (in display units)
+     * @param string The character to be displayed.
+     * @param currentFont The current for for this text position.
+     * @param fontSizeValue The new font size.
+     * @param fontSizeInPt The font size in pt units.
+     */
+    public TextPosition(
+            int pageRotation,
+            float pageWidth,
+            float pageHeight,
+            Matrix textPositionSt,
+            float endX,
+            float endY,
+            float maxFontH,
+            float individualWidth,
+            float spaceWidth,
+            String string,
+            PDFont currentFont,
+            float fontSizeValue,
+            int fontSizeInPt
+    )
+    {
+        this.textPos = textPositionSt;
+
+        this.endX = endX;
+        this.endY = endY;
+
+        this.rot = pageRotation;
+        // make sure it is 0 to 270 and no negative numbers
+        if(this.rot < 0)
+        {
+            rot += 360;
+        }
+
+        this.maxTextHeight = maxFontH;
+        this.pageHeight = pageHeight;
+        this.pageWidth = pageWidth;
+
+        this.widths = new float[]{individualWidth};
+        this.widthOfSpace = spaceWidth;
+        this.str = string;
+        this.font = currentFont;
+        this.fontSize = fontSizeValue;
+        this.fontSizePt = fontSizeInPt;
+    }
+    
+    /**
+     * Return the string of characters stored in this object.
+     *
+     * @return The string on the screen.
+     */
+    public String getCharacter()
+    {
+        return str;
+    }
+
+    /**
+     * Return the Matrix textPos stored in this object.
+     *  
+     * @return The Matrix containing all infos of the starting textposition
+     */
+    public Matrix getTextPos() 
+    {
+        return textPos;
+    }
+
+    /**
+     * Return the direction/orientation of the string in this object
+     * based on its text matrix.
+     * @return The direction of the text (0, 90, 180, or 270)
+     */
+    public float getDir() 
+    {
+        float a = textPos.getValue(0,0);
+        float b = textPos.getValue(0,1);
+        float c = textPos.getValue(1,0);
+        float d = textPos.getValue(1,1);
+
+        // 12 0   left to right
+        // 0 12 
+        if ((a > 0) && (Math.abs(b) < d) && (Math.abs(c) < a) && (d > 0))
+        {
+            return 0;
+        }
+        // -12 0   right to left (upside down)
+        // 0 -12
+        else if ((a < 0) && (Math.abs(b) < Math.abs(d)) && (Math.abs(c) < Math.abs(a)) && (d < 0))
+        {
+            return 180;
+        }
+        // 0  12    up
+        // -12 0 
+        else if ((Math.abs(a) < Math.abs(c)) && (b > 0) && (c < 0) && (Math.abs(d) < b))
+        {
+            return 90;
+        }
+        // 0  -12   down
+        // 12 0 
+        else if ((Math.abs(a) < c) && (b < 0) && (c > 0) && (Math.abs(d) < Math.abs(b)))
+        {
+            return 270;
+        }
+        return 0;
+    }
+
+    /**
+     * Return the X starting coordinate of the text, adjusted by 
+     * the given rotation amount.  The rotation adjusts where the 0,0
+     * location is relative to the text. 
+     *  
+     * @param rotation Rotation to apply (0, 90, 180, or 270).  0 will perform no adjustments. 
+     * @return X coordinate
+     */
+    private float getXRot(float rotation)
+    {
+        if (rotation == 0)
+        {
+            return textPos.getValue(2,0);
+        }
+        else if (rotation == 90)
+        {
+            return textPos.getValue(2,1);
+        }
+        else if (rotation == 180)
+        {
+            return pageWidth - textPos.getValue(2,0);
+        }
+        else if (rotation == 270)
+        {
+            return pageHeight - textPos.getValue(2,1);
+        }
+        return 0;
+    }
+
+    /**
+     * This will get the page rotation adjusted x position of the character.
+     * This is adjusted based on page rotation so that the upper 
+     * left is 0,0. 
+     *
+     * @return The x coordinate of the character.
+     */
+    public float getX()
+    {
+        if(x==Float.NEGATIVE_INFINITY){
+               x = getXRot(rot);
+        }
+        return x;
+    }
+
+    /**
+     * This will get the text direction adjusted x position of the character.
+     * This is adjusted based on text direction so that the first character
+     * in that direction is in the upper left at 0,0.
+     *
+     * @return The x coordinate of the text.
+     */
+    public float getXDirAdj() 
+    {
+        return getXRot(getDir());  
+    }
+
+    /** 
+     * This will get the y position of the character with 0,0 in lower left. 
+     * This will be adjusted by the given rotation. 
+     * @param rotation Rotation to apply to text to adjust the 0,0 location (0,90,180,270)
+     * 
+     * @return The y coordinate of the text
+     */
+    private float getYLowerLeftRot(float rotation)
+    {
+        if (rotation == 0)
+        {
+            return textPos.getValue(2,1);
+        }
+        else if (rotation == 90)
+        {
+            return pageWidth - textPos.getValue(2,0);
+        }
+        else if (rotation == 180)
+        {
+            return pageHeight - textPos.getValue(2,1);
+        }
+        else if (rotation == 270)
+        {
+            return textPos.getValue(2,0);
+        }
+        return 0;
+    }
+
+    /**
+     * This will get the y position of the text, adjusted so that 0,0 is upper left and 
+     * it is adjusted based on the page rotation. 
+     *
+     * @return The adjusted y coordinate of the character.
+     */
+    public float getY()
+    {
+       if(y==Float.NEGATIVE_INFINITY){
+            if ((rot == 0) || (rot == 180))
+            {
+                y = pageHeight - getYLowerLeftRot(rot);
+            }
+            else 
+            {
+                y = pageWidth - getYLowerLeftRot(rot);
+            }
+       }
+       return y;
+    }
+
+    /**
+     * This will get the y position of the text, adjusted so that 0,0 is upper left and 
+     * it is adjusted based on the text direction. 
+     *
+     * @return The adjusted y coordinate of the character.
+     */
+    public float getYDirAdj()
+    {
+        float dir = getDir();
+        // some PDFBox code assumes that the 0,0 point is in upper left, not lower left
+        if ((dir == 0) || (dir == 180))
+        {
+            return pageHeight - getYLowerLeftRot(dir);
+        }
+        else
+        {
+            return pageWidth - getYLowerLeftRot(dir);
+        }
+    }
+
+
+
+    /**
+     * Get the length or width of the text, based on a given rotation. 
+     * 
+     * @param rotation Rotation that was used to determine coordinates (0,90,180,270)
+     * @return Width of text in display units
+     */
+    private float getWidthRot(float rotation)
+    {
+        if ((rotation == 90) || (rotation == 270)) 
+        {
+            return Math.abs(endY - textPos.getYPosition());
+        }
+        else 
+        {
+            return Math.abs(endX - textPos.getXPosition());
+        }
+    }
+
+    /**
+     * This will get the width of the string when page rotation adjusted coordinates are used.
+     *
+     * @return The width of the text in display units.
+     */
+    public float getWidth() 
+    {
+        return getWidthRot(rot);
+    }
+
+    /**
+     * This will get the width of the string when text direction adjusted coordinates are used.
+     *
+     * @return The width of the text in display units.
+     */
+    public float getWidthDirAdj() 
+    {
+        return getWidthRot(getDir());
+    }
+
+    /**
+     * This will get the maximum height of all characters in this string.
+     *
+     * @return The maximum height of all characters in this string.
+     */
+    public float getHeight() 
+    {
+        return maxTextHeight;
+    }
+
+    /**
+     * This will get the maximum height of all characters in this string.
+     *
+     * @return The maximum height of all characters in this string.
+     */
+    public float getHeightDir() 
+    {
+        // this is not really a rotation-dependent calculation, but this is defined for symmetry.
+        return maxTextHeight;
+    }
+
+    /**
+     * This will get the font size that this object is
+     * suppose to be drawn at.
+     *
+     * @return The font size.
+     */
+    public float getFontSize()
+    {
+        return fontSize;
+    }
+
+    /**
+     * This will get the font size in pt.
+     * To get this size we have to multiply the pdf-fontsize and the scaling from the textmatrix
+     *
+     * @return The font size in pt.
+     */
+    public float getFontSizeInPt()
+    {
+        return fontSizePt;
+    }
+
+    /**
+     * This will get the font for the text being drawn.
+     *
+     * @return The font size.
+     */
+    public PDFont getFont()
+    {
+        return font;
+    }
+
+    /**
+     * This will get the current word spacing.
+     *
+     * @return The current word spacing.
+     */
+    @Deprecated 
+    public float getWordSpacing()
+    {
+        return wordSpacing;
+    }
+
+    /**
+     * This will get the width of a space character.  This is useful for some
+     * algorithms such as the text stripper, that need to know the width of a
+     * space character.
+     *
+     * @return The width of a space character.
+     */
+    public float getWidthOfSpace()
+    {
+        return widthOfSpace;
+    }
+    /**
+     * @return Returns the xScale.
+     */
+    public float getXScale()
+    {
+        return textPos.getXScale();
+    }
+
+    /**
+     * @return Returns the yScale.
+     */
+    public float getYScale()
+    {
+        return textPos.getYScale();
+    }
+
+    /**
+     * Get the widths of each individual character.
+     *
+     * @return An array that is the same length as the length of the string.
+     */
+    public float[] getIndividualWidths()
+    {
+        return widths;
+    }
+
+    /**
+     * Show the string data for this text position.
+     *
+     * @return A human readable form of this object.
+     */
+    public String toString()
+    {
+        return getCharacter();
+    }
+
+
+    /**
+     * Determine if this TextPosition logically contains
+     * another (i.e. they overlap and should be rendered on top
+     * of each other).
+     * @param tp2 The other TestPosition to compare against
+     *
+     * @return True if tp2 is contained in the bounding box of this text.
+     */
+    public boolean contains( TextPosition tp2)
+    {
+        double thisXstart = getXDirAdj();
+        double thisXend = getXDirAdj() + getWidthDirAdj();
+
+        double tp2Xstart = tp2.getXDirAdj();
+        double tp2Xend = tp2.getXDirAdj() + tp2.getWidthDirAdj();
+
+        /*
+         * No X overlap at all so return as soon as possible. 
+         */
+        if(tp2Xend <= thisXstart || tp2Xstart >= thisXend)
+        {
+            return false;
+        }   
+        /*
+         * No Y overlap at all so return as soon as possible. 
+         * Note: 0.0 is in the upper left and y-coordinate is 
+         * top of TextPosition
+         */
+        if((tp2.getYDirAdj() + tp2.getHeightDir() < getYDirAdj()) ||
+                (tp2.getYDirAdj() > getYDirAdj() + getHeightDir()))
+        {
+            return false; 
+        }          
+        /* We're going to calculate the percentage of overlap. If its less
+         * than a 15% x-coordinate overlap then we'll return false because its negligible.
+         * .15 was determined by trial and error in the regression test files.
+         */
+        else if((tp2Xstart > thisXstart) && (tp2Xend > thisXend))
+        {
+            double overlap = thisXend - tp2Xstart;
+            double overlapPercent = overlap/getWidthDirAdj();
+            return (overlapPercent > .15);
+        }
+        else if((tp2Xstart < thisXstart) && (tp2Xend < thisXend))
+        {
+            double overlap = tp2Xend - thisXstart;
+            double overlapPercent = overlap/getWidthDirAdj();
+            return (overlapPercent > .15);
+        }
+        return true; 
+    }
+
+    /**
+     * Merge a single character TextPosition into the current object.
+     * This is to be used only for cases where we have a diacritic that
+     * overlaps an existing TextPosition.  In a graphical display, we could
+     * overlay them, but for text extraction we need to merge them. Use the
+     * contains() method to test if two objects overlap. 
+     * 
+     * @param diacritic TextPosition to merge into the current TextPosition.
+     * @param normalize Instance of TextNormalize class to be used to normalize diacritic
+     */
+    public void mergeDiacritic(TextPosition diacritic, TextNormalize normalize)
+    {
+        if (diacritic.getCharacter().length() > 1) 
+        {
+            return;
+        }
+
+        float diacXStart = diacritic.getXDirAdj();
+        float diacXEnd = diacXStart + diacritic.widths[0];
+
+        float currCharXStart = getXDirAdj();
+
+        int strLen = str.length();
+        boolean wasAdded = false;
+
+        for (int i = 0; i < strLen && !wasAdded; i++) 
+        {
+            float currCharXEnd = currCharXStart + widths[i];
+
+            /*
+             * This is the case where there is an overlap of the diacritic character with
+             * the current character and the previous character. If no previous character,
+             * just append the diacritic after the current one. 
+             */
+            if(diacXStart < currCharXStart && diacXEnd <= currCharXEnd)
+            {               
+                if(i == 0)
+                {
+                    insertDiacritic(i, diacritic, normalize);
+                }
+                else
+                {    
+                    float distanceOverlapping1 = diacXEnd - currCharXStart;
+                    float percentage1 = distanceOverlapping1/widths[i];
+
+                    float distanceOverlapping2 = currCharXStart - diacXStart;
+                    float percentage2 = distanceOverlapping2/widths[i-1];
+
+                    if(percentage1 >= percentage2)
+                    {
+                        insertDiacritic(i, diacritic, normalize);
+                    }
+                    else
+                    {
+                        insertDiacritic(i-1, diacritic, normalize);
+                    }
+                }
+                wasAdded = true;
+            }
+            //diacritic completely covers this character and therefore we assume that
+            //this is the character the diacritic belongs to
+            else if(diacXStart < currCharXStart && diacXEnd > currCharXEnd)
+            {
+                insertDiacritic(i, diacritic, normalize);
+                wasAdded = true;
+            }
+            //Otherwise, The diacritic modifies this character because its completely
+            //contained by the character width
+            else if(diacXStart >= currCharXStart && diacXEnd <= currCharXEnd) 
+            {
+                insertDiacritic(i, diacritic, normalize);
+                wasAdded = true;   
+            }
+            /*
+             * Last character in the TextPosition so we add diacritic to the end
+             */
+            else if(diacXStart >= currCharXStart && diacXEnd > currCharXEnd && i == (strLen - 1))
+            {
+                insertDiacritic(i, diacritic, normalize);
+                wasAdded = true;
+            }        
+            /*
+             * Couldn't find anything useful so we go to the next character in the 
+             * TextPosition 
+             */
+            currCharXStart += widths[i];
+        }
+    }
+    /**
+     * Inserts the diacritic TextPosition to the str of this TextPosition
+     * and updates the widths array to include the extra character width.
+     * @param i current character
+     * @param diacritic The diacritic TextPosition
+     * @param normalize Instance of TextNormalize class to be used to normalize diacritic
+     */
+    private void insertDiacritic(int i, TextPosition diacritic, TextNormalize normalize)
+    {
+        /* we add the diacritic to the right or left of the character
+         * depending on the direction of the character.  Note that this
+         * is only required because the text is currently stored in 
+         * presentation order and not in logical order. 
+         */
+        int dir = Character.getDirectionality(str.charAt(i));
+        StringBuffer buf = new StringBuffer();
+
+        buf.append(str.substring(0,i));
+
+        float[] widths2 = new float[widths.length+1];
+        System.arraycopy(widths, 0, widths2, 0, i);
+
+        if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
+                || (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+                || (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING)
+                || (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE)) 
+        {
+            buf.append(normalize.normalizeDiac(diacritic.getCharacter()));
+            widths2[i] = 0;
+            buf.append(str.charAt(i));
+            widths2[i+1] = widths[i];
+        } 
+        else 
+        {
+            buf.append(str.charAt(i));
+            widths2[i] = widths[i];
+            buf.append(normalize.normalizeDiac(diacritic.getCharacter()));
+            widths2[i+1] = 0;
+        }
+
+        // Get the rest of the string
+        buf.append(str.substring(i+1, str.length()));
+        System.arraycopy(widths, i+1, widths2, i+2, widths.length-i-1);
+
+        str = buf.toString();
+        widths = widths2;
+    }
+
+    /**
+     * 
+     * @return True if the current character is a diacritic char. 
+     */
+    public boolean isDiacritic() 
+    {
+        String cText = this.getCharacter();
+        return (cText.length() == 1 &&  (Character.getType(cText.charAt(0)) == Character.NON_SPACING_MARK
+                || Character.getType(cText.charAt(0)) == Character.MODIFIER_SYMBOL
+                || Character.getType(cText.charAt(0)) == Character.MODIFIER_LETTER));
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextPositionComparator.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/TextPositionComparator.java
new file mode 100644 (file)
index 0000000..c235809
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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.pdfbox.util;
+
+import java.util.Comparator;
+
+/**
+ * This class is a comparator for TextPosition operators.  It handles
+ * pages with text in different directions by grouping the text based
+ * on direction and sorting in that direction. This allows continuous text
+ * in a given direction to be more easily grouped together.  
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class TextPositionComparator implements Comparator
+{
+    /**
+     * {@inheritDoc}
+     */
+    public int compare(Object o1, Object o2)
+    {
+        int retval = 0;
+        TextPosition pos1 = (TextPosition)o1;
+        TextPosition pos2 = (TextPosition)o2;
+
+        /* Only compare text that is in the same direction. */
+        if (pos1.getDir() < pos2.getDir())
+        {
+            return -1;
+        }
+        else if (pos1.getDir() > pos2.getDir())
+        {
+            return 1;
+        }
+        
+        // Get the text direction adjusted coordinates
+        float x1 = pos1.getXDirAdj();
+        float x2 = pos2.getXDirAdj();
+        
+        float pos1YBottom = pos1.getYDirAdj();
+        float pos2YBottom = pos2.getYDirAdj();
+        // note that the coordinates have been adjusted so 0,0 is in upper left
+        float pos1YTop = pos1YBottom - pos1.getHeightDir();
+        float pos2YTop = pos2YBottom - pos2.getHeightDir();
+
+        float yDifference = Math.abs( pos1YBottom-pos2YBottom);
+        //we will do a simple tolerance comparison.
+        if( yDifference < .1 ||
+            (pos2YBottom >= pos1YTop && pos2YBottom <= pos1YBottom) ||
+            (pos1YBottom >= pos2YTop && pos1YBottom <= pos2YBottom))
+        {
+            if( x1 < x2 )
+            {
+                retval = -1;
+            }
+            else if( x1 > x2 )
+            {
+                retval = 1;
+            }
+            else
+            {
+                retval = 0;
+            }
+        }
+        else if( pos1YBottom < pos2YBottom )
+        {
+            retval = -1;
+        }
+        else
+        {
+            return 1;
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/XMLUtil.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/XMLUtil.java
new file mode 100644 (file)
index 0000000..ae6c6df
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.pdfbox.util;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * This class with handle some simple XML operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class XMLUtil
+{
+    /**
+     * Utility class, should not be instantiated.
+     *
+     */
+    private XMLUtil()
+    {
+    }
+
+    /**
+     * This will parse an XML stream and create a DOM document.
+     *
+     * @param is The stream to get the XML from.
+     * @return The DOM document.
+     * @throws IOException It there is an error creating the dom.
+     */
+    public static Document parse( InputStream is ) throws IOException
+    {
+        try
+        {
+            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder builder = builderFactory.newDocumentBuilder();
+            return builder.parse( is );
+        }
+        catch( Exception e )
+        {
+            IOException thrown = new IOException( e.getMessage() );
+            throw thrown;
+        }
+    }
+
+    /**
+     * This will get the text value of an element.
+     *
+     * @param node The node to get the text value for.
+     * @return The text of the node.
+     */
+    public static String getNodeValue( Element node )
+    {
+        String retval = "";
+        NodeList children = node.getChildNodes();
+        for( int i=0; i<children.getLength(); i++ )
+        {
+            Node next = children.item( i );
+            if( next instanceof Text )
+            {
+                retval = next.getNodeValue();
+            }
+        }
+        return retval;
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginMarkedContentSequence.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginMarkedContentSequence.java
new file mode 100644 (file)
index 0000000..2db3667
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+/**
+ * BMC : Begins a marked-content sequence.
+ * @author koch
+ * @version $Revision$
+ *
+ */
+public class BeginMarkedContentSequence extends OperatorProcessor
+{
+
+    /**
+     * {@inheritDoc} 
+     */
+    @Override
+    public void process(PDFOperator operator, List<COSBase> arguments)
+        throws IOException
+    {
+        COSName tag = null;
+        for (COSBase argument : arguments)
+        {
+            if (argument instanceof COSName)
+            {
+                tag = (COSName) argument;
+            }
+        }
+        if (this.context instanceof PDFMarkedContentExtractor)
+        {
+            ((PDFMarkedContentExtractor) this.context).beginMarkedContentSequence(tag, null);
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginMarkedContentSequenceWithProperties.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginMarkedContentSequenceWithProperties.java
new file mode 100644 (file)
index 0000000..4357e14
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+/**
+ * BDC : Begins a marked-content sequence with property list.
+ *
+ * @author koch
+ * @version $Revision$
+ */
+public class BeginMarkedContentSequenceWithProperties extends OperatorProcessor
+{
+
+    /**
+     * {@inheritDoc}
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+        throws IOException
+    {
+        COSName tag = null;
+        COSDictionary properties = null;
+        for (COSBase argument : arguments)
+        {
+            if (argument instanceof COSName)
+            {
+                tag = (COSName) argument;
+            }
+            else if (argument instanceof COSDictionary)
+            {
+                properties = (COSDictionary) argument;
+            }
+        }
+        if (this.context instanceof PDFMarkedContentExtractor)
+        {
+            ((PDFMarkedContentExtractor) this.context).beginMarkedContentSequence(tag, properties);
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/BeginText.java
new file mode 100644 (file)
index 0000000..fae97cf
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class BeginText extends OperatorProcessor
+{
+
+    /**
+     * process : BT : Begin text object.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        context.setTextMatrix( new Matrix());
+        context.setTextLineMatrix( new Matrix() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/CloseAndStrokePath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/CloseAndStrokePath.java
new file mode 100644 (file)
index 0000000..e560da7
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CloseAndStrokePath extends OperatorProcessor
+{
+
+    /**
+     * s close and stroke the path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        context.processOperator( "h", arguments );
+        context.processOperator( "S", arguments );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/Concatenate.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/Concatenate.java
new file mode 100644 (file)
index 0000000..98b04b1
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+
+public class Concatenate extends OperatorProcessor
+{
+
+    /**
+     * process : cm : Concatenate matrix to current transformation matrix.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If there is an error processing the operator.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+
+        //concatenate matrix to current transformation matrix
+        COSNumber a = (COSNumber) arguments.get(0);
+        COSNumber b = (COSNumber) arguments.get(1);
+        COSNumber c = (COSNumber) arguments.get(2);
+        COSNumber d = (COSNumber) arguments.get(3);
+        COSNumber e = (COSNumber) arguments.get(4);
+        COSNumber f = (COSNumber) arguments.get(5);
+
+        Matrix newMatrix = new Matrix();
+        newMatrix.setValue(0, 0, a.floatValue());
+        newMatrix.setValue(0, 1, b.floatValue());
+        newMatrix.setValue(1, 0, c.floatValue());
+        newMatrix.setValue(1, 1, d.floatValue());
+        newMatrix.setValue(2, 0, e.floatValue());
+        newMatrix.setValue(2, 1, f.floatValue());
+
+        //this line has changed
+        context.getGraphicsState().setCurrentTransformationMatrix(
+                newMatrix.multiply( context.getGraphicsState().getCurrentTransformationMatrix() ) );
+
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/EndMarkedContentSequence.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/EndMarkedContentSequence.java
new file mode 100644 (file)
index 0000000..992095c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * EMC : Ends a marked-content sequence begun by BMC or BDC.
+ * @author koch
+ * @version $Revision: $
+ */
+public class EndMarkedContentSequence extends OperatorProcessor
+{
+
+    /**
+     * {@inheritDoc}
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+        throws IOException
+    {
+        if (this.context instanceof PDFMarkedContentExtractor)
+        {
+            ((PDFMarkedContentExtractor) this.context).endMarkedContentSequence();
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/EndText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/EndText.java
new file mode 100644 (file)
index 0000000..bc7fa6f
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+public class EndText extends OperatorProcessor
+{
+
+    /**
+     * process : ET : End text object.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        context.setTextMatrix( null);
+        context.setTextLineMatrix( null);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/GRestore.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/GRestore.java
new file mode 100644 (file)
index 0000000..896ace9
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+public class GRestore extends OperatorProcessor
+{
+    /**
+     * process : Q : Restore graphics state.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        context.setGraphicsState( (PDGraphicsState)context.getGraphicsStack().pop() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/GSave.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/GSave.java
new file mode 100644 (file)
index 0000000..bca1273
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class GSave extends OperatorProcessor
+{
+    /**
+     * process : q : Save graphics state.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        context.getGraphicsStack().push( (PDGraphicsState)context.getGraphicsState().clone() );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/Invoke.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/Invoke.java
new file mode 100644 (file)
index 0000000..92c1b7b
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Invoke named XObject.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Mario Ivankovits
+ *
+ * @version $Revision: 1.9 $
+ */
+public class Invoke extends OperatorProcessor
+{
+    //private Set inProcess = new TreeSet();
+
+    /**
+     * process : Do - Invoke a named xobject.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If there is an error processing this operator.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSName name = (COSName) arguments.get( 0 );
+
+        Map xobjects = context.getXObjects();
+        PDXObject xobject = (PDXObject) xobjects.get(name.getName());
+        if (this.context instanceof PDFMarkedContentExtractor)
+        {
+            ((PDFMarkedContentExtractor) this.context).xobject(xobject);
+        }
+
+        if(xobject instanceof PDXObjectForm)
+        {
+            PDXObjectForm form = (PDXObjectForm)xobject;
+            COSStream invoke = (COSStream)form.getCOSObject();
+            PDResources pdResources = form.getResources();
+            PDPage page = context.getCurrentPage();
+            if(pdResources == null)
+            {
+                pdResources = page.findResources();
+            }
+
+            getContext().processSubStream( page, pdResources, invoke );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveAndShow.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveAndShow.java
new file mode 100644 (file)
index 0000000..58266dd
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class MoveAndShow extends OperatorProcessor
+{
+    /**
+     * ' Move to next line and show text.
+     * @param arguments List
+     * @param operator The operator that is being executed.
+     * @throws IOException If there is an error processing the operator.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        // Move to start of next text line, and show text
+        //
+
+        context.processOperator("T*", null);
+        context.processOperator("Tj", arguments);
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveText.java
new file mode 100644 (file)
index 0000000..d7cac88
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+public class MoveText extends OperatorProcessor
+{
+
+    /**
+     * process : Td : Move text position.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        COSNumber x = (COSNumber)arguments.get( 0 );
+        COSNumber y = (COSNumber)arguments.get( 1 );
+        Matrix td = new Matrix();
+        td.setValue( 2, 0, x.floatValue() );
+        td.setValue( 2, 1, y.floatValue() );
+        context.setTextLineMatrix( td.multiply( context.getTextLineMatrix() ) );
+        context.setTextMatrix( context.getTextLineMatrix().copy() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveTextSetLeading.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/MoveTextSetLeading.java
new file mode 100644 (file)
index 0000000..7b399bd
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class MoveTextSetLeading extends OperatorProcessor
+{
+
+    /**
+     * process : TD Move text position and set leading.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If there is an error during processing.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        //move text position and set leading
+        COSNumber y = (COSNumber)arguments.get( 1 );
+
+        ArrayList<COSBase> args = new ArrayList<COSBase>();
+        args.add(new COSFloat(-1*y.floatValue()));
+        context.processOperator("TL", args);
+        context.processOperator("Td", arguments);
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/NextLine.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/NextLine.java
new file mode 100644 (file)
index 0000000..34db42f
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class NextLine extends OperatorProcessor
+{
+    /**
+     * process : T* Move to start of next text line.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If there is an error during processing.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        //move to start of next text line
+        ArrayList<COSBase> args = new ArrayList<COSBase>();
+        args.add(new COSFloat(0.0f));
+        // this must be -leading instead of just leading as written in the
+        // specification (p.369) the acrobat reader seems to implement it the same way
+        args.add(new COSFloat(-1*context.getGraphicsState().getTextState().getLeading()));
+        // use Td instead of repeating code
+        context.processOperator("Td", args);
+
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/OperatorProcessor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/OperatorProcessor.java
new file mode 100644 (file)
index 0000000..610991e
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.PDFStreamEngine;
+import java.util.List;
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.3 $
+ */
+public abstract class OperatorProcessor
+{
+
+    /**
+     * The stream engine processing context.
+     */
+    protected PDFStreamEngine context = null;
+
+    /**
+     * Constructor.
+     *
+     */
+    protected OperatorProcessor()
+    {
+    }
+
+    /**
+     * Get the context for processing.
+     *
+     * @return The processing context.
+     */
+    protected PDFStreamEngine getContext()
+    {
+        return context;
+    }
+
+    /**
+     * Set the processing context.
+     *
+     * @param ctx The context for processing.
+     */
+    public void setContext(PDFStreamEngine ctx)
+    {
+        context = ctx;
+    }
+
+    /**
+     * process the operator.
+     * @param operator The operator that is being processed.
+     * @param arguments arguments needed by this operator.
+     *
+     * @throws IOException If there is an error processing the operator.
+     */
+    public abstract void process(PDFOperator operator, List<COSBase> arguments) throws IOException;
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetCharSpacing.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetCharSpacing.java
new file mode 100644 (file)
index 0000000..da1c134
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class SetCharSpacing extends OperatorProcessor
+{
+    /**
+     * process : Tc Set character spacing.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        //set character spacing
+        if( arguments.size() > 0 )
+        {
+            //There are some documents which are incorrectly structured, and have
+            //a wrong number of arguments to this, so we will assume the last argument
+            //in the list
+            Object charSpacing = arguments.get( arguments.size()-1 );
+            if( charSpacing instanceof COSNumber )
+            {
+                COSNumber characterSpacing = (COSNumber)charSpacing;
+                context.getGraphicsState().getTextState().setCharacterSpacing( characterSpacing.floatValue() );
+            }
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetGraphicsStateParameters.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetGraphicsStateParameters.java
new file mode 100644 (file)
index 0000000..3d17877
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetGraphicsStateParameters extends OperatorProcessor
+{
+    /**
+     * gs Set parameters from graphics state parameter dictionary.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        //set parameters from graphics state parameter dictionary
+        COSName graphicsName = (COSName)arguments.get( 0 );
+        PDExtendedGraphicsState gs = (PDExtendedGraphicsState)context.getGraphicsStates().get( graphicsName.getName() );
+        gs.copyIntoGraphicsState( context.getGraphicsState() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetHorizontalTextScaling.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetHorizontalTextScaling.java
new file mode 100644 (file)
index 0000000..62c67d5
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetHorizontalTextScaling extends OperatorProcessor
+{
+    /**
+     * Tz Set horizontal text scaling.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSNumber scaling = (COSNumber)arguments.get(0);
+        context.getGraphicsState().getTextState().setHorizontalScalingPercent( scaling.floatValue() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineCapStyle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineCapStyle.java
new file mode 100644 (file)
index 0000000..855ab8b
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineCapStyle extends OperatorProcessor
+{
+
+    /**
+     * Set the line cap style.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        int lineCapStyle = ((COSNumber)arguments.get( 0 )).intValue();
+        context.getGraphicsState().setLineCap( lineCapStyle );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineDashPattern.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineDashPattern.java
new file mode 100644 (file)
index 0000000..d480be7
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class SetLineDashPattern extends OperatorProcessor
+{
+
+    /**
+     * Set the line dash pattern.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSArray dashArray = (COSArray)arguments.get( 0 );
+        int dashPhase = ((COSNumber)arguments.get( 1 )).intValue();
+        PDLineDashPattern lineDash = new PDLineDashPattern( dashArray, dashPhase );
+        context.getGraphicsState().setLineDashPattern( lineDash );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineJoinStyle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineJoinStyle.java
new file mode 100644 (file)
index 0000000..88964f2
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineJoinStyle extends OperatorProcessor
+{
+
+    /**
+     * Set the line cap style.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        int lineJoinStyle = ((COSNumber)arguments.get( 0 )).intValue();
+        context.getGraphicsState().setLineJoin( lineJoinStyle );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineMiterLimit.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineMiterLimit.java
new file mode 100644 (file)
index 0000000..2f8a917
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+
+public class SetLineMiterLimit extends OperatorProcessor
+{
+    /**
+     * w Set miter limit.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSNumber miterLimit = (COSNumber)arguments.get( 0 );
+        context.getGraphicsState().setMiterLimit( miterLimit.doubleValue() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineWidth.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetLineWidth.java
new file mode 100644 (file)
index 0000000..d36be94
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetLineWidth extends OperatorProcessor
+{
+    /**
+     * w Set line width.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSNumber width = (COSNumber)arguments.get( 0 );
+        context.getGraphicsState().setLineWidth( width.doubleValue() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetMatrix.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetMatrix.java
new file mode 100644 (file)
index 0000000..7499ea6
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class SetMatrix extends OperatorProcessor
+{
+
+    /**
+     * Tm Set text matrix and text line matrix.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        //Set text matrix and text line matrix
+        COSNumber a = (COSNumber)arguments.get( 0 );
+        COSNumber b = (COSNumber)arguments.get( 1 );
+        COSNumber c = (COSNumber)arguments.get( 2 );
+        COSNumber d = (COSNumber)arguments.get( 3 );
+        COSNumber e = (COSNumber)arguments.get( 4 );
+        COSNumber f = (COSNumber)arguments.get( 5 );
+
+        Matrix textMatrix = new Matrix();
+        textMatrix.setValue( 0, 0, a.floatValue() );
+        textMatrix.setValue( 0, 1, b.floatValue() );
+        textMatrix.setValue( 1, 0, c.floatValue() );
+        textMatrix.setValue( 1, 1, d.floatValue() );
+        textMatrix.setValue( 2, 0, e.floatValue() );
+        textMatrix.setValue( 2, 1, f.floatValue() );
+        context.setTextMatrix( textMatrix );
+        context.setTextLineMatrix( textMatrix.copy() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetMoveAndShow.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetMoveAndShow.java
new file mode 100644 (file)
index 0000000..778875b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.6 $
+ */
+
+public class SetMoveAndShow extends OperatorProcessor
+{
+    /**
+     * " Set word and character spacing, move to next line, and show text.
+     * @param operator The operator that is being executed.
+     * @param arguments List.
+     * @throws IOException If there is an error processing the operator.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        //Set word and character spacing, move to next line, and show text
+        //
+        context.processOperator("Tw", arguments.subList(0,1));
+        context.processOperator("Tc", arguments.subList(1,2));
+        context.processOperator("'", arguments.subList(2,3));
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingCMYKColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingCMYKColor.java
new file mode 100644 (file)
index 0000000..8b6b0d1
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class SetNonStrokingCMYKColor extends OperatorProcessor
+{
+    /**
+     * k Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorSpace cs = PDDeviceCMYK.INSTANCE;
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        colorInstance.setColorSpace( cs );
+        float[] values = new float[4];
+        for( int i=0; i<arguments.size(); i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        colorInstance.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingCalRGBColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingCalRGBColor.java
new file mode 100644 (file)
index 0000000..02fef3f
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetNonStrokingCalRGBColor extends OperatorProcessor
+{
+    /**
+     * rg Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        float[] values = new float[3];
+        for( int i=0; i<arguments.size(); i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        colorInstance.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingColor.java
new file mode 100644 (file)
index 0000000..c1d74b1
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+import java.io.IOException;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ * 
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingColor extends OperatorProcessor 
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(SetNonStrokingColor.class);
+
+    /**
+     * sc,scn Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorSpace colorSpace = context.getGraphicsState().getNonStrokingColor().getColorSpace();
+        if (colorSpace != null) 
+        {
+            OperatorProcessor newOperator = null;
+            if (colorSpace instanceof PDDeviceGray) 
+            {
+                newOperator = new SetNonStrokingGrayColor();
+            }
+            else if (colorSpace instanceof PDDeviceRGB)
+            {
+                newOperator = new SetNonStrokingRGBColor();
+            }
+            else if (colorSpace instanceof PDDeviceCMYK)
+            {
+                newOperator = new SetNonStrokingCMYKColor();
+            }
+            else if (colorSpace instanceof PDICCBased)
+            {
+                newOperator = new SetNonStrokingICCBasedColor();
+            }
+            else if (colorSpace instanceof PDCalRGB)
+            {
+                newOperator = new SetNonStrokingCalRGBColor();
+            }   
+            else if (colorSpace instanceof PDSeparation)
+            {
+                newOperator = new SetNonStrokingSeparation();
+            }
+            else if (colorSpace instanceof PDDeviceN)
+            {
+                newOperator = new SetNonStrokingDeviceN();
+            }
+
+            if (newOperator != null) 
+            {
+                newOperator.setContext(getContext());
+                newOperator.process(operator, arguments);
+            }
+            else 
+            {
+                log.warn("Not supported colorspace "+colorSpace.getName() 
+                        + " within operator "+operator.getOperation());
+            }
+        }
+        else
+        {
+            log.warn("Colorspace not found in "+getClass().getName()+".process!!");
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingColorSpace.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingColorSpace.java
new file mode 100644 (file)
index 0000000..539b1ba
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class SetNonStrokingColorSpace extends OperatorProcessor
+{
+    private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+
+    /**
+     * cs Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+//      (PDF 1.1) Set color space for stroking operations
+        COSName name = (COSName)arguments.get( 0 );
+        PDColorSpace cs = PDColorSpaceFactory.createColorSpace( name, context.getColorSpaces() );
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        colorInstance.setColorSpace( cs );
+        int numComponents = cs.getNumberOfComponents();
+        float[] values = EMPTY_FLOAT_ARRAY;
+        if( numComponents >= 0 )
+        {
+            values = new float[numComponents];
+            for( int i=0; i<numComponents; i++ )
+            {
+                values[i] = 0f;
+            }
+            if( cs instanceof PDDeviceCMYK )
+            {
+                values[3] = 1f;
+            }
+        }
+        colorInstance.setColorSpaceValue( values );
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingDeviceN.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingDeviceN.java
new file mode 100644 (file)
index 0000000..b0306b8
--- /dev/null
@@ -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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * 
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingDeviceN extends OperatorProcessor 
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log =
+        LogFactory.getLog(SetNonStrokingDeviceN.class);
+
+    /**
+     * scn Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        PDColorSpace colorSpace = colorInstance.getColorSpace();
+        
+        if (colorSpace != null) 
+        {
+            List<COSBase> argList = arguments;
+            OperatorProcessor newOperator = null;
+
+            if (colorSpace instanceof PDDeviceN) {
+                PDDeviceN sep = (PDDeviceN) colorSpace;
+                colorSpace = sep.getAlternateColorSpace();
+                argList = sep.calculateColorValues(arguments).toList();
+            }
+
+            if (colorSpace instanceof PDDeviceGray)
+            {
+                newOperator = new SetNonStrokingGrayColor();
+            }
+            else if (colorSpace instanceof PDDeviceRGB)
+            {
+                newOperator = new SetNonStrokingRGBColor();
+            }
+            else if (colorSpace instanceof PDDeviceCMYK)
+            {
+                newOperator = new SetNonStrokingCMYKColor();
+            }
+            else if (colorSpace instanceof PDICCBased)
+            {
+                newOperator = new SetNonStrokingICCBasedColor();
+            }
+            else if (colorSpace instanceof PDCalRGB)
+            {
+                newOperator = new SetNonStrokingCalRGBColor();
+            }
+    
+            if (newOperator != null) 
+            {
+                colorInstance.setColorSpace(colorSpace);
+                newOperator.setContext(getContext());
+                newOperator.process(operator, argList);
+            }
+            else
+            {
+                log.warn("Not supported colorspace "+colorSpace.getName() 
+                        + " within operator "+operator.getOperation());
+            }
+        }
+        
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingGrayColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingGrayColor.java
new file mode 100644 (file)
index 0000000..27b3410
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class SetNonStrokingGrayColor extends OperatorProcessor
+{
+    /**
+     * rg Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorSpace cs = new PDDeviceGray();
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        colorInstance.setColorSpace( cs );
+        float[] values = new float[1];
+        if( arguments.size() >= 1 )
+        {
+            values[0] = ((COSNumber)arguments.get( 0 )).floatValue();
+        }
+        else
+        {
+            throw new IOException( "Error: Expected at least one argument when setting non stroking gray color");
+        }
+        colorInstance.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingICCBasedColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingICCBasedColor.java
new file mode 100644 (file)
index 0000000..d124cf5
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * 
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingICCBasedColor extends OperatorProcessor 
+{
+    /**
+     * scn Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        PDColorSpace cs = colorInstance.getColorSpace();
+        int numberOfComponents = cs.getNumberOfComponents();
+        float[] values = new float[numberOfComponents];
+        for( int i=0; i<numberOfComponents; i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        colorInstance.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingRGBColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingRGBColor.java
new file mode 100644 (file)
index 0000000..6cb6339
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetNonStrokingRGBColor extends OperatorProcessor
+{
+    /**
+     * rg Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorSpace cs = PDDeviceRGB.INSTANCE;
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        colorInstance.setColorSpace( cs );
+        float[] values = new float[3];
+        for( int i=0; i<arguments.size(); i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        colorInstance.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingSeparation.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetNonStrokingSeparation.java
new file mode 100644 (file)
index 0000000..cc38284
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * 
+ * @author <a href="mailto:WilliamstonConsulting@GMail.com">Daniel Wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingSeparation extends OperatorProcessor 
+{
+
+    /**
+     * scn Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+        PDColorSpace colorSpace = colorInstance.getColorSpace();
+        
+        if (colorSpace != null) 
+        {
+            PDSeparation sep = (PDSeparation) colorSpace;
+            colorSpace = sep.getAlternateColorSpace();
+            COSArray values = sep.calculateColorValues(arguments.get(0));
+            colorInstance.setColorSpaceValue(values.toFloatArray());
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingCMYKColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingCMYKColor.java
new file mode 100644 (file)
index 0000000..2df4725
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class SetStrokingCMYKColor extends OperatorProcessor
+{
+    /**
+     * K Set color space for stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState color = context.getGraphicsState().getStrokingColor();
+        color.setColorSpace( PDDeviceCMYK.INSTANCE );
+        float[] values = new float[4];
+        for( int i=0; i<arguments.size(); i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        color.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingCalRGBColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingCalRGBColor.java
new file mode 100644 (file)
index 0000000..bcb1628
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structural modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is replaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetStrokingCalRGBColor extends OperatorProcessor
+{
+    /**
+     * RG Set color space for stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState color = context.getGraphicsState().getStrokingColor();
+        float[] values = new float[3];
+        for( int i=0; i<arguments.size(); i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        color.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingColor.java
new file mode 100644 (file)
index 0000000..9d95fc1
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+import java.io.IOException;
+
+/**
+ * <p>Set the stroking color space.</p>
+ * 
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingColor extends OperatorProcessor 
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(SetStrokingColor.class);
+
+    /**
+     * SC,SCN Set color space for stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorSpace colorSpace = context.getGraphicsState().getStrokingColor().getColorSpace();
+        if (colorSpace != null) 
+        {
+            OperatorProcessor newOperator = null;
+            if (colorSpace instanceof PDDeviceGray)
+            {
+                newOperator = new SetStrokingGrayColor();
+            }
+            else if (colorSpace instanceof PDDeviceRGB)
+            {
+                newOperator = new SetStrokingRGBColor();
+            }
+            else if (colorSpace instanceof PDDeviceCMYK)
+            {
+                newOperator = new SetStrokingCMYKColor();
+            }
+            else if (colorSpace instanceof PDICCBased)
+            {
+                newOperator = new SetStrokingICCBasedColor();
+            }
+            else if (colorSpace instanceof PDCalRGB)
+            {
+                newOperator = new SetStrokingCalRGBColor();
+            }   
+            else if (colorSpace instanceof PDSeparation)
+            {
+                newOperator = new SetStrokingSeparation();
+            }
+            else if (colorSpace instanceof PDDeviceN)
+            {
+                newOperator = new SetStrokingDeviceN();
+            }
+
+            if (newOperator != null) 
+            {
+                newOperator.setContext(getContext());
+                newOperator.process(operator, arguments);
+            }
+            else
+            {
+                log.info("Not supported colorspace "+colorSpace.getName() 
+                        + " within operator "+operator.getOperation());
+            }
+        }
+        else 
+        {
+            log.warn("Colorspace not found in "+getClass().getName()+".process!!");
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingColorSpace.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingColorSpace.java
new file mode 100644 (file)
index 0000000..eeaf34b
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetStrokingColorSpace extends OperatorProcessor
+{
+    private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+
+    /**
+     * CS Set color space for stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        //(PDF 1.1) Set color space for stroking operations
+        COSName name = (COSName)arguments.get( 0 );
+        PDColorSpace cs = PDColorSpaceFactory.createColorSpace( name, context.getColorSpaces() );
+        PDColorState color = context.getGraphicsState().getStrokingColor();
+        color.setColorSpace( cs );
+        int numComponents = cs.getNumberOfComponents();
+        float[] values = EMPTY_FLOAT_ARRAY;
+        if( numComponents >= 0 )
+        {
+            values = new float[numComponents];
+            for( int i=0; i<numComponents; i++ )
+            {
+                values[i] = 0f;
+            }
+            if( cs instanceof PDDeviceCMYK )
+            {
+                values[3] = 1f;
+            }
+        }
+        color.setColorSpaceValue( values );
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingDeviceN.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingDeviceN.java
new file mode 100644 (file)
index 0000000..65e0142
--- /dev/null
@@ -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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * 
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingDeviceN extends OperatorProcessor 
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log =
+        LogFactory.getLog(SetStrokingDeviceN.class);
+
+    /**
+     * scn Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState color = context.getGraphicsState().getStrokingColor();
+        PDColorSpace colorSpace = color.getColorSpace();
+
+        if (colorSpace != null) 
+        {
+            OperatorProcessor newOperator = null;
+            List<COSBase> argList = arguments;
+
+            if (colorSpace instanceof PDDeviceN) 
+            {
+                PDDeviceN sep = (PDDeviceN) colorSpace;
+                colorSpace = sep.getAlternateColorSpace();
+                argList = sep.calculateColorValues(arguments).toList();
+            }
+
+            if (colorSpace instanceof PDDeviceGray)
+            {
+                newOperator = new SetStrokingGrayColor();
+            }
+            else if (colorSpace instanceof PDDeviceRGB)
+            {
+                newOperator = new SetStrokingRGBColor();
+            }
+            else if (colorSpace instanceof PDDeviceCMYK)
+            {
+                newOperator = new SetStrokingCMYKColor();
+            }
+            else if (colorSpace instanceof PDICCBased)
+            {
+                newOperator = new SetStrokingICCBasedColor();
+            }
+            else if (colorSpace instanceof PDCalRGB)
+            {
+                newOperator = new SetStrokingCalRGBColor();
+            }
+
+            if (newOperator != null) 
+            {
+                color.setColorSpace(colorSpace);
+                newOperator.setContext(getContext());
+                newOperator.process(operator, argList);
+            }
+            else
+            {
+                log.warn("Not supported colorspace "+colorSpace.getName() 
+                        + " within operator "+operator.getOperation());
+            }
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingGrayColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingGrayColor.java
new file mode 100644 (file)
index 0000000..4122646
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class SetStrokingGrayColor extends OperatorProcessor
+{
+    /**
+     * RG Set color space for stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState color = context.getGraphicsState().getStrokingColor();
+        color.setColorSpace( new PDDeviceGray() );
+        float[] values = new float[1];
+        if( arguments.size() >= 1 )
+        {
+            values[0] = ((COSNumber)arguments.get( 0 )).floatValue();
+        }
+        else
+        {
+            throw new IOException( "Error: Expected at least one argument when setting non stroking gray color");
+        }
+        color.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingICCBasedColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingICCBasedColor.java
new file mode 100644 (file)
index 0000000..4e3009c
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * 
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingICCBasedColor extends OperatorProcessor 
+{
+    /**
+     * scn Set color space for stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState color = context.getGraphicsState().getStrokingColor();
+        PDColorSpace cs = color.getColorSpace();
+        int numberOfComponents = cs.getNumberOfComponents();
+        float[] values = new float[numberOfComponents];
+        for( int i=0; i<numberOfComponents; i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        color.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingRGBColor.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingRGBColor.java
new file mode 100644 (file)
index 0000000..0bd4295
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetStrokingRGBColor extends OperatorProcessor
+{
+    /**
+     * RG Set color space for stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState color = context.getGraphicsState().getStrokingColor();
+        color.setColorSpace( PDDeviceRGB.INSTANCE );
+        float[] values = new float[3];
+        for( int i=0; i<arguments.size(); i++ )
+        {
+            values[i] = ((COSNumber)arguments.get( i )).floatValue();
+        }
+        color.setColorSpaceValue( values );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingSeparation.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetStrokingSeparation.java
new file mode 100644 (file)
index 0000000..f013036
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * 
+ * @author <a href="mailto:WilliamstonConsulting@GMail.com">Daniel Wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingSeparation extends OperatorProcessor 
+{
+
+    /**
+     * scn Set color space for non stroking operations.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PDColorState colorInstance = context.getGraphicsState().getStrokingColor();
+        PDColorSpace colorSpace = colorInstance.getColorSpace();
+
+        if (colorSpace != null) 
+        {
+            PDSeparation sep = (PDSeparation) colorSpace;
+            colorSpace = sep.getAlternateColorSpace();
+            COSArray values = sep.calculateColorValues(arguments.get(0));
+            colorInstance.setColorSpaceValue(values.toFloatArray());
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextFont.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextFont.java
new file mode 100644 (file)
index 0000000..5beeeb1
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+
+public class SetTextFont extends OperatorProcessor
+{
+    /**
+     * Tf selectfont Set text font and size.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        //there are some documents that are incorrectly structured and
+        //arguments are in the wrong spot, so we will silently ignore them
+        //if there are no arguments
+        if( arguments.size() >= 2 )
+        {
+            //set font and size
+            COSName fontName = (COSName)arguments.get( 0 );
+            float fontSize = ((COSNumber)arguments.get( 1 ) ).floatValue();
+            context.getGraphicsState().getTextState().setFontSize( fontSize );
+
+            context.getGraphicsState().getTextState().setFont( (PDFont)context.getFonts().get( fontName.getName() ) );
+            if( context.getGraphicsState().getTextState().getFont() == null )
+            {
+                throw new IOException( "Error: Could not find font(" + fontName + ") in map=" + context.getFonts() );
+            }
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextLeading.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextLeading.java
new file mode 100644 (file)
index 0000000..8d07b04
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class SetTextLeading extends OperatorProcessor
+{
+    /**
+     * TL Set text leading.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        COSNumber leading = (COSNumber)arguments.get( 0 );
+        context.getGraphicsState().getTextState().setLeading( leading.floatValue() );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextRenderingMode.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextRenderingMode.java
new file mode 100644 (file)
index 0000000..ae40667
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetTextRenderingMode extends OperatorProcessor
+{
+    /**
+     * Tr Set text rendering mode.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSNumber mode = (COSNumber)arguments.get( 0 );
+        context.getGraphicsState().getTextState().setRenderingMode( mode.intValue() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextRise.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetTextRise.java
new file mode 100644 (file)
index 0000000..e564728
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetTextRise extends OperatorProcessor
+{
+    /**
+     * Ts Set text rise.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSNumber rise = (COSNumber)arguments.get(0);
+        context.getGraphicsState().getTextState().setRise( rise.floatValue() );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetWordSpacing.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/SetWordSpacing.java
new file mode 100644 (file)
index 0000000..bed107f
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class SetWordSpacing extends OperatorProcessor
+{
+    /**
+     * Tw Set word spacing.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        //set word spacing
+        COSNumber wordSpacing = (COSNumber)arguments.get( 0 );
+        context.getGraphicsState().getTextState().setWordSpacing( wordSpacing.floatValue() );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/ShowText.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/ShowText.java
new file mode 100644 (file)
index 0000000..3731368
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class ShowText extends OperatorProcessor
+{
+
+    /**
+     * Tj show Show text.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If there is an error processing this operator.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSString string = (COSString)arguments.get( 0 );
+        context.processEncodedText( string.getBytes() );
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/ShowTextGlyph.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/ShowTextGlyph.java
new file mode 100644 (file)
index 0000000..c1d1b83
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import java.io.IOException;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.6 $
+ */
+
+public class ShowTextGlyph extends OperatorProcessor
+{
+    /**
+     * TJ Show text, allowing individual glyph positioning.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If there is an error processing this operator.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        COSArray array = (COSArray)arguments.get( 0 );
+        int arraySize = array.size();
+        float fontsize = context.getGraphicsState().getTextState().getFontSize();
+        float horizontalScaling = context.getGraphicsState().getTextState().getHorizontalScalingPercent()/100;
+        for( int i=0; i<arraySize; i++ )
+        {
+            COSBase next = array.get( i );
+            if( next instanceof COSNumber )
+            {
+                float adjustment = ((COSNumber)next).floatValue();
+                Matrix adjMatrix = new Matrix();
+                adjustment=-(adjustment/1000)*horizontalScaling*fontsize;
+                // TODO vertical writing mode
+                adjMatrix.setValue( 2, 0, adjustment );
+                context.setTextMatrix( adjMatrix.multiply(context.getTextMatrix(), adjMatrix) );
+            }
+            else if( next instanceof COSString )
+            {
+                context.processEncodedText( ((COSString)next).getBytes() );
+            }
+            else
+            {
+                throw new IOException( "Unknown type in array for TJ operation:" + next );
+            }
+        }
+    }
+
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/package.html
new file mode 100644 (file)
index 0000000..b3774c2
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains implementations of all of the PDF operators.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/AppendRectangleToPath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/AppendRectangleToPath.java
new file mode 100644 (file)
index 0000000..0b04318
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class AppendRectangleToPath extends OperatorProcessor
+{
+
+
+    /**
+     * process : re : append rectangle to path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        PageDrawer drawer = (PageDrawer)context;
+
+        COSNumber x = (COSNumber)arguments.get( 0 );
+        COSNumber y = (COSNumber)arguments.get( 1 );
+        COSNumber w = (COSNumber)arguments.get( 2 );
+        COSNumber h = (COSNumber)arguments.get( 3 );
+
+        double x1 = x.doubleValue();
+        double y1 = y.doubleValue();
+        // create a pair of coordinates for the transformation 
+        double x2 = w.doubleValue()+x1;
+        double y2 = h.doubleValue()+y1;
+
+        Point2D startCoords = drawer.transformedPoint(x1,y1);
+        Point2D endCoords = drawer.transformedPoint(x2,y2);
+
+        float width = (float)(endCoords.getX()-startCoords.getX());
+        float height = (float)(endCoords.getY()-startCoords.getY());
+        float xStart = (float)startCoords.getX();
+        float yStart = (float)startCoords.getY();
+
+        // To ensure that the path is created in the right direction,
+        // we have to create it by combining single lines instead of
+        // creating a simple rectangle
+        GeneralPath path = drawer.getLinePath();
+        path.moveTo(xStart, yStart);
+        path.lineTo(xStart+width, yStart);
+        path.lineTo(xStart+width, yStart+height);
+        path.lineTo(xStart, yStart+height);
+        path.lineTo(xStart, yStart);
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/BeginInlineImage.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/BeginInlineImage.java
new file mode 100644 (file)
index 0000000..0a41c42
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDInlinedImage;
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class BeginInlineImage extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(BeginInlineImage.class);
+
+    /**
+     * process : BI : begin inline image.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If there is an error displaying the inline image.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)  throws IOException
+    {
+        PageDrawer drawer = (PageDrawer)context;
+        PDPage page = drawer.getPage();
+        //begin inline image object
+        ImageParameters params = operator.getImageParameters();
+        PDInlinedImage image = new PDInlinedImage();
+        image.setImageParameters( params );
+        image.setImageData( operator.getImageData() );
+        BufferedImage awtImage = image.createImage( context.getColorSpaces() );
+
+        if (awtImage == null) 
+        {
+            log.warn("BeginInlineImage.process(): createImage returned NULL");
+            return;
+        }
+        int imageWidth = awtImage.getWidth();
+        int imageHeight = awtImage.getHeight();
+        double pageHeight = drawer.getPageSize().getHeight();
+        
+        Matrix ctm = drawer.getGraphicsState().getCurrentTransformationMatrix();
+        int pageRotation = page.findRotation();
+
+        AffineTransform ctmAT = ctm.createAffineTransform();
+        ctmAT.scale(1f/imageWidth, 1f/imageHeight);
+        Matrix rotationMatrix = new Matrix();
+        rotationMatrix.setFromAffineTransform( ctmAT );
+        // calculate the inverse rotation angle
+        // scaleX = m00 = cos
+        // shearX = m01 = -sin
+        // tan = sin/cos
+        double angle = Math.atan(ctmAT.getShearX()/ctmAT.getScaleX());
+        Matrix translationMatrix = null;
+        if (pageRotation == 0 || pageRotation == 180) 
+        {
+            translationMatrix = Matrix.getTranslatingInstance((float)(Math.sin(angle)*ctm.getXScale()), (float)(pageHeight-2*ctm.getYPosition()-Math.cos(angle)*ctm.getYScale())); 
+        }
+        else if (pageRotation == 90 || pageRotation == 270) 
+        {
+            translationMatrix = Matrix.getTranslatingInstance((float)(Math.sin(angle)*ctm.getYScale()), (float)(pageHeight-2*ctm.getYPosition())); 
+        }
+        rotationMatrix = rotationMatrix.multiply(translationMatrix);
+        rotationMatrix.setValue(0, 1, (-1)*rotationMatrix.getValue(0, 1));
+        rotationMatrix.setValue(1, 0, (-1)*rotationMatrix.getValue(1, 0));
+        AffineTransform at = new AffineTransform(
+                rotationMatrix.getValue(0,0),rotationMatrix.getValue(0,1),
+                rotationMatrix.getValue(1,0), rotationMatrix.getValue( 1, 1),
+                rotationMatrix.getValue(2,0),rotationMatrix.getValue(2,1)
+                );
+        drawer.drawImage(awtImage, at);
+    }
+}
\ No newline at end of file
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClipEvenOddRule.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClipEvenOddRule.java
new file mode 100644 (file)
index 0000000..cc642bc
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel Wilson</a>
+ * @version $Revision: 1.1 $
+ */
+public class ClipEvenOddRule extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(ClipEvenOddRule.class);
+
+    /**
+     * process : W* : set clipping path using even odd rule.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException if there is an error during execution.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+
+        try 
+        {
+            PageDrawer drawer = (PageDrawer)context;
+            drawer.setClippingPath(GeneralPath.WIND_EVEN_ODD);
+        } 
+        catch (Exception e) 
+        {
+            log.warn(e, e);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClipNonZeroRule.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClipNonZeroRule.java
new file mode 100644 (file)
index 0000000..bff5046
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel Wilson</a>
+ * @version $Revision: 1.1 $
+ */
+public class ClipNonZeroRule extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(ClipNonZeroRule.class);
+
+    /**
+     * process : W : Set the clipping path using non zero winding rule.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If there is an error during the processing.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+
+        try 
+        {
+            PageDrawer drawer = (PageDrawer)context;
+            drawer.setClippingPath(GeneralPath.WIND_NON_ZERO);
+        } 
+        catch (Exception e) 
+        {
+            log.warn(e, e);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CloseFillEvenOddAndStrokePath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CloseFillEvenOddAndStrokePath.java
new file mode 100644 (file)
index 0000000..a3bc43d
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class CloseFillEvenOddAndStrokePath extends org.apache.pdfbox.util.operator.OperatorProcessor
+{
+
+    /**
+     * fill and stroke the path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        context.processOperator( "h", arguments );
+
+        context.processOperator( "f*", arguments );
+
+        context.processOperator( "S", arguments );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CloseFillNonZeroAndStrokePath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CloseFillNonZeroAndStrokePath.java
new file mode 100644 (file)
index 0000000..a076f6d
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class CloseFillNonZeroAndStrokePath extends OperatorProcessor
+{
+
+    /**
+     * fill and stroke the path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        context.processOperator( "h", arguments );
+
+        context.processOperator( "f", arguments );
+
+        context.processOperator( "S", arguments );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClosePath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/ClosePath.java
new file mode 100644 (file)
index 0000000..e13323b
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ClosePath extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(ClosePath.class);
+
+    /**
+     * process : h : Close path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * 
+     * @throws IOException if something went wrong during logging
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PageDrawer drawer = (PageDrawer)context;
+        try
+        {
+            drawer.getLinePath().closePath();
+        }
+        catch( Throwable t )
+        {
+            log.warn(t, t);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveTo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveTo.java
new file mode 100644 (file)
index 0000000..1175574
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+import java.awt.geom.Point2D;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CurveTo extends OperatorProcessor
+{
+
+
+    /**
+     * process : c : Append curved segment to path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        PageDrawer drawer = (PageDrawer)context;
+
+        COSNumber x1 = (COSNumber)arguments.get( 0 );
+        COSNumber y1 = (COSNumber)arguments.get( 1 );
+        COSNumber x2 = (COSNumber)arguments.get( 2 );
+        COSNumber y2 = (COSNumber)arguments.get( 3 );
+        COSNumber x3 = (COSNumber)arguments.get( 4 );
+        COSNumber y3 = (COSNumber)arguments.get( 5 );
+
+        Point2D point1 = drawer.transformedPoint(x1.doubleValue(), y1.doubleValue());
+        Point2D point2 = drawer.transformedPoint(x2.doubleValue(), y2.doubleValue());
+        Point2D point3 = drawer.transformedPoint(x3.doubleValue(), y3.doubleValue());
+
+        drawer.getLinePath().curveTo((float)point1.getX(), (float)point1.getY(), 
+                (float)point2.getX(), (float)point2.getY(), (float)point3.getX(), (float)point3.getY());
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveToReplicateFinalPoint.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveToReplicateFinalPoint.java
new file mode 100644 (file)
index 0000000..a6ac69b
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+import java.awt.geom.Point2D;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CurveToReplicateFinalPoint extends OperatorProcessor
+{
+
+
+    /**
+     * process : y : Append curved segment to path (final point replicated).
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        PageDrawer drawer = (PageDrawer)context;
+
+        COSNumber x1 = (COSNumber)arguments.get( 0 );
+        COSNumber y1 = (COSNumber)arguments.get( 1 );
+        COSNumber x3 = (COSNumber)arguments.get( 2 );
+        COSNumber y3 = (COSNumber)arguments.get( 3 );
+
+        Point2D point1 = drawer.transformedPoint(x1.doubleValue(), y1.doubleValue());
+        Point2D point3 = drawer.transformedPoint(x3.doubleValue(), y3.doubleValue());
+
+        drawer.getLinePath().curveTo((float)point1.getX(), (float)point1.getY(), 
+                (float)point3.getX(), (float)point3.getY(), (float)point3.getX(), (float)point3.getY());
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveToReplicateInitialPoint.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/CurveToReplicateInitialPoint.java
new file mode 100644 (file)
index 0000000..d89fca2
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CurveToReplicateInitialPoint extends OperatorProcessor
+{
+
+
+    /**
+     * process : v : Append curved segment to path (initial point replicated).
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        PageDrawer drawer = (PageDrawer)context;
+
+        COSNumber x2 = (COSNumber)arguments.get( 0 );
+        COSNumber y2 = (COSNumber)arguments.get( 1 );
+        COSNumber x3 = (COSNumber)arguments.get( 2 );
+        COSNumber y3 = (COSNumber)arguments.get( 3 );
+        GeneralPath path = drawer.getLinePath();
+        Point2D currentPoint = path.getCurrentPoint();
+
+        Point2D point2 = drawer.transformedPoint(x2.doubleValue(), y2.doubleValue());
+        Point2D point3 = drawer.transformedPoint(x3.doubleValue(), y3.doubleValue());
+
+        drawer.getLinePath().curveTo((float)currentPoint.getX(), (float)currentPoint.getY(),
+                (float)point2.getX(), (float)point2.getY(), (float)point3.getX(), (float)point3.getY());
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/EndPath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/EndPath.java
new file mode 100644 (file)
index 0000000..1c68edd
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class EndPath extends OperatorProcessor
+{
+
+
+    /**
+     * process : n : End path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        PageDrawer drawer = (PageDrawer)context;
+        drawer.getLinePath().reset();
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillEvenOddAndStrokePath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillEvenOddAndStrokePath.java
new file mode 100644 (file)
index 0000000..abaeb47
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 101 $
+ */
+public class FillEvenOddAndStrokePath extends org.apache.pdfbox.util.operator.OperatorProcessor
+{
+
+    /**
+     * fill and stroke the path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PageDrawer drawer = (PageDrawer)context;
+        GeneralPath currentPath = (GeneralPath)drawer.getLinePath().clone();
+
+        context.processOperator( "f*", arguments );
+        drawer.setLinePath( currentPath );
+
+        context.processOperator( "S", arguments );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillEvenOddRule.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillEvenOddRule.java
new file mode 100644 (file)
index 0000000..d8f9b11
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class FillEvenOddRule extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(FillEvenOddRule.class);
+
+    /**
+     * process : f* : fill path using even odd rule.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException if there is an error during execution.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        try 
+        {
+            ///dwilson refactoring
+            PageDrawer drawer = (PageDrawer)context;
+            drawer.fillPath(GeneralPath.WIND_EVEN_ODD);
+        } 
+        catch (Exception e) 
+        {
+            log.warn(e, e);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillNonZeroAndStrokePath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillNonZeroAndStrokePath.java
new file mode 100644 (file)
index 0000000..6dbe214
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FillNonZeroAndStrokePath extends org.apache.pdfbox.util.operator.OperatorProcessor
+{
+
+    /**
+     * fill and stroke the path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PageDrawer drawer = (PageDrawer)context;
+        GeneralPath currentPath = (GeneralPath)drawer.getLinePath().clone();
+
+        context.processOperator( "f", arguments );
+        drawer.setLinePath( currentPath );
+
+        context.processOperator( "S", arguments );
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillNonZeroRule.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/FillNonZeroRule.java
new file mode 100644 (file)
index 0000000..ab2a3b4
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class FillNonZeroRule extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(FillNonZeroRule.class);
+
+    /**
+     * process : F/f : fill path using non zero winding rule.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If there is an error during the processing.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+
+        try 
+        {
+            ///dwilson refactoring
+            PageDrawer drawer = (PageDrawer)context;
+            drawer.fillPath(GeneralPath.WIND_NON_ZERO);
+        } 
+        catch (Exception e) 
+        {
+            log.warn(e, e);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/Invoke.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/Invoke.java
new file mode 100644 (file)
index 0000000..01fc450
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class Invoke extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(Invoke.class);
+
+    /**
+     * process : Do : Paint the specified XObject (section 4.7).
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If there is an error invoking the sub object.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        PageDrawer drawer = (PageDrawer)context;
+        PDPage page = drawer.getPage();
+        COSName objectName = (COSName)arguments.get( 0 );
+        Map xobjects = drawer.getResources().getXObjects();
+        PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
+        if ( xobject == null )
+        {
+            log.warn("Can't find the XObject for '"+objectName.getName()+"'");
+        }
+        else if( xobject instanceof PDXObjectImage )
+        {
+            PDXObjectImage image = (PDXObjectImage)xobject;
+            try
+            {
+                image.setGraphicsState(drawer.getGraphicsState());
+                BufferedImage awtImage = image.getRGBImage();
+                if (awtImage == null) 
+                {
+                    log.warn("getRGBImage returned NULL");
+                    return;//TODO PKOCH
+                }
+                int imageWidth = awtImage.getWidth();
+                int imageHeight = awtImage.getHeight();
+                double pageHeight = drawer.getPageSize().getHeight();
+
+                log.debug("imageWidth: " + imageWidth + "\t\timageHeight: " + imageHeight);
+        
+                Matrix ctm = drawer.getGraphicsState().getCurrentTransformationMatrix();
+                float yScaling = ctm.getYScale();
+                float angle = (float)Math.acos(ctm.getValue(0, 0)/ctm.getXScale());
+                if (ctm.getValue(0, 1) < 0 && ctm.getValue(1, 0) > 0)
+                    angle = (-1)*angle;
+                ctm.setValue(2, 1, (float)(pageHeight - ctm.getYPosition() - Math.cos(angle)*yScaling));
+                ctm.setValue(2, 0, (float)(ctm.getXPosition() - Math.sin(angle)*yScaling));
+                // because of the moved 0,0-reference, we have to shear in the opposite direction
+                ctm.setValue(0, 1, (-1)*ctm.getValue(0, 1));
+                ctm.setValue(1, 0, (-1)*ctm.getValue(1, 0));
+                AffineTransform ctmAT = ctm.createAffineTransform();
+                ctmAT.scale(1f/imageWidth, 1f/imageHeight);
+                drawer.drawImage( awtImage, ctmAT ); 
+            }
+            catch( Exception e )
+            {
+                e.printStackTrace();
+                log.error(e, e);
+            }
+        }
+        else if(xobject instanceof PDXObjectForm)
+        {
+            // save the graphics state
+            context.getGraphicsStack().push( (PDGraphicsState)context.getGraphicsState().clone() );
+            
+            PDXObjectForm form = (PDXObjectForm)xobject;
+            COSStream invoke = (COSStream)form.getCOSObject();
+            PDResources pdResources = form.getResources();
+            if(pdResources == null)
+            {
+                pdResources = page.findResources();
+            }
+            // if there is an optional form matrix, we have to
+            // map the form space to the user space
+            Matrix matrix = form.getMatrix();
+            if (matrix != null) 
+            {
+                Matrix xobjectCTM = matrix.multiply( context.getGraphicsState().getCurrentTransformationMatrix());
+                context.getGraphicsState().setCurrentTransformationMatrix(xobjectCTM);
+            }
+            getContext().processSubStream( page, pdResources, invoke );
+            
+            // restore the graphics state
+            context.setGraphicsState( (PDGraphicsState)context.getGraphicsStack().pop() );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/LineTo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/LineTo.java
new file mode 100644 (file)
index 0000000..16a48a8
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+import java.awt.geom.Point2D;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class LineTo extends OperatorProcessor
+{
+
+
+    /**
+     * process : l : Append straight line segment to path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments)
+    {
+        PageDrawer drawer = (PageDrawer)context;
+
+        //append straight line segment from the current point to the point.
+        COSNumber x = (COSNumber)arguments.get( 0 );
+        COSNumber y = (COSNumber)arguments.get( 1 );
+
+        Point2D pos = drawer.transformedPoint(x.doubleValue(), y.doubleValue());
+        drawer.getLinePath().lineTo((float)pos.getX(), (float)pos.getY());
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/MoveTo.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/MoveTo.java
new file mode 100644 (file)
index 0000000..4d177c1
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.Point2D;
+import java.util.List;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class MoveTo extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(MoveTo.class);
+
+    /**
+     * process : m : Begin new subpath.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If there is an error processing the operator.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        try
+        {
+            PageDrawer drawer = (PageDrawer)context;
+            COSNumber x = (COSNumber)arguments.get( 0 );
+            COSNumber y = (COSNumber)arguments.get( 1 );
+            Point2D pos = drawer.transformedPoint(x.doubleValue(), y.doubleValue());
+            drawer.getLinePath().moveTo((float)pos.getX(), (float)pos.getY());
+        }
+        catch (Exception exception)
+        {
+            log.warn( exception, exception);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SHFill.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SHFill.java
new file mode 100644 (file)
index 0000000..6c1246a
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of sh operator for page drawer.
+ *  See section 4.6.3 of the PDF 1.7 specification.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel Wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class SHFill extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(SHFill.class);
+
+    /**
+     * process : sh : shade fill the path or clipping area.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException if there is an error during execution.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        try 
+        {
+            PageDrawer drawer = (PageDrawer)context;
+            drawer.SHFill((COSName)(arguments.get(0)));
+            
+        } 
+        catch (Exception e) 
+        {
+            log.warn(e, e);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineCapStyle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineCapStyle.java
new file mode 100644 (file)
index 0000000..bee3b1b
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineCapStyle extends org.apache.pdfbox.util.operator.SetLineCapStyle
+{
+
+    /**
+     * Set the line cap style.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        super.process( operator, arguments );
+        int lineCapStyle = context.getGraphicsState().getLineCap();
+        PageDrawer drawer = (PageDrawer)context;
+        BasicStroke stroke = (BasicStroke)drawer.getStroke();
+        if (stroke == null)
+        {
+            drawer.setStroke( new BasicStroke(1,lineCapStyle,BasicStroke.JOIN_MITER) );
+        }
+        else
+        {
+            drawer.setStroke( new BasicStroke(stroke.getLineWidth(), lineCapStyle, stroke.getLineJoin(), 
+                    stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()));
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineDashPattern.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineDashPattern.java
new file mode 100644 (file)
index 0000000..17d5d43
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineDashPattern extends org.apache.pdfbox.util.operator.SetLineDashPattern
+{
+
+    /**
+     * Set the line dash pattern.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        super.process( operator, arguments );
+        PDLineDashPattern lineDashPattern = context.getGraphicsState().getLineDashPattern();
+        PageDrawer drawer = (PageDrawer)context;
+        BasicStroke stroke = (BasicStroke)drawer.getStroke();
+        if (stroke == null) 
+        {
+            if (lineDashPattern.isDashPatternEmpty()) 
+            {
+                drawer.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f) );
+            }
+            else
+            {
+                drawer.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, 
+                        lineDashPattern.getCOSDashPattern().toFloatArray(), lineDashPattern.getPhaseStart()) );
+            }
+        }
+        else 
+        {
+            if (lineDashPattern.isDashPatternEmpty()) 
+            {
+                drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), 
+                        stroke.getLineJoin(), stroke.getMiterLimit()) );
+            }
+            else
+            {
+                drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), stroke.getLineJoin(), 
+                        stroke.getMiterLimit(), lineDashPattern.getCOSDashPattern().toFloatArray(), 
+                        lineDashPattern.getPhaseStart()) );
+            }
+        }
+    }
+    
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineJoinStyle.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineJoinStyle.java
new file mode 100644 (file)
index 0000000..ad22605
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineJoinStyle extends org.apache.pdfbox.util.operator.SetLineJoinStyle
+{
+
+    /**
+     * Set the line cap style.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        super.process( operator, arguments );
+        int lineJoinStyle = context.getGraphicsState().getLineJoin();
+        PageDrawer drawer = (PageDrawer)context;
+        BasicStroke stroke = (BasicStroke)drawer.getStroke();
+        if (stroke == null)
+        {
+            drawer.setStroke( new BasicStroke(1,BasicStroke.CAP_SQUARE,lineJoinStyle) );
+        }
+        else
+        {
+            drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), lineJoinStyle,
+                    stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()) );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineMiterLimit.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineMiterLimit.java
new file mode 100644 (file)
index 0000000..a9e138e
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineMiterLimit extends org.apache.pdfbox.util.operator.SetLineMiterLimit
+{
+
+    /**
+     * Set the line dash pattern.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        super.process(operator, arguments);
+        float miterLimit = (float)context.getGraphicsState().getMiterLimit();
+        PageDrawer drawer = (PageDrawer)context;
+        BasicStroke stroke = (BasicStroke)drawer.getStroke();
+        if (stroke == null)
+        {
+            drawer.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 
+                    miterLimit, null, 0.0f));
+        }
+        else
+        {
+            drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), stroke.getLineJoin(), 
+                    miterLimit, null, 0.0f));
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineWidth.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/SetLineWidth.java
new file mode 100644 (file)
index 0000000..a7444f9
--- /dev/null
@@ -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.
+ */
+package org.apache.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class SetLineWidth extends org.apache.pdfbox.util.operator.SetLineWidth
+{
+
+    /**
+     * w Set line width.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        super.process( operator, arguments );
+        float lineWidth = (float)context.getGraphicsState().getLineWidth();
+        if (lineWidth == 0) 
+        {
+            lineWidth = 1;
+        }
+        PageDrawer drawer = (PageDrawer)context;
+        BasicStroke stroke = (BasicStroke)drawer.getStroke();
+        if (stroke == null)
+        {
+            drawer.setStroke( new BasicStroke( lineWidth ) );
+        }
+        else
+        {
+            drawer.setStroke( new BasicStroke(lineWidth, stroke.getEndCap(), stroke.getLineJoin(), 
+                    stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()) );
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/StrokePath.java b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/StrokePath.java
new file mode 100644 (file)
index 0000000..0baeaae
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.pdfbox.util.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class StrokePath extends OperatorProcessor
+{
+
+    /**
+     * Log instance.
+     */
+    private static final Log log = LogFactory.getLog(StrokePath.class);
+
+    /**
+     * S stroke the path.
+     * @param operator The operator that is being executed.
+     * @param arguments List
+     *
+     * @throws IOException If an error occurs while processing the font.
+     */
+    public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+    {
+        ///dwilson 3/19/07 refactor
+        try
+        {
+            PageDrawer drawer = (PageDrawer)context;
+    
+            float lineWidth = (float)context.getGraphicsState().getLineWidth();
+            Matrix ctm = context.getGraphicsState().getCurrentTransformationMatrix();
+            if ( ctm != null && ctm.getXScale() > 0) 
+            {
+                lineWidth = lineWidth * ctm.getXScale();
+            }
+            
+            BasicStroke stroke = (BasicStroke)drawer.getStroke();
+            if (stroke == null)
+            {
+                drawer.setStroke( new BasicStroke( lineWidth ) );
+            }
+            else
+            {
+                drawer.setStroke( new BasicStroke(lineWidth, stroke.getEndCap(), stroke.getLineJoin(), 
+                        stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()) );
+            }
+            drawer.strokePath();
+        }
+        catch (Exception exception)
+        {
+            log.warn(exception, exception);
+        }
+    }
+}
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/operator/pagedrawer/package.html
new file mode 100644 (file)
index 0000000..b3774c2
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains implementations of all of the PDF operators.
+</body>
+</html>
diff --git a/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/package.html b/fluidbook/tools/fwstk/src/org/apache/pdfbox/util/package.html
new file mode 100644 (file)
index 0000000..1cf7add
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains utility classes that are used by the PDFBox project.
+</body>
+</html>