#1 2024-04-23 17:21:49

rvk
Member
Registered: 2022-04-14
Posts: 93

Clipping problem after image

I have a clipping problem in the resulting PDF after rendering an image (GDI).
I have a TJvRichEdit with an image. Everything after the image is clipped away. It is still there (I can select it in Adobe reader and copy it to a text-editor) but it seems to be clipped away.

I can create a meta file in which the problem isn't present.
Loading the emf in a TMetaFile and rendering it to TPdfDocumentGDI with RenderMetaFile() the problem is that everything after the image doesn't show.

The EMR_STRETCHDIBITS is done correctly. But directly after that is a small EMR_EXTSELECTCLIPRGN block, which doesn't seem to close the clipping path correctly (??)

I can trace the problem to TPdfEnum.ExtSelectClipRgn.

All the way at the bottom there, there is the following.

    Canvas.Closepath;
    Canvas.Clip;
    Canvas.NewPath; // <----
    Canvas.FNewPath := false;

If I comment out the Canvas.NewPath there, it works correctly (see first image).
I'm not familiar with the NewPath and FNewPath mechanisme but I'm guessing there goes something wrong at that point (second image).
So the problem is that I don't know if I can leave that NewPath out, without causing other problems.
(and why is it causing the problem)

Code:

function ExecAssociatedApp(const FileName: string; Action: string = ''): boolean;
var
  rs: Word;
  Shw: integer;
begin
  Shw := SW_SHOW;
  rs := Winapi.ShellAPI.ShellExecute(0, pchar(Action), pchar(FileName), nil, nil, Shw);
  Result := rs > 32;
end;

procedure TestClip;
var
  FileTemp: string;
  meta: TMetaFile;
  pdf: TPdfDocumentGDI;
begin
  FileTemp := 'C:\Temp\Test1.pdf';
  meta := TMetaFile.Create;
  pdf := TPdfDocumentGDI.Create;
  try
    pdf.UseOptionalContent := true;
    pdf.Info.Title := 'Title name';
    pdf.Info.Author := 'User name';
    pdf.Info.Creator := 'Software name';
    pdf.Info.subject := 'Auto generated document';

    { page := } pdf.AddPage;

    meta.LoadFromFile('c:\temp\meta.emf');
    RenderMetaFile(pdf.Canvas, meta);

    pdf.SaveToFile(FileTemp);
    ExecAssociatedApp(FileTemp);

  finally
    meta.Free;
  end;
end;

Metafile (temporarily)
https://www.graficalc.nl/meta.emf

Correct (with the NewPath commented out):
2024-04-23-19-02-58-Test1-pdf.png

Incorrect (original code, with NewPath intact):
2024-04-23-19-04-10-Test1-pdf.png

Last edited by rvk (2024-04-23 17:23:46)

Offline

#2 2024-04-23 17:31:31

rvk
Member
Registered: 2022-04-14
Posts: 93

Re: Clipping problem after image

Argh. I do need the NewPath line there. Otherwise the clipping mechanisme doesn't work for my other part of the PDF (for my signature where the clipping needs to be on another background pdf with pdftk).

So the question remains... why does the the clipping continue after an image (as shown in Image 2)?

You can load the meta.emf in emfexplorer.exe and see what happens there.
Also... rendering it on screen works correctly.
It's just creating a PDF from it, it hides everything after the image.

Last edited by rvk (2024-04-23 17:33:11)

Offline

#3 2024-04-23 19:35:30

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,242
Website

Re: Clipping problem after image

This is a recurrent issue.
https://synopse.info/forum/viewtopic.php?pid=31086
IIRC some users made several modifications (via pull requests) to SynPdf to support clipping, then to fix it, but it was broken, then re-fixed, then re-broken for others...
Try perhaps to search about it here, or try another revision of the source.

But AFAIR the pdf format has no built-in support of imagine cliping: it can clip some vectorial drawings, but not bitmaps.
The easiest is to do the cliping in the bitmap before drawing it to the PDF...

Offline

#4 2024-04-23 20:30:40

rvk
Member
Registered: 2022-04-14
Posts: 93

Re: Clipping problem after image

Clipping works perfectly in synpdf with an image in TJvRichedit.
I use the TPdfDocumentGDI and it enumerates perfectly through the EMR_STRETCHDIBITS for the image.

After that the richedit/Windows does the clipping for transparency through EMR_EXTSELECTCLIPRGN. That also works perfectly for my hand signature as image in the richedit.

It's just that any text after it is also clipped.
Or seems like the TPdfEnum.ExtSelectClipRgn leaves the clipping in a wrong state.

But I'll investigate this further. I have a feeling it's just a minor bug which can be fixed really easy but I was hoping you would have some more expertise as to how that newpath worked and would spot the bug more easily. But I'll get into it and report back when I find something.

After my last pull request the clipping works very good, at least for the RGN_COPY part. It just that the clipping seems to be left in a wrong state (on?).

Offline

