#1 2023-08-28 14:21:58

padule
Member
Registered: 2023-07-19
Posts: 24

Drawing dashed lines on VCLCanvas

Hello everyone,

Drawing a simple dashed line using the VCLCanvas will always draw the line at a specific line width, regardless of the width I set the pen to.
Drawing non-dotted lines works fine.

I passed half a day browsing through the source code (mormot.ui.pdf) trying to find the culprit, but I could not find it, I guess I don't yet have a good enough knowledge of the internals.

Any idea what could cause this?
Thank you.

Last edited by padule (2023-08-28 14:23:09)

Offline

#2 2023-08-29 10:10:27

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

Re: Drawing dashed lines on VCLCanvas

Can you show your code or snippet?
For me it works fine with the latest source of mormot.ui.pdf.

PDF.VCLCanvas.Pen.Width := 1;
PDF.VCLCanvas.Pen.Style := psDash;
PDF.VCLCanvas.MoveTo(100, 600);
PDF.VCLCanvas.LineTo(300, 600);
PDF.VCLCanvas.Pen.Width := 4;
PDF.VCLCanvas.MoveTo(300, 600);
PDF.VCLCanvas.LineTo(600, 600);

lwVWREm.png

Offline

#3 2023-08-29 12:37:03

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

Hello,

Thank you for taking the time to look into this.
Maybe I should have mentioned that I use GDI+ to draw on the VCLCanvas. It works really fine, except for this problem with dotted lines.

I have a function I use to set the pen property values:

procedure TDrawDriver.SetPen(Color:TGPColor;Width:Single;Dash:TDashType);
begin
    //Pen is a TGPPen
    Pen.SetColor(Color);
    Pen.SetWidth(Width);
    Pen.SetLineCap(LineCapRound,LineCapRound,DashCapRound);
    //Dash is an array eg. (5,3,5,3) for a dash
    Pen.SetDashPattern(@Dash,4);
    if Pen.GetLastStatus<>Ok then Pen.SetDashStyle(DashStyleSolid);
end;

Then the actual drawing is done like this (simplified):

procedure TDrawDriver.DrawLine(line:TLine);
var path: TGPGraphicsPath;
     i: integer;
begin
    SetPen(line.Color,line.Width,line.Dash);
    path:= TGPGraphicsPath.Create();
    for i:=0 to line.Points.Count-2 do begin
        path.AddLine(line.Points[i].X,line.Points[i].Y,line.Points[i+1].X,line.Points[i+1].Y);
    end;

    //Graphics is a TGPGraphics created like:
    //Graphics:= TGPGraphics.Create(pdf.VclCanvas.Handle);
    Graphics.DrawPath(Pen,path);
    FreeAndNil(path);
end;

Here is the result. Both lines are width=1. The solid line is correct, the dashed line is drawn much bigger.

JaAxoQ5.png

I also noticed that the line cap doesn't work correctly with dashed lines, just magnifying the corner here. The line cap was set as round.

bhuENjX.png

Any idea?
Thank you.

Last edited by padule (2023-09-01 12:34:37)

Offline

#4 2023-08-29 13:53:52

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

Re: Drawing dashed lines on VCLCanvas

padule wrote:

Maybe I should have mentioned that I use GDI+ to draw on the VCLCanvas. It works really fine, except for this problem with dotted lines.
I have a function I use to set the pen property values:

Looks like you are programming the dash and pen yourself (and not by SynPDF).

I'm not familiar with the rest of the code so I can't say what's going wrong here.
And since there is no code to reproduce this I can't dive in the source myself.

You might want to break the code down to standalone parts, creating a reproducible project which just draws two lines.
With doing so you might find the problem yourself or it would at least be easier to track down the problem.

Also, to begin with, you might want to check if this line doesn't produce a GetLastStatus <> Ok error:
(and what error it does produce, since the problem is that you get a solid line)

Pen.SetDashPattern(@Dash,4);
if Pen.GetLastStatus<>Ok then Pen.SetDashStyle(DashStyleSolid);

Offline

#5 2023-08-29 14:04:18

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

Re: Drawing dashed lines on VCLCanvas

Using GDI+ to draw on the GDI canvas of the VCL is really not a good idea.
And I don't know which library you are using for GDI+ rendering, but usually it uses bitmap rendering over the VCL/GDI canvas handle.
So you loose most of the PDF vector benefits.

My guess is that it is more a problem of your GDI+ library, or how you use it, than a mormot.ui.pdf issue.
Or at least some confusion between how GDI, GDI+ and PDF are implemented and do interact.

A fully stand-alone and reproducible sample code could help.

Offline

#6 2023-08-29 14:17:35

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

rvk wrote:

Also, to begin with, you might want to check if this line doesn't produce a GetLastStatus <> Ok error:
(and what error it does produce, since the problem is that you get a solid line)

There is no error there.
The solid line in the screenshot is an example for reference (correct), the problem is with the dashed line which, despite using width=1 the same as the solid line, it is drawn much bigger.

Offline

#7 2023-08-29 14:32:26

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

ab wrote:

I don't know which library you are using for GDI+ rendering,

