Teknik Makale

Delphi'de PDFium Matrix Prepend ve Append: Pivot Rotasyonu

PDF affine matrisleri, bir noktanın matrisi soldan çarptığı ISO 32000-1 §8.3.3'ün satır-vektör kuralını kullanır: point' = point * M. Delphi ve C++Builder için PDFium Component'te bu tek gerçek, TPdfMatrix'in tüm API yüzeyini belirler: Multiply append yapar, bu yüzden M := M * Op; PreMultiply ise prepend yapar, bu yüzden M := Op * M

Her klasik dönüşüm hatası, o cümlenin tersinden hatırlanmasına kadar iz sürülebilir. Test dosyanızda düzgünce dönen ama müşteri dosyasında sayfanın yarısı dışına düşen filigran. Sayfa zaten çeyrek bir dönüş taşıdığı için iki kez döndürülmüş çıkan küçük resim. A4'te ofseti mükemmel olan ve Letter'de kayan damga. Bunların hiçbiri rendering hatası değildir; çarpım-sırası hatalarıdır ve her işlemin hangi uzayda yazıldığını yüksek sesle söyleyebildiğinizde hepsi düzeltilebilirdir

Kuralları belirleyen satır-vektör kuralı

TPdfMatrix, spek-adlandırmalı altı elemanı saklar ve onları biçimin tanımladığı gibi tam olarak uygular, bu yüzden akıl yürütmenin başladığı yer dönüşümün kendisidir. TPdfMatrix.TransformPoint, x' = x*a + y*c + e ve y' = x*b + y*d + f hesaplar; bu, geçerli dönüşüm matrisine bir matrisi birleştiren cm operatörü için ISO 32000-1 §8.3.4'ün tanımladığı altı-elemanlı biçimdir. (a, b) çifti ilk satırdır, (c, d) ikinci satırdır ve (e, f) öteleme satırıdır. OpenGL'den ya da bir doğrusal cebir dersinden edinilmiş sütun-vektör alışkanlıkları sizi burada yanıltacaktır ve sessizce yanıltacaktır, çünkü yanlış-sıralı bir matris hâlâ mükemmel derecede geçerli bir matristir. Bir bileşiği satır kuralında soldan sağa okuyun, uygulama sırası ücretsiz olarak ortaya çıkar: point * (M * Op), (point * M) * Op'ye eşit olduğundan, eklenmiş (append) bir işlem, mevcut matrisin zaten ürettiği koordinatlar üzerinde etki eder, yani sayfa uzayında, önceden eklenmiş (prepend) bir işlem ise mevcut matris çalışmadan önce, nesnenin kendi girdi uzayında etki eder

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, kaynak verileriniz diğer yönde işaretlendiğinde kullanılabilen ACounterClockwise ve AAngleInRadians ile birlikte, varsayılan olarak saat yönünde ve derece cinsindendir. Salt okunur a'dan f'ye özellikler ve Handle özelliği, FPDFPageObj_SetMatrix'in istediği şey olan ham FS_MATRIX'i size geri verir. Sınıfta hiçbir şey altı sayıyı sizden gizlemez ve bu kasıtlıdır: bir dönüşüm yanlış davrandığında, a'dan f'ye yazdırmak elinizdeki en hızlı tanıdır

Bir öteleme önceden eklemek neden doğrusal kısma ihtiyaç duyar?

Çünkü önceden eklenmiş bir kaydırma matris girdi uzayında yazılır ve öteleme satırına katılabilmesi için önce geçerli doğrusal kısımdan geçirilmesi gerekir. TPdfMatrix.PreTranslate bu yüzden e := dx*a + dy*c + e ve f := dx*b + dy*d + f hesaplar. Append yapmak kolay yöndür: TPdfMatrix.Translate sayfa uzayında yazılır, hiçbir şeyin dönüştürülmesi gerekmediği yerde, bu yüzden yalnızca dx'i e'ye ve dy'yi f'ye ekler. PreTranslate'i iki toplamaya "optimize eden" herkes, kaydırmadan rotasyonu ve ölçeği az önce silmiştir

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;

