You are not logged in.
I tried creating bookmarks:
...
When I open the pdf, there are no bookmarks. Do I need to call another function to enable the bookmarks?
Yeah... 'Bookmarks' is really confusing in PDF/Adobe. Although it's called Bookmarks in the sidebar, they are actually Outlines. It's also called outlines in the PDF specs, so looking for Bookmarks will only confuse you
If you do TPdfDocument.CreateOutline() you can create 'Bookmarks' in the PDF (also had to dive into the sourcecode to find that out ).
function TPdfDocument.CreateOutline(const Title: string; Level: integer; TopPosition: single): TPdfOutlineEntry;
You can create several levels. 0 is the root and 1 etc are levels under it.
For example:
// pdf.addpage etc
pdf.CreateOutline('Test-page-1- root', 0, 1);
pdf.CreateOutline('Sub bookmark', 1, 20{?});
// pdf.addpage etc
pdf.CreateOutline('Test-page-2-root', 0, 1);
BTW. You do need to use AUseOutlines to true when creating TPdfDocumentGDI, otherwise outline doesn't work.
pdf := TPdfDocumentGDI.Create(true); // <- AUseOutlines = true
Yes, there seems to be some problems with Chinese characters (widestring conversion?) in mORMot2 in Lazarus.
This seems to work fine in Delphi (both Canvas.TextOut and Windows.TextOutW):
var
pdf: TPdfDocumentGDI;
S: WideString;
begin
pdf := TPdfDocumentGDI.Create;
try
pdf.AddPage;
// pdf.UseUniscribe := True; // not needed?
// pdf.AddTrueTypeFont('SimSun'); // not needed?
pdf.VCLCanvas.Font.Name := 'SimSun'; // 宋体
pdf.VCLCanvas.Font.size := 20;
S := WideString('测试内容');
pdf.VCLCanvas.TextOut(200, 120, S);
Windows.TextOutW(pdf.VCLCanvas.Handle, 200, 160, PWidechar(S), Length(S));
pdf.SaveToFile(ExtractFilePath(Application.ExeName) + 'ceshi.pdf');
ShellExecute(Application.Handle, 'open', pChar(ExtractFilePath(Application.ExeName) + 'ceshi.pdf'), '', '', SW_SHOWNORMAL);
finally
pdf.Free;
end;
end;
I thought I'd try direct TPdfDocument, skipping the GDI entirely... but that also only works in Delphi and crashes on Lazarus.
(With TextOut instead of TextOutW it works but produces garbage characters logically)
var
pdf: TPdfDocument;
S: WideString;
begin
pdf := TPdfDocument.Create;
try
pdf.AddPage;
S := '发现双引号不正确“问题”';
pdf.Canvas.SetFont('SimSun', 12, []);
Pdf.Canvas.TextOutW(100, 600, PWideChar(S));
pdf.SaveToFile(ExtractFilePath(Application.ExeName) + 'ceshi.pdf');
ShellExecute(Application.Handle, 'open', pChar(ExtractFilePath(Application.ExeName) + 'ceshi.pdf'), '', '', SW_SHOWNORMAL);
finally
pdf.Free;
end;
end;
So yes, definitely a problem with that code on Lazarus.
Do you mean the bookmarks at the right of the PDF?
Have you tried using CreateBookMark yet?
If you search on this forum there are several examples of CreateBookMark (just calling it after page creation).
I don't think we can help without a more complete reproduceable example.
(including the metafiles if not generated by the example)
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
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
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
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 ?
In follow up on the other topic... I found a directory here with lots of EMF files to test.
https://github.com/kakwa/libemf2svg/tre … ources/emf
When putting this in the bugreport directory from the other topic... and changing the following.
It will create all the pdf files in that emf directory.
There are lots of pdf that end up corrupt.
And others have strange result. 000 and 015 doesn't have the axes. and 040 also has lots of things wrong.
It indeed shows the need for automated test
BTW. comparing images extracted from pdf from a testrun with baseline images would be very hard I imagine. They would never be pixel perfect.
And before that... you would first need to fix all the problems that already exist with the above emf's before using these for regression testing
uses
mormot.ui.pdf, ShellApi, System.IOUtils, System.Types;
procedure TForm1.Button1Click(Sender: TObject);
procedure ProcessAllFilesInDirectory(const Directory: string);
var
Files: TStringDynArray;
FileName: string;
begin
if not TDirectory.Exists(Directory) then exit;
Files := TDirectory.GetFiles(Directory, '*.emf', TSearchOption.soAllDirectories);
for FileName in Files do
begin
Self.DoConvertMetafileToPdf(Filename, ChangeFileExt(Filename, '.pdf'));
end;
end;
begin
ProcessAllFilesInDirectory(ExtractFilePath(Application.ExeName) + 'emf');
//Self.DoConvertMetafileToPdf(ExtractFilePath(Application.ExeName) + 'bogus.wmf', ExtractFilePath(Application.ExeName) + 'bogus.pdf');
end;
test-040.pdf
test-015.pdf
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.
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);
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)
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...
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.
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.)
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.
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.
O, and the i + 1 should just be i in your case
(You start at 1, not 0)
In your current code you can put a line like this:
meta.SaveToFile(format('c:\temp\meta-%d.emf', [i + 1])); // this saves the meta to c:\temp
// draw the page content
RenderMetaFile(Pdf.Canvas, aMeta,1,1,0,0); // <-- this is the old line
After that you can test with:
procedure TestMetaToPdf;
var
Pdf: TPdfDocument;
aMeta: TMetaFile;
begin
Pdf := TPdfDocument.Create;
try
Pdf.Info.CreationDate := Now;
Pdf.Info.Creator := 'meme';
Pdf.DefaultPaperSize := psA4;
Pdf.AddPage;
aMeta := TMetaFile.Create;
try
aMeta.LoadFromFile('c:\temp\meta-1.emf');
// draw the page content
RenderMetaFile(Pdf.Canvas, aMeta,1,1,0,0);
finally
aMeta.Free;
end;
try
Pdf.SaveToFile('c:\temp\test9.pdf');
finally
// Send to ...
end;
finally
Pdf.free;
end;
end;
I still get the error on the quickreport.TQRExpBarChart in Delphi 10.2 and QReport V6 Pro.
If you are using the latest mORMot2, can you save the metafile to file and try loading it in a separate procedure.
If it still gives you problems maybe you can share that metafile.
(Just to rule out any potential problems in combination with TQRExpBarChart)
I used meta.LoadFromFile and RenderMetaFile() and that worked fine.
Ok I have installet the mORMot2.
But it will not have this line Pdf.Canvas.RenderMetaFile(aMeta,1,1,0,0);
No, there is no TPDFCanvas.RenderMetaFile.
But you should use procedure RenderMetaFile() for that.
It's in mormot.ui.pdf so you can just call it.
You pass your Pdf.Canvas as first parameter. Rest is the same.
procedure RenderMetaFile(C: TPdfCanvas; MF: TMetaFile; ScaleX: single = 1.0;
ScaleY: single = 0.0; XOff: single = 0.0; YOff: single = 0.0;
TextPositioning: TPdfCanvasRenderMetaFileTextPositioning = tpSetTextJustification;
KerningHScaleBottom: single = 99.0; KerningHScaleTop: single = 101.0;
TextClipping: TPdfCanvasRenderMetaFileTextClipping = tcAlwaysClip);
You are using the older SynPDF? (Newer source is in mORMot2)
I can't spot the problem exactly... but you have a Pdf.CleanupInstance.
TObject.CleanupInstance should never be called directly.
The aMeta.Clear also seems useless (because that should also be done in aMeta.Free).
Remove those two lines and see if it helps.
Although the error doesn't seem to point to those places, it could be because of the try/finally (and exception handling).
Something like this:
procedure TfPrinterOptions.ExportAsPdf(QuickRep: TQRCompositeReport; aFileName, aDescription: String);
var
Pdf: TPdfDocument;
aMeta: TMetaFile;
i: integer;
pdfFilter: TQRPDFDocumentFilter;
begin
if NOT dmVA.aQryVAPraktice.Active then
dmVA.aQryVAPraktice.Active := True;
dmVA.aQryVAPraktice.Active := False;
QuickRep.Prepare;
Pdf := TPdfDocument.Create;
try
Pdf.Info.CreationDate := Now;
Pdf.Info.Creator := dmVA.aQryVAPraktice.FieldByName('PrakticeName').AsString;
Pdf.DefaultPaperSize := psA4;
for i := 1 to QuickRep.QRPrinter.PageCount do
begin
Pdf.AddPage;
aMeta := QuickRep.QRPrinter.GetPage(i);
try
// draw the page content
Pdf.Canvas.RenderMetaFile(aMeta,1,1,0,0);
finally
aMeta.Free;
end;
end;
try
Pdf.SaveToFile(aFileName); // Error Access violation at address 004C4620 in module ...
finally
// Send to ...
end;
finally
Pdf.free;
end;
end;
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
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.
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.
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)
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)]}
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?).
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.
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):
Incorrect (original code, with NewPath intact):
I'm not sure about Delphi 11 (I'm still on 10.2) but TJvRichEdit supports images. They have Ole callback stuff which is probably needed to render the images to the metastream.
(I currently do have a small problem with images, not showing text after an image, but I'll open a new topic for that)
Is this normal?
Works fine for me (with latest mORMot 2 sources).
What version of code are you using?
(otherwise a small sample would be helpful)
This works for me (generates a 1.5 PDF):
procedure MakePdfSynPdf;
var
FileTemp: string;
pdf: TPdfDocumentGDI;
begin
FileTemp := 'C:\Temp\Test1.pdf';
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 1
{ page := } pdf.AddPage;
pdf.VCLCanvas.Font.Name := 'Times New Roman';
pdf.VCLCanvas.Font.size := 24;
pdf.VCLCanvas.TextOut(100, 100, 'some text on page 1');
// page 2
{ page := } pdf.AddPage;
pdf.VCLCanvas.Font.Name := 'Times New Roman';
pdf.VCLCanvas.Font.size := 24;
pdf.VCLCanvas.TextOut(100, 100, 'some text on page 2');
// back to page 1
pdf.Canvas.SetPage(pdf.RawPages[0]);
pdf.VCLCanvas.Font.Name := 'Times New Roman';
pdf.VCLCanvas.Font.size := 24;
pdf.VCLCanvas.TextOut(200, 200, 'some extra text on page 1');
pdf.SaveToFile(FileTemp);
ExecAssociatedApp(FileTemp);
finally
pdf.Free;
end;
end;
You are right, mormot2ui does not install any component - there is no component installed with Lazarus packages anway IIRC.
No, components are installed via packages in Lazarus. Same as with Delphi. Only when you click Install the whole IDE (lazarus) gets re-compiled.
Now the mormot2ui package is made as an install (design-time) package which recompiles the IDE while this shouldn't be necessary because there is no components installed. So it can just be a non-design package (same as the mormot2).
It does work as it is now but "Install" wouldn't be needed I think.
Unless components are added.
BTW. In Delphi only the path is added to the project after which everything gets compiled automatically. This could also be done the same way with Lazarus or FPC. But a package could add that path automatically which is easier. But I think the package could also just contain all the needed paths without adding all the files specifically (but somehow that doesn't work correctly now). I will look into that.
Note that mormot.ui.pdf would work on Windows only, as documented in the unit.
Ah, yeah, I didn't look at that. I think FPC has its own fcl-pdf package anyway.
At least the mORMot 2 unit (should) compile with FPC and Windows.
You probably just want the complete mORMot 2 package. Compiling just the mormot.ui.pdf.pas will pull in everything else anyway.
Compiling mORMot 2 on Lazarus/FPC trunk (from 2 months ago) works fine.
I only had to include mormot.ui.pdf.pas and mormot.lib.uniscribe as files in my console project because they are not in the mormot2ui.lpk.
(mormot.ui.report.pas is missing there too)
I did get this message during install of mormot2ui.lpk.
Suspicious include path
The package mormot2ui 2.0.1 adds the path "$(PkgIncPath)" to the include path of the IDE.
This is probably a misconfiguration of the package.
It probably doesn't add the path to the include path.
The path is in the package but somehow it doesn't get included automatically to the project when adding mormot2ui as requirement.
Manually setting "mORMot2\src\ui;mORMot2\src\lib" as "Other unit files" in projects options works too.
After that, creating a PDF with TPdfDocumentGDI isn't a problem.
Can't speak about the rest of mORMot 2
But overall, using mormot.ui.pdf shouldn't be too hard on Lazarus (Windows, I didn't test Linux).
BTW. mormot2ui should install designtime components, shouldn't it?
I don't think it does. It does have the "Install" button but I don't think it really installs anything.
(or I'm just looking in the wrong place)
Edit: Ah, this probably answers it
// do-nothing-unit on non Delphi + Windows system
// = not yet compatible with FPC/LCL due to a lot of Windowsims and VCLisms :(
procedure Register;
begin
end;
I test with the TPdfDocumentGdi , and set the i from 1 to 70000,it out of memory!
With the latest version of mormot2.ui.pdf your code works fine for me (in Delphi 10.2 and 32 bit app) and results in a pdf of 25MB with 70.000 pages.
So you might want to use the latest version (the mormot2 one, not the synpdf one).
Wait??? Doesn't this line read bytes BEFORE the data structure if len is 12????
movups xmm1, dqword ptr [data + len - 16] // no read after end of page
data is the first pointer, len is 12 so data + len - 16 is going to do data - 4 ?
So using a array of less than 128 bits (16 bytes) is really dangerous with this function
(that would be any bitmap with width < 6 pixels for 24bit pixels which seems to be consistent with my experience)
~~
I tried to test with this but I can't fool it.
var
a: array[1..1] of byte;
hash: THash128Rec;
begin
DefaultHasher128(@hash, @a, 12);
You think that should give an AV.
~~
There would not be an immediate need to fix this, but we should be aware of it
(or a check could be added in drawbitmap and any other place where this hash is used)
And if someone needs a quick "fix".... this works too (just using another hasher128)
DefaultHasher128 := @crc32c128;
Mmm, Weird.
According to this it should indeed be 12.
// Alignment must be a power of 2. Color BMPs require DWORD alignment (32).
function BytesPerScanline(PixelsPerScanline, BitsPerPixel, Alignment: Longint): Longint;
begin
Dec(Alignment);
Result := ((PixelsPerScanline * BitsPerPixel) + Alignment) and not Alignment;
Result := Result div 8;
end;
((3 * 24) + 31) and not 31 = 96 div 8 = 12
Then it must be further on in _AesNiHashXmm0 .
DefaultHasher128 = AesNiHash should not read more than the number of bytes specified.
What is the value of "row" integer in your case?
What is the exact GPF line in the AesNiHash asm?
Ok, then there is something going on with the row.
It's a 24bit bitmap (default)
row := PERROW[B.PixelFormat]; = 24 bits = 3 bytes
w = 3 ---> that should be 3 * 3 = 9 bytes per row ???
row := (((w * row) + 31) and (not 31)) shr 3; // inlined BytesPerScanLine
row = 12
for y := 0 to h - 1 do
DefaultHasher128(@hash, B.{%H-}ScanLine[y], row);
So the row should be 9 bytes for a 3 pixel bitmap, wouldn't it?
The row value is 12. Seems 3 bytes too much
Crashed line is in _AesNiHashXmm0 for 64 bit.
But if row (len) is indeed 12 for a 3 pixel bitmap then that seems logical that it crashes there:
movups xmm1, dqword ptr [data + len - 16] // no read after end of page
I don't know if the scanline is supposed to be aligned at 32 bit but seeing that access violation it seems not.
(the access violation is always on the last scanline)
I'm trying to find out why sometimes the DrawBitmap crashes with an exception.
BTW. The exceptions are handled by the SaveToFile code but it still leaks a TPDFWriter, but that's not the topic here/
The code crashes on this line in TPdfDocument.CreateOrGetImage for bitmaps smaller than 5 pixels:
for y := 0 to h - 1 do
DefaultHasher128(@hash, B.{%H-}ScanLine[y], row);
I was originally doing this in Win32 and there it was really unpredictable. One run ok, other run crash.
When switching to Win64 I could let it crash almost consistently.
My concrete question is... is for that DefaultHasher128 function a bitmap needed with a width bigger or multiple of 4 bytes or 128 bits or something??
If yes, the code should check for that.
Below the code to reproduce (with just a blank bitmap).
Crashes almost consistently with Win64 in mormot.crypt.core.asmx86.inc (_AesNiHashXmm0) and in Win32 sometimes/unpredictable in mormot.crypt.core.asmx86.inc (_AesNiHash128).
(The access violation is handled by exception but results in a PDF with size 0.)
program test_pdf_small;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.Classes, mormot.ui.pdf, Vcl.Graphics, WinApi.Windows, ShellApi, System.IOUtils;
function GetFileSize(const FileName : string) : Int64;
var
Reader: TFileStream;
begin
Reader := TFile.OpenRead(FileName);
try
result := Reader.Size;
finally
Reader.Free;
end;
end;
var
pdf: TPdfDocumentGDI;
MemBmp: Vcl.Graphics.TBitmap;
I: Integer;
begin
for I := 1 to 100 do
begin
pdf := TPdfDocumentGDI.Create;
try
pdf.DefaultPageWidth := 30;
pdf.DefaultPageHeight := 30;
pdf.AddPage;
// create a very small bitmap and copy to canvas
MemBmp := Vcl.Graphics.TBitmap.Create;
try
MemBmp.Width := 4;
MemBmp.Height := 4;
BitBlt(pdf.VclCanvas.Handle, 0, 0, MemBmp.Width, MemBmp.Height, MemBmp.Canvas.Handle, 0, 0, SRCCOPY);
finally
MemBmp.Free;
end;
// crashes here, also note the leak when savetofile has an silent exception
pdf.SaveToFile('test.pdf');
finally
pdf.Free;
end;
if GetFileSize('test.pdf') = 0 then
writeln('error on ' + I.ToString)
else
writeln('correct ' + I.ToString);
end;
// ShellExecute(0, 'open', 'test.pdf', nil, nil, SW_NORMAL);
writeln('done');
readln;
end.
Thanks for the merge.
When looking for the word "clipping" here on the forum I came across this topic from MtwStark.
https://synopse.info/forum/viewtopic.php?id=4276
It seems (s)he made a lot (and I mean A LOT) of changes to SynPDF regarding the clipping-code.
I couldn't find a pull request (or even fixes on github).
I understand that those changes were a LOT to just merge with the existing code (and it wasn't even as fix for mORMot2) and was left behind because of this.
If I have any other fixes I'll try to keep them small and clearly understandable
I searched back to where these changes were reverted.
Here are some references (for documentation and futures sake).
Original commit in SynPDF and mORMot was on 2015-11-09
https://github.com/synopse/SynPDF/commi … 55924c4d9c
https://github.com/synopse/mORMot/commi … b4077dc725
The revert in mORMot was done here on 2017-01-13 (and SynPdf on 2017-01-18):
https://github.com/synopse/mORMot/commi … 215790822e
https://github.com/synopse/SynPDF/commi … 8388dc689a
It points to this topic: https://synopse.info/forum/viewtopic.php?pid=22968
The strange thing is that this is discussed earlier (in 2016) but then this change wouldn't have happened yet.
https://synopse.info/forum/viewtopic.ph … 071#p20071
On the commit on SynPDF there are some comments in 2022 where it's mentioned the code was good (but was never followed upon):
https://github.com/synopse/SynPDF/commi … a#comments
https://github.com/synopse/SynPDF/commi … #r84370305
I've created a pull request (my first ) with the changes (including the check on iMode = 5).
I also fixed the DrawBitmap() to add 1 for Bottom and Right (the bitmap was actually drawn too small).
"Reintroduce SelectClipRgn support for GDI+ and fix DrawBitmap()"
Hopefully nobody has problems with it (otherwise I'm willing to help, with a problem .emf, to find the problem).
Ok, here is another proof that the bitmap was drawn too small.
I added a FillRectangle of 1x1 at coordinates 0,0 1,1 and 4,6.
You see clearly that in the original drawing of bitmap with 2x4 pixels (drawn at 2,2), the lower right is not connected to the FillRectangle.
So, or all the BoxI() for FillRect etc. is not correct (but they seems to be places correctly) or the code for DrawBitmap is incorrect (too small).
After some further testing I will prepare a pull request for the ExtSelectClipRgn with the addition of corrected bottom/right in DrawBitmap.
(After that, when I get the time I'll see if I can tackle the EMR_SELECTCLIPPATH, which is completely missing now).
When corrected:
YES, PERFECT. I think I found a problem in the drawing of the DrawBitmap() in the PDF Engine.
For the old ExtSelectClipRgn code there was a correction of bottom (and I added right) of +1.
This was because in BoxI() there is I2Y(Rect.Bottom - 1) and I2X(Rect.Right - 1) in the correction to coordinates and size.
For the correct handling you would also need to do the Bottom + 1 and Right + 1 in the DrawBitmap() (same as in ExtSelectClipRgn).
So the bitmap is now drawn always 1 bitmap pixel too small (???).
Can you comment on why the BoxI() has the - 1 for Bottom and Right ??
There might be other places where this is a problem and because this is outside of the clipping problem I thought you might have some comments.
I can correct those before calling BoxI() in DrawBitmap() and ExtSelectClipRgn() and the outcome is perfect now:
R := TRect(Rect(xd, yd, wd + xd, hd + yd));
NormalizeRect(R);
Inc(R.Bottom); // <----- these 2 lines
Inc(R.Right); // <----- these 2 lines
box := BoxI(R, true);
clp := GetClipRect;
if (clp.Width > 0) and
(clp.Height > 0) then
Doc.CreateOrGetImage(bmp, @box, @clp) // use cliping
else
Doc.CreateOrGetImage(bmp, @box, nil);
// Doc.CreateOrGetImage() will reuse any matching TPdfImage
// don't send bmi and bits parameters here, because of StretchDIBits above
And this now seems pixel perfect with the EMF Explorer (which is this one):
Grrr. translating these coordinates is really hard.
Still not perfect.
It's also not just adding or substracting a value because the ratio/proportions are also off when using BoxI().
I can't figure out what other translation then BoxI() should be done.
The larger the image, the more deviation.
(There is also a big bug in PDF Engine when dealing with bitmaps of size 7x7 (very small). It crashes, even without the clipping stuff.)
Sure, here is a simple example function (you can plug it into the example project I shared some time ago to test it with the various buttons for pdf, canvas, printer).
Thanks.
I see from the resulting EMF that this one uses EMR_SELECTCLIPPATH.
The other topic is about fixing EMR_EXTSELECTCLIPRGN (for regions).
If I look at the EnumEMFFunc (where all the GDI+ messages are handled), I don't see any EMR_SELECTCLIPPATH.
So, that clipping technique is not supported in the PDF Engine.
Possible clipping records in EMF:
https://learn.microsoft.com/en-us/opens … e1a82e3e03
As far as I can see there are some constants from EMF mention which do nothing.
But the below is a list of the constants which are not even mentioned in the case statement.
Unfortunately EMR_SELECTCLIPPATH is one of them
EMR_SETMAPPERFLAGS = $10;
EMR_SETCOLORADJUSTMENT = 23;
EMR_OFFSETCLIPRGN = 26;
EMR_EXCLUDECLIPRECT = 29;
EMR_SCALEVIEWPORTEXTEX = 31;
EMR_SCALEWINDOWEXTEX = 32;
EMR_ANGLEARC = 41;
EMR_EXTFLOODFILL = 53;
EMR_FLATTENPATH = 65;
EMR_WIDENPATH = 66;
EMR_SELECTCLIPPATH = 67;
EMR_FRAMERGN = 72;
EMR_INVERTRGN = 73;
EMR_PAINTRGN = 74;
EMR_MASKBLT = 78;
EMR_PLGBLT = 79;
EMR_SETDIBITSTODEVICE = 80;
EMR_CREATEMONOBRUSH = 93;
EMR_CREATEDIBPATTERNBRUSHPT = 94;
EMR_POLYTEXTOUTA = 96;
EMR_POLYTEXTOUTW = 97;
EMR_CREATECOLORSPACE = 99;
EMR_SETCOLORSPACE = 100;
EMR_DELETECOLORSPACE = 101;
EMR_GLSRECORD = 102;
EMR_GLSBOUNDEDRECORD = 103;
EMR_PIXELFORMAT = 104;
EMR_DRAWESCAPE = 105;
EMR_EXTESCAPE = 106;
EMR_STARTDOC = 107;
EMR_FORCEUFIMAPPING = 109;
EMR_NAMEDESCAPE = 110;
EMR_COLORCORRECTPALETTE = 111;
EMR_SETICMPROFILEA = 112;
EMR_SETICMPROFILEW = 113;
EMR_ALPHABLEND = 114;
EMR_TRANSPARENTDIB = 117;
EMR_SETLINKEDUFIS = 119;
EMR_SETTEXTJUSTIFICATION = 120;
We observed that the dashed lines are "simulated" by drawing many ellipses.
Yes, I saw that too when tinkering with the code for drawing.
But it is GDI+ that's passing on that info.
I think the problem is that the clipping doesn't work correctly. The stroke should be clipped to the boundaries of the ellipses, so that the resulting line is the correct width. As the clipping doesn't work the result is a fatter line.
For clipping problem I opened a new topic:
https://synopse.info/forum/viewtopic.php?id=6694
I am about to create a pull request with a fix (but still need to learn how to do that).
But even with the fix for clipping, the dashed line is too big. There is no clipping involved in the dashed line from GDI+.
You can create an EMF file and examine it with emfexplorer (and then View>EMF as Text).
There is no clipping records there.
BTW. Can you share your code to create your clipping example. I would like to throw it though the clipping fix to see if that works correctly (especially at the edges).
You can send a pull request with your patched code, I could make a look at it, and probably merge it.
BTW. Do you have some reference (link, documentation or description) for the problems many of those users had with the old clipping code?
(So I could test this and see if there is an alternative fix is this still exists in current code.)
The code is almost identical with the old code (with the check for iMode added).
But I will test this some more (we wouldn't want a bad patch ) and create a pull request with patch (once I know how to do that )
I couldn't find that many problems with the old code on the forum.
No problem... I added the old code back with the addition of checking for data^.iMode is RGN_COPY in the ExtSelectClipRgn (which was mentioned in that topic).
// we are handling RGN_COPY (5) only..
if data^.iMode <> RGN_COPY then exit;
For me that works for now. If there are any problems I will mention them here.
Hmmmm, Yeah, definitely not supported.
Size of region data from GDI is 208 bytes.
EMR_EXTSELECTCLIPRGN:
begin
dum1 := PEMRExtSelectClipRgn(R)^.cbRgnData; // <---- has a block of 208 bytes
E.ExtSelectClipRgn(@PEMRExtSelectClipRgn(R)^.RgnData[0],
PEMRExtSelectClipRgn(R)^.iMode);
end;
But the function ExtSelectClipRgn() doesn't seem to do much with the data parameter??
procedure TPdfEnum.ExtSelectClipRgn(data: PRgnDataHeader; iMode: DWord);
begin
try
with DC[nDC] do
case iMode of
RGN_COPY:
begin
ClipRgn := MetaRgn; // where is the data parameter used??
ClipRgnNull := false;
end;
end;
except
on e: Exception do
; // ignore any error (continue EMF enumeration)
end;
end;
Any ideas?
Was this meant to be functional or is it still in development?
Did this work in the past ???
Seeing this post: https://synopse.info/forum/viewtopic.ph … 071#p20071
Edit:
The code from that old topic (which was once in SynPDF) seems to be working much better. (no anti-alias but that might be fixed.)
Why was that code removed?
I'm trying my hand again at the clipping problem when printing a bitmap in GDI+ with clipping.
Here are two images. One is from the the generated EMF and the other from the PDF generated with the latest mormot2.
It seems that clipping in PDF is only restricted to a box, and not to a complete non-square region.
Is this true?
Or is clipping even supported for EnumEMFFunc() for GDI+?
Seeing that ClipRgn and MetaRgn are TPdfBox it seems that it couldn't even do a region but only a box.
If it should be supported then I can investigate further why it doesn't work correctly.
(otherwise I would be wasting a lot of time )
EMF
PDF
I did see some downsides of my hack above.
When using larger width for pen for dahses it doesn't work anymore for flat caps (probably because EMR_POLYBEZIERTO16 isn't used in that case).
I have a more 'stable' hack for you now. But you would need to have a special unique color for the dashes:
EMR_EXTCREATEPEN: // approx. - fast solution
with PEMRExtCreatePen(R)^ do
if ihPen - 1 < cardinal(length(E.Obj)) then
with E.obj[ihPen - 1] do
begin
kind := OBJ_PEN;
PenColor := elp.elpColor;
PenWidth := elp.elpWidth;
PenStyle := elp.elpPenStyle and (PS_STYLE_MASK or PS_ENDCAP_MASK);
// this doesn't work because we don't have dashes
// if pen.style and PS_STYLE_MASK = PS_DASH then PenWidth := 0;
if elp.elpColor = 0 { r } + 255 { g } shl 8 + 0 { b } shl 16 then PenWidth := 0; // your color for dashes
end;
This will only set the pen width to 0 for when that color is used.
It seems a lot more stable (but is still an ugly hack).
It will also work for flat caps
(If anyone else has any other ideas about how to detect if we are in dashed lines ?)
Result:
(You'll notice the blue line creeps to the left but that's because of the round caps, and is according to specification, using the round cap before start of line).
BTW. This is the original where you see the problem of GDI+ clearly.
And this was with flat caps:
You also notice the corners are slightly different from the EMF version
So that does seem to be a minor 'bug' in synpdf but when using pen=0 those corners are cut anyway.
But maybe that's the reason the 'bug' is there in GDI+ to provide those corners for flat caps