klippa-app / go-pdfium

Easy to use PDF library using Go and PDFium
MIT License
195 stars 16 forks source link

How do I draw a rectangular shape? #177

Closed nonchan7720 closed 4 months ago

nonchan7720 commented 4 months ago

I thought I could draw a rectangle with the following code, but it does not work. It is conducted in a WASM environment.

    doc, err := instance.OpenDocument(&requests.OpenDocument{
        File: &pdfBytes,
    })
    if err != nil {
        return err
    }
    defer instance.FPDF_CloseDocument(&requests.FPDF_CloseDocument{
        Document: doc.Document,
    })

    // Assuming we want to highlight text on the first page
    pageIndex := 0
    page, err := instance.FPDF_LoadPage(&requests.FPDF_LoadPage{
        Document: doc.Document,
        Index:    pageIndex,
    })
    if err != nil {
        return err
    }
    defer instance.FPDF_ClosePage(&requests.FPDF_ClosePage{
        Page: page.Page,
    })

    // Create a rectangle object
    highlight, err := instance.FPDFPageObj_CreateNewRect(&requests.FPDFPageObj_CreateNewRect{
        X: 100,
        Y: 100,
        H: 100,
        W: 200,
    })
    if err != nil {
        return err
    }

    // Set the fill color to yellow with some transparency
    _, err = instance.FPDFPageObj_SetFillColor(&requests.FPDFPageObj_SetFillColor{
        PageObject: highlight.PageObject,
        FillColor: structs.FPDF_COLOR{
            R: 255,
            G: 255,
            B: 0,
            A: 100,
        },
    })
    if err != nil {
        return err
    }

    // Insert the rectangle object into the page
    _, err = instance.FPDFPage_InsertObject(&requests.FPDFPage_InsertObject{
        Page: requests.Page{
            ByReference: &page.Page,
        },
        PageObject: highlight.PageObject,
    })
    if err != nil {
        return err
    }

    // Update the page content
    _, err = instance.FPDFPage_GenerateContent(&requests.FPDFPage_GenerateContent{
        Page: requests.Page{
            ByReference: &page.Page,
        },
    })
    if err != nil {
        return err
    }
    // Save the modified document
    _, saveErr := instance.FPDF_SaveAsCopy(&requests.FPDF_SaveAsCopy{
        Document: doc.Document,
        FilePath: &output,
    })
    if saveErr != nil {
        return saveErr
    }
jerbob92 commented 4 months ago

What exactly doesn't work? Does it result in an error somewhere? Does it create a new PDF? Did you inspect the new PDF to see if the PDF contains the new object? (maybe it's just not visible).

nonchan7720 commented 4 months ago

Thank you for your reply.

I would like to import an existing PDF and draw a rectangular shape. I am trying to draw it using the code you just gave me. I would like a sample of how it is drawn as there is no change in the PDF.

jerbob92 commented 4 months ago