The Delphi Winapi.GDPIAPI, which is just a wrapper for the original Microsoft's gdiplus.dll library.


ab wrote:

usually it uses bitmap rendering over the VCL/GDI canvas handle.
So you loose most of the PDF vector benefits.

As you can see from the screenshots I added above, I can zoom into the pdf without any quality loss, so it is vector it seems.


ab wrote:

My guess is that it is more a problem of your GDI+ library, or how you use it, than a mormot.ui.pdf issue.
Or at least some confusion between how GDI, GDI+ and PDF are implemented and do interact.

This same code is used to draw on screen, on PDF and even on a printer, just by feeding it a different canvas handle.
The drawing is correct on screen and on printer, but wrong on PDF. It works even if I use a PDF printer, like Bullzip etc.

This is the only thing I found so far that doesn't work with the mormot.ui.pdf.


ab wrote:

A fully stand-alone and reproducible sample code could help.

I will try that, thank you.

Last edited by padule (2023-08-31 13:00:06)

Offline

#8 2023-09-04 12:08:39

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

Hello,

I prepared a stand-alone sample code, as requested.
Please note that the code is minimalist (no "try finally", error checking etc) for the sake of the example.

Here is the main function that does the drawing.
It does not use any external library, except mormot.ui.pdf.
It draws two lines, one solid line for reference, one dashed line. Both lines are drawn with a width=1 but the dashed line is wider on PDF.

uses mormot.ui.pdf, Winapi.GDIPAPI, Winapi.GDIPOBJ;

procedure Draw(Handle:HDC);
const Dashed: array [0..1] of Single = (5,4);
var Graphics: TGPGraphics;
    Pen: TGPPen;
    path: TGPGraphicsPath;
begin
    Graphics:= TGPGraphics.Create(Handle);
    Pen:= TGPPen.Create(MakeColor(255,0,0,255),1);
    Pen.SetLineCap(LineCapRound,LineCapRound,DashCapRound);

    //normal line
    path:= TGPGraphicsPath.Create();
    path.AddLine(30,500,40,40);
    path.AddLine(40,40,300,50);
    Graphics.DrawPath(Pen,path);
    FreeAndNil(path);

    //dashed line
    Pen.SetColor(MakeColor(255,0,255,0));
    Pen.SetDashPattern(@Dashed,2);
    path:= TGPGraphicsPath.Create();
    path.AddLine(50,500,60,60);
    path.AddLine(60,60,300,70);
    Graphics.DrawPath(Pen,path);
    FreeAndNil(path);

    FreeAndNil(Pen);
    FreeAndNil(Graphics);
end;

You can then call the drawing function like this, to save on PDF:

procedure DrawOnPdf;
var pdf: TPdfDocumentGDI;
begin
    pdf:= TPdfDocumentGDI.Create;
    pdf.AddPage;
    Draw(pdf.VclCanvas.Handle);
    pdf.SaveToFile('C:\test.pdf');
end;

If you want to check, you can also draw on a form canvas, or even print the drawing (add Vcl.Printers in the uses) like this.
Both on the form and on printer, the drawing is correct (both lines have same width).

procedure DrawOnForm;
begin
    Draw(Form1.Canvas.Handle);
end;

procedure DrawOnPrinter;
begin
    Printer.PrinterIndex:= Printer.Printers.IndexOf('My Printer');
    Printer.BeginDoc;
    Draw(Printer.Canvas.Handle);
    Printer.EndDoc;
end;

I also created a small self-contained project demonstrating the above, with a form and three buttons to perform the three types of drawing (form, pdf, print).
You can download it here: https://file.io/pTvvnrYbisxY  (54Kb).

Thank you.

Offline

#9 2023-09-04 16:24:22

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

Re: Drawing dashed lines on VCLCanvas

The problem is clearly that you are using GdiPlus to draw the content.

The GdiPlus directives are converted by the GdiPlus dll to regular GDI commands, in a way incompatible with what the PDF library can handle.
I am even surprised it even works. I expected the rendering to be at pixel level, not as vectorial calls.

Our usual way of using PDF and GdiPlus is to use our own GDI+ unit, drawing everything with regular VCL TCanvas methods, then either export it as PDF or draw it using GdiPlus as anti-aliaised.
It is e.g. what https://github.com/synopse/mORMot2/blob … report.pas does.
Behind the scene, the drawing itself is done in a TMetaFileCanvas, then it calls either mormot.ui.pdf.pas (for PDF rendering) or mormot.ui.gdiplus.pas (for GDI+ rendering).

Offline

#10 2023-09-05 10:33:10

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

ab wrote:

...in a way incompatible with what the PDF library can handle.

Yes, that's what I thought.

Now for the steps going forward.

Since everything seems to be working, except this, I am inclined to think that it could be fixed.
Do you think there are some intrinsic limitations within the pdf library itself which makes this impossible at the moment?

If you think it can be fixed, it would be helpful if you could point me roughly to the code where this is handled and I'll see if I can fix it myself.

Offline

#11 2023-09-05 11:25:34

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

Re: Drawing dashed lines on VCLCanvas

padule wrote:
ab wrote:

...in a way incompatible with what the PDF library can handle.

