Office-Dokument in PNG umwandeln (Word to PNG)

Hallo Leute!

Heute geht’s erstmals um C#, meiner „Spezial-Disziplin“. Ich wünsche euch viel Spaß beim Lesen!

Mittwoch Vormittag bekam ich den Auftrag, einen Prototypen zu entwickeln, welcher ausloten soll, inwiefern (und wie performant) es möglich ist, Office Dokumente in PNG-Grafiken  umzuwandeln. Um präziser zu werden, es geht um Word, Excel und Powerpoint-Dateien, nicht um z.B. Access oder OneNote-Dokumente.

Teil der Aufgabe war es, sowohl die Möglichkeit einer selbst geschriebenen Komponente, als auch die Verwendung einer kommerziellen Komponente zu testen. So verbrachte ich die ersten Stunden mit Recherchen und bin dabei über einige gute Artikel gestoßen.

Einen Weg zur direkten Konvertierung von z.B. einem Word-Dokument direkt in eine PNG-Grafik habe ich nur mittels einer kommerziellen Komponente, „doc2Any“  von http://www.verydoc.com/, gefunden. Auf der Webseite des Herstellers kann man sich eine Trial-Version herunterladen. Ein Beispiel, wie man die EXE-Datei mittels der notwendigen Startparameter anspricht, fand sich auf der Hersteller-Webseite, siehe http://www.verydoc.com/doc-to-any-shell.html:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
                Process proc = new Process();
                proc.StartInfo.FileName = @"C:\\doc2any.exe";
                string strArguments = "";
                strArguments += " D:\\temp\\sample.doc D:\\temp\\out.pdf";
                Console.WriteLine(strArguments);
                proc.StartInfo.Arguments = @strArguments;
                proc.Start();
                proc.WaitForExit();
        }
    }
}

Nachdem mir die Performance nun vorerst egal war, habe ich mich rein auf das Ergebnis der Konvertierung fokussiert. Dieses war jedoch völlig unbrauchbar. Die Formatierungen im Word-Dokument wurden tw. komplett verloren, die (Standard-)Schriftarten waren im Zieldokument völlig andere und zum teil unlesbar.

Da ich bereits vor dem Versuch mit doc2any keine weiteren, brauchbaren Möglichkeiten für eine Direktkonvertierung gefunden habe. habe ich mir überlegt, das Dokument zuerst in PDF oder XPS (programmatisch) zu exportieren/abzuspeichern. Dafür finden sich im Internet einige gute Beispiele, die meisten bauen auf den COM-Interop-Assemblies von Microsoft Office auf, was bedeutet, dass in der Folge auch am betreibenden Server Office installiert sein müsste, was in unserem Fall jedoch kein Problem darstellt.

Um mit den COM-Interop-Assemblies arbeiten zu können, muss man im Visual Studio dem Projekt bestimmte COM-Assemblies als Referenz hinzufügen. Konkret sind das

  • Microsoft.Office.Core
  • Microsoft.Office.Interop.Excel
  • Microsoft.Office.Interop.PowerPoint
  • Microsoft.Office.Interop.Word
Nachfolgendes Code-Snippet zeigt die Transformation in eine PDF-Datei:
/// <summary>
        /// Processes as word.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="extension">The extension.</param>
        /// <param name="targetExtension">The target extension.</param>
        [STAThread]
        static void ProcessAsWord(string fileName, string extension, string targetExtension)
        {

            var app = new Microsoft.Office.Interop.Word.Application();

            // Use for the parameter whose type are not known or
            // say Missing
            object unknown = Type.Missing;

            Console.WriteLine("Transformation to PDF started at: " + DateTime.Now.ToLongTimeString());

            // Source document open here
            // Additional Parameters are not known so that are
            // set as a missing type
            var objDoc = app.Documents.Open(Path.Combine(rootPath, fileName) + extension, false,
                                  true, ref unknown, ref unknown,
                                  ref unknown, ref unknown, ref unknown,
                                  ref unknown, ref unknown, ref unknown,
                                  false, ref unknown, ref unknown, ref unknown);

            objDoc.Select();
            app.ActiveDocument.Select();

            // Specifying the format in which you want the output file
            var format = Microsoft.Office.Interop.Word.WdExportFormat.wdExportFormatPDF;

            app.ActiveDocument.SaveAs(Path.Combine(rootPath, fileName), format,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown);

            // for closing the application
            app.Quit(false, ref unknown, ref unknown);

            Console.WriteLine("Transformation to PDF completed at: " + DateTime.Now.ToLongTimeString());
        }
