PDF afininės matricos naudoja eilutės-vektoriaus konvenciją iš ISO 32000-1 §8.3.3, kur taškas dauginamas iš matricos iš kairės: point' = point * M. PDFium Component Delphi ir C++Builder aplinkoje tas vienas faktas nulemia visą TPdfMatrix API paviršių: Multiply pridedama, todėl M := M * Op, o PreMultiply prišliejama, todėl M := Op * M
Kiekviena klasikinė transformacijos klaida kyla iš to sakinio, prisimenamo atbulai. Vandenženklis, kuris jūsų testiniame faile puikiai sukasi ir kliento faile atsiduria pusiau nukritęs nuo puslapio. Miniatiūra, kuri išeina pasukta du kartus, nes puslapis jau nešė ketvirtadalio posūkio. Antspaudas, kurio poslinkis puikus A4, bet nuklysta Letter. Nė viena iš jų nėra atvaizdavimo klaidos; tai daugybos tvarkos klaidos, ir jos visos taisomos, kai tik galite garsiai pasakyti, kurioje erdvėje kiekviena operacija parašyta
Eilutės-vektoriaus konvencija, kuri nustato taisykles
TPdfMatrix saugo šešis specifikacijos pavadintus elementus ir juos taiko lygiai taip, kaip apibrėžia formatas, todėl transformacija pati yra tai, nuo ko prasideda samprotavimas. TPdfMatrix.TransformPoint apskaičiuoja x' = x*a + y*c + e ir y' = x*b + y*d + f, kas yra šešių elementų forma, kurią ISO 32000-1 §8.3.4 apibrėžia cm operatoriui, sujungiančiam matricą su dabartine transformacijos matrica. Pora (a, b) yra pirma eilutė, (c, d) antra eilutė, o (e, f) perkėlimo eilutė. Stulpelio-vektoriaus įpročiai, paimti iš OpenGL ar iš tiesinės algebros kurso, jus čia klaidins, ir jie klaidins tyliai, nes neteisingos tvarkos matrica vis tiek yra puikiai galiojanti matrica. Skaitykite sudėtinę operaciją eilutės konvencijoje iš kairės į dešinę, ir taikymo tvarka iškrenta nemokamai: kadangi point * (M * Op) lygu (point * M) * Op, pridėta operacija veikia koordinates, kurias jau sukūrė esama matrica, tai yra, puslapio erdvę, o prišliejama operacija veikia prieš esamai matricai veikiant, objekto pačios įvesties erdvėje
var
M: TPdfMatrix;
Pt: FS_POINTF;
begin
M := TPdfMatrix.Create; // identity
try
// Append order: each call acts on what the previous calls produced.
M.Scale(0.5, 0.5); // M := M * S half size
M.Rotate(90); // M := M * R clockwise, degrees
M.Translate(300, 400); // M := M * T then move on the page
Pt := M.TransformPoint(0, 0); // x*a + y*c + e, x*b + y*d + f
finally
M.Free;
end;
end;
TPdfMatrix.Rotate pagal numatymą sukasi pagal laikrodžio rodyklę ir laipsniais, su ACounterClockwise ir AAngleInRadians, prieinamais, kai jūsų šaltinio duomenys pažymėti kitaip. Skaitymui-tik savybės a iki f ir Handle savybė duoda jums žalius šešis skaičius atgal, kas yra tai, ko nori FPDFPageObj_SetMatrix. Niekas klasėje nuo jūsų neslepia šešių skaičių, ir tai sąmoninga: kai transformacija neveikia teisingai, a iki f spausdinimas yra greičiausia diagnozė, kurią turite
Kodėl prišliejamas perkėlimas reikalauja tiesinės dalies?
Todėl, kad prišliejamas poslinkis parašytas matricos įvesties erdvėje, ir jis turi būti perneštas per dabartinę tiesinę dalį, prieš prisijungiant prie perkėlimo eilutės. TPdfMatrix.PreTranslate todėl apskaičiuoja e := dx*a + dy*c + e ir f := dx*b + dy*d + f. Pridėjimas yra lengvesnė kryptis: TPdfMatrix.Translate parašytas puslapio erdvėje, kur nieko nereikia konvertuoti, todėl jis tik prideda dx prie e ir dy prie f. Bet kas, "optimizuojantis" PreTranslate iki dviejų sudėčių, ką tik ištrynė sukimą ir mastelį iš poslinkio
M := TPdfMatrix.Create;
try
M.Rotate(90); // a=0, b=-1, c=1, d=0
M.Translate(10, 0); // append: e := e + 10
// -> 10 points to the right on the page
M.Reset;
M.Rotate(90);
M.PreTranslate(10, 0); // prepend: e := 10*a + 0*c + e (unchanged)
// f := 10*b + 0*d + f (f - 10)
// -> 10 points along the stamp own x axis,
// which after the turn points down the page
finally
M.Free;
end;
Ta pati asimetrija tęsiasi per mastelio porą, ir verta žinoti, kuriuos elementus kiekvienas iš jų liečia, prieš derinant vieną trečią valandą nakties. TPdfMatrix.PreScale daugina eilutes, mastelindama a ir b pagal scaleX, o c ir d pagal scaleY, ir palieka perkėlimą ramybėje, nes poslinkis jau įvyko žemiau srovės. Pridedama TPdfMatrix.Scale daugina stulpelius vietoj to, imdama a, c, e pagal scaleX ir b, d, f pagal scaleY, todėl esamas poslinkis mastelizuojasi kartu su viskuo kitu. Abu yra vienos-paskirties keliai, praleidžiantys bendrą šešių elementų sandaugą, ir abu tiksliai išsaugo kompozicijos semantiką bendros formos
Kur eina du perkėlimai pivot sukime?
Aplink operaciją, ne aplink visą matricą, ir tokia tvarka. TPdfMatrix.RotateAt prideda Translate(-pivot), tada sukimą, tada Translate(+pivot), kas eilutės-vektoriaus konvencijoje sudaromas kaip Translate(-pivot) * Op * Translate(pivot). Ta seka yra tai, kas išlaiko pivot fiksuotą naujoje operacijoje, tuo pat metu leisdama esamai matricai pirmiausia sukurti savo koordinates ir jas perduoti toliau. Parašykite porą priešinga tvarka, kaip tai būtų teisinga stulpelio-vektoriaus bibliotekoje, ir objektas skrieja apie pradžios tašką vietoj sukimosi vietoje, kas yra būtent tai, kaip centruotas vandenženklis atsiduria už apkirpimo langelio ribų
procedure RotateStampAboutPageCenter(AObj: FPDF_PAGEOBJECT;
const AAngleDegrees, APageWidth, APageHeight: Single);
var
M: TPdfMatrix;
Raw: FS_MATRIX;
begin
if not FPDFPageObj_GetMatrix(AObj, Raw) then
raise Exception.Create('Page object carries no matrix');
M := TPdfMatrix.Create(Raw);
try
// Appends Translate(-pivot) * Rotate * Translate(+pivot) in one call.
M.RotateAt(AAngleDegrees, APageWidth / 2, APageHeight / 2);
Raw := M.Handle;
FPDFPageObj_SetMatrix(AObj, Raw);
finally
M.Free;
end;
end;
Ta pati kompozicija palaiko ScaleAt, SkewAt, HorizontalFlipAt, VerticalFlipAt ir CentralFlipAt, todėl kai tik pasitikite šablonu sukimui, galite juo pasitikėti ir likusiems. TPdfMatrix.CentralFlip vertas išskirti: jis paneigia visus šešis elementus, kad duotų jums 180 laipsnių posūkį be jokios trigonometrijos, kas reiškia jokio cos reikšmei, kuri turėjo būti lygiai nulis, ir jokio kaupiamojo nuokrypio, kai jį taikote cikle. Jei dedate pakartotinius ženklus, o ne sukate vieną, pačio pozicionavimo mechanika aprašyta daugkartinio naudojimo puslapio antspaudai su Form XObjects, o matricos darbas čia sėdi tiesiai virš jo
Ką TryDecompose pasako apie matricą?
TPdfMatrix.TryDecompose praneša perkėlimą, mastelį, sukimą, poslinkį (shear), determinantą ir atspindžio vėliavėlę pagal mastelio-tada-sukimo konvenciją, ir praneša juos pakankamai sąžiningai, kad būtų naudingi sprendimams, ne tik registravimui. ScaleX ateina iš pirmos eilutės ilgio, Sqrt(a*a + b*b), todėl visada teigiamas. ScaleY tada yra Determinant / ScaleX, kas jį paverčia ženkliniu. Sukimas ateina iš ArcTan2(-b, a) laipsniais, o poslinkis iš dviejų eilučių taškinės sandaugos, normalizuotos abiem masteliais
Tas ženklas ant ScaleY yra dalis, kurią žmonės ištrina, ir jos ištrynimas yra reali klaida, ne kosmetinė. Neigiamas determinantas reiškia, kad matrica turi atspindį. Priverskite abu mastelio faktorius teigiamus, kad skaičiai atrodytų tvarkingesni, ir jūs atspindį išmetėte, todėl matrica, atkurta iš dekompozicijos, grįžta veidrodine: tekstas skaitomas atgal, nuskaitytas puslapis apsiverčia, importuotas logotipas žiūri ne ta kryptimi. IsReflected laukas egzistuoja tam, kad jums niekada nereikėtų to daryti išvadų. Tai taip pat patikra, neleidžianti klasikiniam dvigubam sukimui, kai kodas prideda ekrano posūkį prie puslapio, kuris jį jau neša; peržiūros-pusės tos problemos versija aptariama miniatiūros pritaikyme, priartinime ir dvigubame sukime
var
D: TPdfMatrixDecomposition;
begin
if M.TryDecompose(D) then
begin
// D.ScaleX is always positive; D.ScaleY carries the determinant sign.
if D.IsReflected then
Log('mirrored, ScaleY = %.3f', [D.ScaleY]);
if Abs(D.RotationDegrees) > 0.5 then
SkipDisplayRotation; // the object already carries its own turn
end
else
UseIdentityFallback; // near-singular or non-finite: no answer
end;
Vieno stačiakampio pritaikymas kitam neatspėjant
TPdfMatrix.TryCreateRectMapping sukuria šaltinio-į-tikslą matricą už jus ir ima TPdfMatrixFitMode tipą pmfStretch, pmfContain ar pmfCover. Jis pirmiausia normalizuoja abu stačiakampius, nes PDF stačiakampiai nebūtinai atkeliauja su kairiu žemiau dešinio ar apačia žemiau viršaus, tada išveda nepriklausomus X ir Y mastelius: pmfStretch palieka juos nepriklausomus, pmfContain paima mažesnį ir centruoja juodąsias juostas, pmfCover paima didesnį ir centruoja apkirpimą. Papildoma MapRectToRect prideda tą patį susiejimą prie esamos matricos, o NewRectMapping kelia EPdfMatrixError ten, kur Try forma grąžina False. Tai yra primityvas, esantis po kiekvienu langelio pozicionavimu N-up montavime ir puslapių pertvarkyme, kur kiekvienas šaltinio puslapis turi atsidurti apskaičiuotame langelyje, jums nekartojant aritmetikos kiekvienam išdėstymui
Degeneruotos matricos ir sąžiningas nesėkmės kelias
Baigtinė įvestis negarantuoja baigtinio rezultato, todėl pritaikymo kodas skaičiuoja Double, tada iš naujo tikrina susiaurintą Single kandidatą dėl baigtinumo prieš jį publikuodamas; susiejimas, turintis begalybę, niekada negrąžinamas taip, tarsi būtų galiojantis. Ta pati drausmė valdo inversiją. TPdfMatrix.TryGetInverse atmeta matricą naudodama santykinę ribą, palygindama determinantą su epsilon, padaugintu iš didžiausio tiesinio elemento kvadrato, o ne su fiksuota konstanta, kas išlaiko patikrą prasmingą nepriklausomai nuo to, ar jūsų vienetai yra taškai, ar mikrometrai. TryDecompose atsisako tuo pačiu būdu, atsisakydama, kai pirmos eilutės ilgis ar išvestas ScaleY nukrenta ties epsilon ar žemiau
Rinkitės nesėkmės stilių, tinkantį iškvietimo vietai, o ne vyniokite viską į try-except iš įpročio. TryInvert, TryGetInverse, TryInverseTransformPoint, TryTransformBounds ir TryCreateRectMapping grąžina False ir palieka savo taikinius nepaliestus, kas tinka trenksmo-testavimui ir vienetiniams-objektų ciklams, kur degeneruotas objektas turėtų būti praleistas, ne mirtinas. Invert, InverseCopy, InverseTransformPoint, MapRectToRect ir TransformBounds vietoj to kelia EPdfMatrixError, kas tinka sąrankos kodui, kur singuliarinė matrica reiškia, kad iškvietėjas apskaičiavo kažką neteisingai. Paketiniam darbui TransformPoints ir TransformRects paskirsto savo rezultato masyvą lygiai vieną kartą, TransformPointsInPlace ir TransformRectsInPlace pakartotinai naudoja jūsų saugyklą, o TryTransformBounds kaupia ribojantį langelį vienu perėjimu vietoj transformuotų taškų materializavimo pirmiausia
Nė vienas iš to nėra egzotiška matematika. Tai viena konvencija, taikoma nuosekliai, su API, pavadintu taip, kad konvencija matoma iškvietimo vietoje: Multiply ir paprasti veiksmažodžiai prideda, Pre šeima prišlieja, At šeima apgaubia operaciją jos pivot pora. Užrašykite tvarką komentare šalia bet kokio sudėtinio, kurį kuriate, nes kodas, kuris teisingai skaitosi šiandien, yra kodas, kurį kažkas apverčia po šešių mėnesių. Pilna TPdfMatrix nuoroda, kartu su puslapio-objekto ir atvaizdavimo API, kurias šios transformacijos maitina, gyvena su PDFium Component, skirtu Delphi ir C++Builder