Aynı asimetri ölçek çifti boyunca da geçerlidir ve birini sabah üçte hata ayıklamadan önce her birinin hangi elemanlara dokunduğunu bilmeye değer. TPdfMatrix.PreScale satırları çarpar, a ve b'yi scaleX ile, c ve d'yi scaleY ile ölçekler ve ötelemeyi olduğu gibi bırakır, çünkü kaydırma zaten aşağı akışta gerçekleşmiştir. Ekleyen TPdfMatrix.Scale bunun yerine sütunları çarpar, a, c, e'yi scaleX ile ve b, d, f'yi scaleY ile alır, bu yüzden mevcut ofset her şeyle birlikte ölçeklenir. İkisi de genel altı-elemanlı çarpımı atlayan tek-amaçlı yollardır ve ikisi de genel biçimin bileşim anlambilimini tam olarak korur

Bir pivot rotasyonunda iki öteleme nereye gider?

İşlemin etrafına, tüm matrisin etrafına değil, ve o sırayla. TPdfMatrix.RotateAt, Translate(-pivot)'u, ardından rotasyonu, ardından Translate(+pivot)'u ekler; bu, satır-vektör kuralı altında Translate(-pivot) * Op * Translate(pivot) olarak bileşir. O sıra, pivotu yeni işlem altında sabit tutarken mevcut matrisin koordinatlarını önce üretip devretmesine izin veren şeydir. Çifti sütun-vektör bir kütüphanede doğru olacağı diğer sırayla yazın, nesne yerinde dönmek yerine başlangıç noktası etrafında yörüngeye girer; bu, ortalanmış bir filigranın kırpma kutusunun dışında sona ermesinin tam olarak nasıl gerçekleştiğidir

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;

Aynı bileşim ScaleAt, SkewAt, HorizontalFlipAt, VerticalFlipAt ve CentralFlipAt'ı destekler, bu yüzden deseni rotasyon için bir kez güvendiğinizde geri kalanı için de güvenebilirsiniz. TPdfMatrix.CentralFlip ayrı olarak belirtilmeye değer: hiç trigonometri olmadan size 180 derecelik bir dönüş vermek için tüm altı elemanı negatifler; bu, tam olarak sıfır olması gereken bir değerin cos'u olmadığı ve bir döngüde uyguladığınızda birikimli kayma olmadığı anlamına gelir. Birini döndürmek yerine tekrarlanan işaretler yerleştiriyorsanız, yerleştirmenin kendi mekaniği Form XObject'lerle yeniden kullanılabilir sayfa damgalarında ele alınmıştır ve buradaki matris işi doğrudan onun üzerine oturur

TryDecompose bir matris hakkında size ne söyler?

TPdfMatrix.TryDecompose, ölçek-sonra-rotasyon kuralı altında öteleme, ölçek, rotasyon, kayma, determinant ve bir yansıma bayrağı bildirir ve bunları yalnızca günlükleme için değil kararlar için yararlı olacak kadar dürüstçe bildirir. ScaleX, ilk satırın uzunluğundan, Sqrt(a*a + b*b)'den gelir, bu yüzden her zaman pozitiftir. ScaleY daha sonra Determinant / ScaleX'tir, bu da onu işaretli yapar. Rotasyon, derece cinsinden ArcTan2(-b, a)'dan gelir ve kayma, iki satırın her ikisinin ölçeğine göre normalize edilmiş nokta çarpımından gelir

ScaleY üzerindeki o işaret, insanların sildiği kısımdır ve onu silmek kozmetik olmaktan çok gerçek bir hatadır. Negatif bir determinant, matrisin bir yansıma içerdiği anlamına gelir. Sayıları daha derli toplu göstermek için her iki ölçek faktörünü de zorla pozitif yapın, yansımayı atmış olursunuz, bu yüzden ayrıştırmadan yeniden inşa edilen bir matris aynalanmış olarak geri döner: metin tersten okunur, taranmış bir sayfa çevrilir, içe aktarılmış bir logo yanlış yöne bakar. IsReflected alanı, onu hiçbir zaman çıkarım yapmak zorunda kalmamanız için vardır. Bu aynı zamanda kodun zaten bir tane taşıyan bir sayfaya bir görüntüleme dönüşü eklediği klasik çift rotasyonu önleyen kontroldür; o sorunun görüntüleyici-tarafı sürümü küçük resim sığdırma, yakınlaştırma ve çift rotasyonda ele alınmıştır

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;