If you think it can be fixed, it would be helpful if you could point me roughly to the code where this is handled and I'll see if I can fix it myself.

Hi, a non-expert here... wink

For the actual setting of the pen and dash you can look in mormot.ui.pdf.pas at the procedure TPdfEnum.NeedPen.

But this only gets called when you actually save the file to pdf, not before (so the translation from gdi to pdf).

When putting a breakpoint there and watch for DC[nDC].pen.width you see that for both lines the value is 16 (in your example program).

Increasing the width to 100 in second breakpoint-loop seems to increase the width of the dashed line.
But lowering it under 16 to 8 for example doesn't seem to be possible.
Minimum width dashed line seems to be that value.

This is the result of setting the DC[nDC].pen.width to 1 for both lines.

qd7jAjN.png

And this for setting it to 100 for both lines.

HYY4Z6V.png

Offline

#12 2023-09-05 11:32:14

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

Re: Drawing dashed lines on VCLCanvas

O, when commenting out the check on the old pen.width so it ALWAYS sets the pen again, I was able to set a pen.width of 1 for the dashed line.

      // if pen.width * Canvas.fWorldFactorX * Canvas.fDevScaleX <> fPenWidth then

DBbnQID.png

Setting the solid line to 1 in code and the dashed line to 1 reveals that the dashed line is always a bit bigger.

bP2PafJ.png

I'm not sure if this is a PDF thing.
(if you create plain PDF with only vector line is it possible to create a 1pixel dashed line?)

I have two things I wonder about...

1) Is the resolution set to maximum? If the resolution is higher it could be that there is a minimum width for a line (with taking rounding errors in account).
       (for me fWorldFactorX and Canvas.fDevScaleX where not 1 so there could be some problems there. Canvas has a resolution.)

2) When using rounded caps, how would the PDF actually make a round cap for a 1 pixel line? You can't, so the line might always be slightly larger.

Last edited by rvk (2023-09-05 11:50:43)

Offline

#13 2023-09-05 14:03:25

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

rvk wrote:

Hi, a non-expert here... wink

Thank you for taking your time to look into this


rvk wrote:

I'm not sure if this is a PDF thing.
(if you create plain PDF with only vector line is it possible to create a 1pixel dashed line?)

In my small example program, you can use the print button to print on PDF using a PDF printer (eg. change 'My Printer' with 'Microsoft Print on PDF', included in Windows).
The resulting PDF of the print will be perfect (both lines with width=1), so it is not a limitation of the PDF format, it must be something in the mormot pdf library.

Offline

#14 2023-09-05 14:13:37

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

Re: Drawing dashed lines on VCLCanvas

padule wrote:

In my small example program, you can use the print button to print on PDF using a PDF printer (eg. change 'My Printer' with 'Microsoft Print on PDF', included in Windows).
The resulting PDF of the print will be perfect (both lines with width=1), so it is not a limitation of the PDF format, it must be something in the mormot pdf library.

No, actually it's something in GDI.

I had another quick look at the decompressed PDF it seems that there isn't really ONE dashed line (in terms of PDF line).

The line code looks like this and is correct:

/DeviceRGB {} CS
[0 0 1] SC
[] 0 d
1 J
0.75 w
22.5 467 m
30 812 l
225 804.5 l
S

And the dashed line looks like this with many repeats:

/DeviceRGB {} CS
[0 1 0] SC
[] 0 d
0 J
0.75 w
0.75 w
0.75 w
0.75 w
0.75 w
<many times>
/DeviceRGB {} cs
[0 1 0] sc
37.31 473.75 m
37.36 476.75 l
37.36 476.94 37.55 477.13 37.73 477.08 c
37.97 477.08 38.11 476.94 38.11 476.7 c
38.06 473.7 l
38.06 473.52 37.88 473.33 37.69 473.33 c
37.45 473.38 37.31 473.52 37.31 473.75 c
h
37.45 480.5 m
37.5 483.5 l
37.55 483.69 37.69 483.88 37.92 483.83 c
38.11 483.83 38.3 483.69 38.25 483.45 c
38.2 480.45 l
38.2 480.27 38.02 480.08 37.83 480.08 c
37.59 480.13 37.45 480.27 37.45 480.5 c
h
<last part repeated many times>

So it seems that GDI itself translate the dashed line itself to many multiple fragments (and calls to LineTo).

