Teknik Makale

Delphi ile Mevcut Bir PDF'te Metin Arama ve Değiştirme

HotPDF Bileşeni, Delphi ve C++Builder'da mevcut bir PDF içindeki metni arayabilir ve değiştirebilir. SearchLoadedPageText ve SearchLoadedDocumentText bir dizenin her bir geçişini glif düzeyinde hassasiyetle konumlandırır ve ReplaceLoadedPageText ve ReplaceLoadedDocumentText eşleşen baytları yerinde yeniden yazar —her bir yedek karakterin orijinal yazı tipi aracılığıyla yeniden kodlanabilmesi şartıyla; bu makalenin bir dipnotta gizlemek yerine dürüstçe ele aldığı fiziksel bir kısıtlamadır

Bu özelliğin arkasındaki talep her zaman sıradandır. Bir şirket adını değiştirir ve arşivlenmiş üç bin fatura hala eski adı taşır. Geçen yılın son kullanma tarihiyle gönderilen bir sözleşme şablonu. Bir ürün kodunun kullanımdan kaldırılması ve ondan bahseden her veri sayfasının bunun yerine ardıl koda ihtiyaç duyması. Bir kelime işlemcide bunların her biri otuz saniyelik bir iştir. Bir PDF'te ise bu gerçekten zor bir sorundur ve nedenini anlamak, API'yi iyi kullanmak ile gerçekte bir spesifikasyon atfı olan bir hata raporu dosyalamak arasındaki farkı yaratır

Bir PDF'te metin değiştirmek neden bu kadar zordur?

Bir PDF'te metin değiştirmek zordur çünkü bir PDF sayfası düzenlenebilir metin içermez —konumlandırılmış glifler içerir. ISO 32000-1 §9.4'ün metin gösterme modeline göre, bir içerik akışı, metin matrisi tarafından belirlenen koordinatlarda karakter kodu dizilerini boyayan Tj ve TJ gibi operatörleri yönetir. Bu kodlar Unicode değildir; sayfanın yazı tipinin bildirdiği kodlama içindeki indekslerdir ve okunabilir karakterlere geri eşleme bir /ToUnicode CMap'inde, bir kodlama fark dizisinde veya bir CID eşleme zincirinde yaşayabilir. Paragraf nesnesi, metin akışı yoktur ve görsel bir kelimenin tek bir dize olarak saklanacağının garantisi yoktur

Değiştirme, kod çözmenin üzerine ikinci bir zorluk katmanı ekler: orijinal akışın tam olarak hangi baytlarının her glifi ürettiğini bilmelisiniz, böylece yeni baytları tam olarak o aralığa ekleyebilirsiniz ve başka hiçbir şeye dokunmazsınız. Bir metin ayıklayıcı, Unicode'u çıkardıktan sonra bayt konumlarını çöpe atmayı göze alabilir. Bir değiştirici ise bunu yapamaz. Bu nedenle HotPDF çalışmayı iki sürüme ayırdı —v2.251.0 sürümü ofset izleme ve arama katmanını oluşturdu ve v2.252.0 sürümü bunun üzerine yeniden yazma katmanını inşa etti

Metin bulma: bayt ofseti izleme ile glif düzeyinde arama

HotPDF'in SearchLoadedDocumentText işlevi, ham akış baytları yerine her sayfanın kodu çözülmüş Unicode glif dizisiyle eşleştirerek bir kelimenin her geçişini bulur, böylece yazı tipinin onu nasıl kodladığından bağımsız olarak bir isabet elde edilir. Alttaki altyapı v2.251.0'da tanıtıldı: içerik akışı belirteci (tokenizer), ( ) veya < > sınırlayıcıları dahil olmak üzere her dize operandı için bir StartOfs/EndOfs bayt aralığı kaydeder ve kodu çözülmüş her glif, onu üreten tam operanda, TJ dizisi öğesine ve kod birimine işaret eden bir TokenIndex/ItemIndex/ByteOffset üçlüsü taşır. Aynı glif yorumlayıcısı, Delphi'de yüklenen bir PDF'ten metin ayıklama makalesinde açıklanan ayıklama API'sini de yönetir; arama, ayıklamanın attığı köken bilgisini basitçe korur

Her eşleşme, sayfa indeksini, kapsayıcı glif aralığını, isabetin kullanıcı alanı X/Y orijinini ve genişliğini, kaynak belirteç ve öğe indeksini ve eşleşen metnin kendisini taşıyan bir THPDFTextMatch kaydı olarak geri döner. Bu, bir vurgu yerleşimi, bir inceleme kullanıcı arayüzü veya değiştirme adımını yönetmek için yeterlidir. Hiçbir şey bulamayan bir arama, başarısız olmak yerine boş bir dizi döndürür, böylece çağrı modeli basit kalır

