Lomakekenttäerän siirtäminen viime vuoden pohjalta tämän vuoden asetteluun on kohta, jossa FDF- ja XFDF-round tripit lakkaavat riittämästä: arvot saapuvat, mutta ulkoasuvirrat, laskenta-actionit ja oletusresurssit eivät. PDFiumPas vastaa kyseiseen tapaukseen kohteella GraftPdfAcroForm, joka kloonaa koko kenttäobjektigraafin yhdestä PDF:stä ja kirjoittaa sen toiseen
Syy, miksi datatason vienti ei voi tehdä tätä, on rakenteellinen. Kenttä ei ole tietue, se on aligraafi. ISO 32000-1 §12.7 määrittelee interaktiivisen lomakkeen sanakirjan, joka pitää sisällään kohteet /Fields, /CO, /DR ja /DA, §12.7.3 määrittelee sen alla roikkuvat kenttäsankirjat ja §12.5.6.19 määrittelee widget-annotaatiot, jotka antavat kyseisille kentille näkyvän laatikon sivulla. XFDF kuljettaa kyseisen rakenteen lehtiä. Graftaus kuljettaa rakenteen itse
Miksi /Fields-taulukon kopioiminen ei koskaan riitä
Kohteen /Fields kopioiminen yhdestä asiakirjasta toiseen tuottaa lomakkeen, joka on rikki jokaisella kiinnostavalla tavalla, koska taulukko pitää sisällään epäsuoria viittauksia eikä muuta. ISO 32000-1 §7.3.10 tekee epäsuorasta oliosta osoitettavan objektinumerolla plus generoinnilla, ja nuo numerot ovat merkityksellisiä vain sen tiedoston sisällä, josta ne tulevat. Liitä taulukko yli, ja jokainen sen viittaus joko roikkuu tai, pahempaa, ratkeaa hiljaisesti täysin asiaan kuulumattomaan olioon, joka sattuu miehittämään kyseisen paikan kohteessa. Jokaisen viittauksen alla istuu graafi, joka on sekä jaettu että syklinen. Kenttäsankirja osoittaa lapsiinsa, kukin lapsi osoittaa takaisin kohteeseensa /Parent, widget osoittaa ulkoasuvirtoihinsa ja sivuun, joka kantaa sitä kohteen /P kautta, ulkoasuvirrat osoittavat fontteihin lomakkeen oletusresurssisanakirjassa, ja kohteen /AA alla olevat lisäaction-sanakirjat osoittavat yhä muihin olioihin. Kaksi widgetiä eri sivuilla jakavat rutiininomaisesti yhden fontin ja yhden ulkoasu-XObjectin. Joten oikean graftauksen on kuljettava kyseinen graafi, kloonattava jokainen saavutettava olio täsmälleen kerran, ohjattava jokaisen widgetin /P uudelleen kuvattuun kohdesivuun ja lisättävä klonattu widget kyseisen sivun /Annots-taulukkoon — muuten kenttä on olemassa lomakkeessa ja näkymätön sivulla. Jos olet jahdannut eroa kentän, sen widgetin ja sitä näyttävän sivuannotaation välillä, muistiinpanomme widget-indeksistä versus annotaatioindeksistä kattaa täsmälleen kyseisen jaon
Mitä GraftPdfAcroForm tarvitsee sinulta?
Se tarvitsee kolme erillistä virtaa ja eksplisiittisen sivukuvauskartan. GraftPdfAcroForm ottaa kohteet Source, Destination ja Output erillisinä TStream-instansseina, TPdfGraftPageMappings-taulukon, TPdfAcroFormGraftOptions-tietueen, valinnaisen TPdfCrossDocumentGraftMap-objektin ja ulos-parametrin TPdfAcroFormGraftReport. Se palauttaa kohteen Boolean nostamatta, ja epäonnistuessa raportti kantaa syyn kohteessa ErrorMessage. Sivukuvauskartta on 1-pohjainen kummallakin puolella eikä sitä päätellä: jokaisen widgetiä kantavan lähdésivun, jota aiot graftata, on ilmettävä siinä. Kohteen nil välittäminen graftkartaksi on laillista — funktio luo ja vapauttaa silloin yksityisen kartan kutsun ajaksi — ja TPdfAcroFormGraftOptions.Default antaa sinulle kohteen CollisionPolicy arvolla pagcpReject, kohteen RenamePrefix arvolla Imported_, kohteen MaxObjects arvolla 100000, kohteen MaxDepth arvolla 128 ja kohteen AllowSignedDestination arvolla False. Kolme viimeistä ovat budjetteja, ja ne ovat olemassa, koska objektigraafi, jonka aiot kulkea läpi, tuli tiedostosta, jota et kirjoittanut
uses
Classes, SysUtils, FPdfCompress;
var
Source, Destination, Output: TMemoryStream;
Options: TPdfAcroFormGraftOptions;
Mappings: TPdfGraftPageMappings;
Report: TPdfAcroFormGraftReport;
begin
Source := TMemoryStream.Create;
Destination := TMemoryStream.Create;
Output := TMemoryStream.Create;
try
Source.LoadFromFile('claim-template-2025.pdf');
Destination.LoadFromFile('claim-layout-2026.pdf');
Source.Position := 0;
Destination.Position := 0;
Options := TPdfAcroFormGraftOptions.Default;
SetLength(Mappings, 2);
Mappings[0].SourcePageNumber := 1;
Mappings[0].DestinationPageNumber := 1;
Mappings[1].SourcePageNumber := 2;
Mappings[1].DestinationPageNumber := 3;
if GraftPdfAcroForm(Source, Destination, Output, Mappings,
Options, nil, Report) then
Output.SaveToFile('claim-2026-with-fields.pdf')
else
raise Exception.Create(Report.ErrorMessage);
finally
Output.Free;
Destination.Free;
Source.Free;
end;
end;
Kuinka graftkartta välttää jaetun fontin kloonaamisen kahdesti?
TPdfCrossDocumentGraftMap pitää lähteestä kohteeseen -viittaustaulua, jonka avaimet kantavat sekä objektinumeroa että generointia, ja rekursiivinen kloonaaja kuulee sitä ennen kuin laskeutuu. Operaatioiden järjestys on se, mikä tekee sykleistä turvallisia: kloonaaja varaa kohdeobjektinumeron ja rekisteröi kuvauksen ensin, sitten kulkee lähdeolion lapsiviittaukset. Vanhempi, joka saavuttaa lapsen, joka osoittaa takaisin vanhempaansa, löytää vanhemman jo rekisteröitynä ja palauttaa olemassa olevan kohdeviittauksen rekursioon ajautumisen sijaan. Sama haku on se, mikä tekee fontista, ulkoasuvirrasta tai actionista, jota kuusi widgetiä jakaa, kloonin kerran ja viittauksen kuusi kertaa. Kartta sidotaan lähdeasiakirjaan lähdetavujen SHA-256-hajautuksella, näkyvillä kohteena SourceIdentity. Jos ojennat kohteelle GraftPdfAcroForm kartan, jonka identiteetti ei vastaa välittämääsi lähdettä, se kieltää kutsun sen sijaan että käyttäisi uudelleen viittauksia, jotka eivät koskaan olleet kelvollisia kyseiselle tiedostolle. Sivukuvauskartat kylvetään samaan karttaan ennen kloonauksen alkua, ja juuri näin widgetin /P päätyy osoittamaan kohdesivulle: lähdésivuolio ratkeaa jo kuvattuun kohdesivuolioon, joten tavallinen viittausten uudelleenkirjoituspassi hoitaa sen ilman erikoistapausta
uses
Classes, SysUtils, FPdfCompress, FPdfSha256;
var
GraftMap: TPdfCrossDocumentGraftMap;
SourceBytes: TBytes;
EntriesBefore: Integer;
begin
SetLength(SourceBytes, Source.Size);
Source.Position := 0;
if Length(SourceBytes) > 0 then
Source.ReadBuffer(SourceBytes[0], Length(SourceBytes));
GraftMap := TPdfCrossDocumentGraftMap.Create(
AnsiString(SHA256Hex(SHA256Bytes(SourceBytes))));
try
EntriesBefore := GraftMap.Count;
Source.Position := 0;
if not GraftPdfAcroForm(Source, Destination, Output, Mappings,
Options, GraftMap, Report) then
begin
// Tämä kutsu lisäämät merkinnät on rullattu takaisin;
// kaikki ennen sitä rekisteröity on yhä ehjää.
Assert(GraftMap.Count = EntriesBefore);
WriteLn('graft refused: ', Report.ErrorMessage);
end;
finally
GraftMap.Free;
end;
end;
Kyseinen takaisinrullaus on pointti siinä, että omistat kartan itse. PDFiumPas kohtelee kutsujan toimittamaa karttaa transaktionaalisesti: epäonnistunut graftaus hylkää ne merkinnät, jotka kyseinen kutsu lisäsi, ja pitää jokaisen etukäteen olleen kuvauksen, joten yksi kieltäytyminen ei koskaan jätä jälkeensä viittausten välimuistia olioihin, joita ei koskaan kirjoitettu. Pidä kuitenkin yksi kartta per kohdeasiakirja — jokaisen merkinnän kohdepuoli on objektinumero kyseisessä tiedostossa, eikä se merkitse mitään toisessa
Kenttänimien törmäykset: hylkää tai nimeä uudelleen
Täysin täydellistetyt kenttänimet on pysyttävä yksilöllisinä lomakkeen sisällä, eikä PDFiumPas arvaa, mitä tarkoitit, kun ne törmäävät. TPdfAcroFormCollisionPolicy tarjoaa täsmälleen kaksi vastausta. Kohteen pagcpReject alla, joka on oletus, ensimmäinen lähdekenttä, jonka nimike on jo olemassa kohteessa, keskeyttää koko graftauksen virheellä ja jättää tulostusvirran tyhjäksi. Kohteen pagcpRename alla törmäävä lähdekenttä nimetään uudelleen etuliittämällä kohteen RenamePrefix ja graftaus jatkuu, ja kohteen Report.RenamedFieldCount kertoo, kuinka usein se tapahtui
Options := TPdfAcroFormGraftOptions.Default;
Options.CollisionPolicy := pagcpRename;
Options.RenamePrefix := 'Y2025_';
Options.MaxObjects := 20000;
Options.MaxDepth := 64;
if GraftPdfAcroForm(Source, Destination, Output, Mappings,
Options, nil, Report) then
begin
WriteLn('source fields : ', Report.SourceFieldCount);
WriteLn('existing fields: ', Report.DestinationFieldCount);
WriteLn('grafted fields : ', Report.GraftedFieldCount);
WriteLn('renamed fields : ', Report.RenamedFieldCount);
WriteLn('cloned objects : ', Report.GraftedObjectCount);
WriteLn('reused objects : ', Report.ReusedObjectCount);
WriteLn('mapped pages : ', Report.MappedPageCount);
WriteLn('output bytes : ', Report.OutputByteCount);
end
else
WriteLn('graft refused : ', Report.ErrorMessage);
Uudelleennimeäminen ei ole ilmaista, ja sinun pitäisi päättää se harkiten sen sijaan että kurottaisit siihen saadaksesi virheen katoamaan. Uudelleennimetty kenttä on eri kenttä: mikä tahansa kohdeasiakirjan JavaScript, joka osoittaa sitä nimellä, mikä tahansa kohteen /CO laskentamerkintä, jonka ihminen kirjoitti vanhaa nimeä vasten, ja mikä tahansa alasvirran kuluttaja, joka avainnii kenttänimellä, joutuu saamaan tietää etuliitteestä. Jos kaksi asiakirjaa kuvaavaat aidosti samaa kenttää, rehellinen korjaus on yleensä sovittaa nimet ylävirtaan, ei graftauksen hetkellä. Kun graftaus laskeutuu, yhdistetyn lomakkeen läpikäynti vahvistaaksesi, mitä oikeasti sait, on luonnollinen seuraava askel, ja lomakekenttien navigointi PDFiumPasissa kattaa kyseisen kulun
Missä graftaus epäonnistuu tarkoituksella suljettuna
Jokainen monitulkintainen ehto on virhe, ei koskaan parhaan yrityksen tulos, ja se on suunnittelupäätös, joka kannattaa ymmärtää ennen kuin se yllättää sinut tuotannossa. GraftPdfAcroForm palauttaa kohteen False, nollaa tulostusvirran ja raportoi syyn osuessaan mihin tahansa näistä
- Lähdelomake kantaa kohteen
/XFAmerkintää — XFA-paketit ovat rinnakkainen lomakemalli, eikä niitä voi redusoida AcroForm-kenttäsankirjoiksi - Widget asuu lähdésivulla, jolla ei ole merkintää sivukuvauskartassa, mikä muuten pudottaisi kentän hiljaisesti tai kiinnittäisi sen väärälle sivulle
- Sivukuvauskartat ovat alueen ulkopuolella, tai kaksi kuvausta käyttää uudelleen samaan lähde- tai kohdesivua
- Molemmat lomakkeet määrittelevät oletusresurssisanakirjan
/DR, koska kahden resurssinimitilan yhdistäminen risköisi olemassa olevan nimen osoittamisen uudelleen toiseen fonttiin - Objektigraafi ylittää kohteen
MaxObjectstai rekursio ylittää kohteenMaxDepth - Kohde sisältää allekirjoituksen ja kohteen
AllowSignedDestinationonFalse - Toimitettu graftkartta kuuluu eri lähdeasiakirjaan, tai lähdeviittaus roikkuu
Kirjoituspolku on yhtä lailla konservatiivinen. PDFiumPas emittoi tuloksen harvana inkrementaalisena revisiona, joka liitetään kohteeseen, sitten materialisoi kirjoitetun tulosteen uudelleen ja lukee sen lomakkeen uudelleen: jos tuloksen kenttien määrä ei ole yhtä suuri kuin kohteen alkuperäinen kenttien määrä plus lähteen, koko graftaus hylätään ja tuloste tyhjennetään. Et koskaan saa osittain graftattua tiedostoa. Kyseisen politiikan hinta on todellinen — /DR-törmäys tai allekirjoitettu kohde pysäyttää sinut suoraan, ja sinun on ratkaistava se itse sen sijaan että hyväksyisit yhdistetyn approksimaation — mutta vaihtoehto on lomake, joka avautuu hienosti ja laskee väärin
Milloin graftaus on väärä työkalu
Graftaus liikuttaa rakennetta, joten käytä sitä, kun rakenne on se, mitä sinulta puuttuu. Jos molemmat asiakirjat kantavat jo samaa kenttäjoukkoa ja sinun tarvitsee liikuttaa vain arvoja ja annotaatioita niiden välillä, XFDF-lomakedata-artikkelin vienti- ja tuontipolku on kevyempi, standardi ja palautettavissa. Kurota kohteen GraftPdfAcroForm puoleen, kun kohteessa ei ole kenttiä lainkaan tai sillä on eri joukko, ja tarvitset widgetien, ulkoasuvirtojen, actionien ja laskentajärjestyksen saapuvan ehjinä. Viimeinen käytännön huomio identiteetistä: koska graftkartta avainnii objektinumerolla plus generoinnilla ja on sidottu lähdetavujen SHA-256:een, lähteen uudelleentallennus tai optimointi ajojen välillä tuottaa eri identiteetin ja kartan, jota ei enää sovelleta. Tilannekuvaa lähde, josta graftaat, ja pidä se vakaana erän ajan; kohtele sitä syötetöyteenkuuluvana, ei asiana, jonka yöajo saa vapaasti kirjoittaa yli
GraftPdfAcroForm, TPdfCrossDocumentGraftMap ja ympäröivä virtatason PDF-työkalupakki toimituvat tuotteessa PDFiumPas Delphi PDFium Component Delphille, C++Builderille ja Lazarukselle, ja tuotesivu kantaa täydellisen API-referenssin graftausvalinnoille, raporttikentille ja lopulle asiakirjanmuokkauspinnasta