PDFlibPas, losLabin Delphi-PDF-kirjasto, muuntaa EMF:n Poly*-tietueet PDF-poluiksi noudattamalla jokaisen tietueen määritelmää [MS-EMF]:ssä: 32-bittinen EMR_POLYBEZIER alkaa pisteestä 0, polylinet pysyvät avoimina ja niistä tulee vain viiva, PT_CLOSEFIGURE tietueessa EMR_POLYDRAW on flagi, ja jokainen pistemäärä tarkistetaan tietueen kokoa vasten. Kyseiset säännöt toteutuivat versioiden v3.539.39, v3.539.41 ja v3.539.43 aikana. Ennen niitä kaavio saattoi tulla ulos funktiosta ImportEMFFromFile täytetyn sektorin kera trendtiviivan paikalla, Bezier-käyrä taipuneena väärän ohjauspisteen suuntaan tai suljettu ääriviiva ilman viimeistä sivuaan. Mikään näistä ei nostanut virhettä, ja säännöt pätevät mille tahansa Delphi EMF–PDF-muuntimelle tai GDI-tietueparserille
Miksi EMF:n Poly*-tietueet menevät pieleen PDF-muunnoksessa?
Poly*-tietueet menevät pieleen, koska kukin kantaa osan merkityksestään pisteidensä ulkopuolella: onko kuvio avoin, alkaako se nykyisestä sijainnista, mitkä kynä ja sivellin ovat voimassa ja mistä tietueen pisteet alkavat. Enhanced metafile on tallenne GDI-kutsuista laitekontekstia vasten, joten muuntimen on toistettava koordinaattien lisäksi myös laitekontekstin tila. PDF:ssä ei ole laitekontekstia. Siellä on polku, polun sisällä nykyinen piste ja piirto-operaattori, joka valitsee strokin (S), täytön (f) ja molempien (B) väliltä. Jokainen kahden mallin välinen ristiriita muuttuu hiljaiseksi renderöintieroksi
Poly*-perheestä on olemassa myös kapeampi sarja. Jokaisella 32-bittisellä tietueella on 16-bittinen kaksoispari — EMR_POLYLINE ja EMR_POLYLINE16 — joka tallettaa pisteet SmallInt-pareina. GDI tallentaa yleensä kompaktin muodon, kun jokainen koordinaatti mahtuu, joten muuntimen 32-bittiset käsittelijät voivat olla vuosia vialla, sillä arkipäivän testipiirrokset eivät koskaan yletä niihin. Nopein tarkastus on syöttää samat pisteet molempien tietueiden kautta ja verrata syntyviä polkuja. Täällä käsitellyt tietueet kuuluvat kaikki [MS-EMF]:n piirustustietueiden ryhmään (2.3.5 Drawing Record Types)
| Tietue | Alkaa kohdasta | Suljettu? | Nykyinen sijainti |
|---|---|---|---|
EMR_POLYBEZIER | Piste 0 | Ei | Ei käytössä, ei päivity |
EMR_POLYLINE | Piste 0 | Ei (vain kynä) | Ei käytössä, ei päivity |
EMR_POLYLINETO | Nykyinen sijainti | Ei (vain kynä) | Käytössä ja päivittyy |
EMR_POLYPOLYLINE | Jokaisen murtoviivan ensimmäinen piste | Ei (vain kynä) | Ei käytössä, ei päivity |
EMR_POLYDRAW | Ensimmäinen PT_MOVETO tai nykyinen sijainti | Vain kun PT_CLOSEFIGURE on asetettu | Käytössä ja päivittyy |
Missä EMR_POLYBEZIER-käyrä oikeastaan alkaa?
EMR_POLYBEZIER-käyrä alkaa pisteestä 0, ja vain indeksistä 1 eteenpäin olevat pisteet ryhmitellään kolmen ryhmiin: ohjauspiste, ohjauspiste, päätepiste. Seitsemän pisteen tietue piirtää siis kaksi kuutisegmenttiä: 0 on alkupiste, 1–3 muodostavat ensimmäisen segmentin ja 4–6 toisen. 16-bittinen käsittelijä teki tämän PDFlibPasissa jo ennestään. 32-bittinen käsittelijä aloitti ryhmittelyn pisteestä 0, joten alkupiste kului ensimmäisenä ohjauspisteenä ja jokainen myöhempi segmentti siirtyi yhdellä. Käyrä renderöityi kyllä, se oli vain väärä. Versiosta v3.539.41 alkaen molemmat versiot avaavat polun m:llä pisteessä 0 ja kirjoittavat yhden c-operaattorin jokaista täydellistä kolmikkoa kohden sen jälkeen
Omaan parseriisi: pistemäärä, joka ei ole 1 plus 3:n kerrannainen, on epäkelpo, ja jäljellä olevat pisteet kannattaa ohittaa sen sijaan, että ne ommeltaisiin kiinni käyrään
PolyDraw: PT_CLOSEFIGURE on flagi, ei pistetyyppi
Tietueessa EMR_POLYDRAW PT_CLOSEFIGURE (arvo 1) on bitti, joka yhdistyy bittien PT_LINETO (2) tai PT_BEZIERTO (4) kanssa, joten kelvollinen tyyppitavu voi olla 3 tai 5. Pistetyyppi on tavu, jolta kyseinen bitti on maskattu pois, ja flagi tarkoittaa, että kuvio suljetaan segmentin jälkeen, joka päättyy tähän pisteeseen. Vanha PDFlibPas-käsittelijä vertasi tavua yksittäisiin arvoihin case-lauseessa, joten tyyppien 3 ja 5 pisteet eivät osuneet mihinkään ja jätettiin kokonaan väliin. PolyDrawilla piirretty suorakulmio menetti sulkevan sivunsa, ja Bezier-kolmikko, jonka viimeinen piste kantoi flagia, menetti kyseisen pisteen, mikä työnsi jokaisen myöhemmän kolmikon rytmin jälkeen
Versiosta v3.539.39 alkaen tyyppi luetaan muodossa Types[i] and not PT_CLOSEFIGURE, ja sulku kirjoitetaan vasta täyden segmentin jälkeen: viivan jälkeen suljetun PT_LINETOn kohdalla ja Bezier-ryhmän kolmannen pisteen jälkeen. Rikkinäinen tiedosto, joka asettaa flagin kolmikon ensimmäiselle tai toiselle pisteelle, ei sulje kuviota etuajassa. Sama julkaisu toi mukanaan kaksi aiheeseen liittyvää korjausta:
- Jokainen
PT_MOVETO16-bittisenEMR_POLYDRAW16-tietueen sisällä käynnisti koko polun uudelleen, joten kolme kuviota sisältänyt tietue piti vain viimeisen; nyt ensimmäinen siirto käynnistää polun ja myöhemmät siirrot avaavat alipolkuja - PolyDraw-tietue, joka ei ala
PT_MOVETOlla, alkaa nykyisestä sijainnista, kuten tietueen määritelmä sanoo, sen sijaan että kirjoittaisil- taic-operaattorin ilman edeltävääm:ää
Miksi EMF:n polylinea ei saa koskaan täyttää PDF:ssä?
EMF:n polylinea ei saa koskaan täyttää, koska EMR_POLYLINE ja EMR_POLYPOLYLINE ovat avoimia kuvioita, jotka piirretään pelkällä kynällä, ja avoimen polun täyttäminen PDF:ssä sulkee sen implisiittisesti. ISO 32000-1 §8.5.3 sanoo, että täyttöoperaattorit sulkevat jokaisen avoimen alipolun ennen sen maalaamista. Muunnin, joka kirjoittaa B:n tai f:n kolmen pisteen polylinelle, maalaa siis täytetyn kolmion nykyisellä siveltimen värillä: täytetyn sektorin kaavion trendtiviivan alla. Ennen v3.539.41 PDFlibPas täytti molemmat polyline-versiot siveltimellä, ja 32-bittinen tietue suljettiin myös eksplisiittisesti. Nykyään molemmat versiot päättyvät pelkkään strokeen, ja GDI:n erottelu säilyy: Polygon sulkee ja täyttää, Polyline ei koskaan
PolylineTo alkaa nykyisestä sijainnista
EMR_POLYLINETO piirtää nykyisestä sijainnista tietueen jokaisen pisteen kautta, pysyy avoimena ja jättää nykyisen sijainnin viimeiseen pisteeseen. Vanha käsittelijä sisälsi myös erikoistapauksen, joka sammutti kynän, kun kahdella ensimmäisellä pisteellä oli sama y-koordinaatti, eikä mikään koskaan kääntänyt sitä takaisin päälle, joten jokainen myöhempi tietue tiedostossa menetti ääriviivansa. Kynän tila kuuluu tietueille EMR_SELECTOBJECT ja EMR_CREATEPEN; piirustustietueen käsittelijällä ei ole asiaa muuttaa sitä. Kyseinen erikoistapaus poistettiin versiossa v3.539.41, eikä tietueen yhden pisteen muoto enää lue omiensa pisteiden yli (korjattu v3.539.39)
PolyPolyline-tietueen pisteet alkavat määrätaulukon jälkeen
32-bittinen EMR_POLYPOLYLINE tallettaa ensin nPolys määrää ja sitten cptl pistettä, ja pisteet alkavat tavuoffsetilta 32 + nPolys * 4. Ansa piilee RTL:ssä: Windows-yksikkö määrittelee TEMRPolyPolylinen siten, että aPolyCounts ja aptl ovat yhden elementin taulukoita, joten aptl[0] on ensimmäinen piste vain silloin, kun nPolys on 1. Koodi, joka indeksoi aptl:tä suoraan, lukee määräarvot koordinaatteina jokaiselta useita viivoja sisältävältä tietueelta. Vanha PDFlibPas-käsittelijä mitoitti myös rajatarkistuksensa tuon väärän rakenteen mukaan, joten kelvolliset moniviivatietueet hylättiin ja yksiviivaiset eivät piirtäneet mitään. Versiosta v3.539.41 alkaen PDFlibPas paikantaa pistetaulukon lasketulta offsetilta, samaan tapaan kuin sen PolyPolygon-käsittelijä on aina tehnyt, ja piirtää jokaisen murtoviivan omaksi avoimeksi alipolukseen yhdellä strokilla lopussa. Versiossa v3.539.43 myös 16-bittinen pari sai saman kohtelun; se oli piirtänyt segmentti kerrallaan, mikä rikkoi viivojen liitokset ja ohitti valitun NULL_PENin
Oletuskynä ja -sivellin sekä polun sulkeet
Kaksi tilasääntöä täydentävät polyn korjaukset versiossa v3.539.43:
- Tuoreessa GDI-laitekontekstissa on valittuina jo
BLACK_PENjaWHITE_BRUSH, joten metatiedosto, joka piirtää ilman mitäänEMR_SELECTOBJECTia, piirtää silti mustat ääriviivat; muunnin aloitti aiemmin ilman kynää ja ilman täyttöä ja kirjoitti tällaisille tietueillen:n (lopeta polku, älä maalaa mitään) BeginPath/EndPath-sulkeiden sisälläPolylineei käytä eikä päivitä nykyistä sijaintia, joten sen on avattava uusi alipolku ensimmäisestä pisteestään edelliseen kuvioon kytkeytymisen sijaan, eikä mitään saa maalata ennen kuin sulkeet on vedetty viivana tai täytetty
EMF-testitiedoston rakentaminen TMetafileCanvasilla
Nopein tapa tarkistaa muunnin näitä sääntöjä vasten on tallentaa kolme riskialttiinta kutsua yhteen enhanced metatiedostoon TMetafileCanvasilla. Alla oleva piirros tallentaa käyrät ontolla siveltimellä ja valitsee sitten tahallaan tasaisen keltaisen siveltimen polynelle: oikean muuntimen on ohitettava kyseinen sivellin polynen kohdalla, joten mikä tahansa keltainen tulostuvassa PDF:ssä on bugi. PolyDrawilla ei ole TCanvas-käärettä, joten sitä kutsutaan Windows API:n kautta canvas-kahvalla käyttäen tyyppitavuja 3 ja 5 sulkuflagin kokeiluun
uses
Winapi.Windows, System.Types, Vcl.Graphics;
procedure BuildPolyTestEmf(const FileName: string);
const
// Suljettu neliö (3 = LINETO + CLOSEFIGURE) ja sitten suljettu Bezier-
// kuvio, jonka viimeinen ohjauskolmikko päättyy arvoon 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; // vain ääriviivat käyrille
// Piste 0 on alkupiste; 1..3 ja 4..6 ovat kaksi kuutisegmenttiä
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));
// Avoin V-muoto, tasainen sivellin valittuna: vain stroke,
// ei koskaan suljeta keltaiseksi kolmioksi
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clYellow;
Canvas.Polyline([Point(20, 240), Point(120, 160), Point(220, 240)]);
finally
Canvas.Free; // päättää tallenteen
end;
Mf.SaveToFile(FileName);
finally
Mf.Free;
end;
end;
Koska nämä koordinaatit mahtuvat SmallIntiin, GDI tallentaa yleensä 16-bittiset muunnelmat. Päästäksesi 32-bittisiin käsittelijöihin tarvitset tuottajan, joka kirjoittaa niitä, tai tietueita, jotka rakennat käsin. Käsintehtävillä tiedostoilla on oma ansansa: VCL:n TMetafile.LoadFromStream tulkitsee streamin EMF:ksi vain, kun jäljellä oleva pituus on aidosti suurempi kuin 108-tavuinen TEnhMetaHeader. Minimaalinen käsin kirjoitettu EMF lyhyellä otsikolla tai tyhjä, tasan 108 tavun pituinen tiedosto tulkitaan WMF:ksi ja hylätään viestillä "Metafile is not valid". Kirjoita aina täysi 108-tavuinen otsikko laajennuskenttineen ennen omia testitietueitasi
EMF:n tuonti PDF:ään PDFlibPasilla
PDFlibPas tuo EMF:n funktioilla ImportEMFFromFile ja ImportEMFFromStream, jotka palauttavat onnistuessaan nollasta poikkeavan kuvatunnisteen ja epäonnistuessaan arvon 0. GeneralOptions = 0 pitää vektoripolun, josta tämä artikkeli kertoo; arvo 1 rasteroi metatiedoston sen sijaan bittikartaksi. FontOptions = 1 lisää metatiedoston fontit upottamattomina TrueType-fontteina. Stream-versio kelaa streamin takaisin positioon 0 ennen latausta, joten anna stream, joka sisältää vain metatiedoston
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); // vasen yläkulma alkupisteenä DrawImagelle
PDF.SetMeasurementUnits(0); // pisteet
// FontOptions 1 = lisää fontit upottamattomina TrueType-fontteina
// GeneralOptions 0 = vektorituonti, 1 = bittikartta
ImageID := PDF.ImportEMFFromFile(EmfFile, 1, 0);
if ImageID = 0 then
raise Exception.Create('The metafile could not be imported');
PDF.SelectImage(ImageID);
// EMF:lle ImageWidth / ImageHeight on kehyksen koko pisteinä
PDF.DrawImage(36, 36, PDF.ImageWidth, PDF.ImageHeight);
// Sivu ainoastaan kutsuu tuotua formia: 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;
Vektorimuotoinen EMF-tuonti muuttuu form XObjectiksi, joten GetPageContentToString palauttaa vain save-, transform-, Do- ja restore-jonon. Poly*-tietueista syntyvät m-, l-, c-, h- ja S-operaattorit asuvat form XObjectin streamissa, joka on pakattu. Pura pakatut streamit PDF-objekti-inspektorissa ja lue form-stream: yllä olevan testitiedoston kohdalla sinun pitäisi nähdä polynen päättyvän S:ään ilman h:ta sen edellä, h kunkin sulkuflagin kohdalla PolyDraw-kuvioissa, eikä f:tä tai B:tä millään näistä alipoluista. DrawImage skaalaa tuodun EMF:n tasaisesti Widthn ja Heightn pienemmällä, joten piirros säilyttää kuvasuhteensa, vaikka antamasi laatikko ei vastaisi sitä
Free Pascal -kohteissa katso artikkeli miten PDFlibPasin EMF-vektorituonti kääntyy Free Pascalissa; tietueiden semantiikka on sama siellä missä tuoja kääntyykin
Miten EMF-parserin tulee suhtautua tiedoston pistemääriin?
EMF-parserin tulee käsitellä jokaista pistemäärää epäluotettavana syötteenä ja tarkistaa se tietueen kokoa vasten ennen kuin yksikään piste kopioidaan. EnumEnhMetaFile takaa vain, että kunkin tietueen nSize pysyy tiedoston sisällä. Se ei tarkista, että cptl vastaa nSizeä, joten käsittelijä, joka kopioi cptl pistettä Movella, lukee seuraavat tietueet tai metatiedoston loppuun, kun määrä on väärennetty tai rikkoutunut. Versiosta v3.539.39 alkaen PDFlibPas tarkistaa kiinteän otsikon plus määrän kertaa tavua per piste arvoa nSize vasten tietueille PolyDraw, PolyBezier, PolyBezierTo, Polyline, PolylineTo ja Polygon kummassakin leveydessä, yhtä lisätavua per piste PolyDrawin tyyppitavuille. PolyPoly-tietueilla kuvakohtaisten määrien on myös summauduttava enintään ilmoitettuun kokonaismäärään, ja nollapistekuviot ohitetaan
Sama tarkistus on lyhyt kopioitavaksi omaan parseriisi. Tämä versio validoikin 32-bittisen EMR_POLYPOLYLINEin ja palauttaa osoittimen sen todelliseen pistetaulukkoon:
uses
Winapi.Windows;
// Palauttaa nilin, ellei tietue aidosti sisällä ilmoittamiaan pisteitä.
// Pisteet alkavat määrätaulukon jälkeen: 32 + nPolys * 4 tavua sisään, ei
// kohdasta aptl[0], jonka RTL määrittelee yhden elementin taulukoksi
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; // väärennetty tai katkaistu määrä
Total := 0;
Count := @P^.aPolyCounts[0]; // kulje osoittimella: [0..0] kaataa range checkit
for I := 1 to P^.nPolys do
begin
Inc(Total, Count^);
Inc(Count);
end;
if Total > P^.cptl then
Exit; // kuviot vaativat enemmän pisteitä kuin on olemassa
Result := PPoint(NativeUInt(Rec) + NativeUInt(PointsOffset));
end;
Pisteiden mahtumistesti ajetaan ensin, joten määrätaulukon tiedetään olevan tietueen sisällä ennen kuin silmukka kulkee sen läpi. Aritmetiikka on Int64ä, koska 32 bitissä lasketut nPolys * 4 ja cptl * 8 voivat kiertyä ympäri ja päästä vertailun läpi
Pikaopas: EMF:n Poly*-säännöt EMF–PDF-muunnoksessa
EMR_POLYBEZIER: piste 0 on alkupiste; ryhmittele pisteestä 1 alkaen kolmen ryhmissä; korjattu 32-bittisessä tietueessa versiossa v3.539.41EMR_POLYLINE/EMR_POLYPOLYLINE: avoimia kuvioita, strokeSllä, ei koskaanh,ftaiB, koska PDF:n täyttö sulkee avoimet alipolutEMR_POLYLINETO: alku nykyisestä sijainnista, pysy avoimena, päivitä nykyinen sijainti, älä koske koskaan kynän tilaan- 32-bittinen
EMR_POLYPOLYLINE: pisteet alkavat tavusta32 + nPolys * 4, ei kohdastaaptl[0] EMR_POLYDRAW: maskaaPT_CLOSEFIGUREpois ennen haarautusta, sulje valmiin segmentin jälkeen, aloita nykyisestä sijainnista, kun ensimmäinen piste ei olePT_MOVETO- Laitekontekstin oletustila on
BLACK_PENplusWHITE_BRUSH; versio v3.539.43 ja uudemmat kunnioittavat sitä BeginPath/EndPath-sulkeiden sisällä jokainen polyline avaa oman alipolkunsa, eikä mitään maalata ennen kuin sulkeet käytetään- Validoi jokainen
cptl/cptsarvoanSizevasten 64-bittisessä aritmetiikassa ennen pisteiden kopiointia - Käsintehdyt testi-EMF:t tarvitsevat täyden 108-tavuisen otsikon, muuten
TMetafile.LoadFromStreamlukee ne WMF:nä
Jos raporttisi kulkevat toisen komponentin kautta, sama tietuesemantiikka pätee; artikkeli HotPDF EMF- ja WMF-vektorituonti kertoo, miten kyseinen komponentti muuttaa liukuväri- ja hatch-siveltimet PDF-kuvioiksi, ja artikkeli vektorigrafiikka, shaderit ja liukuvärit PDFlibPasissa kertoo samojen muotojen piirtämisestä suoraan kirjaston API:lla metatiedoston sijaan
PDFlibPas v3.539.43 tai uudempi sisältää kaikki yllä olevat säännöt. Yksityiskohdat ja kokeiluversiot ovat PDFlibPas Delphi PDF library -tuotesivulla