#101 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-06 13:16:04

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

#102 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-06 10:33:23

rvk

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.

#103 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-06 10:03:17

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

#104 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 22:48:39

rvk

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

#105 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 21:16:12

rvk

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

#106 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 19:42:27

rvk

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.

#107 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 15:51:53

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

#108 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 14:21:05

rvk

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.

#109 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 14:13:37

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

#110 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 11:32:14

rvk

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.

#111 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-09-05 11:25:34

rvk
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

#112 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-08-29 13:53:52

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

#113 Re: PDF Engine » Drawing dashed lines on VCLCanvas » 2023-08-29 10:10:27

rvk

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

#114 Re: PDF Engine » MultilineTextRect does not work correctly » 2023-06-28 08:14:40

rvk

It seems like you are misinterpreting some things.
First of all there are these lines in MultilineTextRect.

      MoveToNextLine;
      ARect.Top := ARect.Top - FPage.Leading;
      if ARect.Top < ARect.Bottom + FPage.FontSize then
        Break;

So the ARect.Top is decreasing with each line (by FPage.Leading which is default 0)
That means you have your Top and Bottom wrong.

Change them to Top := 800 and Bottom := 300; (the coordinates are from the bottom up)

Next... for the MoveToNextLine function to function you will need to set the Leading.

    /// Set the text leading, Tl, to the specified leading value
    // - leading which is a number expressed in unscaled text space units;
    // it specifies the vertical distance between the baselines of adjacent
    // lines of text
    // - Text leading is used only by the MoveToNextLine and ShowTextNextLine methods
    // - you can force the next line to be just below the current one by calling:
    // ! SetLeading(Attributes.FontSize);
    // - Default value is 0
    procedure SetLeading(leading: Single);                       {  TL  }

So if you change your code to this it should work:

      R.Left := 30;
      R.Top := 800; // this is almost at the top of the page
      R.Right := 100;
      R.Bottom := 300; // needs to be lower than the top
      PDF.Canvas.SetFont('Times New Roman', 12, []);
      PDF.Canvas.SetLeading(12); // font size, for newline
      PDF.Canvas.MultilineTextRect(R, 'Welcome to mORMot Open Source', True);

#115 Re: PDF Engine » SynPdf - Select active page / canvas » 2023-04-27 08:39:00

rvk

I don't think that's (directly) possible with TPdfDocumentGDI.
You would think that it should work with TPdfDocumentGDI.RawPages but in TPdfDocumentGDI.AddPage there is this line at the top:

if (FCanvas<>nil) and (FCanvas.FPage<>nil) then
  TPdfPageGdi(FCanvas.FPage).FlushVCLCanvas;

That line flushes and frees TPdfPage.fVCLCurrentCanvas of the previous page.
That's the pdf.VCLCanvas you want to use for the page.
When going back to page one with TPdfDocumentGDI.RawPages the needed TPdfPage.VCLCanvas is already freed and destroyed so when accessing it again, it will be created again, new but empty.
That's why the page is overwritten.

You could try to comment out the above line. Then you get the result you want.
The TPdfPage.fVCLCurrentCanvas is kept after TPdfDocumentGDI.AddPage.
The TPdfPage.VCLCanvas is flushed in that case at TPdfDocumentGDI.SaveToStream() and not at AddPage.

But I'm not sure if there are any problems with it down the road.

#116 Re: PDF Engine » Problem with resaving an encrypted PDF » 2022-12-14 12:19:27

rvk
ab wrote:

My guess is that it is because you specified PDF_PERMISSION_NOCOPY.

You deny to make any copy - so you deny any save?

Woops, yes. But in my problem code I have this:

Enc := TPdfEncryption.New(elRC4_40 { elRC4_128 same result } , '', 'testpassword', PDF_PERMISSION_NOMODIF + [epFillingForms, epAuthoringComment]);

(Try it yourself with these options)

With this it works

Enc := TPdfEncryption.New(elRC4_40 { elRC4_128 same result } , '', 'testpassword', PDF_PERMISSION_NOMODIF);

Why would adding epFillingForms and epAuthoringComment make Adobe not be able to save?

These are the permissions when doing that:
qUYUIiq.png

When merging a PDF with pdftk and including the same options it does work. Permissions:
l2XbFLi.png

Those permissions are the same but with the SynPDF in Adobe Reader I get the error when "saving as".

I found this out because normally the SynPDF goes through pdftk.exe to merge a background and there is no problem with saving.
But when creating a "blanc" PDF (without background), so clean from SynPDF, saving as in Adobe isn't possible.

Edit:
It's also a problem if you just use PDF_PERSMISSION_NOPRINT. Copying should be allowed then.
In PDF_PERSMISSION_NOPRINT the epContentCopy and epAuthoringComment are included.

Examining further, it seems to happen when epAuthoringComment is included, while this is no problem with another PDF generator.

Edit #2: BTW. Subsequently this also doesn't work because it includes epAuthoringComment.

Enc := TPdfEncryption.New(elRC4_40 { elRC4_128 same result } , '', 'testpassword', PDF_PERMISSION_ALL);

Then I thought this would work but it also doesn't:

Enc := TPdfEncryption.New(elRC4_40 { elRC4_128 same result } , '', 'testpassword', PDF_PERMISSION_ALL - [epAuthoringComment]);

(Also epContentCopy is only for Content copying. "Save as" should always work in Adobe reader. Even with the most strict PDF.)

#117 PDF Engine » Problem with resaving an encrypted PDF » 2022-12-14 11:24:01

rvk
Replies: 4

