You are not logged in.
Hi,
The latest version of SynPDF2 seem to have a regression which wasn't present in SynPDF1: a part of the content is not drawn.
Screenshot: https://snipboard.io/a7T6tn.jpg
Sample project with source WMF, generate PDF and screenshot of the problem: https://we.tl/t-bLUC4A84kA
I'm using Delphi 12 in 32/64-bit mode with latest SynPDF2 from Git.
I thought I'd post it here before I open a bug on GitHub but I can do so if you wish.
Thank you for any help.
Last edited by jonjbar (2024-05-14 13:44:44)
Offline
Did you try with a previous version of mORMot 2?
Did you try e.g. BEFORE https://github.com/synopse/mORMot2/comm … ca832a5931 ?
or BEFORE https://github.com/synopse/mORMot2/comm … 928c8b882b ?
My guess is that it is, once again, a regression introduced by a pull request from external source...
Offline
Thank you very much for your help.
I can confirm that this commit introduces the problem: https://github.com/synopse/mORMot2/comm … 928c8b882b
I see that is has been discussed in that thread so I'll post there: https://synopse.info/forum/viewtopic.php?pid=40358
Offline
The interpretation of the EMR_EXTSELECTCLIPRGN inside the WMF indeed seems to cut away the wrong part in the PDF.
R0109: [034] EMR_RESTOREDC (s=12) {iRelative(-1)}
R0110: [030] EMR_INTERSECTCLIPRECT (s=24) {rclClip(16,82,776,113)}
R0111: [037] EMR_SELECTOBJECT (s=12) {Stock object: 13=OBJ_FONT}
R0112: [040] EMR_DELETEOBJECT (s=12) {ihObject(6)}
R0113: [037] EMR_SELECTOBJECT (s=12) {Table object: 4=OBJ_FONT}
R0114: [024] EMR_SETTEXTCOLOR (s=12) {0x00000000}
R0115: [022] EMR_SETTEXTALIGN (s=12) {iMode(24= TA_BASELINE)}
R0116: [075] EMR_EXTSELECTCLIPRGN (s=64) {iMode(5=RGN_COPY), RGNDATA[ptr:0x071d0cb8, size:48, box(110,176,882,314)]}
R0117: [022] EMR_SETTEXTALIGN (s=12) {iMode(24= TA_BASELINE)}
R0118: [022] EMR_SETTEXTALIGN (s=12) {iMode(24= TA_BASELINE)}
R0119: [022] EMR_SETTEXTALIGN (s=12) {iMode(24= TA_BASELINE)}
This could be fixed by reverting back to just removing the complete TPdfEnum.ExtSelectClipRgn() to do nothing (putting an exit above fixes it).
But that's not a real solution of course.
Better would be to find out why the values (110,176,882,314) are interpreted wrong.
I'll try to see how this is done with the other (more developed solutions) for GDI rendering.
Offline
So I've added debug code to visualize the clipping rectangle and it looks like it is simply offset by the PDF page's margins! Could we simply subtract the margins from there ? How to get them ?
Screenshot: https://snipboard.io/MxHLvX.jpg
Test code:
procedure TPdfEnum.ExtSelectClipRgn(data: PEMRExtSelectClipRgn);
var
RGNs: PRgnData;
i: Integer;
RCT: TRect;
ClipRect: TPdfBox;
begin
// see http://www.codeproject.com/Articles/1944/Guide-to-WIN-Regions
if data^.iMode <> RGN_COPY then exit; // we are handling RGN_COPY (5) only..
if not DC[nDC].ClipRgnNull then // if current clip then finish
begin
Canvas.GRestore;
Canvas.NewPath;
Canvas.fNewPath := False;
DC[nDC].ClipRgnNull := True;
fFillColor := -1;
end;
if Data^.cbRgnData > 0 then
begin
Canvas.GSave;
Canvas.NewPath;
DC[nDC].ClipRgnNull := False;
RGNs := @Data^.RgnData;
for i := 0 to RGNs^.rdh.nCount - 1 do
begin
Move(RGNs^.Buffer[i * SizeOf(TRect)], RCT, SizeOf(RCT));
Inc(RCT.Bottom);
Inc(RCT.Right);
ClipRect := Canvas.BoxI(RCT, false);
// Draw the clipping rectangle for debugging
Canvas.SetRGBStrokeColor($55FF00FF); // Set a distinct color for the clipping rectangle
Canvas.Rectangle(ClipRect.Left,ClipRect.Top,ClipRect.Width,ClipRect.Height);
Canvas.Stroke; // Draw the outline of the rectangle
// Apply the clipping path
Canvas.NewPath;
Canvas.Rectangle(ClipRect.Left, ClipRect.Top, ClipRect.Width, ClipRect.Height);
end;
Canvas.Closepath;
Canvas.Clip;
Canvas.NewPath;
Canvas.FNewPath := False;
end;
end;
Offline
Yes, I also found it is the Canvas.BoxI translation that goes wrong.
MtwStark had a lot of changes to the GDI and clipping code.
But apparently the changes where so big, with several regression errors, it was chosen to revert instead of fixing those problems (which is a shame).
That code is here:
https://synopse.info/forum/viewtopic.ph … 786#p25786
There the TPdfEnum.ExtSelectClipRgn uses an adjusted BoxI() function. Namely Canvas.absBoxI().
// MTW 2017-11-22 - Logical units to Device units with absolute reference to page origin
// used for EMF clipping functions, region data coordinats are absolute (not relative to WorldTransform)
// Inc(RCT.Bottom);
//ClipRect := Canvas.BoxI(RCT, False);
// need normalization for winding rule
ClipRect := Canvas.absBoxI(RCT, true);
Canvas.Rectangle(ClipRect.Left, ClipRect.Top, ClipRect.Width,
ClipRect.Height);
Still need to figure out if this can be compressed to just a few simple changes (like you mentioned about offset or size).
Without changing too much in the complete rest of the code.
Offline
This will draw the rectangle correctly:
RCT.Left := RCT.Left + round(Canvas.fWinOrg.X * Canvas.fViewSize.cx / Canvas.fWinSize.cx);
RCT.Right := RCT.Right + round(Canvas.fWinOrg.X * Canvas.fViewSize.cx / Canvas.fWinSize.cx);
RCT.Top := RCT.Top + round(Canvas.fWinOrg.Y * Canvas.fViewSize.cy / Canvas.fWinSize.cy);
RCT.Bottom := RCT.Bottom + round(Canvas.fWinOrg.Y * Canvas.fViewSize.cy / Canvas.fWinSize.cy);
ClipRect := Canvas.BoxI(RCT, false);
That corrects the I2X/I2Y to not use fWinOrg.X/Y.
But the comment from the other source is:
Logical units to Device units with absolute reference to page origin used for EMF clipping functions, region data coordinats are absolute (not relative to WorldTransform)
Which seems to suggest it does need to be relative to fWinOrg but not to fWorldOffsetX.
(But in the WMF the WinOrg.X/Y is -100/-100. I'll need to readup on this as to what it actually should be.)
Last edited by rvk (2024-05-14 18:30:57)
Offline
Looks like it's just absolute coordinates.
So should be something like this:
ClipRect.Left := RCT.Left * Canvas.fDevScaleX;
ClipRect.Top := Canvas.fPage.GetPageHeight - (RCT.Top * Canvas.fDevScaleX);
ClipRect.Width := (RCT.Right - RCT.Left) * Canvas.fDevScaleX;
ClipRect.Height := -(RCT.Bottom - RCT.Top) * Canvas.fDevScaleY; // Origin is bottom left so reversed
// ClipRect := Canvas.BoxI(RCT, false);
The problem now is that we don't have a set of EMF/WMF files with complex and diverse structures to test this with.
From the other topic (from which my patch came) the fix worked perfectly for me.
But somehow, in your code, the fWinOrg is (-100,-100) which throws things off.
Maybe there are other possible problems with other complex EMF files (like when Canvas.fViewSize.cx isn't 1, for which we don't have an example to test with).
For example... in this topic on another forum, you can download a A.EMF and B.EMF.
https://www.vbforums.com/showthread.php … ing-Issues
It seems that SynPDF also has problems with A.EMF to place the objects correctly (namely the big blue ball).
And there is also a small artifact at te right bottom half of that ball.
This has nothing to do with this clipping code but it shows there are more problems with coordinates in the GDI+/EMF translation of SynPDF.
Last edited by rvk (2024-05-15 08:41:46)
Offline
Excellent work ? Both of your updates fixes the specific problem for this EMF file.
I'm even more confident that this is the correct fix because LibreOffice also fixed EMR_EXTSELECTCLIPRGN this way: https://github.com/LibreOffice/core/com … 60a101f661
See these lines: https://github.com/LibreOffice/core/blo … r.cxx#L372
@rvk Do you believe that this is good enough for a pull request ?
@ab Would you accept those changes in the main repository ?
Offline
I'm even more confident that this is the correct fix because LibreOffice also fixed EMR_EXTSELECTCLIPRGN this way: https://github.com/LibreOffice/core/com … 60a101f661
See these lines: https://github.com/LibreOffice/core/blo … r.cxx#L372
But that's the really strange part.
It says:
Make EMR_EXTSELECTCLIPRGN factor in WinOrg coordinates
But... the current code DOES factor in fWinOrg.
But I think this is wrong.
If you look at my last changes
ClipRect.Left := RCT.Left * Canvas.fDevScaleX;
ClipRect.Top := Canvas.fPage.GetPageHeight - (RCT.Top * Canvas.fDevScaleX);
ClipRect.Width := (RCT.Right - RCT.Left) * Canvas.fDevScaleX;
ClipRect.Height := -(RCT.Bottom - RCT.Top) * Canvas.fDevScaleY; // Origin is bottom left so reversed
// ClipRect := Canvas.BoxI(RCT, false);
You see it doesn't use fWinOrg at all.
It just uses the coordinates without translating it with WinOrg at all.
Weird...
Offline
Ha, if you look at the code in LibreOffice... this fix was not for NOT for RegionMode::RGN_COPY but for everything else.
So for RGN_COPY it is absolute coordinates... and for anything else, that fix you point at, translates with WinOrg.
case EMR_EXTSELECTCLIPRGN :
{
sal_uInt32 nRemainingRecSize = nRecSize - 8;
if (nRemainingRecSize < 8)
bStatus = false;
else
{
sal_Int32 nClippingMode(0), cbRgnData(0);
mpInputStream->ReadInt32(cbRgnData);
mpInputStream->ReadInt32(nClippingMode);
nRemainingRecSize -= 8;
// This record's region data should be ignored if mode
// is RGN_COPY - see EMF spec section 2.3.2.2
if (static_cast<RegionMode>(nClippingMode) == RegionMode::RGN_COPY)
{
SetDefaultClipPath();
}
else
{
basegfx::B2DPolyPolygon aPolyPoly;
if (cbRgnData)
ImplReadRegion(aPolyPoly, *mpInputStream, nRemainingRecSize, GetWinOrg());
const tools::PolyPolygon aPolyPolygon(aPolyPoly);
SetClipPath(aPolyPolygon, static_cast<RegionMode>(nClippingMode), false);
}
}
For this individual issue... this might work correctly.
(And if anything pops up, we should fix it at the spot)
Last edited by rvk (2024-05-15 09:17:52)
Offline
I now have this.
But I wonder if now the inc for r.bottom and r.right are still needed. Need to check this.
r := pr^;
inc(r.Bottom); //?????????
inc(r.Right); //?????????
ClipRect.Left := r.Left * Canvas.fDevScaleX;
ClipRect.Top := Canvas.fPage.GetPageHeight - (r.Top * Canvas.fDevScaleX);
ClipRect.Width := (r.Right - r.Left) * Canvas.fDevScaleX;
ClipRect.Height := -(r.Bottom - r.Top) * Canvas.fDevScaleY; // Origin is bottom left so reversed
// with Canvas.BoxI(r, false) do
with ClipRect do
Canvas.Rectangle(Left, Top, Width, Height);
inc(pr);
Offline
Yep. Just checked.
My original is this (this is an image with a lot of clip-data):
With the adjusted code I get this (see the white pixels at the lower right edges):
Then with commenting out the 2 inc() lines it's correct again:
So now I have this:
var
ClipRect: TPdfBox;
//...
r := pr^;
// inc(r.Bottom);
// inc(r.Right);
ClipRect.Left := r.Left * Canvas.fDevScaleX;
ClipRect.Top := Canvas.fPage.GetPageHeight - (r.Top * Canvas.fDevScaleX);
ClipRect.Width := (r.Right - r.Left) * Canvas.fDevScaleX;
ClipRect.Height := -(r.Bottom - r.Top) * Canvas.fDevScaleY; // Origin is bottom left so reversed
// with Canvas.BoxI(r, false) do
with ClipRect do
Canvas.Rectangle(Left, Top, Width, Height);
inc(pr);
Now it's still the question if this all still works correctly if there is another fWorldOffsetX and fWorldFactorX.
For that we need more diverse EMF files.
Last edited by rvk (2024-05-15 09:39:28)
Offline
This program can be used to create EMF files with various clipping regions operations (RGN_AND, RGN_COPY, RGN_DIFF...). It could be a useful base for testing purposes:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows, SysUtils, Classes, Vcl.Graphics;
procedure CreateEMFWithClipRegion(const FileName: string; Mode: Integer);
var
DC: HDC;
MetaFileDC: HDC;
MetaFile: HENHMETAFILE;
aRect: TRect;
RectRgn, TriangleRgn, CombinedRgn: HRGN;
Brush: HBRUSH;
Pen: HPEN;
Points: array[0..2] of TPoint;
begin
// Create a device context for the screen
DC := GetDC(0);
try
// Define the bounding rectangle for the metafile
aRect := Rect(0, 0, 300, 300);
// Create an enhanced metafile device context
MetaFileDC := CreateEnhMetaFile(DC, PChar(FileName), @aRect, nil);
try
// Draw a colored background
Brush := CreateSolidBrush(RGB(240, 240, 240)); // Light gray background
Pen := CreatePen(PS_SOLID, 1, RGB(240, 240, 240)); // Light gray pen
SelectObject(MetaFileDC, Brush);
SelectObject(MetaFileDC, Pen);
Rectangle(MetaFileDC, aRect.Left, aRect.Top, aRect.Right, aRect.Bottom);
// Define a rectangle clipping region in the middle
RectRgn := CreateRectRgn(75, 75, 225, 225);
// Define a triangle region
Points[0] := Point(50, 250);
Points[1] := Point(150, 50);
Points[2] := Point(250, 250);
TriangleRgn := CreatePolygonRgn(Points, Length(Points), WINDING);
// Combine the rectangle region and the triangle region
CombinedRgn := CreateRectRgn(0, 0, 0, 0); // Create an empty region
CombineRgn(CombinedRgn, RectRgn, TriangleRgn, Mode);
// Select the combined region with the specified mode
ExtSelectClipRgn(MetaFileDC, CombinedRgn, RGN_COPY);
// Draw a green rectangle
Brush := CreateSolidBrush(RGB(0, 255, 0)); // Green brush
Pen := CreatePen(PS_SOLID, 1, RGB(0, 255, 0)); // Green pen
SelectObject(MetaFileDC, Brush);
SelectObject(MetaFileDC, Pen);
Rectangle(MetaFileDC, 30, 30, 200, 200);
// Draw a red circle
Brush := CreateSolidBrush(RGB(255, 0, 0)); // Red brush
Pen := CreatePen(PS_SOLID, 1, RGB(255, 0, 0)); // Red pen
SelectObject(MetaFileDC, Brush);
SelectObject(MetaFileDC, Pen);
Ellipse(MetaFileDC, 100, 100, 270, 270);
// Deselect the clipping region
SelectClipRgn(MetaFileDC, 0);
// Clean up the brush, pen, and regions
DeleteObject(Brush);
DeleteObject(Pen);
DeleteObject(RectRgn);
DeleteObject(TriangleRgn);
DeleteObject(CombinedRgn);
finally
// Close the metafile and get the handle
MetaFile := CloseEnhMetaFile(MetaFileDC);
end;
// Save the metafile to a file
if MetaFile <> 0 then
begin
DeleteEnhMetaFile(MetaFile);
end;
finally
// Release the screen device context
ReleaseDC(0, DC);
end;
end;
procedure TestCreateEMF;
begin
CreateEMFWithClipRegion('C:\Tmp\_meta\Metafile_RGN_AND.emf', RGN_AND);
CreateEMFWithClipRegion('C:\Tmp\_meta\Metafile_RGN_COPY.emf', RGN_COPY);
CreateEMFWithClipRegion('C:\Tmp\_meta\Metafile_RGN_DIFF.emf', RGN_DIFF);
CreateEMFWithClipRegion('C:\Tmp\_meta\Metafile_RGN_OR.emf', RGN_OR);
CreateEMFWithClipRegion('C:\Tmp\_meta\Metafile_RGN_XOR.emf', RGN_XOR);
end;
begin
TestCreateEMF;
end.
Offline
This program can be used to create EMF files with various clipping regions operations (RGN_AND, RGN_COPY, RGN_DIFF...). It could be a useful base for testing purposes:
I'm not sure what should be visible.
With EMFexplorer, nothing is really shown.
Also... in paint and IrfanView, nothing is shown. (I see in both that the actual image is just 11x11 pixels ?
Offline
Illustrator seems to work. Perhaps InkScape too?
Offline
AFAIR IrfanView is using the Windows GDI rendering directly.
So if it can't display anything, there is something wrong with the content.
A simple VCL app should be able to display this EMF.
Offline
I think CreateEnhMetaFile takes .01-millimeter unit as size, not pixels.
https://learn.microsoft.com/en-us/windo … hmetafilea
300*0.01 = 3mm which is 11 pixels at 96 dpi.
(according to https://pixelcalculator.com/en)
You can also see here that it needs mm measurements.
https://github.com/synopse/mORMot/blob/ … taFile.pas
Offline
Something like this:
aRect := Rect(0, 0, (300 * 2540) div 96, (300 * 2540) div 96);
But that results in 295x295 for me... ?
(and 96 needs to be the dpi for DC(0))
This program can be used to create EMF files with various clipping regions operations (RGN_AND, RGN_COPY, RGN_DIFF...). It could be a useful base for testing purposes:
BTW. That test project doesn't create a EMF which contains an offset WinOrg or non-standard scale, like your original code from this topic does.
So it wouldn't detect the regression for which you opened this topic
Last edited by rvk (2024-05-15 17:49:40)
Offline
AFAIR IrfanView is using the Windows GDI rendering directly.
So if it can't display anything, there is something wrong with the content.A simple VCL app should be able to display this EMF.
You're right. I could see the result in Illustrator so I though it was good enough for a quick and dirty first test. I'll see if that can be improved.
BTW. That test project doesn't create a EMF which contains an offset WinOrg or non-standard scale, like your original code from this topic does.
So it wouldn't detect the regression for which you opened this topic lol
That was just a quick and dirty first try to produce sample EMF files for testing purposes as you said previously: "For that we need more diverse EMF files."
I believe that it would be perfectly possible to generate the WinOrg using something like this at the beginning of the process:
// Set the window origin
SetWindowOrgEx(MetaFileDC, XOrigin, YOrigin, nil);
Offline
I adjusted the test project a bit to include creating a pdf directly after generating the PDF (with correct dimensions).
Also included a SetWindowOrgEx() line. That indeed shows my previous changes work correctly for offset window but I'm still unsure if it needs adjusting at other places.
I also noticed that the test for RGN_AND, RGN_DIFF, RGN_OR and RGN_XOR also worked correctly in PDF.
But that's because it doesn't test for those modes in EMR_EXTSELECTCLIPRGN but only in CombineRgn.
And using CombineRgn() doesn't actually put that mode RGN_AND etc in the metafile !!!
(it just buffers it and exports the final EMR_EXTSELECTCLIPRGN with RGN_COPY (which is done in the ExtSelectClipRgn() line.)
(edit: sorry for posting the code... moved to gist...)
For adjusted testproject:
https://gist.github.com/rvk01/60453f5bd … 8d75bba8fa
Last edited by rvk (2024-05-16 07:58:33)
Offline
Great! Meanwhile, I've greatly updated the EMF sample generator to produce 84 variations of valid metafiles for testing purposes. It can produce variations with different values for:
- SetWindowOrgEx (on or off)
- SetWorldTransform (including none, normal, scale, translate, rotate, shear, reflect)
- Clipping regions (on or off)
Metafiles are visible in IrfanView:
Source code: https://gist.github.com/jonjbar/5e32409 … 349c66ff07
Hopefully, this could help test and enhance the SynPDF producer in the future.
Offline
var ClipRect: TPdfBox; //... r := pr^; // inc(r.Bottom); // inc(r.Right); ClipRect.Left := r.Left * Canvas.fDevScaleX; ClipRect.Top := Canvas.fPage.GetPageHeight - (r.Top * Canvas.fDevScaleX); ClipRect.Width := (r.Right - r.Left) * Canvas.fDevScaleX; ClipRect.Height := -(r.Bottom - r.Top) * Canvas.fDevScaleY; // Origin is bottom left so reversed // with Canvas.BoxI(r, false) do with ClipRect do Canvas.Rectangle(Left, Top, Width, Height); inc(pr);
I can confirm that this patch fixes the clipping problem. @ab could it be added to the official repository ? Would you accept a PR ? If so @rvk would you like to do it as I don't want to "steal" your code.
Thank you.
Offline