var
  Pdf: THotPDF;
  Matches: THPDFTextMatchArray;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoices-2025.pdf') > 0 then
    begin
      if Pdf.SearchLoadedDocumentText('Acme Corp', False, Matches) then
        for I := 0 to Length(Matches) - 1 do
          WriteLn(Format('page %d at (%.1f, %.1f): "%s"',
            [Matches[I].PageIndex, Matches[I].X, Matches[I].Y,
             Matches[I].Text]));
    end;
  finally
    Pdf.Free;
  end;
end;

Bilinçli bir tasarım seçimi bir notu hak ediyor. CaseSensitive parametresi False olduğunda, karşılaştırma büyük-küçük harfi yalnızca ASCII karakterleri için katlar; bu bilinçli bir karardır: tam Unicode büyük-küçük harf katlama (case folding), HotPDF'in desteklediği Delphi 5'ten XE'ye kadar olan araç zincirlerinde farklı davranır ve uygulamanızı hangi derleyicinin derlediğine bağlı olarak farklı eşleşmeler bulan bir arama API'si, belgelenmiş ve öngörülebilir bir sınırı olandan daha kötüdür. Latin iş metinleri —adlar, kodlar, tarihler— için ASCII katlama pratik durumları kapsar

Metin değiştirme: ters kodlama ve cerrahi ekleme

HotPDF v2.252.0'da eklenen ReplaceLoadedDocumentText, kod çözme mekanizmasını tersten çalıştırarak bir kelimenin her geçişini yeniden yazar. HPDFEncodeUnicode işlevi, karakter kodu çözücünün tersidir: her bir yedek karakteri orijinal yazı tipi donanımının beklediği karakter kodu baytlarına geri döndürmek için aynı strateji zincirini tersten yürütür —/ToUnicode bfchar ve bfrange araması, kodlama akışı CID eşleşmesi, Type0 kimlik eşlemeleri ve önceden tanımlanmış WinAnsi ve MacRoman tabloları. Yeniden kodlanan baytlar daha sonra iyi biçimlendirilmiş bir dize sabit değerine veya hex dizesine serileştirilir; bu, ayrıştır → yeniden serileştir çift yönlü döngüsünün kararlı olması için belirtecin kendi kaçış kurallarını yansıtır

Eklemenin kendisi toptan olmaktan ziyade cerrahidir. Dize operandı içinde yalnızca eşleşmenin kapsadığı kod baytı aralığı değiştirilir; aynı operanddaki eşleşmeyen baytlar, belirteçler arasındaki boşluklar ve çevreleyen her operatör harfi harfine, bayt bayt korunur. abcabc içindeki bca kısmının değiştirilmesi, zarar görmüş bir operand yerine a + değiştirme + bc sonucunu verir. Değiştirmeler kelimeden daha kısa veya daha uzun olabilir —sabit değer yeniden serileştirilir ve akışın /Length değeri yenilenir— ve çok akışlı bir sayfanın her bir /Contents akışı tek başına işlenir, böylece sayfa iyi biçimlendirilmiş olarak kalır

