Search code examples
wpfpdf

How to use PDF4NET to convert PDFPathVisualObject to System.Windows.Shapes.Path for wpf


I am trying to extract path elements from PDF files using PDF4NET and display them in WPF

I referenced the example of PDF4NET and obtained the PDFPathVisualObject through the following code.

            PDFFixedDocument document = new PDFFixedDocument("path.pdf");

            PDFContentExtractor ce = new PDFContentExtractor(document.Pages[0]);
            PDFVisualObjectCollection voc = ce.ExtractVisualObjects(false);

            PDFPathVisualObject pvo = voc[0] as PDFPathVisualObject

However, I encountered a problem when converting the PDFPathVisualObject to System.Windows.Shapes.Path. The path information displayed in the PathItems in PDFPathVisualObject seems to be inconsistent with the actual path information, As shown in the following figure (I randomly found a PDF with path information)

Actual content displayed in PDFXplorer

PathItems in PDFPathVisualObject

I would like to know how to convert PDFPathVisualObject to System. Windows. Shapes. Path


Solution

  • The values from the 1st screenshot (PDFXplorer application) are raw path points in standard PDF coordinate system.

    The points in 2nd screenshot are the path points in display coordinate system relative to top left corner of the visible page area. The raw path points are transformed through the current transformation matrix that is active when the path is painted and then converted from standard PDF coordinate system to display coordinate system considering the page mediabox/cropbox and page rotation.

    You can use directly the path points in the 2nd screenshot to create your WPF paths as they use the same coordinate system.

    The code below provides a start, it shows how to convert the PDFPathVisualObject to XAML paths:

    private void ConvertPDFPathsToXAML()
    {
        PDFFixedDocument document = new PDFFixedDocument("paths.pdf");
        PDFContentExtractor ce = new PDFContentExtractor(document.Pages[0]);
        PDFVisualObjectCollection voc = ce.ExtractVisualObjects(false, false);
    
        MemoryStream xamlStream = new MemoryStream();
        XmlTextWriter xamlWriter = new XmlTextWriter(xamlStream, Encoding.UTF8);
        xamlWriter.WriteStartElement("Canvas");
        xamlWriter.WriteAttributeString("xmlns", "http://schemas.microsoft.com/xps/2005/06");
        xamlWriter.WriteAttributeString("Width", string.Format(CultureInfo.InvariantCulture, "{0:0.####}", document.Pages[0].Width));
        xamlWriter.WriteAttributeString("Height", string.Format(CultureInfo.InvariantCulture, "{0:0.####}", document.Pages[0].Height));
        xamlWriter.WriteAttributeString("Background", "White");
    
        xamlWriter.WriteStartElement("Canvas.Clip");
        xamlWriter.WriteStartElement("RectangleGeometry");
        xamlWriter.WriteAttributeString("Rect", string.Format(CultureInfo.InvariantCulture, "0,0,{0:0.#####},{1:0.#####}", document.Pages[0].Width, document.Pages[0].Height));
        xamlWriter.WriteEndElement();
        xamlWriter.WriteEndElement();
    
        for (int i = 0; i < voc.Count; i++) 
        { 
            if (voc[i].Type == PDFVisualObjectType.Path)
            {
                PDFPathVisualObject path = (PDFPathVisualObject)voc[i];
    
                string pathGeometry = PdfPathToXamlPathGeometry(path, path.Brush != null, path.FillMode);
    
                xamlWriter.WriteStartElement("Path");
                xamlWriter.WriteAttributeString("Data", pathGeometry);
                if (path.Pen != null) 
                {
                    PDFRgbColor rgb = path.Pen.Color.ToRgbColor();
                    xamlWriter.WriteAttributeString("Stroke", string.Format("#{0:X02}{1:X02}{2:X02}", rgb.R, rgb.G, rgb.B));
                    xamlWriter.WriteAttributeString("StrokeThickness", string.Format("{0:0.####}", path.Pen.Width));
                }
                if (path.Brush != null)
                {
                    PDFRgbColor rgb = path.Brush.Color.ToRgbColor();
                    xamlWriter.WriteAttributeString("Fill", string.Format("#{0:X02}{1:X02}{2:X02}", rgb.R, rgb.G, rgb.B));
                }
                xamlWriter.WriteEndElement();
            }
        }
        xamlWriter.WriteEndElement();
        xamlWriter.Flush();
        xamlStream.Position = 0;
    
        Canvas canvas = (Canvas)XamlReader.Load(xamlStream);
        mainGrid.Children.Add(canvas);
    }
    
    private string PdfPathToXamlPathGeometry(PDFPathVisualObject path, bool isFilled, PDFFillMode fillMode)
    {
        double crtX = 0, crtY = 0;
        double x1, y1, x2, y2, x3, y3;
        StringBuilder pathGeometry = new StringBuilder();
    
        if (isFilled)
        {
            pathGeometry.Append(fillMode == PDFFillMode.EvenOdd ? "F0 " : "F1 ");
        }
    
        for (int i = 0; i < path.PathItems.Count; i++)
        {
            switch (path.PathItems[i].Type)
            {
                case PDFPathItemType.MoveTo: // m
                    x1 = path.PathItems[i].Points[0].X;
                    y1 = path.PathItems[i].Points[0].Y;
                    pathGeometry.AppendFormat(CultureInfo.InvariantCulture, "M {0:0.######} {1:0.######} ", x1, y1);
                    crtX = x1;
                    crtY = y1;
                    break;
                case PDFPathItemType.LineTo: // l
                    x1 = path.PathItems[i].Points[0].X;
                    y1 = path.PathItems[i].Points[0].Y;
                    pathGeometry.AppendFormat(CultureInfo.InvariantCulture, "L {0:0.######} {1:0.######} ", x1, y1);
                    crtX = x1;
                    crtY = y1;
                    break;
                case PDFPathItemType.CCurveTo: // c
                    x1 = path.PathItems[i].Points[0].X;
                    y1 = path.PathItems[i].Points[0].Y;
                    x2 = path.PathItems[i].Points[1].X;
                    y2 = path.PathItems[i].Points[1].Y;
                    x3 = path.PathItems[i].Points[2].X;
                    y3 = path.PathItems[i].Points[2].Y;
                    pathGeometry.AppendFormat(CultureInfo.InvariantCulture,
                        "C {0:0.######} {1:0.######} {2:0.######} {3:0.######} {4:0.######} {5:0.######} ", x1, y1, x2, y2, x3, y3);
                    crtX = x3;
                    crtY = y3;
                    break;
                case PDFPathItemType.VCurveTo: // v
                    x1 = path.PathItems[i].Points[0].X;
                    y1 = path.PathItems[i].Points[0].Y;
                    x2 = path.PathItems[i].Points[1].X;
                    y2 = path.PathItems[i].Points[1].Y;
                    pathGeometry.AppendFormat(CultureInfo.InvariantCulture,
                        "C {0:0.######} {1:0.######} {2:0.######} {3:0.######} {4:0.######} {5:0.######} ", crtX, crtY, x1, y1, x2, y2);
                    crtX = x2;
                    crtY = y2;
                    break;
                case PDFPathItemType.YCurveTo: // y
                    x1 = path.PathItems[i].Points[0].X;
                    y1 = path.PathItems[i].Points[0].Y;
                    x2 = path.PathItems[i].Points[1].X;
                    y2 = path.PathItems[i].Points[1].Y;
                    pathGeometry.AppendFormat(CultureInfo.InvariantCulture,
                        "C {0:0.######} {1:0.######} {2:0.######} {3:0.######} {4:0.######} {5:0.######} ", x1, y1, x2, y2, x2, y2);
                    crtX = x2;
                    crtY = y2;
                    break;
                case PDFPathItemType.CloseSubpath: // h
                    pathGeometry.Append("z ");
                    break;
            }
        }
    
        return pathGeometry.ToString();
    }
    

    This PDF file: paths.pdf

    is converted to this XAML:

    <Canvas xmlns="http://schemas.microsoft.com/xps/2005/06" Width="612" Height="792" Background="White">
        <Canvas.Clip>
            <RectangleGeometry Rect="0,0,612,792"/>
        </Canvas.Clip>
        <Path Data="M 0 792 L 612 0 " Stroke="#FF0080" StrokeThickness="16"/>
        <Path Data="M 0 396 L 612 396 " Stroke="#000080" StrokeThickness="16"/>
    </Canvas>
    

    and it looks like this: enter image description here

    Note: I work for the company that develops PDF4NET.