Above that code are many 0.75 w parts. (which stands for line width.

The kicker now.... I changed all the 0.75 w parts into 0.175 w to make the lines thinner.
The result is this:
BLy0NEe.png

Here you see clearly that it is GDI that is drawing the line.

When that is drawn on a canvas with 1 pixel resolution, you get the screen and printer result.
But when it is drawn to a canvas with much higher resolution, it's going to draw those circel thingies.

PS. I extracted the pdf with pdf2ps (from xpdf-tools) and looked at the resulting .ps file.
You can read the ps file afterwards with a postscript interpreter or with gimp (and setting a resolution of 1200 during import).

I know you can hack the pdf writer to write smaller lines (like I showed you earlier).
But it's definitely GDI that's drawing the dashed line itself.

Offline

#15 2023-09-05 14:21:05

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

Re: Drawing dashed lines on VCLCanvas

Actually. If you change all those 0.75 w into 0.075 w you get this:

8wLgHcn.png

Maybe GDI uses fill but shouldn't be drawn with a line length when using dash (because it already circling the dashes and only the inner should be filled, not the line itself) ??

O, and the multiple "xx w" was because I commented out the check on width.

Last edited by rvk (2023-09-05 14:27:52)

Offline

#16 2023-09-05 14:36:37

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

Here is the PDF created with the Microsoft PDF printer (correct with both solid and dashed line with width=1): https://file.io/jwK2sU2v0k2J  (3Kb)

rvk wrote:

So it seems that GDI itself translate the dashed line itself to many multiple fragments (and calls to LineTo).

Actually, I don't really care as long as the result is correct.

Offline

#17 2023-09-05 15:51:53

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

Re: Drawing dashed lines on VCLCanvas

padule wrote:

Here is the PDF created with the Microsoft PDF printer (correct with both solid and dashed line with width=1): https://file.io/jwK2sU2v0k2J  (3Kb)

rvk wrote:

So it seems that GDI itself translate the dashed line itself to many multiple fragments (and calls to LineTo).

Actually, I don't really care as long as the result is correct.

Well, you should care if you want to fix this.

I'm not sure why GDI+ is drawing the dashes as ellipses but it's definitely doing that at a higher resolution.

1) You could draw this to another drawable canvas and after that copy that canvas to VCLCanvas and see if that helps (probably with loosing some resolution).

2) Second option is to find out why GDI+ is drawing ellipses and seeing if you can alter that behavior (maybe with setting the stroke color to transparent?).

3) Third option is to hack mormot.ui.pdf and adjust the line width when there is a dashed line. But I looked at the TPdfEnum.NeedPen and when it's called, it's not called with any dash style because GDI+ takes the drawing of dash completely for itself. So it's difficult to know if there is a dash being drawn. You could put a global boolean switch and hack the TPdfEnum.NeedPen, to pass 0.1 if that boolean is set (and set the boolean yourself when you want to draw a dash).

But you'll see it's not something the mormot.ui.pdf itself can do because from mormot's point of view, there is no dashed line being drawn, but multiple smaller ellipses, by GDI+.

Offline

#18 2023-09-05 18:57:17

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

I really appreciate your efforts, did you look at the PDF I linked in the previous post? https://file.io/jwK2sU2v0k2J  (3Kb)

This was done with the same code just by printing on the Microsoft PDF printer.

In your previous post you were able to look into the PDF file internal commands, maybe you can look into this and see how it is done properly to achieve the correct result.

Since the printer canvas and the PDF file can handle the result well, the problem must be somewhere between the GDI and the way the mormot.ui.pdf library reads it.

Offline

#19 2023-09-05 19:42:27

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

Re: Drawing dashed lines on VCLCanvas

I did check and you can see the the print to pdf has a much lower resolution.
You can see that clearly when you zoom in on the end-points. They are not as smooth as the synpdf ones.

The line code in print to pdf is

/DeviceRGB {} CS
[0 0 1] SC
1 J
0 j
10 M
0.96 w
28.799999 480 m
38.400002 38.400002 l
288 48 l
S
Q
q
[1 0 0 1 47.52 480] cm

Line code in synpdf is

/DeviceRGB {} CS
[0 0 1] SC
[] 0 d
1 J
0.75 w
22.5 467 m
30 812 l
225 804.5 l
S

Next the dashed line in print to pdf is

/DeviceRGB {} cs
[0 1 0] sc
0 0 m
0.16 -3.84 l
0.32 -4.16 l
0.64 -4.32 l
0.96 -4.16 l
1.12 -3.68 l
0.96 0.16 l
0.96 0.48 l
0.48 0.48 l
0.16 0.48 l
0 0 l
h
0.32 -8.64 m
0.32 -12.48 l
0.48 -12.8 l
0.8 -12.8 l
1.12 -12.8 l
1.28 -12.32 l
1.28 -8.48 l
1.12 -8.16 l
0.8 -8 l
0.32 -8.16 l
0.32 -8.64 l
h
<last repeated many times>

In synpdf

/DeviceRGB {} cs
[0 1 0] sc
37.13 467 m
37.22 470 l
37.22 470.19 37.41 470.38 37.59 470.38 c
37.83 470.33 37.97 470.19 37.97 469.95 c
37.88 466.95 l
37.88 466.77 37.73 466.58 37.5 466.63 c
37.31 466.63 37.13 466.77 37.13 467 c
h
37.31 473.75 m
37.36 476.75 l
37.36 476.94 37.55 477.13 37.73 477.08 c
37.97 477.08 38.11 476.94 38.11 476.7 c
38.06 473.7 l
38.06 473.52 37.88 473.33 37.69 473.33 c
37.45 473.38 37.31 473.52 37.31 473.75 c
h
37.45 480.5 m
37.5 483.5 l
37.55 483.69 37.69 483.88 37.92 483.83 c
38.11 483.83 38.3 483.69 38.25 483.45 c
38.2 480.45 l
38.2 480.27 38.02 480.08 37.83 480.08 c
37.59 480.13 37.45 480.27 37.45 480.5 c
h

