PDFlibPas, losLabova knjižnica PDF za Delphi, pretvarja EMF zapise Poly* v PDF poti po definiciji vsakega zapisa v [MS-EMF]: 32-bitni EMR_POLYBEZIER se začne pri točki 0, zapisi Polyline ostanejo odprti in se samo obrišejo, PT_CLOSEFIGURE v EMR_POLYDRAW je zastavica, vsako število točk pa se preveri proti velikosti zapisa. Ta pravila so pristala skozi v3.539.39, v3.539.41 in v3.539.43. Pred njimi je lahko iz ImportEMFFromFile izšel graf poročila z zapolnjenim klinom tam, kjer naj bi bila trendna črta, Bezierjeva krivulja, upognjena proti napačni kontrolni točki, ali zaprt obris, ki mu je manjkala zadnja stranica. Nobeno od teh stanj ni javilo napake, pravila pa veljajo za kateri koli pretvornik EMF v PDF za Delphi ali razčlenjevalnik GDI zapisov
Zakaj se EMF zapisi Poly* pri pretvorbi v PDF pokvarijo?
Zapisi EMF Poly* se pokvarijo, ker vsak del svojega pomena nosi zunaj svojih točk: ali je figura odprta, ali se začne pri trenutnem položaju, kateri pisalo in čopič veljata ter kje v zapisu se točke začnejo. Enhanced metafile je posnetek klicev GDI nad device contextom, zato mora pretvornik predvajati tudi stanje tega device contexta, ne samo koordinate. PDF nima device contexta. Ima pot, trenutno točko znotraj te poti in risalni operator, ki se odloča med obrisom (S), polnjenjem (f) in obojim (B). Vsako neujemanje med tema modeloma postane tiha razlika v izrisu
Družina Poly* obstaja tudi v dveh širinah. Vsak 32-bitni zapis, kot je EMR_POLYLINE, ima 16-bitnega dvojčka, kot je EMR_POLYLINE16, ki shranjuje točke kot pare SmallInt. GDI običajno zabeleži kompaktno obliko, kadar se prilega vsaka koordinata, zato lahko 32-bitni handlerji pretvornika ostanejo pokvarjeni leta, medtem ko vsakodnevne testne risbe nikoli ne pridejo do njih. Najhitrejši pregled je, da iste točke posredujete skozi oba zapisa in primerjate nastali poti. Zapisi, obravnavani tukaj, so vsi v skupini risalnih zapisov [MS-EMF] (2.3.5 Drawing Record Types)
| Zapis | Se začne pri | Zaprt? | Trenutni položaj |
|---|---|---|---|
EMR_POLYBEZIER | Točka 0 | Ne | Ni v uporabi, se ne posodablja |
EMR_POLYLINE | Točka 0 | Ne (samo pisalo) | Ni v uporabi, se ne posodablja |
EMR_POLYLINETO | Trenutni položaj | Ne (samo pisalo) | V uporabi in se posodablja |
EMR_POLYPOLYLINE | Prva točka vsake polyline | Ne (samo pisalo) | Ni v uporabi, se ne posodablja |
EMR_POLYDRAW | Prvi PT_MOVETO ali trenutni položaj | Samo kjer je nastavljena PT_CLOSEFIGURE | V uporabi in se posodablja |
Kje se krivulja EMR_POLYBEZIER sploh začne?
Krivulja EMR_POLYBEZIER se začne pri točki 0, točke od indeksa 1 naprej pa se zbirajo po tri v trojčke kontrolna točka, kontrolna točka, končna točka. Zapis s 7 točkami torej nariše dva kubična odseka: 0 je začetek, 1 do 3 tvorijo prvi odsek, 4 do 6 drugi. To je 16-bitni handler v PDFlibPas že počel. 32-bitni handler je pričel združevati pri točki 0, zato je bila začetna točka porabljena kot prva kontrolna točka in vsak kasnejši odsek je zdrsnil za ena. Krivulja se je izrisala, le napačna. Od v3.539.41 obe širini odpreta pot z m pri točki 0 in izdatata en c za vsak popoln trojček za njo
Za vaš lasten razčlenjevalnik: kadar število točk ni 1 plus mnogokratnik 3, je zapis pokvarjen, presežne točke na koncu pa je treba prezreti, namesto da bi jih zašili v krivuljo
PolyDraw: PT_CLOSEFIGURE je zastavica, ne tip točke
V EMR_POLYDRAW je PT_CLOSEFIGURE (vrednost 1) bit, ki se združi z PT_LINETO (2) ali PT_BEZIERTO (4), zato je veljaven bajt tipa lahko 3 ali 5. Tip točke je ta bajt z maskiranim tem bitom, zastavica pa pomeni, da se figura zapre za odsekom, ki se konča na tej točki. Stari handler v PDFlibPas je primerjal bajt s posameznimi vrednostmi v stavku case, zato točki tipa 3 in 5 nista zadeli ničesar in sta bili popolnoma preskočeni. Pravokotnik, narejen s PolyDraw, je izgubil zaključno stranico, trojček Bezierja, katerega zadnja točka je nosila zastavico, pa je izgubil to točko, s čimer je vsak kasnejši trojček zdrsnil iz koraka
Od v3.539.39 se tip prebere kot Types[i] and not PT_CLOSEFIGURE, zaprtje pa se izda šele po celotnem odseku: po črti pri zaprtem PT_LINETO in po tretji točki Bezierjeve skupine. Pokvarjena datoteka, ki nastavi zastavico na prvi ali drugi točki trojčka, figure ne zapre predčasno. V isti izdaji sta pristala še dva povezana popravka:
- Vsak
PT_MOVETOv 16-bitnemEMR_POLYDRAW16je znova zagnal celotno pot, zato je zapis s tremi figurami obdržal samo zadnjo; zdaj prvi premik zažene pot, kasnejši pa odprejo podpoti - Zapis PolyDraw, ki se ne začne s
PT_MOVETO, se začne pri trenutnem položaju, kot pravi definicija zapisa, namesto da bi zapisal operatorlalicbrez predhodnegam
Zakaj EMF polyline v PDF-ju nikoli ne sme biti zapolnjena?
EMF polyline se nikoli ne sme zapolniti, ker sta EMR_POLYLINE in EMR_POLYPOLYLINE odprti figuri, narisani samo s pisalom, polnjenje odprte poti v PDF-ju pa jo implicitno zapre. ISO 32000-1 §8.5.3 pravi, da operatorji polnjenja zaprejo vsako odprto podpot, preden jo narišejo. Pretvornik, ki izda B ali f za polyline s tremi točkami, torej nariše zapolnjen trikotnik v barvi trenutnega čopiča: zapolnjeni klin pod trendno črto grafa. Pred v3.539.41 je PDFlibPas zapolnjeval obe širini polyline s čopičem, 32-bitni zapis pa je bil izrecno še zaprt. Danes obe širini končata samo z obrisom, GDI razlikovanje pa je ohranjeno: Polygon zapre in zapolni, Polyline tega nikoli ne stori
PolylineTo se začne pri trenutnem položaju
EMR_POLYLINETO riše od trenutnega položaja skozi vsako točko v zapisu, ostane odprt in pusti trenutni položaj na zadnji točki. Stari handler je vseboval tudi poseben primer, ki je izklopil pisalo, kadar sta si prvi dve točki delili y koordinato, in nič ga nikoli ni znova vklopilo, zato je vsak kasnejši zapis v datoteki izgubil svoj obris. Stanje pisala spada k EMR_SELECTOBJECT in EMR_CREATEPEN; handler risalnega zapisa nima kaj spreminjati. Ta poseben primer je odstranjen v v3.539.41, enotočkovna oblika zapisa pa ne bere več čez svoje točke (popravljeno v v3.539.39)
Točke PolyPolyline se začnejo za poljem števil
32-bitni EMR_POLYPOLYLINE shrani nPolys števil in nato cptl točk, točke pa se začnejo pri bajtnem odmiku 32 + nPolys * 4. Past je v RTL: enota Windows deklarira TEMRPolyPolyline s aPolyCounts in aptl kot enoelementnima poljema, zato je aptl[0] prva točka samo, kadar je nPolys enak 1. Koda, ki indeksira aptl neposredno, bere vrednosti števil kot koordinate za vsak veččrtovni zapis. Stari handler v PDFlibPas je velikost svojega preverjanja meja prav tako osnoval na tej napačni postavitvi, zato so bili veljavni veččrtovni zapisi zavrnjeni, enočrtovni pa niso narisali nič. Od v3.539.41 PDFlibPas poišče polje točk na izračunanem odmiku, kot je njegov handler PolyPolygon počel vselej, in nariše vsako polyline kot njeno lastno odprto podpot z enim obrisom na koncu. V v3.539.43 je enako obravnavo dobil 16-bitni dvojček; ta je risal odsek za odsekom, kar je pokvarilo spoje črt in ignoriralo izbrani NULL_PEN
Privzeti pisalo in čopič ter oklepaji poti
Z dvema praviloma o stanju se zaokrožita popravka polyline v v3.539.43:
- Svež GDI device context že ima izbrana
BLACK_PENinWHITE_BRUSH, zato metafile, ki riše brez kakršnega koliEMR_SELECTOBJECT, vseeno riše črne obrise; pretvornik je začenjal brez pisala in brez polnila ter je za take zapise zapisaln(konec poti, brez risanja) - Znotraj oklepaja
BeginPath/EndPathPolylinene uporablja in ne posodablja trenutnega položaja, zato mora odpreti novo podpot pri svoji prvi točki, namesto da bi se povezala s prejšnjo figuro, in nič se ne sme narisati, dokler oklepaja ne obrišete ali zapolnite
Izdelava preskusne datoteke EMF s TMetafileCanvas
Najhitrejši način, da pretvornik preverite proti tem pravilom, je, da tri tvegane klice posnamete v en enhanced metafile s TMetafileCanvas. Risba spodaj posname krivulje z votlim čopičem, za polyline pa namenoma izbere poln rumen čopič: pravilen pretvornik mora ta čopič za polyline ignorirati, zato je vsak rumeni odtenek v izhodnem PDF-ju hrošč. PolyDraw nima ovoja TCanvas, zato se kliče prek Windows API s ročajem canvasa, pri čemer bajta tipa 3 in 5 preizkušata zapiralno zastavico
uses
Winapi.Windows, System.Types, Vcl.Graphics;
procedure BuildPolyTestEmf(const FileName: string);
const
// Zaprta figura: kvadrat (3 = LINETO + CLOSEFIGURE), potem še zaprta
// Bezierjeva figura, katere zadnji kontrolni trojček konča s 5 = BEZIERTO + CLOSEFIGURE
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; // samo obrisi za krivulje
// Točka 0 je začetek; 1..3 in 4..6 so dva kubična odseka
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));
// Odprta oblika V z izbranim polnim čopičem: samo obris,
// nikoli zaprta v rumen trikotnik
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clYellow;
Canvas.Polyline([Point(20, 240), Point(120, 160), Point(220, 240)]);
finally
Canvas.Free; // konča snemanje
end;
Mf.SaveToFile(FileName);
finally
Mf.Free;
end;
end;
Ker te koordinate spravijo v SmallInt, GDI običajno shrani 16-bitne različice. Do 32-bitnih handlerjev pridete le s producentom, ki jih zapisuje, ali z zapisi, ki jih zgradite ročno. Ročno zgrajene datoteke prinašajo svojo past: VCL TMetafile.LoadFromStream tok obravnava kot EMF samo, kadar je preostala dolžina strogo večja od 108-bajtnega TEnhMetaHeader. Minimalni ročno napisani EMF s kratko glavo ali prazna datoteka, dolga natančno 108 bajtov, je sprejet za WMF in zavrnjen z "Metafile is not valid". Pred testnimi zapisi vedno zapišite celotno 108-bajtno glavo, vključno s polji razširitve
Uvoz EMF v PDF s PDFlibPas
PDFlibPas uvozi EMF s ImportEMFFromFile ali ImportEMFFromStream, ki vrneta neničelni ID slike ob uspehu in 0 ob napaki. GeneralOptions = 0 ohrani vektorsko pot, o kateri govori ta članek; 1 namesto tega rasterizira metafile v bitmapo. FontOptions = 1 doda pisave metafilea kot nevgrajene pisave TrueType. Različica s tokom tok pred nalaganjem prevrne na položaj 0, zato podajte tok, ki vsebuje samo metafile
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); // izhodišče zgoraj levo za DrawImage
PDF.SetMeasurementUnits(0); // točke
// FontOptions 1 = doda pisave kot nevgrajene TrueType
// GeneralOptions 0 = vektorski uvoz, 1 = bitmapa
ImageID := PDF.ImportEMFFromFile(EmfFile, 1, 0);
if ImageID = 0 then
raise Exception.Create('The metafile could not be imported');
PDF.SelectImage(ImageID);
// Za EMF sta ImageWidth / ImageHeight velikost okvirja v točkah
PDF.DrawImage(36, 36, PDF.ImageWidth, PDF.ImageHeight);
// Stran samo prikliče uvoženo formo: 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;
Vektorski uvoz EMF postane form XObject, zato GetPageContentToString vrne samo zaporedje shrani, transformiraj, Do in povrni. Operatorji m, l, c, h in S, izdelani iz zapisov Poly*, živijo v toku form XObject, ki je stisnjen. Za pregled razstisnite shranjeno datoteko v PDF objektnem inšpektorju in preberite tok forme: pri zgornji preskusni datoteki bi morali videti, da se polyline konča s S brez h pred njim, po en h pri vsaki zapiralni zastavici v figurah PolyDraw ter brez f ali B na kateri koli teh podpoti. DrawImage uvožen EMF tudi enakomerno skalira po manjšem od Width in Height, zato risba ohrani razmerje stranic, tudi kadar polje, ki ga podate, temu ne ustreza
Za tarče Free Pascal poglejte, kako se uvoznik vektorske grafike EMF v PDFlibPas zgradi pod Free Pascal; semantika zapisov je ista, kjer koli se uvoznik prevede
Kako naj EMF razčlenjevalnik ravna s števili točk iz datoteke?
EMF razčlenjevalnik naj vsako število točk obravnava kot nezaupljiv vhod in ga preveri proti velikosti zapisa, preden kopira eno samo točko. EnumEnhMetaFile jamči le, da ostane nSize vsakega zapisa znotraj datoteke. Ne preverja, da se cptl ujema s nSize, zato bo handler, ki s Move kopira cptl točk, bral naslednje zapise ali čez konec metafilea, kadar je število ponarejeno ali pokvarjeno. Od v3.539.39 PDFlibPas preverja fiksno glavo plus število krat bajti na točko proti nSize za PolyDraw, PolyBezier, PolyBezierTo, Polyline, PolylineTo in Polygon v obeh širinah, z enim dodatnim bajtom na točko za bajte tipa PolyDraw. Pri zapisih PolyPoly morajo števila na figuro sešteti v največ deklarirano skupno vsoto, figure brez točk pa se preskočijo
Isti pregled je dovolj kratek, da ga skopirate v svoj razčlenjevalnik. Ta različica potrdi 32-bitni EMR_POLYPOLYLINE in vrne kazalec na njegovo pravo polje točk:
uses
Winapi.Windows;
// Vrne nil, razen če zapis resnično vsebuje točke, ki jih deklarira.
// Točke se začnejo za poljem števil: 32 + nPolys * 4 bajtov znotraj,
// ne pri aptl[0], ki ga RTL deklarira kot enoelementno polje
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; // ponarejeno ali skrajšano število
Total := 0;
Count := @P^.aPolyCounts[0]; // hoja po kazalcu: [0..0] sproži preverjanja obsegov
for I := 1 to P^.nPolys do
begin
Inc(Total, Count^);
Inc(Count);
end;
if Total > P^.cptl then
Exit; // figure zahtevajo več točk, kot jih je
Result := PPoint(NativeUInt(Rec) + NativeUInt(PointsOffset));
end;
Preizkus, ali točke gredo noter, teče najprej, zato je znano, da je polje števil znotraj zapisa, preden ga zanka prehodi. Aritmetika je Int64, ker se nPolys * 4 in cptl * 8, izračunana v 32 bitih, lahko prevrnteta in uidejo primerjavi
Hiter pregled: pravila EMF Poly* za pretvorbo EMF v PDF
EMR_POLYBEZIER: točka 0 je začetna točka; združujte od točke 1 naprej po tri; popravljeno za 32-bitni zapis v v3.539.41EMR_POLYLINE/EMR_POLYPOLYLINE: odprte figure, obris sS, nikolih,faliB, ker polnjenje v PDF zapre odprte podpotiEMR_POLYLINETO: začetek pri trenutnem položaju, ostane odprt, posodablja trenutni položaj, stanja pisala nikoli ne dotakne- 32-bitni
EMR_POLYPOLYLINE: točke se začnejo na bajtu32 + nPolys * 4, ne priaptl[0] EMR_POLYDRAW: maskirajtePT_CLOSEFIGURE, preden razporedite, zaprite po zaključenem odseku, začnite pri trenutnem položaju, kadar prva točka niPT_MOVETO- Privzeto stanje device contexta je
BLACK_PENplusWHITE_BRUSH; v3.539.43 in novejši ga upoštevajo - Znotraj
BeginPath/EndPathvsaka polyline odpre svojo podpot in nič se ne nariše, dokler oklepaja ne uporabite - Potrdite vsak
cptl/cptsprotinSizev 64-bitni aritmetiki, preden kopirate točke - Ročno zgrajene preskusne EMF datoteke potrebujejo celotno 108-bajtno glavo, sicer jih
TMetafile.LoadFromStreambere kot WMF
Če vaša poročila gredo skozi drugačno komponento, velja ista semantika zapisov; HotPDF EMF in WMF vektorski uvoz pokriva, kako ta komponenta pretvori čopiče gradientov in šrafur v vzorce PDF, vektorska grafika, shaderji in gradienti v PDFlibPas pa risanje istih likov neposredno z API knjižnice, namesto prek metafilea
PDFlibPas v3.539.43 ali novejši vključuje vsa zgoraj navedena pravila. Podrobnosti in preskusni prenosi so na strani izdelka PDFlibPas Delphi PDF knjižnice