Diferent result between pymupdf and mupdf_net

Hi there! I am converting a prototype (made using pymupdf to mupdf_net, and using the same commands (page.GetDrawings) and re-drawing using the same strategy in python and C#, I got different results… Anyone have any clue on how (or why) to solve this?

This is the python code:

“def draw_graphics_from(input_doc: pymupdf.Document, output_doc: pymupdf.Document) → pymupdf.Document:

\# page = input_doc\[0\]

outpdf = output_doc #pymupdf.open()

file_export = open('./assets/exported_paths_python.txt', 'w')

for page_num, page in enumerate(input_doc):

    paths = page.get_drawings()

    outpage = outpdf.load_page(page_num)

    shape = outpage.new_shape()

    percent = 0



    print(f'Drawing {len(paths)} items on page {page_num + 1}: ', flush=True, end=' ')

    for i, path in enumerate(paths):

        \# if i % (len(paths) // 10) == 0 and i > 0:

        \#     percent += 10

        \#     print(f'{percent}%...', flush=True, end='')

        for item in path\["items"\]:  # these are the draw commands

            if item\[0\] == "l":  # line

                shape.draw_line(item\[1\], item\[2\])

                file_export.write(f'{i:03}\] line: {item\[0\]} >>> {item\[1\]}, {item\[2\]}\\n')

            elif item\[0\] == "re":  # rectangle

                shape.draw_rect(item\[1\])

                file_export.write(f'{i:03}\] rect: {item\[0\]} >>> {item\[1\]}\\n')

            elif item\[0\] == "qu":  # quad

                shape.draw_quad(item\[1\])

                file_export.write(f'{i:03}\] quad: {item\[0\]} >>> {item\[1\]}\\n')

            elif item\[0\] == "c":  # curve

                shape.draw_bezier(item\[1\], item\[2\], item\[3\], item\[4\])

                file_export.write(f'{i:03}\] curve: {item\[0\]} >>> {item\[1\]}, {item\[2\]}, {item\[3\]}, {item\[4\]}\\n')

            else:

                raise ValueError("unhandled drawing", item)

            



        lwidth = path.get("width", 0.03)

        if type(lwidth) is not float:

            lwidth = float(DEFAUL_LINE_WIDTH)

        if lwidth <= 0.03:

            lwidth = float(DEFAUL_LINE_WIDTH)



        \# print(f'Path width: {lwidth}, type: {type(lwidth)}', flush=True)



        shape.finish(

            fill=path\["fill"\],  # fill color

            color = (random.random(), random.random(), random.random()) if USE_RANDOM_COLORS else DEFAULT_COLOR, #path\["color"\],  # line color

            dashes=path\["dashes"\],  # line dashing

            even_odd=path.get("even_odd", True),  # control color of overlaps

            closePath=path\["closePath"\],  # whether to connect last and first point

            lineJoin= 1, #max(path.get("lineJoin", 0), 1),  # how line joins should look like

            lineCap = 1, #(1, 1, 1), # max(path.get("lineCap", 1)),  # how line ends should look like

            width = lwidth, # line_width,  # path\["width"\],  # line width

            stroke_opacity= 1, # path.get("stroke_opacity", 1.0),  # same value for both

            fill_opacity= 1, #path.get("fill_opacity", 1.0),  # opacity parameters

        )



        file_export.write(f'Path {i:03}\] width: {lwidth}, dashes: {path\["dashes"\]}, closePath: {path\["closePath"\]}\\n')



    print('Done.', flush=True)

    shape.commit()



file_export.close()

return outpdf“

And this is the C# code:

“ public Document? draw_graphics_from(Document inputDoc, Document outputDoc, float newWidth, IProgress progress)
{

    string filePath = @"D:\\Vectorlab\\Jobs\\2025\\PACE\\pdf_fix\\assets\\exported_paths_net.txt";
    StreamWriter writer = new StreamWriter(filePath);

    if (inputDoc.PageCount != outputDoc.PageCount)
    {
        return null;
    }

    for (int pagNum = 0; pagNum < inputDoc.PageCount; pagNum++)
    {
        Page page = inputDoc.LoadPage(pagNum);
        Page outPage = outputDoc.LoadPage(pagNum);
        List<PathInfo> paths = page.GetDrawings(extended: false);
        int totalPaths = paths.Count;
        Shape shape = outPage.NewShape();

        int i = 0;
        progress.Report(0);
        foreach (PathInfo pathInfo in paths)
        {
            
            foreach (Item item in pathInfo.Items)
            {
                if (item != null)
                {
                    if (item.Type == "l")
                    {
                        shape.DrawLine(item.P1, item.LastPoint);
                        writer.Write($"{i:000}\] line: {item.Type} >>> {item.P1}, {item.LastPoint}\\n");
                    }
                    else if (item.Type == "re")
                    {
                        shape.DrawRect(item.Rect, item.Orientation);
                        writer.Write($"{i:000}\] rect: {item.Type} >>> {item.Rect}, {item.Orientation}\\n");
                    }
                    else if(item.Type == "qu")
                    {
                        shape.DrawQuad(item.Quad);
                        writer.Write($"{i:000}\] quad: {item.Type} >>> {item.Quad}\\n");
                    }
                    else if(item.Type == "c")
                    {
                        shape.DrawBezier(item.P1, item.P2, item.P3, item.LastPoint);
                        writer.Write($"{i:000}\] curve: {item.Type} >>> {item.P1},  {item.P2}, {item.P3}, {item.LastPoint}\\n");
                    }
                    else
                    {
                        throw new Exception("unhandled drawing. Aborting...");
                    }
                }
            }

            //pathInfo.Items.get
            float newLineWidth = pathInfo.Width;
            if (pathInfo.Width <= newWidth)
            {
                newLineWidth = newWidth;
            }

            shape.Finish(
                fill: pathInfo.Fill,
                color: pathInfo.Color, //this.\_m_DEFAULT_COLOR,
                dashes: pathInfo.Dashes,
                evenOdd: true, //pathInfo.EvenOdd,
                closePath: pathInfo.ClosePath,
                lineJoin: (int)1,
                lineCap: (int)1,
                width: newLineWidth,
                strokeOpacity: (int)pathInfo.StrokeOpacity,
                fillOpacity: (int)pathInfo.FillOpacity
             );

            double progressValue = ((double)i / (double)totalPaths) \* 100.0;

            progress.Report((int)progressValue);

            // file_export.write(f'Path {i:03}\] width: {lwidth}, dashes: {path\["dashes"\]}, closePath: {path\["closePath"\]}\\n')
            writer.Write($"Path {i:000}\] with: {newLineWidth}, dashes: {pathInfo.Dashes}, closePath: {pathInfo.ClosePath}\\n");

            i++;
        }
        
        shape.Commit();

    }

    writer.Close();
    return outputDoc;
}“

And the image above is the results

exported_paths_net.txt (18,9,KB)

exported_paths_python.txt (33,1,KB)

I also create this “debuging” info to see what was write to the PDF, and looks like the .net implementation has less data than the python:

@Fabio_Nascimento Implementations look sound in both. I guess, like you expect, the .NET is “finding” less data than the Python for some reason.

Can you confirm 3 things for me:

  1. version of PyMuPDF used?
  2. version of .NET used?
  3. Supply the input PDF you use if possible?

@Maksym_Tkachuk Please watch this post for updates and let us know what you think!

PyMuPDF version: 1.26.6

MuPDF.NET version: 3.2.11

drawing2.pdf (1,2,KB)

If you look in the exported txt, Path.Fill and Path.Color are not like the .py version… This may ring a bell on what is wrong, I guess…

@Fabio_Nascimento @Jamie_Lemon

I created new nuget release to fix this issue.

Please test this problem with 3.2.13-rc.1.

dotnet add package MuPDF.NET --version 3.2.13-rc.1

If any problem, please contact me at any time.

Thanks.

Nice one @Maksym_Tkachuk ! @Fabio_Nascimento Please do let us know if it resolves your issue!

Hey guys, first tests looks great! I will continue with differents pdf’s and report here if I find something… Thank you very much!

1 Like

Awesome!!!