In code you can also notice that (when debugging) the EMR_POLYBEZIERTO16 is called for every EMR_LINETO.
That's what results in those x c#10 lines.

/// EMF enumeration callback function, called from GDI
// - draw most content on PDF canvas (do not render 100% GDI content yet)
function EnumEMFFunc(DC: HDC; var Table: THandleTable; R: PEnhMetaRecord;
  NumObjects: DWord; E: TPdfEnum): LongBool; stdcall;

I'm not sure why those curve calls are done in the synpdf version and not in the print to pdf one.
BUT, as I already mentioned... the print to pdf doesn't have smooth end-points. I think that's why there are so many "x l #10" lines for one section.
In synpdf those are smooth curves.

CqlXdLL.png
versus
gQOcGlX.png

Do note... this could also be due to the print to pdf driver just simulating a round end-point.

Last edited by rvk (2023-09-05 19:44:14)

Offline

#20 2023-09-05 21:16:12

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

Re: Drawing dashed lines on VCLCanvas

BTW If I don't use Print To PDF from Microsoft but my own PDF writer (based on Ghostscript driver) I get a better result.

NEGHHOF.png

Now the COMPLETE PDF looks like this (only relevant print part).
This is including the solid line AND the dashed line.

%%Page: 1 1
%%BeginPageSetup
%%PageOrientation: Portrait
pdfStartPage
0 0 612 792 re W
%%EndPageSetup
[] 0 d
1 i
0 j
0 J
10 M
1 w
/DeviceGray {} cs
[0] sc
/DeviceGray {} CS
[0] SC
false op
false OP
{} settransfer
0 0 612 792 re
W
q
q
[0.12 0 0 0.12 0 0] cm
6 w
1 J
/DeviceRGB {} CS
[0 0 1] SC
180 3600 m
240 6360 l
1800 6300 l
S
[24 30] 0 d
/DeviceRGB {} CS
[0 1 0] SC
300 3600 m
360 6240 l
1800 6180 l
S
Q
Q
showpage
%%PageTrailer
pdfEndPage
%%Trailer
end

This is how it should be.
The [24 30] 0 d is the actual dashed line setup for pdf (as you could see in TPdfCanvas.SetDash()).
And the corresponding x l #10 after that prints the dashed line.

But I have no idea how to setup GDI+ to do exactly the same for synpdf.

I think it has to do with the fact synpdf uses a EMR/enhanced meta canvas and gdi will pass the drawing different from passing it to the printerdriver via canvas. But I'm not sure (as I said, I'm no expert).

Last edited by rvk (2023-09-05 22:11:29)

Offline

#21 2023-09-05 22:48:39

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

Re: Drawing dashed lines on VCLCanvas

Last post for tonight. The problem is indeed the GDI+ to EnhancedMetaCanvas interface.
For example... if you change the FormButtom method to create a TMetaFileCanvas and use that and save it you see that the dashed line is also bigger.
(emf file can be opened in Paint)

procedure TForm1.btnFormClick(Sender: TObject);
var
  MyMetafile: TMetafile;
  MyMetaFileCanvas: TMetaFileCanvas;
begin
  MyMetafile := TMetafile.Create;
  MyMetaFileCanvas := TMetafileCanvas.Create(MyMetafile, CreateCompatibleDC(0));
  Draw(MyMetaFileCanvas.Handle);
  MyMetaFileCanvas.Free; // freeing puts it in MetaFile

  MyMetafile.SaveToFile('C:\Users\Rik\Downloads\test_pdf\test.emf');

  // Draw(Form1.Canvas.Handle);  // old line

end;

5NmWdQp.png
(still need to bump up the resolution here)

I'm not sure how the printer driver takes it's data from the printer-canvas but via a TMetaFileCanvas this might be a 'bug' in GDI+ to MetaCanvas.

But it's clear that the printerdriver uses a different method to receive it's data over the MetaFileCanvas method.

(In the past I used the ghostscript printerdriver for PDF in my program which would have solved that.)

Offline

#22 2023-09-06 08:48:47

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

Hello rvk,

I think you are on the right track with this.
The MetaFile (emf) is just a list of GDI commands.
After a bit of googling, apparently, there is a different format (emf+) for GDI+, so here is where probably the incompatibility arises.
I had read that mormot.ui.pdf uses MetaFiles internally, but wasn't aware of the incompatibility.

So now I don't know what to do next hmm

Offline

#23 2023-09-06 09:59:53

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

Re: Drawing dashed lines on VCLCanvas

EMF and EMF+ are not the same format, for sure.
When you draw something on a EMF or EMF+ canvas, you just emit GDI or GDI+ API calls.
The EMF format is well known and documented, whereas EMF+ is more like a format internal to GdiPlus.dll.

So in our case, when we draw the GDI+ into a GDI handle, there is a conversion into GDI API calls.
So there is no EMF+ format involved here.

Offline

#24 2023-09-06 10:03:17

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

Re: Drawing dashed lines on VCLCanvas

padule wrote:

