AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

SynPDF und TMetaFile

Ein Thema von runningsoft · begonnen am 2. Mär 2022 · letzter Beitrag vom 3. Mär 2022
Antwort Antwort
Benutzerbild von runningsoft
runningsoft

Registriert seit: 8. Okt 2004
Ort: Bernau
108 Beiträge
 
Delphi 10.4 Sydney
 
#1

SynPDF und TMetaFile

  Alt 2. Mär 2022, 16:29
Hallo Delphianer,

ich bastele gerade an einer Druckvorschau-Komponente. Zum Erzeugen der einzelnen Druckseiten nutze ich TMetafile, Druckvorschau und Ausdruck funktioniert so wie es sein soll.
Nun möchte ich als Sahnehäubchen noch eine direkte PDF-Ausgabe dabei haben - also flugs SynPDf eingebunden.

Mit folgendem Code gebe ich den Inhalt des Metafiles seitenweise als PDF-Dokument aus:

Delphi-Quellcode:
procedure TSDPrintPreview.Document2PDF(FN: string;);
var lPdf : TPdfDocumentGDI;
    i : integer;
    TempMetaFile : TMetafile;
    Stream : TFileStream;
begin
  lPdf := TPdfDocumentGDI.Create;
  lPdf.ScreenLogPixels := CurrentPrinterRes.X;
  lPdf.DefaultPaperSize := psA4; //das reicht für meine Zwecke erst mal
  lPdf.DefaultPageLandscape := FPHeightMM < FPWidthMM;
  lPdf.ForceNoBitmapReuse := false;
  lPdf.EmbeddedTTF := true;
  lPdf.GeneratePDF15File := true;
  lPdf.ForceJPEGCompression := 90; //Wert vlt. noch skalierbar machen
  try
  Stream := TFileStream.Create(FN, fmCreate);
   try
   lPdf.SaveToStreamDirectBegin(Stream);
   //FMFList ist eine TObjectList<TMetaFile>;
     for I := 0 to FMFList.Count-1 do
        begin
        lPdf.AddPage;
        TempMetaFile := FMFList.Items[I];
        TempMetafile.SetSize(FPWidthPrintPxUnscaled,FPHeightPrintPxUnscaled);
        lPdf.VCLCanvas.StretchDraw(Rect(0,0,TempMetaFile.Width,TempMetaFile.Height),TempMetaFile);
        lPdf.SaveToStreamDirectPageFlush; // direct writing
        end;
     lPdf.SaveToStreamDirectEnd;
   finally
     Stream.Free;
  end;
  finally
  lPdf.Free;
  end;
end;
Mein Problem ist die Ausgabe von Bildern (JPG, PNG ...).

Alle im Testdokument enthaltenen Bilder werden korrekt in der Druckvorschau angezeigt oder ausgedruckt.
Im erzeugten PDF jedoch fehlen Grafiken vollständig die
  • vertikal mehr als 1446px vom oberen Seitenrand positioniert sind
  • horizontal mehr als 2566px vom linken Seitenrand entfernt sind
Befinden sich Grafiken innerhalb der genannten Grenzen, werden sie vollständig ausgegeben, egal wie breit oder hoch die Grafik ist. Auch über die genannten Pixelgrenzen hinaus.
Auch Text oder grafische Elemente (Linien, Rechtecke) werden komplett über die gesamte Seitenbreite und -höhe ausgegeben.

Es ist also nicht so, dass das Druckbild irgendwie abgeschnitten wird, sondern es fehlen ausschließlich Bilder unter den oben genannten Bedingungen.

Hab ich bei der Initialisierung des lPdf-Objektes irgendwas übersehen?

Vielen Dank schon mal für eure Mithilfe
  Mit Zitat antworten Zitat
Redeemer

Registriert seit: 19. Jan 2009
Ort: Kirchlinteln (LK Verden)
1.081 Beiträge
 
Delphi 2009 Professional
 
#2

AW: SynPDF und TMetaFile

  Alt 2. Mär 2022, 17:15
Das Erstellen von Metafiles wird in bestimmten Zusammenhängen beeinflusst davon,
  • ob du Bildschirmskalierung benutzt,
  • ob du RDP benutzt
  • und ob dein Bildschirm ein anderes Seitenverhältnis als 4:3 hat.
