You are not logged in.
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: TrueMaybe the Postscript level but I haven't looked into that.
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) ![]()
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.
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).
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 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;
(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.)
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.

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
endThis 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).
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] cmLine 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
SNext 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
hIn 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.

versus
Do note... this could also be due to the print to pdf driver just simulating a round end-point.
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+.
Actually. If you change all those 0.75 w into 0.075 w you get this:

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.
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
SAnd 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:
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.
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
Setting the solid line to 1 in code and the dashed line to 1 reveals that the dashed line is always a bit bigger.

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.
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... ![]()
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.

And this for setting it to 100 for both lines.

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);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);
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);
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.
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:
When merging a PDF with pdftk and including the same options it does work. Permissions:
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.)
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.
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.
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
)
Maybe @ab can elaborate on the reason for the need for UseUniscribe.
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:

From the WMF loaded in LibreOffice Draw:

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.
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).
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.)
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.
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).
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.)
Glad it works now ![]()
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
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.
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 ![]()
Where is your TGDIPages defined?
I'm not seeing an uses for mORMotReport in your code??
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:
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).
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.)
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.)
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;
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
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
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.
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).
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).
About mORMot 1 backport of Data instead of fFontDescriptor bug:
https://synopse.info/fossil/info/a5e5d4c449
Yes, that seems to do the trick now.
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 0So the Subset TT are now indeed seen as sub.
The CID is not marked as sub although in Adobe it does.

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.
ab wrote:You are right!
Please try https://github.com/synopse/mORMot2/commit/c857b693
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
).
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 ![]()
(if this is something that usually takes more time you can forget this reminder)
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
)
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);Have a nice holiday ![]()
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.
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)
Yes. The ReduceTTF does a good job as far as I can see for now ![]()
Thanks.
You are right!
Please try https://github.com/synopse/mORMot2/commit/c857b693
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
).
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 lineBut 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 embeddedWhen 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 embeddedAnd 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
![]()
(With svn update your changes did get merged nicely locally with my already changed fEmbeddedSubsetCleanup changes. Never tried that before
)
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.
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 ![]()
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 490For 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
![]()
(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.