The MetaFile (emf) is just a list of GDI commands.
After a bit of googling, apparently, there is a different format (emf+) for GDI+, so here is where probably the incompatibility arises.

I think actually that the Enhanced Mata Canvas for sympdf is already EMF+.

Ths is from Winapi.GDIPOBJ in Delphi:

// The following methods are for playing an EMF+ to a graphics
// via the enumeration interface.  Each record of the EMF+ is
// sent to the callback (along with the callbackData).  Then
// the callback can invoke the Metafile::PlayRecord method
// to play the particular record.
function EnumerateMetafile(metafile: TGPMetafile; const destPoint: TGPPointF;
  callback: EnumerateMetafileProc; callbackData: Pointer = nil;
  imageAttributes: TGPImageAttributes = nil): TStatus; overload;

And mormot.ui.pdf.pas receives that in:

/// EMF enumeration callback function, called from GDI
// - draw most content on PDF canvas (do not render 100% GDI content yet)
function EnumEMFFunc(DC: HDC; var Table: THandleTable; R: PEnhMetaRecord;
  NumObjects: DWord; E: TPdfEnum): LongBool; stdcall;

I think the problem is that GDI+ thinks it has to do with a device which can't interpret postscript.

There is an option with POSTSCRIPT_IDENTIFY to set PSIDENT_GDICENTRIC or PSIDENT_PSCENTRIC for GDI centric or Postscript centric but that's for the device driver I think. (I don't even know if that would help)

I don't know how to influence GDI+ into thinking we have the dash-line capability (so it sends DASH instead of those ellipses).

ab wrote:

So in our case, when we draw the GDI+ into a GDI handle, there is a conversion into GDI API calls.
So there is no EMF+ format involved here.

Yes, but when GDI+ is going to pass on the records it stored, it does so via the EnumerateMetafile which is EMF+.
Synpdf itself works with Enhanced Meta Canvas with EMF+ structures doesn't it?

GDI+ does have the dash stored as real dash-structure (like passed to it). It gives it correctly to the printer driver (via the canvas) on Draw() command.
But for the TEnhancedMetaCanvas this is translated to ellipsed (which contain a bug that it draws the line length which is too big).

I whish I knew how the printerdriver did this. It also receives the GDI+ records on Draw() but seems to translate them much more efficient (postscript dash codes instead of ellipses).

Last edited by rvk (2023-09-06 10:25:50)

Offline

#25 2023-09-06 10:28:35

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

Re: Drawing dashed lines on VCLCanvas

Perhaps with something like https://learn.microsoft.com/en-us/windo … -extescape

But I am not sure if it is enough for GDI+ to generate dash lines.

Offline

#26 2023-09-06 10:33:23

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

Re: Drawing dashed lines on VCLCanvas

Yes, I was already trying that:

  mode := PSIDENT_GDICENTRIC;
  mode := PSIDENT_PSCENTRIC;
  if ExtEscape(MyMetaFileCanvas.Handle, POSTSCRIPT_IDENTIFY, sizeof(DWORD), @mode, 0, nil) = 0 then
    Showmessage('error');

But it doesn't seem to work (so no dice, no error but still the ellipses) smile

There are also some calls from GDI+ to determine the capabilities of the 'driver' / canvas.

https://learn.microsoft.com/en-us/previ … 1(v=vs.85)

  if ExtEscape(MyMetaFileCanvas.Handle, GETTECHNOLOGY, 0, nil, SizeOf(DWord), @mode) > 0 then
    Showmessage(mode.ToString);