Diese Bugs verhalten sich zudem in Details abhängig von der Windows-Version unterschiedlich. Unter Windows 7 verhalten sie sich anders falsch als unter Windows 10.

Ich schreibe mir meine Metafile in einen TMemoryStream, lade mir den PENHMETAHEADER (oder wie das Ding heißt), änder da bestimmte Felder und lade die Metafile dann erneut.
Janni
2005 PE, 2009 PA, XE2 PA
  Mit Zitat antworten Zitat
Benutzerbild von runningsoft
runningsoft

Registriert seit: 8. Okt 2004
Ort: Bernau
108 Beiträge
 
Delphi 10.4 Sydney
 
#3

AW: SynPDF und TMetaFile

  Alt 2. Mär 2022, 18:46
Mit dem Bildschirm hat es anscheinend tatsächlich etwas zu tun.
Auf einem 4k-Monitor hab ich den Effekt wie beschrieben, auf einem FullHD-Monitor sind plötzlich gar keine Bilder mehr zu sehen, nur noch Text. Aber der über die volle Blattgröße, da ist nichts abgeschnitten oder fehlt.

Ich initialisiere den MetafileCanvas mit

MetaFileCanvas.Font.PixelsPerInch := GetDeviceCaps(printers.Printer.Handle,LOGPIXELSY);

Damit werden die Fonts auf jeden Fall in der richtigen Größe dargestellt. Gibt es so etwas auch für die "allgemeine" Auflösung des MetafileCanvas?
Ich finde "PixelsPerInch" in der VCL.Graphics nur im Zusammenhang mit Fonts.
Der Canvas des MetaFiles hat auf jeden Fall die richtige Größe in Px, ausgehend von der Druckerauflösung, andernfalls wird der Canavas beim Druck verkleinert poder vergrößert dargestellt.

Was mich halt wundert, ist, dass sowohl die Druckvorschau (Darstellung über TMyImage.Picture.Assign(MyMetaFile) ) als auch die Druckausgabe völlig korrekt sind. Nur die PDF-Ausgabe über SynPDF funktioniert nicht richtig.
  Mit Zitat antworten Zitat
Benutzerbild von Harry Stahl
Harry Stahl

Registriert seit: 2. Apr 2004
Ort: Bonn
2.538 Beiträge
 
Delphi 11 Alexandria
 
#4

AW: SynPDF und TMetaFile

  Alt 2. Mär 2022, 22:52
Ich habe auch erstmals auf einem 4K Monitor mit einem Programm bei der Erzeugung von PDF-Dateien (TRichView Komponente mit Gnostice-PDF) festgestellt, dass die Font-Größen und Dimensionierung der Seite nicht mehr stimmten. Ausweg war, das Programm mit GDI-Skalierung zu compilieren dann stimmte es in allen Fällen wieder (habe mir als Workaround eine zweite, nicht sichtbare Fassung des Programms erstellt, welches die PDF-Erzeugung im Falle der Nutzung auf HighDPI Monitoren durchführt). Nicht schön, aber funktioniert erst mal soweit.

Keine Ahnung ob das in Deinem Falle auch weiterhelfen könnte...
  Mit Zitat antworten Zitat
Redeemer

Registriert seit: 19. Jan 2009
Ort: Kirchlinteln (LK Verden)
1.081 Beiträge
 
Delphi 2009 Professional
 
#5

AW: SynPDF und TMetaFile

  Alt 2. Mär 2022, 22:57
Ich würde vermuten, dass sich SynPDF da irritieren lässt bzw. die Bugs einfach nicht kennt und daher keinen Workaround einsetzt. Wenn du WMF benutzt, schießt du möglicherweise schnell über die 16 Bit hinaus, die dieses Format maximal für Koordinaten vorsieht. EMF kann 32-Bit-Koordinaten (Enhanced := True). Die Koordinaten hängen von rclFrame ab und rclFrame hängt von der Bildschirmauflösung ab (Verhältnis von szlDevice zu szlMillimeters), genau weiß ich das aber nicht mehr. Das könnte erklären, warum die von dir genannten Grenzen sehr nahe an WQHD sind.
(Fun Fact: Was TMetafileCanvas im not-Enhanced-Modus produziert, ist eine EMF mit eingebetteter WMF (oder war es andersherum?). EMF-fähige Software ignoriert teilweise die WMF.)

Für TMetafileCanvas habe ich mir damals dieses Ding hier geschrieben. Da sind auch noch Relikte aus Windows 7 drin und auskommentiert, da diese Software nur noch unter Windows 10 laufen muss. (Parameter Handle ist irgendein DC, man kann sich z.B. eine leere Graphic erstellen, in meinem Fall ein TPNGImage mit Größe 1x1.)
Delphi-Quellcode:
procedure TSVGMetafile.FixRDPBug(const Handle: HDC);
var
  Temp: TMemoryStream;
  Header: PENHMETAHEADER;
begin
  // Workaround für einen Fehler in RDP, durch den szlMillimeters und rclFrame falsch sind, wenn der Hauptbildschirm des verbindenden Rechners nicht das Seitenverhältnis 4:3 hat
  // vgl. https://stackoverflow.com/a/1533053
  // Kann leider nicht beim geladenen Bild geändert werden, da man an szlDevice (HORZRES und VERTRES bei GetDeviceCaps) nicht im Speicher des vorhandenen Bildes ran kommt
  // Bug scheint in Windows 10 nicht mehr zu existieren.
  // Nachtrag 15.6.21: Dafür existiert ein ähnlicher, auf dieselbe Weise behebbarer Bug:
  // - Beim ursprünglichen RDP-Bug waren HORZSIZE und VERTSIZE zwar gleich szlMillimeters, aber falsch in Relation und im Verhältnis zu HORZRES und VERTRES.
  // - Beim neuen Bug sind HORZSIZE und VERTSIZE gleich szlMillimeters und korrekt, aber HORZRES und VERTRES sind ungleich szlDevice.

  // Besteht KEINE Remote-Desktop-Verbindung? Dann Abbruch.
  // Keine Ahnung, ob das Anfang August 2019 funktioniert hat, aber ich habe jetzt Ende Oktober 2019 keinen Self.Handle mehr, die Methode gibt daher 0 zurück, was anders als 320 bzw. 240 ist.
  // Daher wird der Handle von TMetafileCanvas übergeben.
  //if GetDeviceCaps(Handle, HORZSIZE) <> 320 then Exit;
  //if GetDeviceCaps(Handle, VERTSIZE) <> 240 then Exit;
  // Theoretisch können diese Werte auch ohne RDP auftreten, wenn der Nutzer einen 4:3-Bildschirm besitzt.
  // FixRDPBug macht korrekte Bilder nicht kaputt, daher geht es hier ausschließlich um die Performance, die bei einem Durchlauf des folgenden Codes verloren geht.
  // 15.6.21: Obige Zeilen auskommentiert da Kriterien für Bildschirmskalierungs-Bug unzureichend. Kriterien können erst nach dem Laden bestimmt werden.

  Temp := TMemoryStream.Create();
  try
    Self.SaveToStream(Temp);
    if Temp.Size > 0 then
    begin
      Temp.Position := 0;
      Header := Temp.Memory;

      if (Header^.szlDevice.cx = GetDeviceCaps(Handle, HORZRES)) and
         (Header^.szlDevice.cy = GetDeviceCaps(Handle, VERTRES)) then
      Exit;

      Header^.rclFrame.Right := (Header^.rclBounds.Right + 1) * 100;
      Header^.rclFrame.Bottom := (Header^.rclBounds.Bottom + 1) * 100;
      Header^.szlDevice.cx := 320; // gleiche Werte für beide szl-Felder machen es einfacher, Rundungsfehler hierüber zu vermeiden (man kann einfach mit 100 multiplizieren)
      Header^.szlDevice.cy := 240;
      Header^.szlMillimeters.cx := 320;
      Header^.szlMillimeters.cy := 240;

      inherited LoadFromStream(Temp);
    end;
  finally
    Temp.Free();
  end;
end;
Die hier "behandelten" Metafiles wurden von einer angepassten Version meines RedeemerSVG in der o.g. Klasse TSVGMetafile = class (TMetafile) erzeugt und per List & Label ausgegeben. List & Label basiert selbst komplett auf EMF, sprich alle angezeigten Dokumente liegen im Temp-Ordner mit einer EMF pro Seite.

