Programmaticlly print & or save documents

Jan 14, 2012 at 5:43 AM

I've read the example on declaring a Command="{Binding PrintCommand, ElementName=Printer}" in a button or hyperlink button to execute prompting a print window, and properly printing the currently loaded document in the document viewer.

What I would like to know... Is there a way to print in the code behind?

Also,

Is there a way to save a document in the code behind? For clarification, I'm looking for a way to programmatically save a document with all annotations (possibly get a Byte() or Stream of the document with the annotaions for me to send to a service or something?) AND a way for my users to click "save" and have the document download to their desktop or where ever. 

Any help is greatly appreciated.

Coordinator
Jan 15, 2012 at 1:53 PM

The DocumentPrinter control has a method Print that use you can use to start a print job in the code.

Saving a document is not included in Document Toolkit, but is fairly easy to implement. Somewhere you create a package reader that loads a document from a stream. Use this stream and copy its contents to a file stream when you want to save the document.

Serialized annotations are accessible using the AnnotationStore class. You can save your custom annotations inside an XPS document, it's just a ZIP file where you can add arbitrary content such as custom annotations.

- Koen

Jan 17, 2012 at 6:44 AM

kozw... Thanks!

So, I have a situation where I will have word documents that I want the user to be able to annotate. They obviously can't annotate that actual word doc  through the toolkit, so I create a .doc and .docx to .xps converter. I will be saving the annotated document as a new version of the original. My question is how do I combine the stream I copy of the .xps document with the annotations? I get the I can just take stream when I receive it from my raised event, and copy it as a private property or variable, and I get that I will have to get the annotations from the document from the annotationstore class, but how do I save these "inside" the xps document? Would you mind giving a small example?

Coordinator
Jan 17, 2012 at 4:57 PM

You'll need to use a ZIP manager to open the document stream and add a file entry for the annotations xml. ZIP managers are available in the DotNetZip and SharpZipLib packages (open sourced in this project). This is a fairly standard zip modification procedure.

- Koen

Feb 5, 2012 at 9:16 AM

You wouldn't by chance want to post an example of this? I understand how to get the filestream of the currently displayed document, but do I take that, and somehow "add" the annotation into the filestream via DotNetZip? I've never used this library, and am trying to finish off this last feature. Also, it is possible to save the annotation related to XPS, TIFF, and PDF... correct?.. not just XPS?

Feb 5, 2012 at 9:54 AM

Here's what I started with a couple weeks ago when I originally asked the question, and then had to complete a bunch of other stuff.. so back to this.

 Dim ans As List(Of Annotation) = annotations.ToList()

   Using zip As New ZipFile(Current_FileBytes.ToStream()) ' <--- This can't be right

                For Each a As Annotation In ans
                    'add annotations to zip file??
                Next

  End Using

... Here's what I need it to do, and I think I will end up having to send the annotations as xml back to the server, and save them. This is a document viewer with a tree navigation, that allow for users to view a document... add a sticky note, and save it as an annotated version. I realize I will probably have to use my pdf to tif converter, and then save the tif pages as an xps document, and then .zip them up. I can't save files to disk from SL, I'm sending the files back via a service, and saving them while logging SQL data, and firing WCF PUB SUB events. 

Coordinator
Feb 6, 2012 at 4:14 PM

Please note that there's no single solution for embedding custom serialized annotations into any document. In the case of XPS is relatively easy since XPS uses ZIP as package store. For PDF, TIFF, etc. I'm not sure there is solution at all.

If you are sure you want to continue with the approach of adding annotations xml into an XPS document I can recommend DotNetZip. You'll need to create a new ZipFile instance and provide it with the stream of the XPS document (as you did in your code snippet). The annotation store can save annotations to a stream in XML format. Use the ZipFile.AddEntry to add the annotations;

using (var zipFile = new ZipFile(Current_FileBytes.ToStream())) {
  using (var stream = new MemoryStream()) {
    // save annotations
    annotationStore.Save(stream);

    // reset stream position
    stream.Seek(0, SeekOrigin.Begin);

    // and add annotation to ZipFile
    zipFile.AddEntry("annotations.xml", stream);
  }
}

- Koen

Feb 6, 2012 at 4:36 PM

Thank you. I was planning on making an annotated version of each document, and leaving the original untouched anyways, so I will just convert the PDF's and TIFF's to XPS, and add the serialized annotations. Thank you very much very the example.

Feb 10, 2012 at 7:38 PM

I having this issue, so I'm assuming I'm using ZipFile from the wrong namespace or something, but the comment below describes what the blue squigily line is telling me.