Does it result in an error somewhere? Does it create a new PDF? Did you inspect the new PDF to see if the PDF contains the new object? (maybe it's just not visible). With inspect I mean not just looking at it, but inspecting the data of the PDF, for example with iRUPS.

nonchan7720 commented 4 months ago

No errors. We want to achieve that the object is not only added, but that the shape is visually drawn.

jerbob92 commented 4 months ago

Did you inspect the new PDF to see if the PDF contains the new object? (maybe it's just not visible). With inspect I mean not just looking at it, but inspecting the data of the PDF, for example with iRUPS.

nonchan7720 commented 4 months ago

The challenge is that it is not visible. Please check that area as well using the code just described.

nonchan7720 commented 4 months ago

func mm2pt[T float32 | float64](mm T) T {
    return mm * 72.0 / 25.4
}

func addRect(output string) error {
    pool := single_threaded.Init(single_threaded.Config{})
    instance, err := pool.GetInstance(time.Second * 30)
    if err != nil {
        return err
    }
    defer instance.Close()

    doc, err := instance.FPDF_CreateNewDocument(&requests.FPDF_CreateNewDocument{})
    if err != nil {
        return err
    }
    defer instance.FPDF_CloseDocument(&requests.FPDF_CloseDocument{
        Document: doc.Document,
    })
    page, err := instance.FPDFPage_New(&requests.FPDFPage_New{
        Document:  doc.Document,
        PageIndex: 0,
        Width:     mm2pt[float64](210),
        Height:    mm2pt[float64](297),
    })
    if err != nil {
        return err
    }
    defer instance.FPDF_ClosePage(&requests.FPDF_ClosePage{
        Page: page.Page,
    })

    rectangle, err := instance.FPDFPageObj_CreateNewRect(&requests.FPDFPageObj_CreateNewRect{
        Y: mm2pt[float32](100),
        X: mm2pt[float32](100),
        H: mm2pt[float32](100),
        W: mm2pt[float32](200),
    })
    if err != nil {
        return err
    }
    _, err = instance.FPDFPageObj_SetFillColor(&requests.FPDFPageObj_SetFillColor{
        PageObject: rectangle.PageObject,
        FillColor: structs.FPDF_COLOR{
            R: 255,
            G: 0,
            B: 0,
            A: 255,
        },
    })
    if err != nil {
        return err
    }
    _, err = instance.FPDFPageObj_SetStrokeColor(&requests.FPDFPageObj_SetStrokeColor{
        PageObject: rectangle.PageObject,
        StrokeColor: structs.FPDF_COLOR{
            R: 0,
            G: 0,
            B: 0,
            A: 255,
        },
    })
    if err != nil {
        return err
    }
    _, err = instance.FPDFPageObj_SetStrokeWidth(&requests.FPDFPageObj_SetStrokeWidth{
        PageObject:  rectangle.PageObject,
        StrokeWidth: 1.5,
    })
    if err != nil {
        return err
    }
    _, err = instance.FPDFPage_InsertObject(&requests.FPDFPage_InsertObject{
        Page: requests.Page{
            ByIndex: &requests.PageByIndex{
                Document: doc.Document,
                Index:    0,
            },
        },
        PageObject: rectangle.PageObject,
    })
    if err != nil {
        return err
    }
    _, err = instance.FPDFPage_GenerateContent(&requests.FPDFPage_GenerateContent{
        Page: requests.Page{
            ByReference: &page.Page,
        },
    })
    if err != nil {
        return err
    }
    // Save the modified PDF.
    _, err = instance.FPDF_SaveAsCopy(&requests.FPDF_SaveAsCopy{
        Document: doc.Document,
        FilePath: &output,
    })
    if err != nil {
        return err
    }
    return nil
}

func main() {
    os.Remove("./test.pdf")
    err := addRect("./test.pdf")
    if err != nil {
        log.Printf("%v", err)
    }
}

I have also attached a PDF of the results of running the above code. test.pdf

jerbob92 commented 4 months ago

Did you inspect the new PDF to see if the PDF contains the new object? (maybe it's just not visible). With inspect I mean not just looking at it, but inspecting the data of the PDF, for example with iRUPS.

nonchan7720 commented 4 months ago

I think it's a bug because it hasn't been added.

jerbob92 commented 4 months ago

If you change the FPDFPage_InsertObject instruction to

    _, err = instance.FPDFPage_InsertObject(&requests.FPDFPage_InsertObject{
        Page: requests.Page{
            ByReference: &page.Page,
        },
        PageObject: rectangle.PageObject,
    })

It works fine here.

This is because the page was created with FPDFPage_New, if you use ByIndex it will open it again and you will have two different instances of the page object.

The rectangle won't show but it is added to the PDF data, so why it's not showing is what you have to figure out on your own. I can only give support on the Go part of the implementation, not on how pdfium works.

Jiaget commented 3 months ago

I've met the same issue, and I found the objects of the page are changing, maybe it is a bug?

    rect, err := instance.FPDFPageObj_CreateNewRect(&requests.FPDFPageObj_CreateNewRect{
        X: 200,
        Y: 200,
        W: 100,
        H: 100,
    })
    if err != nil {
        return err
    }

    _, err = instance.FPDFPage_InsertObject(&requests.FPDFPage_InsertObject{
        Page: requests.Page{
            ByReference: &page.Page,
        },
        PageObject: rect.PageObject,
    })
    if err != nil {
        return err
    }
    for i := 0; i < 10; i++ {
        res, err := instance.FPDFPage_GetObject(&requests.FPDFPage_GetObject{
            Page: requests.Page{
                ByReference: &page.Page,
            },

            Index: 0,
        })
        if err != nil {
            return err
        }
        fmt.Printf("%+v %+v %+v\n ", res.PageObject, rect.PageObject)
    }
--- output ---
a495d253-77ef-4d68-aa54-83af7cecaef9 1efee545-9435-4cbd-ade0-b7868454ec04
 1b547bf5-34b8-4175-bc05-f4e7c32c9e1f 1efee545-9435-4cbd-ade0-b7868454ec04
 4d339d48-8bc6-4356-af04-ece7f2249434 1efee545-9435-4cbd-ade0-b7868454ec04
 fd61a58a-4000-4fac-bac1-0a78213306c2 1efee545-9435-4cbd-ade0-b7868454ec04
 7e8c96b3-f238-4f6c-b182-ac821464c3fe 1efee545-9435-4cbd-ade0-b7868454ec04
 19853acf-f968-49c4-8923-384804374982 1efee545-9435-4cbd-ade0-b7868454ec04
 ad4229f4-b2bd-43ee-bd21-1787f3a41816 1efee545-9435-4cbd-ade0-b7868454ec04
 e06076a2-6c78-4e40-b945-52bba99c3a2e 1efee545-9435-4cbd-ade0-b7868454ec04
 aad32269-9d14-4207-ad9c-1b383613024f 1efee545-9435-4cbd-ade0-b7868454ec04
 ecd2a05f-6699-4bc1-805f-e3299fdbd9b3 1efee545-9435-4cbd-ade0-b7868454ec04
jerbob92 commented 3 months ago

@Jiaget that's not a bug, the UUID is what go-pdfium uses to reference pointers without putting the pointers on the wire (gRPC), FPDFPage_GetObject generates a new UUID everytime you call it. The pointer itself could very well be the same.

Can you share your full code?

Jiaget commented 3 months ago
func (p *Pdfium) AddSquad(inputPath string, outputPath string) error {
    instance, err := p.pool.GetInstance(30 * time.Second)
    if err != nil {
        return err
    }

    defer instance.Close()

    doc, err := instance.OpenDocument(&requests.OpenDocument{
        FilePath: &inputPath,
    })

    defer instance.FPDF_CloseDocument(&requests.FPDF_CloseDocument{
        Document: doc.Document,
    })

    page, err := instance.FPDF_LoadPage(&requests.FPDF_LoadPage{
        Document: doc.Document,
        Index:    0,
    })

    if err != nil {
        return err
    }

    defer instance.FPDF_ClosePage(&requests.FPDF_ClosePage{
        Page: page.Page,
    })

    rect, err := instance.FPDFPageObj_CreateNewRect(&requests.FPDFPageObj_CreateNewRect{
        X: 200,
        Y: 300,
        W: 200,
        H: 100,
    })
    if err != nil {
        return err
    }

    _, err = instance.FPDFPageObj_SetFillColor(&requests.FPDFPageObj_SetFillColor{
        PageObject: rect.PageObject,
        FillColor: structs.FPDF_COLOR{
            R: 255,
            G: 0,
            B: 0,
            A: 255,
        },
    })
    if err != nil {
        return err
    }

    _, err = instance.FPDFPageObj_SetStrokeWidth(&requests.FPDFPageObj_SetStrokeWidth{
        PageObject:  rect.PageObject,
        StrokeWidth: 10,
    })
    if err != nil {
        return err
    }

    _, err = instance.FPDFPage_InsertObject(&requests.FPDFPage_InsertObject{
        Page: requests.Page{
            ByReference: &page.Page,
        },
        PageObject: rect.PageObject,
    })
    if err != nil {
        return err
    }

    _, err = instance.FPDFPage_GenerateContent(&requests.FPDFPage_GenerateContent{
        Page: requests.Page{
            ByReference: &page.Page,
        },
    })

    if err != nil {
        return err
    }

    _, err = instance.FPDF_SaveAsCopy(&requests.FPDF_SaveAsCopy{
        FilePath: &outputPath,
        Document: doc.Document,
    })
    if err != nil {
        return err
    }

    return nil

}

Here is my full code, i can't see rectangle in my output pdf

And I reopen the output pdf , call FPDFPage_CountObjects(), and the result is 1

jerbob92 commented 3 months ago

@Jiaget When I add the following it does work:

    _, err = instance.FPDFPath_SetDrawMode(&requests.FPDFPath_SetDrawMode{
        PageObject: rect.PageObject,
        FillMode:   enums.FPDF_FILLMODE_ALTERNATE,
    })
    if err != nil {
        return 0, err
    }

I think pdfium just filters it out when the rect doesn't contain anything to draw. When setting the fillmode there is something to draw.

Jiaget commented 3 months ago

@Jiaget When I add the following it does work:

  _, err = instance.FPDFPath_SetDrawMode(&requests.FPDFPath_SetDrawMode{
      PageObject: rect.PageObject,
      FillMode:   enums.FPDF_FILLMODE_ALTERNATE,
  })
  if err != nil {
      return 0, err
  }

I think pdfium just filters it out when the rect doesn't contain anything to draw. When setting the fillmode there is something to draw.

That works!!!!!!! Save me