jung-kurt / gofpdf

A PDF document generator with high level support for text, drawing and images
http://godoc.org/github.com/jung-kurt/gofpdf
MIT License
4.31k stars 777 forks source link

Render problems with the pdf.Arc() function #282

Open maberer opened 5 years ago

maberer commented 5 years ago

I need to create some very fine details (in the mm range) on certain parts of my PDF based on the pdf.Arc() function.

I found that if I create circle segments with the Arc() function using two different rx, ry settings, the endings of the segments do not align.

To illustrate the effect, I created the demo program (please see below), zoomed in with my PDF-viewer, and exported the image as a png. A few of the bad parts are encircled by a red circle. Some segments do align, like the one thats encircled with a green circle.

Because gofpdf creates the PDF as vector graphics, I expected the arc endings to align - independent of the size of the element.

I think that the effects seen within the red circles should not be there...

func testRender() {

    pdf := gofpdf.New("P", "mm", "A3", "")
    pdf.AddPage()

    const x = 200.0
    const y = 200.0
    const gapAngle = 40.00
    const segmAngle = 16.00
    const lineWidthOuterRing = 0.20
    const lineWidthInnerRing = 0.25

    drawArcs := func(val int, rx, ry float64) {

        for i := 19; i >= 0; i-- {
            drawSegm := (val & (0x01 << uint(i))) != 0
            if drawSegm {
                startAngle := segmAngle * float64(19-i)
                endAngle := startAngle + segmAngle
                rotAngle := 90.0 + (gapAngle / 2.0)
                pdf.Arc(x, y, rx, ry, rotAngle, startAngle, endAngle, "D")
            }
        }
    }

    pdf.SetFillColor(0, 0, 0)

    pdf.SetLineWidth(lineWidthOuterRing)
    drawArcs(0xFFAAAAB, 1.0, 1.0)

    pdf.SetLineWidth(lineWidthInnerRing)
    drawArcs(0xFFAAAAB, 0.775, 0.775)

    pdf.OutputFileAndClose("arcs.pdf")
}

pdf_render

jung-kurt commented 5 years ago

These results surprise me too. I'll look into this.

jung-kurt commented 5 years ago

I think the angle values used in the Arc() method are particularly sensitive to rounding errors. The misalignment artifacts are apparent even when the radius and line width values are increased by a factor of 100. Floating point values in a PDF are stored as text with a fixed precision. In gofpdf's case, the precision is five digits. It may be worth bumping this up further.

As an expedient in your particular example, eliminating one of the calls to DrawArcs() and using a line width of 0.45 seems to achieve the intended result.

maberer commented 5 years ago

Thanks for looking into this.

The example is primary made to illustrate the alignment problem. In my actual use case, I am using the first parameter of drawArcs() as a bitmask that can be different for the inner and outer circles - thus drawing only one circle would hide the problem but is not applicable in this case... Under normal conditions, the bit masks are not identical, but some segments will sometimes align and cause this undesired effect.

I would be grateful if we could bump up the resolution because the parts have to be machine readable thus require high precision...

jung-kurt commented 5 years ago

I bumped the precision of all floats stored by gofpdf from 5 to 9 and there was no discernible difference with the arc misalignment that you demonstrate. Since everything is smooth when one line width is used, I am inclined to think that the misalignment derives from a slight influence of line width in the way arc endpoints are calculated. I'll think about how this could be verified and, if it can be shown to be a problem, how it can be corrected.

maberer commented 5 years ago

Hi, is there a way to support you with this issue...? - I do not know the inner workings of the library but if there is something I can help you with - I do what I can...

jung-kurt commented 5 years ago

Thanks for the offer, @tindli. I haven't been able to figure out the source of this problem. One thing I have not investigated is whether the aberrations in the generated output are due to way a PDF renderer calculates arc angles with lines of varying thickness. Depending on the rendering engine's algorithm, the end point of an arc might only be approximately radial. (That is, the outside of the line might end at a different angle than the inside of the line.) In this case, the ending position would depend on where within the line the stop angle is applied.

One approach would be to write the absolute smallest PDF with gofpdf that demonstrates the problem. See if you can come up with a much simpler example with just two arcs of different thickness and no loops. This generated PDF could then be compared with the documents generated by other PDF packages.

jung-kurt commented 5 years ago

@tindli, once you write a simplified demonstration of the problem, you could see if documents made with jsPDF (which supports arcs) have or do not have the alignment problem that you found.

maberer commented 5 years ago

@jung-kurt Sorry for the late response. Was quite busy...

Now some of my results:

  1. I tried to reduce the script to a few simple statements without utilizing a for(). So I started to draw arcs with a simple start an end angle value. I tried several different angles but the arcs always lined up perfectly - thus I was not able to reproduce the issue with a simpler script.

  2. I tried more or less the same script like above with jsPDF. To my surprise, the error seems to exist there as well. See the code Result in the picture below: jspdf_precision_error

  3. I was able to avoid the issue by using a constant start/end angle values and only utilizing the rotation feature of gofpdf. Code So far, the results with this version have been far better.

I am not sure why this happens, and I am not sure why the function produces different results (using start+end+rotation vs. rotation only). Because jsPDF produces similar results, the problem might not be within gofpdf...

For my use case, I can probably go with the method described on step 3 (although I have to verify that further)...

Nevertheless, I am not really sure what to do with the issue...

jung-kurt commented 5 years ago

Thanks for the report, @tindli. These are really interesting findings.