#5 2024-04-26 06:54:39

rvk
Member
Registered: 2022-04-14
Posts: 93

Re: Clipping problem after image

Shoutout to @everyone...
Does anyone have more experience with rendering metafiles through GDI to PDF via mORMot2 ?

I have emf streams/files (created via TJvRichEdit) with images.
In those emf streams, after the image is drawn with EMR_STRETCHDIBITS.
Followed by a EMR_EXTSELECTCLIPRGN to clip the drawn region to size.
And again followed by several EMR_RESTOREDC.

But in mORMot2 (SynPdf), the clipped region isn't reset when calling RestoreDC.
As a result... everything after this, is clipped away.

My feeling is, that in EMR_SAVEDC/EMR_RESTOREDC (so in Canvas.GSave/Canvas.GRestore), the clipping region should also be reset.

I changed my code here to include GSave/GRestore inside and that does seems to do the job.
But I'm unsure if this is the whole solution. Maybe the other GSave/GRestores can be removed because it should already be done in the emf itself.

(BTW. A small look at llPDFLib DoRestoreDC I see it does the same.)

procedure TPdfEnum.RestoreDC;
begin
  Assert(nDC > 0);
  Canvas.GRestore; // <--- add this
  dec(nDC);
end;

procedure TPdfEnum.SaveDC;
begin
  Assert(nDC < high(DC));
  Canvas.GSave; // <--- add this
  DC[nDC + 1] := DC[nDC];
  inc(nDC);
end;

I will change this in my code and see how it does in production. But maybe someone with more experience can confirm my findings.
@MtwStark @ArnoBrinkman ??

Edit: Ha, I now see MtwStark was already working on that.

procedure TPdfEnum.SaveDC;
begin
  Assert(nDC < high(DC));
  DC[nDC + 1] := DC[nDC];
  inc(nDC);
{$IFDEF TMW_TEST_NEW_CLIPRGN}
  // MTW 2018-01-30 - the problem wasn't GRestore/GSave, but was the invalidation of graphic state parameters
  Canvas.GSave;
{$ENDIF}
end;

https://synopse.info/forum/viewtopic.ph … 786#p25786

It's a real shame more extensive GDI support isn't implemented in mORMot2 itself over the years.


R0141: [037] EMR_SELECTOBJECT	(s=12)	{Table object: 6=OBJ_FONT}
R0142: [033] EMR_SAVEDC	(s=8)
R0143: [022] EMR_SETTEXTALIGN	(s=12)	{iMode(0= TA_LEFT TA_TOP)}
R0144: [033] EMR_SAVEDC	(s=8)
R0145: [030] EMR_INTERSECTCLIPRECT	(s=24)	{rclClip(118,222,131,262)}
R0146: [033] EMR_SAVEDC	(s=8)
R0147: [030] EMR_INTERSECTCLIPRECT	(s=24)	{rclClip(118,242,131,255)}
R0148: [017] EMR_SETMAPMODE	(s=12)	{iMode(8=MM_ANISOTROPIC)}
R0149: [012] EMR_SETVIEWPORTORGEX	(s=16)	{ptlOrigin(118,242)}
R0150: [011] EMR_SETVIEWPORTEXTEX	(s=16)	{szlExtent(13,13)}
R0151: [010] EMR_SETWINDOWORGEX	(s=16)	{ptlOrigin(0,0)}
R0152: [009] EMR_SETWINDOWEXTEX	(s=16)	{szlExtent(106,106)}
R0153: [081] EMR_STRETCHDIBITS	(s=168)	{rclBounds(118,242,130,254), Dest[x:0, y:0, cx:106, cy:106)], dwRop(0x00CC0020), Src[x:0, y:0, cx:4, cy:4, iUsage:0, offBmi:80, Bmi:40, offBits:120, Bits:48] BmiHeader[biSize:40, biWidth:4, biHeight:4, biPlanes:1, biBitCount:24, biCompression:0, biSizeImage:48, biXPelsPerMeter:3779, biYPelsPerMeter:3779, biClrUsed:0, biClrImportant:0]}
R0154: [037] EMR_SELECTOBJECT	(s=12)	{Table object: 2=OBJ_PEN.(PS_SOLID | COSMETIC)}
R0155: [037] EMR_SELECTOBJECT	(s=12)	{Table object: 3=OBJ_BRUSH.(BS_SOLID)}
R0156: [048] EMR_SELECTPALETTE	(s=12)	{ihPal(Stock object: 15)}
R0157: [075] EMR_EXTSELECTCLIPRGN	(s=64)	{iMode(5=RGN_COPY), RGNDATA[ptr:0x066b12f0, size:48, box(118,242,131,255)]}
R0158: [034] EMR_RESTOREDC	(s=12)	{iRelative(-1)}
R0159: [034] EMR_RESTOREDC	(s=12)	{iRelative(-1)}
R0160: [034] EMR_RESTOREDC	(s=12)	{iRelative(-1)}
R0161: [037] EMR_SELECTOBJECT	(s=12)	{Table object: 5=OBJ_FONT}
R0162: [084] EMR_EXTTEXTOUTW	(s=76)	{ TXT=[] [exScale(8.458333) eyScale(8.467624) iGraphicsMode(1), Bounds(0,0,-1,-1)] TxOPT[fOptions(0), nChars(0), offDx(76), ptlRef(131,213), rcl(0,0,-1,-1)] Spacing[84 => Total(84) =>xPtRefRight(214)]}
R0163: [084] EMR_EXTTEXTOUTW	(s=76)	{ TXT=[] [exScale(8.458333) eyScale(8.467624) iGraphicsMode(1), Bounds(0,0,-1,-1)] TxOPT[fOptions(16|ETO_GLYPH_INDEX), nChars(0), offDx(76), ptlRef(131,213), rcl(0,0,-1,-1)] Spacing[84 => Total(84) =>xPtRefRight(214)]}
R0164: [084] EMR_EXTTEXTOUTW	(s=76)	{ TXT=[] [exScale(8.458333) eyScale(8.467624) iGraphicsMode(1), Bounds(0,0,-1,-1)] TxOPT[fOptions(0), nChars(0), offDx(76), ptlRef(118,262), rcl(0,0,-1,-1)] Spacing[84 => Total(84) =>xPtRefRight(201)]}
R0165: [084] EMR_EXTTEXTOUTW	(s=76)	{ TXT=[] [exScale(8.458333) eyScale(8.467624) iGraphicsMode(1), Bounds(0,0,-1,-1)] TxOPT[fOptions(16|ETO_GLYPH_INDEX), nChars(0), offDx(76), ptlRef(118,262), rcl(0,0,-1,-1)] Spacing[37 => Total(37) =>xPtRefRight(154)]}