var
  Pdf: THotPDF;
  ReplaceCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('contract-draft.pdf') > 0 then
    begin
      if Pdf.ReplaceLoadedDocumentText('2025-12-31', '2026-12-31',
        True, ReplaceCount) then
        WriteLn(Format('%d operand rewrites performed', [ReplaceCount]));
      Pdf.SaveLoadedDocument('contract-final.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

API'nin neyi yapmadığına dikkat edin: sayfayı yeniden dizmez. PDF'te metin kaydırma (reflow) yoktur, bu nedenle görsel olarak orijinalinden daha geniş olan bir değiştirme basitçe daha fazla yatay alan kaplayacak ve sağında boyanmış olan her şeyi sıkıştırabilecektir. Aynı uzunlukta veya yakın uzunluktaki ikameler —tarihler, sürüm dizeleri, parça numaraları, ad düzeltmeleri— en uygun yerlerdir. Toptan kelime değişiklikleri PDF'e değil, kaynak belgeye aittir

Neden metni, yazı tipi alt kümesinin hiçbir zaman içermediği karakterlerle değiştiremezsiniz?

Metni, gömülü yazı tipi alt kümesinin hiçbir zaman içermediği bir karakterle değiştiremezsiniz, çünkü o karakteri seçecek bayt dizisi yazı tipinin eşleme tablolarında basitçe mevcut değildir. Bir PDF üreticisi bir alt küme yazı tipi gömdüğünde, onun /ToUnicode CMap ve kodlama yapıları yalnızca orijinal belgenin gerçekten kullandığı glifleri kapsar. HPDFEncodeUnicode yalnızca mevcut olan bir eşlemeyi tersine çevirebilir: eğer belge o yazı tipinde hiçbir zaman E harfini içermediyse, E harfinin geri döneceği hiçbir karakter kodu yoktur. Bu, belirli bir kütüphanenin sınırlaması değil, dosyanın fiziksel bir özelliğidir —hiçbir araç hiçbir zaman gömülmemiş bir glif eşlemesini sihirli bir şekilde ortaya çıkaramaz

HotPDF başarısızlığı temkinli bir şekilde yönetir. Değiştirmenin tek bir karakteri bile yeniden kodlanamazsa, o kelime geçişinin tamamı atlanır —istisna yok, kısmi bozuk metin yok ve bu geçiş ReplaceCount içinde sayılmaz. Pratik sonuç: ReplaceCount değerini önceki bir aramanın eşleşme sayısıyla karşılaştırın ve eksikliği bir sinyal olarak kabul edin. Yukarıdaki tarih örneğinde, yeniden yazmanın başarılı olması için 6 rakamının o belgenin metninde aynı yazı tipinde bir yerlerde görünmesi gerekir —fatura için olasıdır, genel olarak hiçbir zaman garanti edilmez. İhtiyacınız olan karakterler mevcut olmadığında ve amaç kelimeleri değiştirmek yerine hassas metinleri kaldırmak olduğunda, gerçek içerik kaldırma zaten daha iyi bir araçtır; see redacting and restructuring loaded PDFs in Delphi for that path

var
  Matches: THPDFTextMatchArray;
  Expected, Replaced: Integer;
begin
  Pdf.SearchLoadedDocumentText('Acme Corp', True, Matches);
  Expected := Length(Matches);
  Pdf.ReplaceLoadedDocumentText('Acme Corp', 'Apex Corp', True, Replaced);
  if Replaced < Expected then
    WriteLn(Format('%d occurrence(s) skipped: characters missing ' +
      'from the font subset, or match spans multiple operands',
      [Expected - Replaced]));
end;

Bu mesajdaki ikinci atlama koşulu, belgelenmiş diğer sınırdır: birden fazla dize operandına yayılan bir kelime —örneğin [(He)(llo)] TJ öğelerine bölünmüş Hello— arama tarafından bulunur, çünkü arama kodu çözülmüş glif dizisiyle eşleşir; ancak değiştirme tarafından atlanır, çünkü operand sınırları boyunca yeniden yazmak bitişik bayt aralıklarının birleştirilmesini gerektirir. Önce ara sonra doğrula yöntemi, her iki sınırı da sessiz kalmak yerine görünür kılar

Kaydettiğinizde dosyada ne değişir?

Değiştirilen bir /Contents akışı sıkıştırılmadan kaydedilir. FlateDecode sıkıştırmalı akışlar düzenleme için açılır ve HotPDF yeniden oluşturulan baytları yazarken akışın /Filter girdisini atar ve yeniden sıkıştırmak yerine /Length değerini yeniler. Sonuçta ortaya çıkan PDF tamamen geçerlidir ve ana akım görüntüleyicilerde normal şekilde işlenir; buradaki takas, düzenlenen her akış için daha büyük bir dosyadır. Binlerce belgeyi işleyen bir toplu iş hattı için bu büyümeyi bütçeleyin veya ardışıl olarak ayrı bir sıkıştırma geçişi çalıştırın. Yeniden yazılan nesnelerin kaydetme sırasında belgenin çapraz referans yapısıyla nasıl etkileşime girdiği kendi başına bir konudur ve HotPDF'te nesne akışları ve artımlı güncellemeler makalesinde ele alınmıştır

Dosyayla ilgili diğer her şey dokunulmadan bırakılır. Dokunulmayan akışlar sıkıştırmalarını korur, yazı tipleri ve görüntüler yeniden yazılmaz ve operand düzeyindeki ekleme, düzenlenen akışların bile orijinalinden yalnızca bir eşleşmenin düştüğü yerde farklılık gösterdiği anlamına gelir. Bu muhafazakarlık bilinçlidir: bir kütüphane yüklenen belgenin ne kadar çok yerini yeniden yazarsa, öngörmediği bir üretici tuhaflığını bozma olasılığı o kadar artar

Metin arama ve değiştirme; HotPDF'in yüklenen belge araç setinde ayıklama, karartma ve sayfa dönüştürmeye katılır; hepsi aynı içerik akışı yorumlayıcısı tarafından yönetilir ve harici bağımlılıklar olmadan Delphi 5'ten güncel RAD Studio sürümlerine kadar mevcuttur. Tam API referansı ve deneme sürümü indirmesi HotPDF Bileşeni ürün sayfasındadır