(above just raw draft, doesn't work.)
Later on I'll try to find if this could be something.

Offline

#27 2023-09-06 12:59:45

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

I still have one question though.

You confirmed that in the "print as pdf" file that I posted, the dashed line is still created as many segments (ellipses) and not as a real dashed line.
How does it display correctly then? What does it do differently?

Offline

#28 2023-09-06 13:16:04

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

Re: Drawing dashed lines on VCLCanvas

padule wrote:

I still have one question though.

You confirmed that in the "print as pdf" file that I posted, the dashed line is still created as many segments (ellipses) and not as a real dashed line.
How does it display correctly then? What does it do differently?

Yes, that's from the Microsoft Print to PDF. That driver is really bad. It also creates very large PDF's and isn't optimized.
I usually avoid that printer.
(But I don't know why those segments are printed/interpreted correctly)

I have my own printer for my application. It creates a Postscript printer from which a catch the output to a .ps file and I throw that through ghostscript.
Somehow the .ps file from that Postscript printer is very small and the dashed line doesn't contain segments.

(you can do this yourself by creating a postscript printer and let it print to a port FILE:)

Attached is a Temp.zip with an example of a .ps file printed with the Print to PDF driver and one with Ghostscript.
https://file.io/sH9jya1SyMEj

BTW. Both drivers use the same base driver (PScript5.dll) so it must be something that the ghostscript.ppd is setting (because that's the only difference).
(Edit: actually, the print to pdf from Microsoft now uses some other driver I see)

Here is the resulting PDF from my ghostscript printer and the one from Microsoft Print to PDF.
https://file.io/4FeFlH109Psb

You can clearly see the difference when you zoom in. And the ghostscript one really has the dash (as you can see in the .ps versions).

From ghostscript.ppd.

*% == Basic Capabilities
*LanguageLevel: "3"
*ColorDevice: True
*DefaultColorSpace: RGB
*FileSystem: True
*Extensions: CMYK FileSystem Composite
*TTRasterizer: Type42
*FreeVM: "10000000"
*PrintPSErrors: True
*ContoneOnly: True
*% AccurateScreensSupport: True

Maybe the Postscript level but I haven't looked into that.

Last edited by rvk (2023-09-06 13:24:29)

Offline

#29 2023-09-06 15:05:45

igors233
Member
Registered: 2012-09-10
Posts: 235

Re: Drawing dashed lines on VCLCanvas

> I have my own printer for my application. It creates a Postscript printer from which a catch the output to a .ps file and I
> throw that through ghostscript.

This is very interesting, so you kind of have your own printToPDF printer installed as part of your app.
Can you explain a bit more how you achieved it?

Offline

#30 2023-09-06 15:42:31

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

Re: Drawing dashed lines on VCLCanvas

igors233 wrote:

This is very interesting, so you kind of have your own printToPDF printer installed as part of your app.
Can you explain a bit more how you achieved it?

The original reason for this was that I needed to print from my program to a pdf and after that merge it with a stationery background.pdf.

I do it the same like all other PDF creators, like PDFCreator, BullZip PDF printer etc. etc. (PDFCreator also even has the ability to merge afterwards with pdftk)

In my setup I installed Ghostscript and then I create a 'Postscript' printer with the ghostscript.ppd as main driver-description.
ghostscript.ppd uses the standard PScript5.dll from Windows.
For Windows 7+ I created my own version of the PDD-package because it needed to be signed, and the version from Ghostscript back then wasn't signed (I think now it is).

In my application I set the output for the print-job to a file (instead of the installed port LPT1: or NULL:) and print to the 'Postscript' printer.
That creates a .ps file which a can throw through ghostscript.exe (gswinc32.exe or gswin64c.exe). You could also use the gsdll64.dll.
That will result in a pdf (without background).
Then you could use pdftk to merge a background.pdf with your just created pdf.

I recently switched to SynPdf for creating a pdf. For this I also use TGdiPages (essentially the same as TPdfDocumentGDI).
(That way you would only have one code-base for the actual printing, PDF or real printer doesn't matter)

Using SynPdf has the advantage of bypassing the print-spooler of Windows and process of using ghostscript.exe (although the pdftk step is still needed for background).

There is only one downside in SynPdf, I recently discovered. In the Postscript driver from Windows I could print a transparent bitmap (hand signature on stationery).
When merging with a background (with background colors and images) the bitmap kept it's transparency.
In SynPdf it's only transparent for the current PDF/job (so over your own text in the same job).
But when merging the resulting PDF with a background.pdf (full lightgreen.pdf for example, the background of the bitmap is white).

(Transparency and GDI+ -> PDF is not an easy thing. I think this is a known problem.)

Offline

#31 2023-09-06 19:00:17

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

I found this on stackoverflow, which shows the exact same problem with TMetaFile and dashed lines:
https://stackoverflow.com/questions/763 … ile-canvas

Unfortunately, no solution there.

Offline

#32 2023-09-06 20:27:30

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

Re: Drawing dashed lines on VCLCanvas

Yes. I found more references stating that GDI+ and the enhanced meta canvas is inaccurate below a width of 1.5 or 2.
I'm still investigating the problem a bit but it is hard.

As I understand it now... the Microsoft Print to PDF uses a complete different method of using the GDI+ for printing.
You have GDI-based printing and XPS-based printing. Microsoft Print to PDF does the latter.

That's probably also the reason the dashed line is a path instead of actual dashed line.
I think XPS-based printing always uses the ellipse version which is badly rounded.
But they did fixed the bug that the TEnhancedMetaFile has (i.e. the bigger line width which the ellipses are drawn).
So we can forget about that one.

You also have several flavors of GDI-printing. You can print via the print spooler (which uses EMF format) or directly to the printer.
I'm not sure how I can force the EMF method to see if my printer then also has the same problem (setting the processor didn't hold).
I think the printer version prints directly (via the dll's) but I'm not sure about that and how.

This is for printing Microsoft Print to PDF and the XPS writer in Windows:

procedure TForm1.btnPrintClick(Sender: TObject);
begin
  // Printer.PrinterIndex := Printer.Printers.IndexOf('Microsoft Print to PDF');
  // shows as technology = http://schemas.microsoft.com/xps/2005/06

  Printer.PrinterIndex := Printer.Printers.IndexOf('Postscript');
  // shows as technology = Postscript

  Printer.PrinterIndex := Printer.Printers.IndexOf('Microsoft XPS Document Writer');
  // shows as technology = http://schemas.microsoft.com/xps/2005/06

  Printer.BeginDoc;
  // Showmessage( GetTechnologyString( Printer.Canvas.Handle ) );
  Draw(Printer.Canvas.Handle);
  Printer.EndDoc;

  showmessage('done');

end;

This is the XPS Writer (and is the same as the Microsoft Print to PDF because it both uses XPS-based printing).
uWff2E3.png


BTW. The used GetTechnologyString() looks like this. (the SetPostscriptIdentity doesn't seem to do much.)

const
  PSIDENT_GDICENTRIC = 0;
  PSIDENT_PSCENTRIC = 1;
  POSTSCRIPT_IDENTIFY = 4117;
  GETTECHNOLOGY = 20;

function GetTechnologyString(Handle: HDC): string;
var
  indata: DWord;
  outdata: array [0 .. 255] of Byte;
  S: string;
begin
  indata := GETTECHNOLOGY;
  if ExtEscape(Handle, QUERYESCSUPPORT, SizeOf(indata), @indata, 0, nil) > 0 then
  begin
    if ExtEscape(Handle, GETTECHNOLOGY, 0, nil, SizeOf(outdata), @outdata) > 0 then
    begin
      SetString(S, PAnsiChar(@outdata[0]), StrLen(PAnsiChar(@outdata[0])));
      Result := 'ok = ' + S;
    end
    else
        Result := 'gettechnology error';
  end
  else
      Result := 'query error';
end;

// doesn't do much ??
function SetPostscriptIdentity(Handle: HDC): string;
var
  indata: DWord;
  outdata: DWord;
begin
  indata := POSTSCRIPT_IDENTIFY;
  if ExtEscape(Handle, QUERYESCSUPPORT, SizeOf(indata), @indata, 0, nil) > 0 then
  begin
    indata := PSIDENT_GDICENTRIC;
    indata := PSIDENT_PSCENTRIC;
    if ExtEscape(Handle, POSTSCRIPT_IDENTIFY, SizeOf(indata), @indata, 0, nil) > 0 then
    begin
      Result := 'ok set POSTSCRIPT_IDENTIFY';
    end
    else
        Result := 'POSTSCRIPT_IDENTIFY error';
  end
  else
      Result := 'query error';
end;

Offline

#33 2023-09-06 21:00:38

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

Re: Drawing dashed lines on VCLCanvas

BTW. I have a REALLY REALLY BAD hack for you.

      EMR_POLYBEZIERTO16:
        begin
          if not pen.null then
            E.NeedPen;
          E.Canvas.SetLineWidth(0); // <--- add this line
          if not E.Canvas.fNewPath then
            if not Moved then

It will generate this for the PDF.
tAjPTyK.png

But I'm sure this has many undesired side-effects smile

It will fix the bug in GDI+ with drawing the dashes with a pen width of 0 because GDI+ didn't calculatie the pen width when doing the dashes.

But there might be other drawings which need EMR_POLYBEZIERTO16 to be drawn with a pen with of > 0.
(I'm also sure this 'fix' can be improved upon, for example by setting the pen correctly so other drawings will reset the pen if needed.)

BTW. This still doesn't the dashes in native postscript but with simulated dashes, but then with a pen width of 0.

Last edited by rvk (2023-09-06 21:06:13)

Offline

#34 2023-09-07 08:40:09

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

Re: Drawing dashed lines on VCLCanvas

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

Last edited by rvk (2023-09-07 08:46:38)

Offline

#35 2023-09-08 09:14:41

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

I made an experiment with this.

We observed that the dashed lines are "simulated" by drawing many ellipses.
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.

So, here is my experiment: I draw a simple triangle with a texture inside, and it is not clipped on PDF via mormot.ui.pdf.
Again, the same code works correctly on canvas and on printer (the texture is clipped to the shape).

PlS9zsj.jpg

What do you think?

Offline

#36 2023-09-08 09:40:59

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

Re: Drawing dashed lines on VCLCanvas

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

Offline

#37 2023-09-08 10:20:35

padule
Member
Registered: 2023-07-19
Posts: 24

Re: Drawing dashed lines on VCLCanvas

rvk wrote:

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

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

procedure Draw2(Handle:HDC);
var Graphics: TGPGraphics;
    Pen: TGPPen;
    Texture: TGPImage;
    path: TGPGraphicsPath;
    brush: TGPBrush;
begin
    Graphics:= TGPGraphics.Create(Handle);
    Pen:= TGPPen.Create(MakeColor(255,0,0,255),1);
    Pen.SetLineCap(LineCapRound,LineCapRound,DashCapRound);
    Texture:= TGPImage.Create('C:\texture.bmp');
    brush:= TGPTextureBrush.Create(Texture, MakeRect(0,0,Texture.GetWidth,Texture.GetHeight));

    //draw shape
    path:= TGPGraphicsPath.Create();
    path.AddLine(70,15,160,80);
    path.AddLine(160,80,20,185);
    path.AddLine(20,185,70,15);
    Graphics.FillPath(brush,path);
    Graphics.DrawPath(Pen,path);
    FreeAndNil(path);

    FreeAndNil(brush);
    FreeAndNil(Texture);
    FreeAndNil(Pen);
    FreeAndNil(Graphics);
end;

Last edited by padule (2023-09-08 10:21:33)

Offline

#38 2023-09-08 10:54:31

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

Re: Drawing dashed lines on VCLCanvas

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;

Last edited by rvk (2023-09-08 10:56:30)

Offline

Board footer

Powered by FluxBB