There still seems to be a problem with password protecting a PDF.
When I try to resave a password protected PDF (generated from SynPDF), Adobe Reader complains with the message

The document could not be saved. Bad parameter.

Code to duplicate this behavior:

uses SynPdf;

procedure MakePdfSynPdf;
var
  FileTemp: string;
  PDF: TPdfDocumentGDI;
  Enc: TPdfEncryption;
begin
  FileTemp := 'C:\Temp\Test1.pdf';
  Enc := TPdfEncryption.New(elRC4_40 { elRC4_128 same result } , '', 'testpassword', PDF_PERMISSION_NOCOPY);
  PDF := TPdfDocumentGDI.Create(false, 0, false, Enc);
  try
    PDF.Info.Author := 'User name';
    PDF.Info.Creator := 'Software name';
    PDF.Info.subject := 'Auto generated document';
    PDF.CompressionMethod := cmNone;
    PDF.EmbeddedTTF := true;
    PDF.EmbeddedTTFIgnore.Text := RawUTF8('Arial'#13#10'Times New Roman'#13#10'Courier New'#13#10'Symbol'#13#10'WingDings');;
    PDF.EmbeddedWholeTTF := true;
    PDF.Root.ViewerPreference := [vpFitWindow];
    PDF.DefaultPageLandscape := false;
    PDF.AddPage;
    PDF.VCLCanvas.Font.Name := 'Arial';
    PDF.VCLCanvas.Font.size := 20;
    PDF.VCLCanvas.TextOut(400, 400, 'Test');
    PDF.SaveToFile(FileTemp);
    // ExecAssociatedApp(FileTemp);
  finally
      PDF.Free;
  end;
end;

Open C:\Temp\Test1.pdf with Adobe Reader (Windows) and try to save it to the desktop.
Without password it works just fine.

#118 Re: PDF Engine » Duplex printing issue? » 2022-10-19 10:02:08

rvk

Works fine for me.

Does this also happen with TGDIPages.PrintPages()?
How do you set the duplex? Manually through the dialog or in code?

Some simple example code which reproduces this?
Something like this (which works for me):

procedure TForm1.Button1Click(Sender: TObject);
var
  PDF: mORMotReport.TGDIPages;
begin
  PDF := mORMotReport.TGDIPages.Create(Self);
  try
    PDF.BeginDoc;
    PDF.DrawTextAt('Test page 1', 10);
    PDF.NewPage;
    PDF.DrawTextAt('Test page 2', 20);
    PDF.EndDoc;
    PDF.ShowPreviewForm; // setting printer to duplex and printing works correctly
  finally
      PDF.Free;
  end;
end;

BTW, when printing though Adobe Reader, the chances are that the entire page is filled with whitespace and send to the printer, and thus overlapping your previous text. So this could still be a driver fault. You could test this by creating a small example with printdialog (to set the duplex option) which directly prints two pages to the printer.

#119 Re: PDF Engine » PDF rendering not correct » 2022-08-02 20:59:05

rvk

Yes, I can confirm the UseUniscribe := true works in this case.
The UseFontFallBack were not needed for me (and seeing that the PDF already contains Arial I think they don't do much).

I don't now why UseUniscribe is needed when a fairly simple Arial font is used (I'm not that familiar with the inner workings of SynPDF).

But maybe it has something to do with the way everything is rendered into the WMF and after that the font-info becomes complex in which case there is a need for UseUniscribe (just guessing here).

BTW The reason some lines work fine and others not is in the fact that the space between words is wrong. And text which is combined is stretched. But text snippets that were placed on a certain position is fine.
The amount (Betrag) on the productline is on a specific position (so that works fine).
The amount  for subtotal and MWST probably have multiple spaces in front of them and that's because they are shifted too much to the right (the multiple spaces are all expanded).

(hey, and that's where that format('%11.2n) and format('%7.2n) comes in. The 11.2 has more spaces and each space is expanded to multiple spaces. 7.2 has less space and that's why that one seemed to fix some of the issues smile )

Maybe @ab can elaborate on the reason for the need for UseUniscribe.

#120 Re: PDF Engine » PDF rendering not correct » 2022-08-02 12:20:02

rvk

Yes, download went fine. I see what the problem is.
Somehow the spaces in between words are too large.
And that throws off the entire layout.

(I haven't got time right now to look into it further but maybe someone else recognizes this problem.)
Maybe TPdfDocumentGDI.UseMetaFileTextPositioning can help? (have to test that if I got some time)


For clarity here the PDF screenshot:

bsF9Yr7.png

From the WMF loaded in LibreOffice Draw:

1p6Oips.png

#121 Re: PDF Engine » PDF rendering not correct » 2022-08-02 10:38:54

rvk

We still haven't seen what exactly the problem is. Alignment problems? Or something else? "Doesn't render properly" isn't really a clear description.

KlausV wrote:

I changed the length of the format command from format('%11.2n) to format('%7.2n) and now it seems correct, but why?

That would suggest some alignment problems. If you use spaces you would need to use (right aligned or decimal aligned) tabs to always correctly align numbers.

These also don't align in text:
    145.12
133241.12
(and the first has 4 spaces, so is 7.2, second has 1 space so is also 7.2)

But I'm not sure why this would be correct in a WMF format (IF that's the actual problem of course).
Maybe it has to do with font substitution (in WMF one font and when rendered to PDF another font is chosen).

KlausV wrote:

Now, I have created the meta WMF file and it looks correct, but the PDF file does not.
Additionally, I have created the WMF file out of QR and it looks perfect as well.

In that case it might be useful (especially for the developer) to have the sample meta.wmf to check where this goes wrong.

(It then can be loaded with aMeta.LoadFromFile('C:\temp\WMF\meta.wmf); and debugged properly.)

#122 Re: PDF Engine » PDF rendering not correct » 2022-07-31 20:23:49

rvk
KlausV wrote:

Hello,
When I create the invoice, sometimes the rendering works, but sometimes not. The quickreport result always looks perfect. When I print the quickreport report by FREEPDF it looks good as well.
What's wrong.

Since you don't show the actual code where you used format
and don't even mention what exactly isn't working or HOW it doesn't render correctly it is impossible to answer this question.

A correct pdf and incorrect pdf or screenshots, preferably with the code to generate it, would go a long way to explain your exact problem.

And additionally, you do a aMeta := aReport.QRPrinter.GetPage(i);
So you could also save that aMeta to a metafile (.wmf) and look with a metaviewer if the problem is not just in QRPrinter itself.

#123 Re: PDF Engine » ViewerPreference not working properly? » 2022-07-22 22:11:36

rvk

And what did you mean by "The expected PDF-items are added to the file, but after the pages."?

If I use vpHideToolbar and vpHideMenubar in ViewerPreference they seem to work fine.
Even setting Landscape to true works.
Even for vpFitWindow it seems to be set at the top of the pdf.

%PDF-1.3
1 0 obj
<</Type/Catalog/PageLayout/SinglePage/Pages 3 0 R/ViewerPreferences<</FitWindow true>>>>

It's only because my pagelayout is set at SinglePage I don't think FitWindow is actually used/works.
The SinglePage is standard set as default. (FRoot.PageLayout := plSinglePage in TPdfDocument.NewDoc).

If you force it to plOneColumn you might get the result you want?

    PDF.Root.PageLayout := plOneColumn;
    PDF.Root.ViewerPreference := [vpFitWindow];

I'm not sure why for you the PageLayout and ViewerPreferences would be at the end of the pdf (if it is there).

#124 Re: PDF Engine » ViewerPreference not working properly? » 2022-07-22 20:25:58

rvk

Why not put the ViewerPreference, ScreenLogPixels and DefaultPaper and Page size before you do FPdfDoc.NewDoc ?

(B.T.W. I use PDF.Root.PageLayout := plSinglePage; for opening the PDF as whole page. Not sure if that's what you want with fitWindow.)

#126 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-02 14:22:55

rvk

You shouldn't use SQLite3Pages anymore.
It was renamed mORMotReport.

https://github.com/synopse/SynPDF/blob/ … Report.pas

Version 1.18
  - renamed SQLite3Pages.pas to mORMotReport.pas

#127 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-02 14:10:55

rvk
drmoorejr wrote:

With an empty project and the code you sent as well as the rtf file  I got:

You have SQLite3Pages in there.
I don't have that. I use mORMotReport.pas.

I'm not sure where that SQLite3Pages comes from and how different it is from mORMotReport.pas.

I see you can get it from here https://synopse.info/forum/viewtopic.php?id=41
Still not sure how it is different.
Maybe that's the problem.

Edit: That one doesn't have the AppenRichEdit.

Where did you get the SQLite3Pages??
It has a date of 2010 !! Really OLD.

#128 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 22:02:48

rvk

I wonder if your AppendRichEdit is really the one in TGDIPages because I don't see it included in your uses.

Try to trace into that function tomorrow and see where it leads you.
(I wonder if it's in mORMotReport or somewhere else)

Sleep tight smile

#129 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 22:00:05

rvk

Where is your TGDIPages defined?
I'm not seeing an uses for mORMotReport in your code??

#130 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 21:54:39

rvk

Mmm, the code I found here https://synopse.info/forum/viewtopic.php?id=76 was missing an EndDoc.

Your code does have it and doesn't crash for me :confused:

#131 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 21:50:15

rvk
drmoorejr wrote:

Also to the best if my knowledge I am using the newest version.

Did you read my previous post?

I can confirm the crash with your selected code (found on the forum here).
I got that too.

But try the exact code I gave (with an empty project).

#132 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 21:30:47

rvk

Yeah, I found that code too when I tried to reproduce this and I can confirm THIS code doesn't work.
It crashes in ExportPDF with a invalid meta image.

Try my code (found it somewhere else). Does that work???

(I remember with TGDIPages I got this error before when I used BeginDoc or something. Not sure anymore because I completely rewrote it differently for myself with TPDFDocumentGDI.)

#133 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 21:26:13

rvk
drmoorejr wrote:

Are you suggesting I try a blank project?

Yes, I just created an empty project with one TButton and TRichEdit and dropped this code in the Button1.OnClick.

What bitness and platform are you compiling for? 32 bit or 64 bit (I tried 32 bit VCL).

(Just tried 64 bit VCL too and that also works for me, so I don't know what's wrong on your end. Maybe someone else knows more about it.)

#134 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 21:17:43

rvk

This works fine for me. I'm on Delphi 10.2 (Embarcadero® Delphi 10.2 Version 25.0.27659.1188)

Do you have the latest version of SynPDF?
Did you really try an empty project with just a button and TRichEdit with this code???
(important because it could also be something else in your program)

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TGDIPages.Create(self) do
    try
      Caption := self.Caption;
      BeginDoc;
      AddTextToHeader(paramstr(0));
      SaveLayout;
      Font.Style := [fsItalic];
      TextAlign := taRight;
      AddTextToFooterAt('http://synopse.info', RightMarginPos);
      RestoreSavedLayout;
      AddTextToFooter(DateTimeToStr(Now));
      NewHalfLine;

      RichEdit1.Clear;
      RichEdit1.Lines.Add('Some text');
      RichEdit1.Lines.Add('Some more text');
      RichEdit1.Lines.Add('even more text');

      DrawTitle('Rich Edit Content', true);
      AppendRichEdit(RichEdit1.Handle);
      DrawTitle('Last page content', true);

      EndDoc;
      ExportPDF(ChangeFileExt(paramstr(0),'.pdf'),true,false);
      ShowPreviewForm;
    finally
        Free;
    end;
end;

3o0NV80.png

#135 Re: PDF Engine » EOutOfResources Error (RTF to PDF) » 2022-06-01 20:59:02

rvk

Can you show an example (preferable with example rtf)?

If I use this code (with a sizable rtf, see attached) it works fine for me.

Simple project with just a button and a TRichEdit on form.

procedure TForm1.Button1Click(Sender: TObject);
begin
  RichEdit1.Lines.LoadFromFile(GetEnvironmentVariable('USERPROFILE') + '\Downloads\sample.rtf');

  with TGDIPages.Create(self) do
    try
      Caption := self.Caption;
      BeginDoc;
      AddTextToHeader(paramstr(0));
      SaveLayout;
      Font.Style := [fsItalic];
      TextAlign := taRight;
      AddTextToFooterAt('http://synopse.info', RightMarginPos);
      RestoreSavedLayout;
      AddTextToFooter(DateTimeToStr(Now));
      NewHalfLine;

      DrawTitle('Rich Edit Content', true);
      AppendRichEdit(RichEdit1.Handle);
      DrawTitle('Last page content', true);

      EndDoc;
      ExportPDF(ChangeFileExt(paramstr(0),'.pdf'),true,false);
      ShowPreviewForm;
    finally
        Free;
    end;
end;

Rtf download here: https://jeroen.github.io/files/sample.rtf

#136 Re: Other components » Try to decrypt a string from C# » 2022-05-31 09:04:51

rvk

Fixed it. Here the solution for Delphi/TAESCBC.
(pass Key[0] to Create and no need to base-encode the result)

procedure TForm1.Button1Click(Sender: TObject);
const
  cryptedString: string = 'WKaCNKYZpF9tb/CLp8fW/mX4r95OfjztavXhycsgDpA=';
  keystring: string = 'TW4YnFfPEhLeehtwdEAZyJYg/zYxeeaDjlhKIifwGKI=';
var
  AES: TAESCBC;
  InBytes, OutBytes, Key: TBytes;
  S: ANSIstring;
begin
  InBytes := TNetEncoding.Base64.DecodeStringToBytes(cryptedString);
  Key := TNetEncoding.Base64.DecodeStringToBytes(keystring);
  AES := TAESCBC.Create(Key[0], 256); // Pass the string, not the pointer to string
  try
    OutBytes := AES.DecryptPKCS7(InBytes, True);
    S := TEncoding.ANSI.GetString(OutBytes); // No need to base64 encode again
    Memo1.Lines.Add(S);
  finally
      FreeAndNil(AES);
  end;
end;

Result:

Hallo

#137 Re: Other components » Try to decrypt a string from C# » 2022-05-31 08:23:22

rvk

I can confirm the C# code is correct (or at least I can decrypt it in FPC/DCPcrypt2).

uses DCPrijndael, DCPcrypt2, base64;

function myDecryptCBC: ansistring;
var
  Cipher: TDCP_rijndael;
  Data, DataString, Key, IV: ansistring;
begin
  Data := DecodeStringBase64('WKaCNKYZpF9tb/CLp8fW/mX4r95OfjztavXhycsgDpA=');
  Key := DecodeStringBase64('TW4YnFfPEhLeehtwdEAZyJYg/zYxeeaDjlhKIifwGKI=');
  Assert(Length(Key) = 32);
  IV := Copy(Data, 1, 16);
  DataString := Copy(Data, 17);
  Cipher := TDCP_rijndael.Create(nil);
  Cipher.CipherMode := cmCBC;
  Cipher.Init(Key[1], Length(Key) * 8, @IV[1]);
  Cipher.DecryptCBC(DataString[1], DataString[1], Length(DataString));
  // PKCS7, last char is #11 which results in 16 - 11 = 5, length of Hallo
  DataString := Copy(DataString, 1, Length(DataString) - Ord(DataString[Length(DataString)]));
  Result := DataString;
  Cipher.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  S: ansistring;
begin
  S := myDecryptCBC;
  Memo1.Lines.Add(S);
end;

Gives result

Hallo

I'm not sure why TAESCBC gives another result.

#138 Re: Other components » Try to decrypt a string from C# » 2022-05-30 08:36:45

rvk

You also use Rfc2898DeriveBytes to get a hash of the password.
In Delphi you use the same password as exact key (which is not the same as the passwordDeriveBytes password hash).
https://9to5answer.com/why-do-i-need-to … -key-or-iv

Rfc2898DeriveBytes is an implementation of PBKDF2. What it does is repeatedly hash the user password along with the salt.

You could look at the function PBKDF2_HMAC_SHA256 after you translated the C# to SHA256 (but I'm not sure if they give the exact same result).

You could also just try to use a exact 32 bytes key in your C# code which is the same as in Delphi (without the Rfc2898DeriveBytes step).

#139 Re: Other components » Try to decrypt a string from C# » 2022-05-25 16:11:05

rvk

Could be useful to show the C# code too of how you encrypted this.

How did you pad the key in C#, with spaces? With #0? etc

The problem is that during decryption the last byte (which should contain the padding) is larger than the sizeof(TAESBlock).

Key should also be exactly 32 bytes (or padding to that number should be the same on both ends).

#140 Re: PDF Engine » Embedded fonts with Subset are not marked as Subset in PDF » 2022-05-16 12:12:03

rvk
ab wrote:

About mORMot 1 backport of Data instead of fFontDescriptor bug:
https://synopse.info/fossil/info/a5e5d4c449

Yes, that seems to do the trick now.

ab wrote:

About subsetting of CID embedded fonts, I tried to fix it with the following:
https://synopse.info/fossil/info/1ba26a0223
but it was not successful. At least the 'BaseFont' name match for the Ansi and CID fonts, and for the descriptor.
I don't know what is required for CID fonts - I couldn't find anything in the official PDF reference manual.

It's not a critical thing I guess. Adobe reader does mark both the TT and CID as Subset.
But Adobe doesn't really use the 6 random characters to differentiate between Subset and Full (it uses something else).

The pdffont gives this

S:\pdfs\xpdf-tools-win-4.02\bin32>pdffonts c:\temp\test3.pdf
name                                           type              emb sub uni prob object ID
---------------------------------------------- ----------------- --- --- --- ---- ---------
Arial                                          TrueType          no  no  no            6  0
Arial,BoldItalic                               TrueType          no  no  no            8  0
ELKHBC+Code3de9                                TrueType          yes yes no           10  0
PPIMAN+Code128                                 TrueType          yes yes no           12  0
JIGIKN+KIXBarcode                              TrueType          yes yes no           14  0
OMPCCH+SegoeScript                             TrueType          yes yes no           16  0
SegoeScript                                    CID TrueType      yes no  yes          18  0

So the Subset TT are now indeed seen as sub.
The CID is not marked as sub although in Adobe it does.

7mPbWNl.png

So, it probably isn't a critical thing (it all seems to work correctly).
If I find more exact official documentation about CID I'll let you know.

Thanks.

#141 Re: PDF Engine » Embedded fonts with Subset are not marked as Subset in PDF » 2022-05-16 08:41:29

rvk
rvk wrote:
ab wrote:

I'm using SynPDF. Not mORMot2. And SynPdf hasn't changed for me yet on github (still at revision 214).
I'll try again tomorrow and/or monday.

(The ReduceTTF is already in SynPdf trunk (r213) and seems to do a good job smile ).

FYI (and reminder). Changes of mormot.ui.pdf.pas from Revision 3316 haven't made it to SynPDF trunk yet.

(It still uses fFontDescriptor.ValueByName('BaseFont') instead of Data.ValueByName('BaseFont'))

Also, the subsetting of CID embedded fonts are also not prefixed (see post above).

Not a problems for me, but I thought I mention it before the changes grow further apart smile
(if this is something that usually takes more time you can forget this reminder)

#142 Re: PDF Engine » Apparent bug in SynPDF? » 2022-05-04 19:03:57

rvk

I don't think Uniscribe helps in this case (at least when I tried it).

Those characters don't seem to be "complex" so Uniscribe wouldn't be needed (found this out when tracing through AddUnicodeHexTextUniScribe).

If I use this it seems to work (setting the font to SimSun directly instead of Tahoma):

pdf.UseUniscribe := false; // <- no need to UseUniscribe ??
// ......
// pdf.VCLCanvas.Font.Assign(Self.Font); // <- don't use Tahoma

pdf.VCLCanvas.Font.Name := 'SimSun'; // <- but set SimSun directly

pdf.VCLCanvas.Font.Size := 14;
pdf.VCLCanvas.Font.PixelsPerInch := 72;
pdf.VCLCanvas.Brush.Style := bsClear;
pdf.VCLCanvas.TextOut(100, 100, Edit1.Text);

So somehow the automatic switch to SimSun (or extension) (which is done in TEdit/Windows) isn't done when creating a PDF.

(Disclaimer: I'm a novice concerning PDF and Font issues big_smile)

Edit:
Using SimSun as Fallbackfont will also work:

pdf.UseUniscribe := false;  // <-- maybe needed for other complex constructs of characters but not for this example
pdf.FontFallBackName := 'SimSun';
pdf.UseFontFallBack := true;
//...
pdf.VCLCanvas.Font.Assign(Self.Font);
pdf.VCLCanvas.Font.Size := 14;
pdf.VCLCanvas.Font.PixelsPerInch := 72;
pdf.VCLCanvas.Brush.Style := bsClear;
pdf.VCLCanvas.TextOut(100, 100, Edit1.Text);

#143 Re: Meet Us » One week off » 2022-05-03 09:06:23

rvk

Have a nice holiday  cool

#144 Re: PDF Engine » Embedded fonts with Subset are not marked as Subset in PDF » 2022-05-01 14:50:39

rvk

Besides that the BaseFont still needs to be prefixed I also noticed something else with CID embedding.

In Adobe reader those embedded CID fonts are marked as SubSet but with pdffonts they are not.
tpZR2mW.png

S:\pdfs\xpdf-tools-win-4.02\bin32>pdffonts -loc c:\temp\test2.pdf
name                                           type              emb sub uni prob object ID location
---------------------------------------------- ----------------- --- --- --- ---- --------- --------
Arial                                          TrueType          no  no  no            6  0 external: C:\WINDOWS\Fonts\arial.ttf
Arial,BoldItalic                               TrueType          no  no  no            8  0 external: C:\WINDOWS\Fonts\arialbi.ttf
GJGBGN+Code3de9                                TrueType          yes yes no           10  0 embedded
LJFFNH+Code128                                 TrueType          yes yes no           12  0 embedded
LOPGJG+KIXBarcode                              TrueType          yes yes no           14  0 embedded
EAMHGK+SegoeScript                             TrueType          yes yes no           16  0 embedded
SegoeScript                                    CID TrueType      yes no  yes          18  0 embedded          // <---- this one is subset = no ??

(That last one doesn't get marked as subset)

Maybe they also need to be prefixed.
I'm not sure why it all seems to works correctly with even those fontnames not named correct but I thought I would mention it.

CID is a subset, isn't it? (Adobe says so)

#145 Re: PDF Engine » Removing unwanted data from embedded subset fonts in PDF » 2022-04-30 21:32:08

rvk

Yes. The ReduceTTF does a good job as far as I can see for now smile
Thanks.

#146 Re: PDF Engine » Embedded fonts with Subset are not marked as Subset in PDF » 2022-04-30 21:29:38

rvk
ab wrote:

I'm using SynPDF. Not mORMot2. And SynPdf hasn't changed for me yet on github (still at revision 214).
I'll try again tomorrow and/or monday.

(The ReduceTTF is already in SynPdf trunk (r213) and seems to do a good job smile ).

#147 Re: PDF Engine » Embedded fonts with Subset are not marked as Subset in PDF » 2022-04-29 22:04:24

rvk

I think there was still a small error in you previous change.
You had this:

// see 5.5.3 Font Subsets: begins with a tag followed by a +
TPdfName(fFontDescriptor.ValueByName('FontName')).AppendPrefix;
TPdfName(fFontDescriptor.ValueByName('BaseFont')).AppendPrefix; // <---- this line

But I think BaseFont isn't part of fFontDescriptor but of Data.
So it should be

TPdfName(Data.ValueByName('BaseFont')).AppendPrefix;

Otherwise BaseFont isn't found and isn't changed with prefix. And BOTH FontName AND BaseFont need to be prefixed.

(Adobe reader does show it as Subset when only FontName is prefixed but pdffonts.exe does not show it as subset.)

This was the result if BaseFont is not prefixed.

S:\pdfs\xpdf-tools-win-4.02\bin32>pdffonts -loc c:\temp\test2.pdf
name                                           type              emb sub uni prob object ID location
---------------------------------------------- ----------------- --- --- --- ---- --------- --------
Tahoma                                         TrueType          no  no  no            6  0 external: C:\WINDOWS\Fonts\tahoma.ttf
Code3de9                                       TrueType          yes no  no            8  0 embedded
Code128                                        TrueType          yes no  no           10  0 embedded
KIXBarcode                                     TrueType          yes no  no           12  0 embedded
SegoeScript                                    TrueType          yes no  no           14  0 embedded

When I change the line to

TPdfName(Data.ValueByName('BaseFont')).AppendPrefix;

I get

S:\pdfs\xpdf-tools-win-4.02\bin32>pdffonts -loc c:\temp\test2.pdf
name                                           type              emb sub uni prob object ID location
---------------------------------------------- ----------------- --- --- --- ---- --------- --------
Tahoma                                         TrueType          no  no  no            6  0 external: C:\WINDOWS\Fonts\tahoma.ttf
NFHHHJ+Code3de9                                TrueType          yes yes no            8  0 embedded
DABIJH+Code128                                 TrueType          yes yes no           10  0 embedded
ANHKBL+KIXBarcode                              TrueType          yes yes no           12  0 embedded
DCMDKN+SegoeScript                             TrueType          yes yes no           14  0 embedded

And that one seems to be correct.

BTW. Nice idea to use random32 and just snip 4 bits off each time for the random 6 letters smile wink

(With svn update your changes did get merged nicely locally with my already changed fEmbeddedSubsetCleanup changes. Never tried that before wink)

#148 Re: PDF Engine » Embedded fonts with Subset are not marked as Subset in PDF » 2022-04-28 20:14:11

rvk

I meant merging with another pdf with another tool which also merges embedded subset fonts.

But I'm not sure exactly why this requirement for random prefix is there.

It says random and unique over all subsets. Not just subsets per font. (It also says clearly that the prefix tag should be unique, so not the combination of prefix with fontname).

But I'll use it like this for now.
If I find a more clear source that this would be against specifications I will let you know.

#149 Re: PDF Engine » Embedded fonts with Subset are not marked as Subset in PDF » 2022-04-28 16:11:29

rvk

It does not seem to give a real error . I tried is with just 'ABCDEF+' for every font/subset and that worked too.

But the documentation states:

The tag consists of exactly six uppercase letters; the choice of letters is arbitrary, but different subsets in the same PDF file must have different tags.

Or do you think they mean with different subsets, multiple subsets from the same font? That wouldn't really make much sense.

I need to find some tool online which check the validity of the PDF to make sure (but all the generators I've seen use really random characters for all subsets).

Edit: The original PDF from SynPDF (so all PDF's) give an error for this online validator https://www.datalogics.com/products/pdf … f-checker/ (Ghostscript ones are fine)

Edit #2: Ah. 1.3 files are fine.

Edit #3: This way you could also potentially have trouble if merging multiple SynPdf files with the same font but different subsets, I think wink

#150 PDF Engine » Removing unwanted data from embedded subset fonts in PDF » 2022-04-28 15:47:57

rvk
Replies: 2

Well, today I also got to tackle the compression of the fonts in SynPdf.

In my previous post I thought I found a possible solution with the TTFCFP_FLAGS_COMPRESS flag. But it turns out I put that flag in the wrong place. I used it with usSubsetFormat and not the usFlags where it should be. When using it in usSybsetFormat the $0002 will act as TTFCFP_DELTA to create incremental characters. But it did show how much initial data was getting into the PDF because the actual characters just make up 15KB (instead of 60KB).

So I went to investigate the TTF data from CreateFontPackage for the SegoeScript. It contains A LOT of garbage. Copyright notices, certificates etc. I don't think they are really needed for embedding. So I needed to see what makes a TTF tick.

As I see it now... TTF fonts are made up of tables. Result from CreateFontPackage for the SegoeScript Subset ("Hello World").

==== Table directory
Version: 1.0, number of tables: 21
Name     Offset     Length
DSIG     160736       7620
GDEF      79464         90
GPOS      79556      26766
GSUB     106324      54340
LTSH       5800       1944
OS/2        472         96
cmap      54408        608
cvt       57892        476
fpgm      55016       2384
gasp      79448         16
glyf      58368      14488
hdmx       7744      46664
head        348         54
hhea        404         36
hmtx        568       5232
loca      72856       3882
maxp        440         32
meta     160664         72
name      76740       2675
post      79416         32
prep      57400        490

For embedding a font in a PDF we actually only need the following 10 tables.
cvt (476), fpgm (2384), prep (490), head (54), hhea (36), maxp (32), hmtx (5232), cmap (608), loca (3882) and glyf (14488).
Thats 27.682 instead of 167.997 (before compression). I got this information from here. (I hope that is all correct)

I think 'name' and 'post' are not required for embedding. They ARE required for saving a .ttf file (but we are not doing that).

So... let's go stripping. We don't need to do the subsetting ourselves because the CreateFontPackage has already done that. We just need to remove the unwanted tables.

This is my final result. Calling code:

// subset was created successfully -> save to PDF file
SetString(TTF, SubSetData, SubSetSize);
FreeMem(SubSetData);

if fDoc.fEmbeddedSubsetCleanup then // I added this one to the interface
  CleanUpSubsetTTFTables(TTF);

// this is from the other topic to mark the fonts as subset correctly
Prefix := '';
if System.RandSeed = 0 then Randomize; // only call when needed
for i := 1 to 6 do Prefix := Prefix + Chr(65 + Random(26));
Prefix := Prefix + '+';
if fFontDescriptor.ValueByName('FontName') <> nil then
    TPdfName(fFontDescriptor.ValueByName('FontName')).Value := Prefix + TPdfName(fFontDescriptor.ValueByName('FontName')).Value;
if Data.ValueByName('BaseFont') <> nil then
    TPdfName(Data.ValueByName('BaseFont')).Value := Prefix + TPdfName(Data.ValueByName('BaseFont')).Value;

The CleanUpSubsetTTFTables looks like this. It takes the TTF string, reads it into a TMemoryStream and only outputs the tables we actually want back into TTF.

// ============================================

type
  TByte2 = array [0 .. 1] of byte; // 16-bit
  TByte4 = array [0 .. 3] of byte; // 32-bit

function WordToBytes(const Data: word): TByte2;
begin
  Result[0] := (Data shr 8) and 255;
  Result[1] := Data and 255;
end;

function CardinalToBytes(const Data: Cardinal): TByte4;
begin
  Result[0] := (Data shr 24) and 255;
  Result[1] := (Data shr 16) and 255;
  Result[2] := (Data shr 8) and 255;
  Result[3] := Data and 255;
end;

function BytesToWord(const Data: TByte2): word;
begin
  Result := (Data[0] * 256) + Data[1];
end;

function BytesToCardinal(const Data: TByte4): Cardinal;
begin
  Result := (Data[0] * 16777216) + (Data[1] * 65536) + (Data[2] * 256) + Data[3];
end;

type
  recTableDirectory = record
    sfntVersion: TByte4; // 0x00010000 for version 1.0
    numTables: TByte2; // number of tables
    searchRange: TByte2; // (Maximum power of 2 <= NumTables) x 16
    entrySelector: TByte2; // Log2(maximum power of 2 <= NumTables
    rangeShift: TByte2; // NumTables x 16 - SearchRange
  end;

  recTableEntry = record
    Tag: array [0 .. 3] of AnsiChar; // table identifier
    CheckSum: TByte4; // checksum for this table
    offset: TByte4; // offset from start of font file
    length: TByte4; // length of this table
  end;

  recTableData = TBytes;

procedure CleanUpSubsetTTFTables(var TTF: PDFString);
const
  TablesWeWant: array [0 .. 9] of AnsiString =
    ('cvt ', 'fpgm', 'prep', 'head', 'hhea', 'maxp', 'hmtx', 'cmap', 'loca', 'glyf');
  // 'name', 'post' are not needed for embedding, they are needed for a .ttf file
var
  Input: TMemoryStream;
  Output: TMemoryStream;
  TD: recTableDirectory;
  FontEntries: array of recTableEntry;
  FontData: array of recTableData;
  numTables: word;
  i, j: integer;
  Off, Len: Cardinal;
begin
  Input := TMemoryStream.Create;
  Output := TMemoryStream.Create;
  try
    Input.Write(TTF[1], length(TTF));
    Input.Position := 0;
    Input.Read(TD, SizeOf(TD));
    numTables := BytesToWord(TD.numTables);
    SetLength(FontEntries, numTables);
    SetLength(FontData, numTables);
    Input.Read(FontEntries[0], numTables * SizeOf(recTableEntry));
    for i := 0 to numTables - 1 do
    begin
      Off := BytesToCardinal(FontEntries[i].offset);
      Len := BytesToCardinal(FontEntries[i].length);
      Input.Position := Off;
      SetLength(FontData[i], Len);
      Input.Read(FontData[i], Len);
    end;
    for i := numTables - 1 downto 0 do
    begin
      if not MatchStr(FontEntries[i].Tag, TablesWeWant) then
      begin
        for j := i + 1 to numTables - 1 do FontEntries[j - 1] := FontEntries[j];
        for j := i + 1 to numTables - 1 do FontData[j - 1] := FontData[j];
        dec(numTables);
        SetLength(FontEntries, numTables);
        SetLength(FontData, numTables);
      end;
    end;
    Output.Position := SizeOf(TD) + numTables * SizeOf(recTableEntry); // always on 4 byte boundary
    for i := 0 to numTables - 1 do
    begin
      Off := Output.Position;
      FontEntries[i].offset := CardinalToBytes(Off);
      Len := BytesToCardinal(FontEntries[i].length);
      Output.Write(FontData[i], Len);
      Off := 0;
      while (Output.Position mod 4 <> 0) do Output.Write(Off, 1); // align on 4 bytes boundary
    end;
    TD.numTables := WordToBytes(numTables);
    System.Move(TD, (PByte(Output.Memory))^, SizeOf(TD));
    System.Move(FontEntries[0], (PByte(Output.Memory) + SizeOf(TD))^, numTables * SizeOf(recTableEntry));
    SetString(TTF, PAnsiChar(Output.Memory), Output.size);
  finally
    Output.Free;
    Input.Free;
  end;
end;


// ============================================

Testing code:

procedure MakePdfSynPdf;
var
  FileTemp: string;
  Doc: TPdfDocumentGDI;
  // Page: TPdfPage;
begin
  // if CheckC39 then; // For testing I installed this font for current users

  FileTemp := 'C:\Temp\Test2.pdf';
  Doc := TPdfDocumentGDI.Create;
  try
    Doc.GeneratePDF15File := true; // kleiner
    Doc.EmbeddedTTF := true;
    Doc.EmbeddedTTFIgnore.Text := MSWINDOWS_DEFAULT_FONTS;
    Doc.EmbeddedWholeTTF := false;
    Doc.EmbeddedSubsetCleanup := false;
    Doc.Root.PageLayout := plSinglePage;
    Doc.NewDoc;
    { Page := } Doc.AddPage;

    Doc.VCLCanvas.TextOut(40, 40, 'Test1');
    Doc.VCLCanvas.TextOut(60, 60, 'Test2');

    Doc.VCLCanvas.Font.Name := 'Code 3 de 9';
    Doc.VCLCanvas.Font.size := 24;
    Doc.VCLCanvas.TextOut(80, 80, '*123456789*'); // blocks

    Doc.VCLCanvas.Font.Name := 'Code 128';
    Doc.VCLCanvas.Font.size := 24;
    Doc.VCLCanvas.TextOut(120, 120, '*123456789*'); // blocks

    Doc.VCLCanvas.Font.Name := 'KIX Barcode';
    Doc.VCLCanvas.Font.size := 12;
    Doc.VCLCanvas.TextOut(160, 160, '5569LB33'); // correct

    Doc.VCLCanvas.Font.Name := 'Segoe Script';
    Doc.VCLCanvas.Font.size := 14;
    Doc.VCLCanvas.TextOut(190, 190, 'Hello World'); // correct

    Doc.SaveToFile(FileTemp);
    // ExecAssociatedApp(FileTemp);
  finally
      Doc.Free;
  end;

  FileTemp := 'C:\Temp\Test3.pdf';
  Doc := TPdfDocumentGDI.Create;
  try
    Doc.GeneratePDF15File := true; // kleiner
    Doc.EmbeddedTTF := true;
    Doc.EmbeddedTTFIgnore.Text := MSWINDOWS_DEFAULT_FONTS;
    Doc.EmbeddedWholeTTF := false;
    Doc.EmbeddedSubsetCleanup := true;
    Doc.Root.PageLayout := plSinglePage;
    Doc.NewDoc;
    { Page := } Doc.AddPage;

    Doc.VCLCanvas.TextOut(40, 40, 'Test1');
    Doc.VCLCanvas.TextOut(60, 60, 'Test2');

    Doc.VCLCanvas.Font.Name := 'Code 3 de 9';
    Doc.VCLCanvas.Font.size := 24;
    Doc.VCLCanvas.TextOut(80, 80, '*123456789*'); // blocks

    Doc.VCLCanvas.Font.Name := 'Code 128';
    Doc.VCLCanvas.Font.size := 24;
    Doc.VCLCanvas.TextOut(120, 120, '*123456789*'); // blocks

    Doc.VCLCanvas.Font.Name := 'KIX Barcode';
    Doc.VCLCanvas.Font.size := 12;
    Doc.VCLCanvas.TextOut(160, 160, '5569LB33'); // correct

    Doc.VCLCanvas.Font.Name := 'Segoe Script';
    Doc.VCLCanvas.Font.size := 14;
    Doc.VCLCanvas.TextOut(190, 190, 'Hello World'); // correct

    Doc.SaveToFile(FileTemp);
    // ExecAssociatedApp(FileTemp);
  finally
      Doc.Free;
  end;

end;

Result with EmbeddedSubsetCleanup false is 60KB (thanks to the fixed EmbeddedWholeTTF otherwise it was 352KB)
Result with EmbeddedSubsetCleanup true is 15KB smile smile

(Both PDF's seem to be correct in Adobe reader)

I hope this is all correct and it would need to be thoroughly tested (with multiple fonts) but with the option EmbeddedSubsetCleanup default as false it couldn't hurt either.

Board footer

Powered by FluxBB