zipFile.AddEntry("annotations.xml", stream) '<----- AddEntry is not a memeber of FirstFloor.Documents.IO.ZipFile

Feb 10, 2012 at 8:00 PM
Edited Feb 10, 2012 at 8:28 PM

 

ICSharpCode.SharpZipLib.ZipFile does not have a set of parameter or methods that coincide with the example.

Coordinator
Feb 10, 2012 at 8:03 PM

FirstFloor.Documents.IO.ZipFile is only for reading ZIP files. You'll need to use DotNetZip or SharpZipLib. My example was based on DotNetZip which is available in this open source project (or as NuGet package)

- Koen

Feb 10, 2012 at 8:24 PM

I have tried the DotNetZip and SharpZipLib added from Nuget, as well as http://dotnetzip.codeplex.com/releases/view/68268 library.

strongly typed namesspaces with class as follows:

 

ICSharpCode.SharpZipLib.ZipFile

 

Ionic.Zip.ZipFile

 

These zip file classes do not contain the methods you speak of.

 

the SharpZipLib offers a constructor with stream, but no AddEntry ... it has add, but no parameter for a stream

 

then ionic.zip has no constructor parameters for a stream and no Add or AddEntry

 

Can you please type out the entire namespance of the DotNetZip.ZipFile Class that you are using? 

 

*note: I'm user SL 4, because silverlight 5 is insanely buggy still, so our team is not allowed to use it yet.

Coordinator
Feb 10, 2012 at 8:34 PM

Use the Silverlight binaries of either ZIP library as available in this project. The NuGet packages are from firstfloorsoftware.com. Instructions at http://documenttoolkit.codeplex.com/wikipage?title=How%20To%20Install%20Document%20Toolkit%20Using%20NuGet

Feb 10, 2012 at 8:38 PM

The instruction in the link are exactly what I have done. After adding these, I have 3 different ZipFile classes to choose from depending on the namespace... all of which don't have parameters as you have shown here. I guess I will just search around in intelli-sense until I find it. Thanks man.

Feb 10, 2012 at 8:50 PM

Can you please just type out the full namespace with your classes? (ie  Namespace.Namespace.DotNetZip.ZipFile)

 

Firstfloor.documunts.IO.DotNetZip does not contain a class name ZipFile ... and that is the namespace that comes down from NuGet

Coordinator
Feb 10, 2012 at 9:53 PM

I was mistaken, the Silverlight version of the DotNetZip indeed doesn't contain AddEntry methods. I referred to the full .NET version. You may want to try the SharpZipLib, that library seems to have the AddEntry methods. 

- Koen

Feb 19, 2012 at 4:48 AM
Edited Feb 20, 2012 at 1:54 AM

Question:

 

Do I create zip file with an empty stream, and add both the .xps document, and the annotations as an .xml file to the zip file? 

 

 Dim dti As TreeViewItem = Me.docTreeView.SelectedItem
        Dim item As DocTreeViewItem = dti.Tag
        Using zippedMemoryStream As System.IO.MemoryStream = New System.IO.MemoryStream()
            Using ZipOutStream As New ICSharpCode.SharpZipLib.ZipOutputStream(zippedMemoryStream)
                ZipOutStream.SetLevel(9)
                'add xps document to zip                Dim xpsDoc As Byte() = Me.Current_FileBytes
                Dim docEntry As New ICSharpCode.SharpZipLib.ZipEntry(item.VersionName)
                ZipOutStream.PutNextEntry(docEntry)
                ZipOutStream.Write(xpsDoc, 0, xpsDoc.Length)

                'add annotations to zip                Dim annoStream As New System.IO.MemoryStream
                Me.annotations.Save(annoStream)
                annoStream.Seek(0, SeekOrigin.Begin)
                Dim annoBytes() As Byte = annoStream.ToArray()
                Dim annoEntry As New ICSharpCode.SharpZipLib.ZipEntry("annotations.xml")
                ZipOutStream.PutNextEntry(annoEntry)
                ZipOutStream.Write(annoBytes, 0, annoBytes.Length)
                ZipOutStream.Finish()
                'get byte() of zip file                Dim AnnotatedDocumentBytes() As Byte = zippedMemoryStream.ToArray()
                Me.FileService.UploadAnnotatedVersionAsync("Annotated_" & item.VersionName & ".zip", AnnotatedDocumentBytes, item.DocId.ToGuid(), item.VersionId.ToGuid(), Me.ThisUserMeta.CompanyId)
                annoStream.Close()                annoStream.Dispose()
            End Using
        End Using

 

or