Offline

#6 2024-04-26 07:35:39

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,242
Website

Re: Clipping problem after image

I agree it should be reset, but not always.
Adding GRestore/GSave always would pollute the output with a lot of q and Q commands for no benefit if clipping is not involved.
Those GRestore/GSave should be done only when needed.

I looked at llPDFEMF.pas it is a nice library - I did not know of its existence.
But to me it sounds like if GStateRestore is not called always, but only within text processing.

Offline

#7 2024-04-26 09:36:13

rvk
Member
Registered: 2022-04-14
Posts: 93

Re: Clipping problem after image

ab wrote:

I agree it should be reset, but not always.
Adding GRestore/GSave always would pollute the output with a lot of q and Q commands for no benefit if clipping is not involved.
Those GRestore/GSave should be done only when needed.

Yes, probably. But I rather have a lot of q/Q than clipping in the wrong place where text (and in my case even an amount/currency column) is clipped wink

That being said... shouldn't EMR_RESTOREDC always do a restore Q in PDF? It's not for nothing EMR_RESTOREDC is in the EMF.

The q/Q are for pushing/restoring the graphical state. But Line Width/Brush etc. are also part of the graphical state (PDF1.7 4.3.1).
So when SAVEDC/RESTOREDC is called in EMF, it's only logical to (ALWAYS) do the same in PDF, regardless if it's needed (because you can't possibly determine the actual need to do so without evaluating everything that comes before it).

GDI

The SaveDC function saves the current state of the specified device context (DC) by copying data describing selected objects and graphic modes (such as the bitmap, brush, palette, font, pen, region, drawing mode, and mapping mode) to a context stack.

PDF

These operators can be used to encapsulate a graphical element so that it can modify parameters of the graphics state and later restore them to their previous values.

I think with the adjustments of MtwStark, it will also always save/restore on SaveDC/RestoreDC. But those where developing adjustments.

In llPDFEMF.pas it seems to set a Clipping := true on EMR_EXTSELECTCLIPRGN and only does FCanvas.GStateRestore in RestoreDC when that Clipping is set.
It seems clipping is a lot more 'mature' in that code. But that was already known.

ab wrote:

But to me it sounds like if GStateRestore is not called always, but only within text processing.

It looks like most calls to GStateRestore are done when Clipping is true (so not only within text processing).

But as stated before... in EMF... even a simple SAVEDC, set Line Width to 100, do nothing else and RESTOREDC, should restore the line width.
I don't think that's being done now when translating this to PDF.

So I don't think lot's of q/Q is polluting the PDF... it's just following the EMF.
You could check the complete graphical stack (not only clipping, but also bitmap, brush, palette, font, pen, region, drawing mode, and mapping mode) and see if that's changed. If it's not changed, you could omit the q/Q. But that seems like a lot of work. Besides IsClipping you would need IsLineWidthSet, IsBrushSet, IfFontSet etc. etc. Just doing a q/Q would be much simpler (and it follows the EMF).

(If I got some time I'll check some other EMF->PDF converters. Until then I'll test the stability of adding the q/Q)

Offline

Board footer

Powered by FluxBB