Das Erzeugen der PDF-Datei funktioniert zuverlässig. Jedoch öffnet sich im Background bei jeder Konvertierung ein winword.exe-Prozess, der schon mal wenige Sekunden auf sich warten lassen kann. Bei kleinen Dateien (Word-Dokumenten mit 5-8 Seiten) ca. 3 Sekunden, bei größeren Dateien (Word-Dokumenten mit 60-70 Seiten) auch schon mal bis zu 8 Sekunden.
Auf Codeproject.com bin ich auf einen sehr gelungenen Artikel von „Lord TaGoH“ gestoßen, welcher die Konvertierung von PDF mittels Ghostscript in TIFF/PNG beschreibt: http://www.codeproject.com/KB/cs/GhostScriptUseWithCSharp.aspx
Im nachfolgenden Code-Snippet seht ihr, wie ich die Komponente von Lord Tagoh versucht habe zu implementieren:
static PDFConvert converter = new PDFConvert();
/// <summary>
        /// Converts the PDF.
        /// </summary>
        /// <param name="inputFile">The input file.</param>
        [STAThread]
        static void ConvertPDF(string inputFile)
        {
            bool Converted = false;
            //Setup the converter
            converter.RenderingThreads = 1;
            converter.TextAlphaBit = 2;
            converter.TextAlphaBit = 2;
            converter.OutputToMultipleFile = true;
            converter.FirstPageToConvert = -1;
            converter.LastPageToConvert = -1;
            converter.FitPage = true;
            converter.JPEGQuality = 100;//(int)numQuality.Value;
            converter.OutputFormat = "png16m";
            converter.DefaultPageSize = "a1";

            string extension = ".png";
            System.IO.FileInfo input = new FileInfo(inputFile);
            string output = string.Format("{0}\\{1}{2}",input.Directory,input.Name,extension);

            //If the output file exist alrady be sure to add a random name at the end until is unique!
            while (File.Exists(output))
            {
                output = output.Replace(extension, string.Format("{1}{0}", extension,DateTime.Now.Ticks));
            }

            converter.Convert(input.FullName, output);
        }
Wie ihr seht, kann man dem Konverter einige Parameter mitgeben, die u.a. die Qualität oder das Output-Format beeinflussen. Eine genauere Beschreibung der möglichen Parameter findet Ihr unter: http://www.hrangel.com.br/index.php/2006/12/04/converter-pdf-para-imagem-jpeg-em-c/
Das Ergebnis waren, wie wartet, einzelne PNG-Dateien. Qualitativ nicht ganz in Ordnung, deshalb habe ich die Pagesize auf „a1“ gesetzt, damit man auch am Screen etwas erkennen kann.
Mich machte diese Lösung jedoch noch nicht Glücklich. Zum einen, weil ich wieder auf eine „externe“ Komponente (Ghostscript) setzen musste und zum anderen, weil ich eine C#-nähere Lösung, am besten eine native Lösung, bevorzugt hätte. Also warum nicht XPS benutzen?
Das nachfolgende Codesnippet zeigt die Konvertierung einer Word-Datei in eine XPS-Datei.
/// <summary>
        /// Processes as word.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="extension">The extension.</param>
        /// <param name="targetExtension">The target extension.</param>
        [STAThread]
        static void ProcessAsWord(string fileName, string extension, string targetExtension)
        {

            var app = new Microsoft.Office.Interop.Word.Application();

            // Use for the parameter whose type are not known or
            // say Missing
            object unknown = Type.Missing;

            Console.WriteLine("Transformation to XPS started at: " + DateTime.Now.ToLongTimeString());

            // Source document open here
            // Additional Parameters are not known so that are
            // set as a missing type
            var objDoc = app.Documents.Open(Path.Combine(rootPath, fileName) + extension, false,
                                  true, ref unknown, ref unknown,
                                  ref unknown, ref unknown, ref unknown,
                                  ref unknown, ref unknown, ref unknown,
                                  false, ref unknown, ref unknown, ref unknown);

            objDoc.Select();
            app.ActiveDocument.Select();

            // Specifying the format in which you want the output file
            var format = Microsoft.Office.Interop.Word.WdExportFormat.<strong>wdExportFormatXPS;</strong>

            //Changing the format of the document
            /*app.ActiveDocument.ExportAsFixedFormat(Path.Combine(rootPath, fileName), format, false,
                                                   WdExportOptimizeFor.wdExportOptimizeForOnScreen,
                                                   WdExportRange.wdExportFromTo, 1, 2,
                                                   WdExportItem.wdExportDocumentWithMarkup, false, false,
                                                   WdExportCreateBookmarks.wdExportCreateNoBookmarks, false, true, false,
                                                   unknown);*/

            app.ActiveDocument.SaveAs(Path.Combine(rootPath, fileName), format,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown, ref unknown,
                                         ref unknown, ref unknown);

            // for closing the application
            app.Quit(false, ref unknown, ref unknown);

        }
