Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add watermark to PDF #211

Closed
hamedali1336 opened this issue May 25, 2023 · 7 comments
Closed

Add watermark to PDF #211

hamedali1336 opened this issue May 25, 2023 · 7 comments

Comments

@hamedali1336
Copy link

My question is .. How to add oblique text with custom opacity (watermark)

@galkahana
Copy link
Owner

Hi :)

To write Oblique text all you have to do is find a font that's oblique. Same as you would for writing text in bold or italic. For example, here is a courier oblique version - https://fontsgeek.com/fonts/Courier-Oblique.

As for opacity, there are a few discussions in HummusJS (the JS version of this lib) and i think the most fitting is this one. While it discusses regular paths and not text, the solution is the same. Please have a look, it might be a shorter version of a longer explanation following here. If you want help with converting to C++ do let me know and i'll help.

The point is to apply a graphic static prior to drawing the said text (or path) that includes an opacity factor in it.
There are 3 steps to this:

  1. Creating the external graphic state dictionary
  2. Referring to this graphic state from the page or form that you want to draw the text in and giving it a name
  3. Applying the graphic state with the GS operator providing it the said name.
    Now you just draw the text with the usual text operators and the opacity defined in the graphic state will be applied.

here's an example of how to create the graphic state dict:

ObjectsContext *objCxt = pdfWriter->GetObjectsContext();
ObjectIDType gsId = objCxt->StartNewIndirectObject();
DictionaryContext* dict = objCxt->StartDictionary()
dict->WriteKey("type");
dict->WriteNameValue("ExtGState");
dict->WriteKey("ca");
objCxt->WriteNumber(0.5);
objCxt->EndLine();
objCxt->EndDictionary(dict);

gsId holds the object number for this dictionary. you will use it for the 2nd step.

here's an example of referring to the dictionary from a page resources dictionary:

string gstateName = myPage->GetResourcesDictionary().AddExtGStateMapping(gsId);

gstateName now holds the name of the extgstate that can be used within the page drawing context to apply the state, and allow drawing entities with 0.5 opacity.

For example:

PageContentContext* cxt = myPage->StartPageContentContext(page);
AbstractContentContext::TextOptions textOptions(
    pdfWriter.GetFontForFile("fonts/arial.ttf"),
    14,
    AbstractContentContext::eGray,
    0);
cxt->q();
cxt->gs(gstateName);
cxt->WriteText(75,805,"Yolo",textOptions);
cxt->Q();

That should be it. You can use the more low level methods to write the text, or alternatively draw a path...doesn't matter as long as you call the gs command prior to set the opacity level.
It is OK to combine stages 2 and 3, like this:

cxt->gs(myPage->GetResourcesDictionary().AddExtGStateMapping(gsId));

p.s. there's an example here (and links to code) that shows how to create a single watermark in a form and then reuse that form in multiple pages. again in JS, but can be translated to the C++ operators by mostly moving from camelcasing to titlecasing.

@hamedali1336
Copy link
Author

hamedali1336 commented May 25, 2023

Thanks for replying

Here is my code now ..

pdfWriter.StartPDF(input, ePDFVersion13);
ObjectsContext objCtx = pdfWriter.GetObjectsContext();
ObjectIDType gsID = objCtx.StartNewIndirectObject();
DictionaryContext* dict = objCtx.StartDictionary();
dict->WriteKey("type");
dict->WriteNameValue("ExtGState");
dict->WriteKey("ca");
objCtx.WriteInteger(0.5); //// cannot find WriteNumber
objCtx.EndLine();
objCtx.EndDictionary(dict);
auto* page = new PDFPage();
page->SetMediaBox(PDFRectangle(0,0,595,842));
std::string gsName = page->GetResourcesDictionary().AddExtGStateMapping(gsID);
PageContentContext* pageContentContext = pdfWriter.StartPageContentContext(page);
PDFUsedFont* font = pdfWriter.GetFontForFile(font_path);
pageContentContext->q();
pageContentContext->gs(gsName);
pageContentContext->BT();
pageContentContext->k(0, 100, 100, 0);
pageContentContext->Tf(font, font_size);
pageContentContext->Tm(1,0,0,1,100,100);
pageContentContext->Tj("Test Text");
pageContentContext->ET();
pageContentContext->Q();
pdfWriter.EndPageContentContext(pageContentContext);
pdfWriter.WritePageAndRelease(page);
pdfWriter.EndPDF();

But opacity not changing

@galkahana
Copy link
Owner

Hi,
Sorry for not providing a proper example. I just translated lazily from the JS and this resulted in some errors.

To directly deal with errors in the example:

  • this ObjectsContext objCtx = pdfWriter.GetObjectsContext(); should change to this ObjectsContext& objCtx = pdfWriter.GetObjectsContext(); otherwise, we're just creating a copy of the objects context and not actual reference, so what records it has gets lost (specifically - the dictionary object gets overwritten by a later object.
  • this objCtx.WriteInteger(0.5); should change to this objCtx.WriteDouble(0.5);, otherwise int will get rounded to 0 and you will see no watermark.
  • the indirect object should be ended with objCtx.EndIndirectObject();

PDFVersion should probably also change to 14 and not stay in 13, as things like "ca" key are only supported from 14...but that's not a biggy, i think acrobat ignores it.

So the code should be:

pdfWriter.StartPDF(input, ePDFVersion14);
ObjectsContext& objCtx = pdfWriter.GetObjectsContext();
ObjectIDType gsID = objCtx.StartNewIndirectObject();
DictionaryContext* dict = objCtx.StartDictionary();
dict->WriteKey("type");
dict->WriteNameValue("ExtGState");
dict->WriteKey("ca");
objCtx.WriteDouble(0.5);
objCtx.EndLine();
objCtx.EndDictionary(dict);
objCtx.EndIndirectObject();
auto* page = new PDFPage();
page->SetMediaBox(PDFRectangle(0,0,595,842));
std::string gsName = page->GetResourcesDictionary().AddExtGStateMapping(gsID);
PageContentContext* pageContentContext = pdfWriter.StartPageContentContext(page);
PDFUsedFont* font = pdfWriter.GetFontForFile(font_path);
pageContentContext->q();
pageContentContext->gs(gsName);
pageContentContext->BT();
pageContentContext->k(0, 100, 100, 0);
pageContentContext->Tf(font, font_size);
pageContentContext->Tm(1,0,0,1,100,100);
pageContentContext->Tj("Test Text");
pageContentContext->ET();
pageContentContext->Q();
pdfWriter.EndPageContentContext(pageContentContext);
pdfWriter.WritePageAndRelease(page);
pdfWriter.EndPDF();

I added a test called WatermarkTest now to serve as an example.
It also features more higher level commands that's available for writing text and using the dictionary api to write the double.

@hamedali1336
Copy link
Author

Worked like a charm
Thanks :)

@hamedali1336
Copy link
Author

hamedali1336 commented May 27, 2023

How to use it in modified page ? Not new empty page
Specifically this line

std::string gsName = page->GetResourcesDictionary().AddExtGStateMapping(gsID);

@galkahana
Copy link
Owner

PDFModifiedPage has GetCurrentResourcesDictionary that you can use for the same purpose as yo would use GetResourcesDictionary for a regular page.

The rest is the same.
here's an example for creating modified page, content context and writing the page.

@galkahana
Copy link
Owner

Hi,
for the sake of another project, for supporting color emojis (#223) i added a SetOpacity operator on content context. hummus manages the creation of relevant extgstate dicts in the background, so you don't have to worry about it. the implementation is also available on the higher level commands with Text options and graphic options now including an opacity factor.

See example here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants