Delphi için losLab PDF kütüphanesi olan PDFlibPas, EMF Poly* kayıtlarını [MS-EMF]'teki her kayıt tanımına uyarak PDF yollarına çevirir: 32 bitlik EMR_POLYBEZIER 0. noktadan başlar, polyline'lar açık kalır ve yalnızca stroke edilir, EMR_POLYDRAW'daki PT_CLOSEFIGURE bir bayraktır ve her nokta sayısı kayıt boyutuyla karşılaştırılarak denetlenir. Bu kurallar v3.539.39, v3.539.41 ve v3.539.43 sürümlerine dağıldı. Ondan önce, bir rapor grafiği ImportEMFFromFile'dan trend çizgisinin olması gereken yerde dolu bir dilim, yanlış kontrol noktasına kıvrılan bir Bezier eğrisi ya da son kenarı eksik kapanmış bir kontur ile çıkabilirdi. Bunların hiçbiri hata üretmedi ve kurallar her Delphi EMF-to-PDF dönüştürücüsü ile GDI kayıt ayrıştırıcısı için geçerli
EMF Poly* kayıtları PDF dönüşümünde neden yanlış gider?
EMF Poly* kayıtları yanlış gider, çünkü her biri anlamının bir kısmını noktalarının dışında taşır: şeklin açık olup olmadığı, geçerli konumdan başlayıp başlamadığı, hangi kalem ve fırçanın geçerli olduğu ve noktaların kayıt içinde nerede başladığı. Bir enhanced metafile, bir device context'e karşı yapılan GDI çağrılarının kaydıdır; dolayısıyla dönüştürücünün koordinatların yanı sıra o device context durumunu da yeniden oynatması gerekir. PDF'in device context'i yoktur. Elinde bir path, o pathin içinde bir current point ve stroke (S), fill (f) ile ikisi (B) arasında karar veren bir boyama operatörü vardır. İki model arasındaki her uyumsuzluk sessiz bir render farkına dönüşür
Poly* ailesi iki genişlikte de gelir. EMR_POLYLINE gibi her 32 bitlik kaydın, noktaları SmallInt çiftleri olarak saklayan EMR_POLYLINE16 gibi 16 bitlik bir ikizi vardır. GDI genellikle tüm koordinatlar sığdığında kompakt biçimi kaydeder; böylece bir dönüştürücünün 32 bitlik işleyicileri, günlük test çizimleri onlara hiç ulaşmadığı için yıllarca yanlış kalabilir. En hızlı denetim, aynı noktaları iki kayıttan geçirmek ve ortaya çıkan yolları karşılaştırmaktır. Burada ele alınan kayıtların hepsi [MS-EMF]'in çizim kayıt grubundadır (2.3.5 Drawing Record Types)
| Kayıt | Başlangıç | Kapalı mı? | Geçerli konum |
|---|---|---|---|
EMR_POLYBEZIER | 0. nokta | Hayır | Kullanılmaz, güncellenmez |
EMR_POLYLINE | 0. nokta | Hayır (yalnızca kalem) | Kullanılmaz, güncellenmez |
EMR_POLYLINETO | Geçerli konum | Hayır (yalnızca kalem) | Kullanılır ve güncellenir |
EMR_POLYPOLYLINE | Her polyline'ın ilk noktası | Hayır (yalnızca kalem) | Kullanılmaz, güncellenmez |
EMR_POLYDRAW | İlk PT_MOVETO ya da geçerli konum | Yalnızca PT_CLOSEFIGURE setli yerlerde | Kullanılır ve güncellenir |
Bir EMR_POLYBEZIER eğrisi aslında nereden başlar?
Bir EMR_POLYBEZIER eğrisi 0. noktadan başlar ve yalnızca 1. indeksten itibaren noktalar kontrol noktası, kontrol noktası, bitiş noktası biçiminde üçlü gruplanır. 7 noktalı bir kayıt bu yüzden iki kübik segment çizer: 0 başlangıçtır, 1-3 ilk segmenti, 4-6 ikinciyi oluşturur. PDFlibPas'taki 16 bitlik işleyici bunu zaten yapıyordu. 32 bitlik işleyici gruplamaya 0. noktadan başlıyordu; böylece başlangıç noktası ilk kontrol noktası olarak tüketiliyordu ve sonraki her segment bir kayıyordu. Eğri yine de render ediliyordu, sadece yanlışı. v3.539.41'den beri her iki genişlik de pathi 0. noktada m ile açar ve ardından her tam üçlü için bir c üretir
Kendi ayrıştırıcınız için: 1 artı 3'ün katı olmayan bir nokta sayısı bozuktur ve sondaki noktalar bir eğriye dikilmek yerine yok sayılmalıdır
PolyDraw: PT_CLOSEFIGURE bayraktır, nokta tipi değil
EMR_POLYDRAW'da PT_CLOSEFIGURE (değeri 1), PT_LINETO (2) ya da PT_BEZIERTO (4) ile birleştirilen bir bittir; dolayısıyla geçerli bir tip baytı 3 ya da 5 olabilir. Nokta tipi, o bit maskelenmiş hâliyle baytın kendisidir ve bayrak, bu noktada biten segmentin ardından şekli kapat anlamına gelir. Eski PDFlibPas işleyicisi baytı bir case deyiminde tek değerlerle eşliyordu; böylece 3 ve 5 tipli noktalar hiçbir şeyle eşleşmedi ve tamamen atlandı. PolyDraw ile çizilen bir dikdörtgen kapanış kenarını kaybediyor, bayrağı taşıyan son noktalı bir Bezier üçlüsü o noktayı kaybediyordu ve bu da sonraki her üçlüyü kaydırıyordu
v3.539.39'dan beri tip, Types[i] and not PT_CLOSEFIGURE olarak okunuyor ve kapatma yalnızca tam bir segmentin ardından üretiliyor: kapanan bir PT_LINETO için çizginin ardından, bir Bezier grubunun üçüncü noktasının ardından. Bir üçlünün ilk ya da ikinci noktasına bayrak set eden bozuk bir dosya şekli erken kapatmaz. Aynı sürümde iki ilişkili düzeltme daha çıktı:
- 16 bitlik
EMR_POLYDRAW16'daki herPT_MOVETOtüm pathi yeniden başlatıyordu; böylece üç şekil taşıyan bir kayıt yalnızca sonuncuyu tutuyordu. Artık ilk hareket pathi başlatıyor, sonraki hareketler subpath açıyor PT_MOVETOile başlamayan bir PolyDraw kaydı, önündemolmayan birlya dacoperatörü yazmak yerine kayıt tanımının söylediği gibi geçerli konumdan başlar
Bir EMF polyline neden PDF'te asla doldurulmamalı?
Bir EMF polyline asla doldurulmamalıdır, çünkü EMR_POLYLINE ve EMR_POLYPOLYLINE yalnızca kalemle çizilen açık şekillerdir ve PDF'te açık bir pathi doldurmak onu dolaylı olarak kapatır. ISO 32000-1 §8.5.3, fill operatörlerinin boyamadan önce her açık subpath'i kapattığını söyler. Üç noktalı bir polyline için B ya da f üreten bir dönüştürücü bu yüzden geçerli fırça renginde dolu bir üçgen boyar: grafik trend çizgisi altındaki dolu dilim, yani. v3.539.41 öncesinde PDFlibPas her iki polyline genişliğini de fırçayla dolduruyor ve 32 bitlik kayıt ayrıca açıkça kapatılıyordu. Bugün her iki genişlik de yalnızca stroke ile bitiyor ve GDI ayrımı korunuyor: Polygon kapatır ve doldurur, Polyline asla yapmaz
PolylineTo geçerli konumdan başlar
EMR_POLYLINETO geçerli konumdan kayıttaki her noktaya doğru çizer, açık kalır ve geçerli konumu son noktada bırakır. Eski işleyici ayrıca ilk iki nokta aynı y koordinatını paylaştığında kalemi kapatan bir özel durum içeriyordu ve onu hiçbir şey geri açmıyordu; böylece dosyadaki sonraki her kayıt konturunu kaybediyordu. Kalem durumu EMR_SELECTOBJECT ve EMR_CREATEPEN'e aittir; bir çizim kaydı işleyicisinin onu değiştirmeye işi yoktur. O özel durum v3.539.41'de kaldırıldı ve kaydın tek noktalı biçimi artık kendi noktalarının ötesini okumuyor (v3.539.39'da düzeltildi)
PolyPolyline noktaları counts dizisinin ardından başlar
32 bitlik EMR_POLYPOLYLINE önce nPolys adet sayı sonra cptl nokta saklar ve noktalar 32 + nPolys * 4 bayt ofsetinde başlar. Tuzak RTL'dedir: Windows uniti, TEMRPolyPolyline'ı aPolyCounts ile aptl'i tek elemanlı diziler olarak bildirir; dolayısıyla aptl[0], yalnızca nPolys 1 olduğunda ilk noktadır. aptl'i doğrudan indeksleyen kod, her çoklu çizgi kaydında sayı değerlerini koordinat olarak okur. Eski PDFlibPas işleyicisi sınır denetimini de o yanlış düzene göre boyutlandırıyordu; böylece geçerli çoklu çizgi kayıtları reddediliyor, tek çizgililer hiçbir şey çizmiyordu. v3.539.41'den beri PDFlibPas nokta dizisini hesaplanan ofsetten bulur — PolyPolygon işleyicisinin her zaman yaptığı gibi — ve her polyline'ı sonunda tek bir stroke ile kendi açık subpath'i olarak çizer. v3.539.43'te 16 bitlik ikiz de aynı muameleyi gördü; o ise segment segment çiziyordu, bu da line join'leri bozuyor ve seçili bir NULL_PEN'i yok sayıyordu
Varsayılan kalem ve fırça, path parantezleri
v3.539.43'teki polyline düzeltmelerini iki durum kuralı tamamlar:
- Yeni bir GDI device context'inde zaten
BLACK_PENveWHITE_BRUSHseçilidir; dolayısıyla hiçbirEMR_SELECTOBJECTolmadan çizen bir metafile yine de siyah konturlar çizer. Dönüştürücü ise kalemsiz ve dolgusuz başlıyor ve bu tür kayıtlar içinn(pathi bitir, hiçbir şey boyama) yazıyordu - Bir
BeginPath/EndPathparantezi içinde birPolylinegeçerli konumu ne kullanır ne günceller; dolayısıyla önceki şekle bağlanmak yerine ilk noktasında yeni bir subpath açmalı ve parantez stroke edilene ya da doldurulana dek hiçbir şey boyanmamalıdır
TMetafileCanvas ile bir EMF test dosyası oluşturmak
Bir dönüştürücüyü bu kurallara karşı denemenin en hızlı yolu, üç riskli çağrıyı TMetafileCanvas ile tek bir enhanced metafile içine kaydetmektir. Aşağıdaki çizim eğrileri içi boş bir fırçayla kaydediyor ve ardından polyline için kasıtlı olarak dolu sarı bir fırça seçiyor: doğru dönüştürücü o fırçayı polyline için yok saymalıdır, yani çıktı PDF'teki her sarı bir hatadır. PolyDraw'un TCanvas sarmalayıcısı yoktur; canvas handle'ı üzerinden Windows API'siyle çağrılır ve kapatma bayrağını yoklamak için 3 ve 5 tip baytları kullanılır
uses
Winapi.Windows, System.Types, Vcl.Graphics;
procedure BuildPolyTestEmf(const FileName: string);
const
// Kapalı bir kare (3 = LINETO + CLOSEFIGURE), ardından son kontrol üçlüsü
// 5 = BEZIERTO + CLOSEFIGURE ile biten kapalı bir Bezier şekli
DrawPts: array[0..7] of TPoint = (
(X: 300; Y: 40), (X: 380; Y: 40), (X: 380; Y: 120), (X: 300; Y: 120),
(X: 420; Y: 120), (X: 440; Y: 40), (X: 520; Y: 40), (X: 540; Y: 120));
DrawTypes: array[0..7] of Byte = (
PT_MOVETO, PT_LINETO, PT_LINETO, PT_LINETO or PT_CLOSEFIGURE,
PT_MOVETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO or PT_CLOSEFIGURE);
var
Mf: TMetafile;
Canvas: TMetafileCanvas;
begin
Mf := TMetafile.Create;
try
Mf.Enhanced := True;
Mf.Width := 600;
Mf.Height := 260;
Canvas := TMetafileCanvas.Create(Mf, 0);
try
Canvas.Pen.Color := clNavy;
Canvas.Pen.Width := 2;
Canvas.Brush.Style := bsClear; // eğriler için yalnızca konturlar
// 0. nokta başlangıçtır; 1..3 ve 4..6 iki kübik segmenttir
Canvas.PolyBezier([Point(20, 120), Point(60, 20), Point(100, 220),
Point(140, 120), Point(180, 20), Point(220, 220), Point(260, 120)]);
PolyDraw(Canvas.Handle, DrawPts[0], DrawTypes[0], Length(DrawPts));
// Dolu fırça seçiliyken açık V şekli: stroke edilir, asla sarı bir
// üçgene kapatılmaz
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clYellow;
Canvas.Polyline([Point(20, 240), Point(120, 160), Point(220, 240)]);
finally
Canvas.Free; // kaydı bitirir
end;
Mf.SaveToFile(FileName);
finally
Mf.Free;
end;
end;
Bu koordinatlar bir SmallInt'e sığdığı için GDI normalde 16 bitlik varyantları saklar. 32 bitlik işleyicilere ulaşmak için onları yazan bir üreticiye ya da elle kurduğunuz kayıtlara ihtiyacınız vardır. El yapımı dosyaların kendi tuzağı vardır: VCL TMetafile.LoadFromStream, akışı yalnızca kalan uzunluk 108 baytlık TEnhMetaHeader'dan kesinlikle büyük olduğunda EMF sayar. Kısa başlıklı elle yazılmış minimal bir EMF ya da tam 108 bayt uzunluğundaki boş bir dosya WMF sanılır ve "Metafile is not valid" ile reddedilir. Test kayıtlarınızdan önce uzantı alanları dahil tam 108 baytlık başlığı daima yazın
EMF'i PDFlibPas ile bir PDF'e içe aktarmak
PDFlibPas bir EMF'i ImportEMFFromFile ya da ImportEMFFromStream ile içe aktarır; ikisi de başarıda sıfır olmayan bir image ID, başarısızlıkta 0 döndürür. GeneralOptions = 0, bu yazının konusu olan vector pathi tutar; 1, metafile'ı bunun yerine bir bitmap'e rasterleştirir. FontOptions = 1, metafile fontlarını non-embedded TrueType fontlar olarak ekler. Stream varyantı yüklemeden önce akışı 0 konumuna geri sarar; dolayısıyla yalnızca metafile'ı içeren bir stream geçirin
uses
System.SysUtils, PDFlibrary;
procedure EmfToPdf(const EmfFile, PdfFile: WideString);
var
PDF: TPDFlib;
ImageID: Integer;
PageOps: AnsiString;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1); // DrawImage için sol üst köken
PDF.SetMeasurementUnits(0); // point
// FontOptions 1 = fontları non-embedded TrueType olarak ekler
// GeneralOptions 0 = vector içe aktarma, 1 = bitmap
ImageID := PDF.ImportEMFFromFile(EmfFile, 1, 0);
if ImageID = 0 then
raise Exception.Create('The metafile could not be imported');
PDF.SelectImage(ImageID);
// Bir EMF için ImageWidth / ImageHeight point cinsinden çerçeve boyutudur
PDF.DrawImage(36, 36, PDF.ImageWidth, PDF.ImageHeight);
// Sayfa yalnızca içe aktarılan formu çağırır: q ... cm /Name Do Q
PageOps := PDF.GetPageContentToString;
if Pos(AnsiString(' Do'), PageOps) = 0 then
raise Exception.Create('Expected a form XObject invocation');
if PDF.SaveToFile(PdfFile) <> 1 then
raise Exception.Create('The PDF could not be saved');
finally
PDF.Free;
end;
end;
Vector bir EMF içe aktarması bir form XObject'e dönüşür; dolayısıyla GetPageContentToString yalnızca save, transform, Do ve restore dizisini döndürür. Poly* kayıtlarından üretilen m, l, c, h ve S operatörleri sıkıştırılmış olan form XObject streaminde yaşar. Onları denetlemek için kaydedilen dosyayı bir PDF object inspector'da açıp form streamini okuyun: yukarıdaki test dosyası için polyline'ın önünde h olmadan S ile bittiğini, PolyDraw şekillerindeki her kapatma bayrağında bir h olduğunu ve bu subpath'lerin hiçbirinde f ya da B bulunmadığını görmelisiniz. DrawImage ayrıca içe aktarılan bir EMF'i Width ile Height'ın küçüğü ölçüsünde tek düze ölçekler; yani geçirdiğiniz kutu orana uymasa bile çizim en-boy oranını korur
Free Pascal hedefleri için PDFlibPas EMF vector içe aktarıcısının Free Pascal altında derlenmesi yazısına bakın; kayıt semantiği içe aktarıcının derlendiği her yerde aynıdır
Bir EMF ayrıştırıcısı dosyadan gelen nokta sayılarına nasıl davranmalı?
Bir EMF ayrıştırıcısı her nokta sayısını güvenilmeyen girdi saymalı ve tek bir nokta kopyalamadan önce kayıt boyutuyla denetlemelidir. EnumEnhMetaFile yalnızca her kaydın nSize'ının dosya içinde kaldığını garanti eder. cptl ile nSize'ın uyduğunu denetlemez; dolayısıyla cptl noktayı Move ile kopyalayan bir işleyici, sayı uydurulmuş ya da bozulmuş olduğunda izleyen kayıtları hatta metafile'ın sonunu okur. v3.539.39'dan beri PDFlibPas, PolyDraw, PolyBezier, PolyBezierTo, Polyline, PolylineTo ve Polygon için sabit başlık artı nokta başına bayt sayısı çarpı sayı toplamını her iki genişlikte nSize ile karşılaştırır; PolyDraw tip baytları için nokta başına bir bayt fazladır. PolyPoly kayıtlarında şekil başına sayıların ayrıca bildirilen toplamı aşmaması gerekir ve sıfır noktalı şekiller atlanır
Aynı denetim kendi ayrıştırıcınıza kopyalanacak kadar kısadır. Bu sürüm 32 bitlik bir EMR_POLYPOLYLINE doğrular ve gerçek nokta dizisine bir işaretçi döndürür:
uses
Winapi.Windows;
// Kayıt gerçekte bildirdiği noktaları tutmadıkça nil döndürür.
// Noktalar counts dizisinin ardından başlar: 32 + nPolys * 4 bayt içeride,
// RTL'nin tek elemanlı dizi olarak bildirdiği aptl[0] değil
function PolyPolylinePoints(Rec: PEnhMetaRecord): PPoint;
var
P: PEMRPolyPolyline;
Count: PDWORD;
PointsOffset, Total: Int64;
I: Cardinal;
begin
Result := nil;
if (Rec^.iType <> EMR_POLYPOLYLINE) or (Rec^.nSize < 32) then
Exit;
P := PEMRPolyPolyline(Rec);
if P^.nPolys = 0 then
Exit;
PointsOffset := 32 + Int64(P^.nPolys) * SizeOf(DWORD);
if PointsOffset + Int64(P^.cptl) * SizeOf(TPoint) > Rec^.nSize then
Exit; // sahte ya da kesilmiş sayı
Total := 0;
Count := @P^.aPolyCounts[0]; // işaretçiyle yürü: [0..0] aralık denetimini tetikler
for I := 1 to P^.nPolys do
begin
Inc(Total, Count^);
Inc(Count);
end;
if Total > P^.cptl then
Exit; // şekiller var olandan çok nokta iddia ediyor
Result := PPoint(NativeUInt(Rec) + NativeUInt(PointsOffset));
end;
Nokta-sığma testi önce koşar; dolayısıyla döngü gezinmeden önce counts dizisinin kayıt içinde olduğu bilinir. Aritmetik Int64'tür, çünkü 32 bitte hesaplanan nPolys * 4 ve cptl * 8 taşabilir ve karşılaştırmayı geçebilir
Hızlı başvuru: EMF-to-PDF dönüşümü için EMF Poly* kuralları
EMR_POLYBEZIER: 0. nokta başlangıç noktasıdır; 1. noktadan itibaren üçlü gruplayın; 32 bitlik kayıt için v3.539.41'de düzeltildiEMR_POLYLINE/EMR_POLYPOLYLINE: açık şekiller,Sile stroke; aslah,fya daB, çünkü PDF fill açık subpath'leri kapatırEMR_POLYLINETO: geçerli konumdan başla, açık kal, geçerli konumu güncelle, kalem durumuna asla dokunma- 32 bitlik
EMR_POLYPOLYLINE: noktalar32 + nPolys * 4baytında başlar,aptl[0]'da değil EMR_POLYDRAW: dağıtımdan öncePT_CLOSEFIGURE'ü maskeleyin, tamamlanan segmentin ardından kapatın, ilk noktaPT_MOVETOdeğilse geçerli konumdan başlayın- Varsayılan device context durumu
BLACK_PENartıWHITE_BRUSH'tır; v3.539.43 ve sonrası buna uyar BeginPath/EndPathiçinde her polyline kendi subpath'ini açar ve parantez kullanılana dek hiçbir şey boyanmaz- Noktaları kopyalamadan önce her
cptl/cptsdeğerininSizeile 64 bit aritmetikte doğrulayın - El yapımı test EMF'leri tam 108 baytlık başlık ister, yoksa
TMetafile.LoadFromStreamonları WMF okur
Raporlarınız başka bir bileşenden geçiyorsa aynı kayıt semantiği geçerlidir; HotPDF EMF ve WMF vector içe aktarma o bileşenin gradient ve hatch fırçalarını PDF pattern'lerine nasıl çevirdiğini kapsar, PDFlibPas'ta vector grafik, shader ve gradientler ise aynı şekilleri metafile üzerinden değil doğrudan kütüphane API'siyle çizmeyi anlatır
PDFlibPas v3.539.43 ve sonrası yukarıdaki her kuralı içerir. Ayrıntılar ve deneme indirmeleri PDFlibPas Delphi PDF library ürün sayfasındadır