Do I create zip file with the stream of the .xps document, and they just add annotations?

   Dim dti As TreeViewItem = Me.docTreeView.SelectedItem
        Dim item As DocTreeViewItem = dti.Tag
        Dim annoVersionStream As System.IO.MemoryStream = Me.Current_FileBytes.ToStream()
        Using ZipOutStream As New ICSharpCode.SharpZipLib.ZipOutputStream(annoVersionStream)
            'add annotations to zip            Dim annoStream As New System.IO.MemoryStream
            Me.annotations.Save(annoStream)
            annoStream.Seek(0, SeekOrigin.Begin)
            Dim annoBytes() As Byte = annoStream.ToArray()
            Dim annoEntry As New ICSharpCode.SharpZipLib.ZipEntry("annotations.xml")
            ZipOutStream.PutNextEntry(annoEntry)
            ZipOutStream.Write(annoBytes, 0, annoBytes.Length)
            ZipOutStream.Finish()
            'get byte() of zip file            Dim AnnotatedDocumentBytes() As Byte = annoVersionStream.ToArray()
            Me.FileService.UploadAnnotatedVersionAsync("Annotated_" & item.VersionName, AnnotatedDocumentBytes, item.DocId.ToGuid(), item.VersionId.ToGuid(), Me.ThisUserMeta.CompanyId)
            annoStream.Close()            annoStream.Dispose()
        End Using

or... something different. (I've tried both of these, and then tried passing the .zip or .xps in either paradigm, and no success.)

 

I've tried reading the stream like this:

 

   Me.DataSource.PackageReader = New FirstFloor.Documents.IO.SharpZipLib.SharpZipPackageReader(fileBytes.ToStream())

 

   Me.DataSource.PackageReader = New DefaultPackageReader(fileBytes.ToStream())

 

with no success. I get an error saying the xps document in not valid or the zip file is not compatible with the package reader... 

 

An example of saving annotations (sticky notes, and highlights), to a file, and then opening the file with a package reader would be a great help.. or at least pointing out what I'm doing wrong (fyi, I tried to format the code, and it added all these crazy characters, so I just went with plain text.)

 

Coordinator
Feb 20, 2012 at 12:52 PM

You'll need to read the XPS document itself as zip file. I've created a sample project doing just that. ICSharpZip has a bit of a archaic API, but it works well.

Sample project available at http://firstfloorsoftware.com/downloads/ModifyXps.zip

Hope this helps,

- Koen

Feb 20, 2012 at 4:56 PM

I am able to save the xps document via the code in your example, but when I go to load the document, I get a "Cannot find central directory"

 

I am loading it like so:

   Me.DataSource.PackageReader = New FirstFloor.Documents.IO.SharpZipLib.SharpZipPackageReader(fileBytes.ToStream())

 

Here's is my translation of your code in VB.Net. I changed getting the annotations from the Me.annotations.Save ( annotations store ) method, and am saving the zipStream.ToArray ( built in extension method of memory stream) ... other than that, it's the same as yours. The only thing I noticed in the example is that You declare a new entry, but don't add it to the zip file (or maybe just declaring it adds it? Anyway, let me know if you can see where I'm going wrong.

Private Sub SaveAnnotations()

Dim zipStream As System.IO.MemoryStream = Me.Current_FileBytes.ToStream()
        ' load the XPS document as ZIP       

Using zipFile = New ICSharpCode.SharpZipLib.ZipFile(zipStream)

  Dim entry = New ICSharpCode.SharpZipLib.ZipEntry("annotations.xml")

Using annotationsStream = New MemoryStream()
                Me.annotations.Save(annotationsStream)


                annotationsStream.Seek(0, SeekOrigin.Begin)
                Dim annotationsSource = New StreamDataSource(annotationsStream)

  zipFile.BeginUpdate()               

zipFile.Add(annotationsSource, "annotations.xml", CompressionMethod.Deflated)           

    zipFile.CommitUpdate()

  Dim AnnotatedDocumentBytes() As Byte = zipStream.ToArray()

                Me.FileService.UploadAnnotatedVersionAsync("Annotated_" & item.VersionName, AnnotatedDocumentBytes, item.DocId.ToGuid(), item.VersionId.ToGuid(), Me.ThisUserMeta.CompanyId)

            End Using

        End Using

End Sub

Coordinator
Feb 20, 2012 at 9:25 PM

I've updated the sample at http://firstfloorsoftware.com/downloads/ModifyXps.zip 

Apparently the ZipFile needs to be closed before the update is actually committed to stream. Also the new ZipEntry call is not needed and has been removed.

- Koen