Bir dikdörtgeni tahmin etmeden diğerine sığdırmak

TPdfMatrix.TryCreateRectMapping, sizin için kaynaktan-hedefe matrisi oluşturur ve pmfStretch, pmfContain ya da pmfCover'dan bir TPdfMatrixFitMode alır. Önce her iki dikdörtgeni de normalleştirir, çünkü PDF dikdörtgenlerinin sol tarafın sağın altında ya da altın üstün altında gelmesi zorunlu değildir, ardından bağımsız X ve Y ölçekleri türetir: pmfStretch onları bağımsız tutar, pmfContain daha küçüğünü alır ve letterbox'ı ortalar, pmfCover daha büyüğünü alır ve kırpmayı ortalar. Eşlik eden MapRectToRect, aynı eşlemeyi mevcut bir matrise ekler ve NewRectMapping, Try biçiminin False döndürdüğü yerde EPdfMatrixError fırlatır. Bu, N-up imposition ve sayfa yeniden sıralamadaki her hücre yerleştirmenin altındaki ilkeldir; burada her kaynak sayfa, aritmetiği düzen başına yeniden türetmenize gerek kalmadan hesaplanmış bir hücrenin içine inmelidir

Dejenere matrisler ve dürüst başarısızlık yolu

Sonlu girdiler sonlu bir sonucu garanti etmez, bu yüzden sığdırma kodu Double'da hesaplar ve ardından daraltılmış Single adayı yayınlamadan önce sonluluk açısından yeniden kontrol eder; sonsuzluk içeren bir eşleme asla geçerliymiş gibi geri verilmez. Aynı disiplin tersine çevirmeyi yönetir. TPdfMatrix.TryGetInverse, bir matrisi göreli bir eşik kullanarak reddeder; determinantı sabit bir sabit yerine en büyük doğrusal eleman'ın karesi çarpı epsilon ile karşılaştırır; bu, biriminiz nokta ya da mikrometre olsun kontrolü anlamlı tutan şeydir. TryDecompose, ilk satır uzunluğu ya da türetilmiş ScaleY epsilon'a eşit ya da altına düştüğünde reddederek aynı şekilde çıkış yapar

Alışkanlıktan her şeyi try-except içine sarmak yerine çağrı noktasına uyan başarısızlık stilini seçin. TryInvert, TryGetInverse, TryInverseTransformPoint, TryTransformBounds ve TryCreateRectMapping, False döndürür ve hedeflerini dokunulmadan bırakır; bu, dejenere bir nesnenin ölümcül değil atlanması gereken hit-testing ve nesne-başına döngülere uyar. Invert, InverseCopy, InverseTransformPoint, MapRectToRect ve TransformBounds bunun yerine EPdfMatrixError fırlatır; bu, tekil bir matrisin çağıranın bir şeyi yanlış hesapladığı anlamına geldiği kurulum koduna uyar. Toplu iş için TransformPoints ve TransformRects, sonuç dizilerini tam olarak bir kez tahsis eder, TransformPointsInPlace ve TransformRectsInPlace depolamanızı yeniden kullanır ve TryTransformBounds, önce dönüştürülmüş noktaları somutlaştırmak yerine sınırlayıcı kutuyu tek bir geçişte biriktirir

Bunların hiçbiri egzotik matematik değildir. Tutarlı biçimde uygulanan, kuralın çağrı noktasında görünür olması için API'si adlandırılmış tek bir kuraldır: Multiply ve düz fiiller append yapar, Pre ailesi prepend yapar, At ailesi işlemi pivot çiftiyle parantezler. Oluşturduğunuz herhangi bir bileşiğin yanına sırayı bir yoruma yazın, çünkü bugün doğru okunan kod, birinin altı ay sonra tersine çevirdiği koddur. Bu dönüşümlerin beslendiği page-object ve rendering API'leriyle birlikte tam TPdfMatrix referansı, Delphi ve C++Builder için PDFium Component'te yaşar