#1 Re: PDF Engine » Bookmarks example » 2024-11-01 15:15:50

rvk
Kaiser wrote:

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 wink

If you do TPdfDocument.CreateOutline() you can create 'Bookmarks' in the PDF (also had to dive into the sourcecode to find that out wink ).

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

#2 Re: PDF Engine » mORMot2 export PDF file output Chinese string prompt error problem. » 2024-09-27 08:21:06

rvk

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.

#3 Re: PDF Engine » Bookmarks example » 2024-09-13 12:19:32

rvk

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).

#4 Re: PDF Engine » Graphics are distorted when DrawMeta function is used in GDIPages » 2024-09-13 12:14:04

rvk

I don't think we can help without a more complete reproduceable example.
(including the metafiles if not generated by the example)

#5 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-16 07:50:24

rvk

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

#6 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 17:32:26

rvk

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))

jonjbar wrote:

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 lol

#7 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 17:25:41

rvk

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

#8 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 17:09:57

rvk
jonjbar wrote:

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 ?

B6rUUbZ.png

#9 Re: PDF Engine » Automated PDF testing » 2024-05-15 10:52:31

rvk

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 wink

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 wink

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
ViYSlwT.png

test-015.pdf
DciMuVV.png

#10 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 09:36:12

rvk

Yep. Just checked.
My original is this (this is an image with a lot of clip-data):

SU3CMHS.png

With the adjusted code I get this (see the white pixels at the lower right edges):

B2U9Nj2.png

Then with commenting out the 2 inc() lines it's correct again:

BCxIRSp.png

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.

#11 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 09:25:18

rvk

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);

#12 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 09:16:48

rvk

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)

#13 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 09:11:27

rvk
jonjbar wrote:

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...

#14 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-15 08:30:49

rvk

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.

ubi4qdl.png

#15 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-14 18:29:40

rvk

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.)

#16 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-14 16:33:41

rvk

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.

#17 Re: PDF Engine » Regression: simple EMF file is drawn with an overlapping/missing rect » 2024-05-14 15:21:44

rvk

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.

#18 Re: PDF Engine » SynPdf library 1.18 Delphi QuickReport 6 Pro and ExpBarChart error » 2024-05-09 17:20:14

rvk

O, and the i + 1 should just be i in your case smile
(You start at 1, not 0)

#19 Re: PDF Engine » SynPdf library 1.18 Delphi QuickReport 6 Pro and ExpBarChart error » 2024-05-08 18:17:56

rvk

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;

#20 Re: PDF Engine » SynPdf library 1.18 Delphi QuickReport 6 Pro and ExpBarChart error » 2024-05-08 17:51:26

rvk
HansVM wrote:

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.

#21 Re: PDF Engine » SynPdf library 1.18 Delphi QuickReport 6 Pro and ExpBarChart error » 2024-05-08 16:27:44

rvk
HansVM wrote:

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);

#22 Re: PDF Engine » SynPdf library 1.18 Delphi QuickReport 6 Pro and ExpBarChart error » 2024-05-08 09:50:06

rvk

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;

#23 Re: PDF Engine » Clipping problem after image » 2024-04-26 09:36:13

rvk
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)

#24 Re: PDF Engine » Clipping problem after image » 2024-04-26 06:54:39

rvk

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)]}

#25 Re: PDF Engine » Clipping problem after image » 2024-04-23 20:30:40

rvk

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?).

#26 Re: PDF Engine » Clipping problem after image » 2024-04-23 17:31:31

rvk

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.

#27 PDF Engine » Clipping problem after image » 2024-04-23 17:21:49

rvk
Replies: 6

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

#28 Re: PDF Engine » TGDIPages.AppendRichEdit() doesn't import some PNG images » 2024-04-23 16:55:19

rvk

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)

#29 Re: PDF Engine » PDF Info (title etc) not saved in PDF version >1.3 » 2023-12-28 16:08:11

rvk
padule wrote:

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;

#30 Re: PDF Engine » Compile SynPdf error » 2023-11-22 20:55:46

rvk
ab wrote:

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.

ab wrote:

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.

#31 Re: PDF Engine » Compile SynPdf error » 2023-11-22 19:34:04

rvk
ab wrote:

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 wink

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 wink

// 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;

#32 Re: PDF Engine » Compile SynPdf error » 2023-11-22 13:58:38

rvk

@loadymf. In addition... you might want to try mORMot 2 which includes src/ui/mormot.ui.pdf.pas with the latest changes to the PDF engine. It might give you more luck.

#33 Re: PDF Engine » PDF:AddXObject is slow and out of memory! » 2023-10-15 13:38:12

rvk
Flashcqxg wrote:

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).

#34 Re: PDF Engine » Access violation in Drawbitmap in PDF engine with small bitmaps » 2023-09-09 21:01:15

rvk

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 wink

(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 smile

(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) wink

  DefaultHasher128 := @crc32c128;

#35 Re: PDF Engine » Access violation in Drawbitmap in PDF engine with small bitmaps » 2023-09-09 20:43:35

rvk

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 .

#36 Re: PDF Engine » Access violation in Drawbitmap in PDF engine with small bitmaps » 2023-09-09 20:28:31

rvk
ab wrote:

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 smile


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)

#37 PDF Engine » Access violation in Drawbitmap in PDF engine with small bitmaps » 2023-09-09 19:42:57

rvk
Replies: 4

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.

#38 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-09 10:36:11

rvk

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 smile

#39 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-08 18:46:04

rvk

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 wink ) 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).

#40 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-08 16:09:13

rvk

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).

FdtqgnK.png

When corrected:
D8aEvBq.png

#41 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-08 15:25:58

rvk

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

8MlFHSJ.png


47FiQoA.png

KhdSXMd.png

And this now seems pixel perfect with the EMF Explorer (which is this one):
UIg7UZO.png

#42 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-08 14:22:15

rvk

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.

Hryy7y6.png

Vj3tsw8.png

(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.)

#43 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-08 10:54:31

rvk
padule wrote:

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 wink

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;

#44 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-08 09:40:59

rvk
padule wrote:

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.

padule wrote:

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).

#45 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-07 16:26:32

rvk
ab wrote:

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.)

#46 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-07 15:32:19

rvk

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 wink ) and create a pull request with patch (once I know how to do that smile )

#47 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-07 13:28:46

rvk

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.

#48 Re: PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-07 11:37:45

rvk

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?

q6cDhkt.png

#49 PDF Engine » CreateRegion and SelectClipRgn support in PDF (EMR_EXTSELECTCLIPRGN) » 2023-09-07 10:28:56

rvk
Replies: 17

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 wink )

EMF
FDiRYHN.png

PDF
vrxzbqU.png

#50 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-07 08:40:09

rvk

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).
rcbBiEd.png

BTW. This is the original where you see the problem of GDI+ clearly.
naYnUpV.png

And this was with flat caps:
CDXVYmK.png

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 smile
BOzEgz8.png

Board footer

Powered by FluxBB