You are not logged in.
Subtopic: ExtGState injection + xref handling in SynPDF - How to do it cleanly?
Hello,
I'm adding support for alpha blending to SynPDF. What I have now looks promising. Example of alpha blending generated with updated SynPDF as an effect of drawing on a canvas with GDI:
Unfortunately I'm struggling with the layer of pdf creation. So I have a question related to the definition of PDF structures which I need to add to the PDF file to make it work nicely. What I have now is a dirty proof-of-concept solution, and I know I should improve it but I have problems doing it. So here goes the question to SynPDF developers.
Changes needed to appear in the structure of the generated PDF (example):
1. ExtGState definition object, e.g. directly after the header:
25 0 obj << /Type /ExtGState /ca 0.1 /CA 0.1 >>
2. Declaration of the ExtGState object in the Resources dictionary in the catalog object, e.g.:
/Resources << /ExtGState << /GS1 25 0 R >> ...
3. Update the xref structure to include information about object 25 (defined in point 1).
4. It would be great if I could force the ID of the object to be as I want (e.g. 1000025).
While this is against the automatic ID assignment done by SynPDF, it would be good to be able to override it. But I can live without it.
What I managed to do to achieve the above (assuming that 25 is not colliding with IDs of the other objects in the PDF file):
Ad. 2: Adding a predefined declaration of the object to the Resources dictionary — in function TPdfDocument.AddPage: TPdfPage;
with:
FResources.AddItem('ExtGState', TPdfRawText.Create('<</GS1 25 0 R>>'));
Ad. 1: Adding the object by "manually" injecting it into the output stream just after the header — at the end of the procedure TPdfDocument.SaveToStreamDirectBegin;
with:
fSaveToStreamWriter.Add('25 0 obj << /Type /ExtGState /ca 0.1 /CA 0.1 >>');
Ad. 3: While I was unable to actually create/register the above objects in SynPDF structures, the xref structure is not correct. A very, very dirty temporary solution I found is invalidating the xref address (found after startxref at the end of the PDF file) to force its regeneration by the Acrobat Reader. I'm doing it in the procedure TPdfDocument.SaveToStreamDirectEnd;
with:
FTrailer.XrefAddress := fSaveToStreamWriter.Position - 2;
The above "works" as a proof of concept, but I would like to improve it — at least by properly registering an object in SynPDF structures (point 1) and making xref valid (point 3).
I would be grateful for advices — e.g. to which internal object and in which method I should add the object (point 1), and how to create it according to the SynPDF rules.
BR,
Ami
Last edited by hukmac (2025-06-30 17:13:47)
Offline
Happily, I was able to change the above dirty solution to something more-or-less acceptable. So, I'm adding example answers to the above questions (for further reference) and an additional question at the end of this reply (how to 'properly' create the reference to a pdf object based on its xref id?).
Answ. 1. Definition of ExtGState object can be added to pdf structure e.g. in TPdfDocument, similarly to this:
var ExtGState: TPdfDictionary;
...
ExtGState := TPdfDictionary.Create(FXref);
ExtGState.AddItem('Type', TPDFName.Create('ExtGState'));
ExtGState.AddItem('ca', TPDFReal.Create(aAlpha_fill));
ExtGState.AddItem('CA', TPDFReal.Create(aAlpha_stroke));
ExtGState.AddItem('BM', BlendModeId2Str(aBlend_mode));
FXref.AddObject(ExtGState);
Adding object to FXref of TPdfDocument will add this object to the pdf structure and will automatically update the xref section.
AddObject() will also set ExtGState.ObjectNumber to the ID of the ExtGState as it appears in the pdf file. It can be later used to create needed reference in page /Resources dictionary.
Answ. 2. It is good to add FPageExtGStateList: TPdfDictionary to TPdfCanvas (similarly to FPageFontList). If it is set to ExtGState dictionary of page /Resources, e.g.:
procedure TPdfCanvas.SetPage(APage: TPdfPage);
...
FPageExtGStateList := FPage.GetResources('ExtGState');
then adding to FPageExtGStateList references to those ExtGStates which are used on the page will generate appropriate entries in /Resources dictionary of the page. In simplified version it can look e.g. like this:
gs_name := FDoc.GetExtGStateName(ExtGStateId); // gives e.g. 'GS1'
xref_id := FDoc.GetExtGStateXRefID(ExtGStateId);
FPageExtGStateList.AddItem(gs_name, TPdfRawText.Create(IntToStr(xref_id) + ' 0 R'));
The last line creates a reference using TPdfRawText. Maybe there is a better way of creating a reference to an object with given xref_id. How to do it better?
If someone has some neat idea how to improve the creation of the reference to a pdf object based on its xref id (or the object itself), I will be happy to hear it.
Offline
Some remarks:
1) In the engine, a dedicated class could be a better pattern than TPdfRawText.
2) FPageExtGStateList is a good idea - with proper late initialization, only if really needed.
3) Consider adding it to mormot.ui.pdf.pas since SynPdf.pas itself is somewhat deprecated/frozen now.
Offline
Dear ab, thank you for the remarks. I'm still learning details of the engine.
Ad. 3 - I'm considering doing this when I finish it in SynPdf.
Ad. 2 - Thanks, you reminded me that it would be good not to add an empty ExtGState dictionary to page /Resources when no ExtGStates were used for the page.
Edit: I have done this. But there is a second thought - it makes the code a little bit more complicated and ExtGState is added in a different way than e.g. Fonts. I see that both in SynPDF.pas and mormot.ui.pdf.pas /Font and /XObject dictionaries are added to page /Resources also when they are empty (fragment of generated pdf: "/Resources<</Font<<>>/XObject<<>> ..."). So, what is the point to perform late init (when first ExtGState is added to the page) for ExtGState dictionary?
Ad. 1 - I have a dedicated TPdfExtGState class which is derived from TObject:
TPdfExtGState = class(TObject)
private
name : String;
alpha_fill, alpha_stroke : Double; // values 0-1
blend_mode : TPdfBlendMode;
xref_id : Integer;
...
and a list FExtGStateList: TList of such objects in TPdfDocument (analogous to FFontList: TList). Not a SynPdf way, but I'm intentionally doing small steps.
But now I see that maybe a better idea would be to derive TPdfExtGState from TPdfObject and set those objects to be otIndirectObject. This might allow adding references to ExtGSState in page /Resources with TPdfObject.WriteTo() after those objects will be added to FPageExtGStateList. This would also remove the need to keep a redundant xref_id in TPdfExtGState.
Do you think it sounds like a good idea? I have tried it and works nicely both with pdf ver 1.5 and 1.4.
Last edited by hukmac (Yesterday 22:20:28)
Offline