In meiner Recherche, XPS in PNG umzuwandeln, bin ich auf folgenden Artikel gestossen:http://stackoverflow.com/questions/6560041/how-convert-xps-file-to-image-high-quality
Dort wird mittels Beispiel gut beschrieben, wie meine Aufgabe zu lösen wäre. Was mir dabei besonders gefallen hat, ist, dass es native Implementierungen für XPS gibt, u.a. der Namespace: System.Windows.XPS oder System.Windows.Documents.
Nachfolgend ein Codesnippet, welches meine Implementierung zeigt:
<summary>
        /// Converts the XPS to target format.
        /// </summary>
        /// <param name="inputFile">The input file.</param>
        /// <param name="targetFileName">Name of the target file.</param>
        ///
        [STAThread]
        static void ConvertXpsToTargetFormat(string inputFile, string fileName, string extension)
        {
            // XPS Document
            XpsDocument xpsDoc = new XpsDocument(inputFile, System.IO.FileAccess.Read);
            FixedDocumentSequence docSeq = xpsDoc.GetFixedDocumentSequence();
            // The number of pages
            var PageCount = docSeq.References[0].GetDocument(false).Pages.Count;

            // Convert a XPS page to a PNG file
            for (int pageNum = 0; pageNum < PageCount; pageNum++)
            {
                DocumentPage docPage = docSeq.DocumentPaginator.GetPage(pageNum);
                BitmapImage bitmap = new BitmapImage();
                RenderTargetBitmap renderTarget =
                    new RenderTargetBitmap((int)docPage.Size.Height + 1,(int)docPage.Size.Height + 1,
                                           96,
                                           96, PixelFormats.Pbgra32);
                renderTarget.Render(docPage.Visual);

                PngBitmapEncoder encoder = new PngBitmapEncoder();

                encoder.Frames.Add(BitmapFrame.Create(renderTarget));

                MemoryStream pageOutStream = new System.IO.MemoryStream();
                encoder.Save(pageOutStream);

                FileStream pageOutStream = new FileStream(inputFile.Replace(".xps", "") + ".Page" + pageNum + ".png", FileMode.Create, FileAccess.Write);
                encoder.Save(pageOutStream);

            }
            Console.WriteLine("created png at: " + DateTime.Now.ToLongTimeString());
        }

 

Performance
Die Performance stellt sich leider noch als größtes Problem dar. Die Konvertierung, egal ob mittels XPS oder PDF, benötigt für ein 70 Seiten starkes Word-Dokument bis zu 18 Sekunden. Dabei habe ich gerade beim Erzeugen der PNG-Dateien mittels XPS schon Multithreading (in den Codesnippets nicht ersichtlich, hier darf jeder seine eigene Lösung finden) verwendet. Die 18 Sekunden unterteilen sich in ca. 50% für das Konvertieren von Word in PDF/XPS und ca. 50% für das Erzeugen der PNG Grafiken.
Kleinere Word-Dateien konnten so auch in zwei bis drei Sekunden erzeugt werden.

 

 

Fazit
Es muss jeder für sich entscheiden, ob diese Durchlaufzeit für einen Akzeptabel ist. Bei uns wird dieses Service vmtl. nicht On-Demand zur Verfügung stehen, sondern eher in einem nächtlichen Job seine Verwendung finden. Die Ergebnisse der Konvertierugen waren durchwegs gut, sogar die Überarbeitungs-Kommentare in Word konnten so mitgenommen werden.

 

 

Cheers,
Chris aka lahme Sache…

 

 

PS: 
Beim Versuch alle PNG-Grafiken zu einer zusammenzufügen bin ich auf eine Limitierung gestoßen, welche ich euch nicht vorenthalten will. Als ich ca. 70 zusammengesetzte Seiten in einer PNG-Grafik abspeichern wollte, bekam ich eine Exception. „Allgemeiner GDI+ Fehler“, ohne weitere Details. Meine Recherchen haben ergeben, dass GDI+ und Windows nur PNG-Dateien mit einer Maximal-Berite/Höhe von ca. 37.000 px erzeugen kann. In meinem Fall wären das jedoch deutlich über 77.000 px gewesen. Es ist also definitiv nicht möglich, zumindest mittels GDI+, derartige PNG-Dateien zu erzeugen und wohl auch wenig praktikabel. Ich würde euch daher davon abraten!
Advertisements

Über Christian

Ich komme aus Wien, wo ich gemeinsam mit meiner Lebensgefährtin und unserem brummenden Cocker-Spaniel Chewbacca lebe. Derzeit arbeite ich bei einem größeren Konzern, welcher in den Bereichen öffentlicher Verkehr, Energie, Telekommunikation, bis hin zur Bestattung tätig ist. Im Rahmen meiner Tätigkeit liegt der Fokus stark auf Eigenentwicklungen mittels div. .NET/Microsoft-Technologien. Derzeit arbeite ich hauptsächlich mit/am Microsoft Dyncamics CRM 2011, mit dem Ziel Geschäftsprozesse abzubilden.
Dieser Beitrag wurde unter C Sharp, GDI, Office veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Eine Antwort zu Office-Dokument in PNG umwandeln (Word to PNG)

  1. Pingback: Office-Dokument in PDF/XPS umwandeln « Programmers Horror

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s