Zurück zu meinem Problem: Der Fehler äußerte sich vorrangig darin, dass das Bild gestaucht wurde (ein quadratisches Bild wurde hochkant), wenn man mit einem Breitbildmonitor über RDP verbunden war. Die Koordinaten zu korrigieren brachte nichts, denn dann waren horizontale Linien dicker als vertikale. Laut PENHMETAHEADER verwendet Windows RDP hardcoded eine Auflösung von 320x240.

Für alle, die es interessiert, ein paar wirre Informationen aus meinem OneNote, größtenteils über Windows 7:
Zitat:
RDP-Bug: Runde 1 "RDP"
Vor dem Erstellen von TMetaFileCanvas
Width x Height: 187x47
MMWidth x MMHeight: 0x0

Nach dem Erstellen von TMetaFileCanvas
Width x Height: 0x0
MMWidth x MMHeight: 1558x551


OFFSETS in EMF-Header (Little Endian):
OffsetHeader-NameZumindest bei 100% und (bei Win7) ohne RDP gleich
32rclFrame.RightSelf.MMWIDTH
36rclFrame.BottomSelf.MMHEIGHT
72szlDevice.cxGetDeviceCaps(Handle, HORZRES)
76szlDevice.cyGetDeviceCaps(Handle, VERTRES)
80szlMillimeters.cxGetDeviceCaps(Handle, HORZSIZE)
84szlMillimeters.cyGetDeviceCaps(Handle, VERTSIZE)
100szlMicrometers.cxGetDeviceCaps(Handle, HORZSIZE) * 1000
104szlMicrometers.cyGetDeviceCaps(Handle, VERTSIZE) * 1000
Die letzten zwei sind (als einzige Felder) in Delphi 2010 noch nicht definiert (wohl aber in 10.4).
nSize ist dennoch 108 und die Felder sind in der Windows-API-Dokumentation von Delphi 2010 drin.
Ein Nutzen ist mir nicht bekannt, der Korrektur-Algorithmus von mir schreibt sie somit auch nicht.

Rechner von Jannik: 3840x2048
Code:
HORZSIZE 320
HORZRES  3840
VERTSIZE 240
VERTRES  2048
Rechner von mir: 1920?x1080
Code:
HORZSIZE 677
HORZRES  1920
VERTSIZE 381
VERTRES  1080
RDP-Bug: Runde 2 "Bildschirmskalierung" (geht auch ohne RDP nicht)
Rechner von mir: 2560x1440 100%
Code:
HORZSIZE 527
HORZRES  2560
VERTSIZE
VERTRES  1440
Rechner von mir: 2560x1440 125%
Code:
HORZSIZE 527
HORZRES  2048
VERTSIZE 296
VERTRES  1152
In der Datei sind aber HORZRES an 72 und VERTRES an 76 die Werte von 100%.
Janni
2005 PE, 2009 PA, XE2 PA

Geändert von Redeemer ( 2. Mär 2022 um 23:00 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von runningsoft
runningsoft

Registriert seit: 8. Okt 2004
Ort: Bernau
108 Beiträge
 
Delphi 10.4 Sydney
 
#6

AW: SynPDF und TMetaFile

  Alt 3. Mär 2022, 13:18
Vielen Dank für Eure Hinweise. Leider hatte mich das irgendwie doch nicht weitergebracht.
Hab mir daraufhin SynPDF noch einmal etwas genauer angeschaut und bin auf eine zweite Methode gestoßen, MetaFiles zu rendern:

lPdf.Canvas.RenderMetaFile(TempMetafile, 1, 0, 0, 0,lPdf.UseMetaFileTextPositioning, lPdf.KerningHScaleBottom, lPdf.KerningHScaleBottom,tcNeverClip);

Und genau das hat es gebracht. Ursprünglich hatte ich das Metafile über

lPdf.VCLCanvas.StretchDraw(Rect(0,0,TempMetaFile.Width,TempMetaFile.Height),TempMetaFile);

ausgegeben. Der VCLCanvas soll, laut eigener Aussage der Entwickler von Synopse besser geeignet sein, wenn man direkt darauf zeichnen will, weil er die gleichen Methoden wie der bekannte Canvas aus Delphi hat. Zum Rendern von Metafiles ist er aber offensichtlich nicht so gut geeignet.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:33 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz