Technical Article

Tworzenie dostępnych czytników PDF z funkcją Text-to-Speech w Delphi

Dostępność (accessibility) nie jest już opcjonalną funkcją w nowoczesnym tworzeniu oprogramowania. Zgodnie z przepisami, takimi jak ADA (Americans with Disabilities Act) oraz unijną dyrektywą w sprawie dostępności stron internetowych, oprogramowanie obsługujące dokumenty musi zapewniać wsparcie dla technologii asystujących. W przypadku przeglądarek PDF oznacza to wdrożenie solidnej integracji z funkcją Text-to-Speech (TTS) oraz czytnikami ekranu.

W tym artykule przyjrzymy się, jak wyodrębnić semantyczne strumienie tekstu z pliku PDF przy użyciu PDFium w Delphi, a następnie przekazać ten tekst do interfejsu Windows Speech API (SAPI), aby zbudować w pełni dostępny czytnik PDF.

Wyzwanie ekstrakcji tekstu z PDF

Zasadniczo PDF to po prostu obszar roboczy wypełniony instrukcjami rysowania. Format ten z natury nie "wie", czym jest "akapit" czy "kolumna"; po prostu umieszcza glify w określonych współrzędnych X i Y. Aby móc odczytać dokument na głos w logicznej kolejności, parser musi najpierw zrekonstruować odpowiednią kolejność czytania.

W PDFium zadanie to realizuje rodzina interfejsów API FPDFText_*, która analizuje relacje przestrzenne glifów, wyprowadzając z nich spójne strumienie tekstowe.

Krok 1: Ekstrakcja tekstu przy użyciu PDFium

Zanim będziemy mogli wypowiedzieć tekst na głos, musimy go wyodrębnić. Poniższy kod Delphi demonstruje, jak zainicjować stronę tekstową i wyodrębnić jej zawartość do standardowego ciągu znaków.

uses
  System.SysUtils, pdfium_lib;

function ExtractPageText(Doc: FPDF_DOCUMENT; PageIndex: Integer): string;
var
  Page: FPDF_PAGE;
  TextPage: FPDF_TEXTPAGE;
  CharCount: Integer;
  Buffer: array of WideChar;
begin
  Result := '';
  Page := FPDF_LoadPage(Doc, PageIndex);
  if Page = nil then Exit;
  
  try
    // Initialize the text extraction engine for this page
    TextPage := FPDFText_LoadPage(Page);
    if TextPage <> nil then
    begin
      try
        CharCount := FPDFText_CountChars(TextPage);
        if CharCount > 0 then
        begin
          SetLength(Buffer, CharCount + 1);
          // Extract the text into the wide string buffer
          FPDFText_GetText(TextPage, 0, CharCount, @Buffer[0]);
          Result := WideCharToString(@Buffer[0]);
        end;
      finally
        FPDFText_ClosePage(TextPage);
      end;
    end;
  finally
    FPDF_ClosePage(Page);
  end;
end;

Krok 2: Integracja Windows Speech API (SAPI)

Kiedy mamy już tekst semantyczny, możemy przekazać go do interfejsu Windows Speech API. SAPI udostępnia interfejs COM SpVoice, który umożliwia asynchroniczną syntezę mowy, wybór głosu oraz kontrolę tempa czytania.

uses
  System.Win.ComObj, Winapi.ActiveX;

const
  SVSFlagsAsync = 1;

procedure SpeakText(const TextToSpeak: string);
var
  SpVoice: OLEVariant;
begin
  CoInitialize(nil);
  try
    SpVoice := CreateOleObject('SAPI.SpVoice');
    // Speak asynchronously so the UI does not freeze
    SpVoice.Speak(TextToSpeak, SVSFlagsAsync);
  finally
    CoUninitialize;
  end;
end;

Krok 3: Synchronizacja mowy z podświetlaniem

Naprawdę dostępny czytnik nie tylko "na ślepo" odczytuje tekst; on również podświetla słowa na ekranie w miarę ich wypowiadania. SAPI dostarcza zdarzenia (za pośrednictwem punktów połączeń), które są wyzwalane po osiągnięciu granicy słowa.

Mapując indeks znaku zwrócony przez zdarzenie granicy słowa SAPI z powrotem na indeks znaku w PDFium za pomocą funkcji FPDFText_GetCharBox(), możesz pobrać prostokąt ograniczający (bounding rectangle) aktualnie wypowiadanego słowa i narysować podświetlenie na płótnie swojego czytnika.

procedure HighlightWord(TextPage: FPDF_TEXTPAGE; CharIndex: Integer; CharCount: Integer);
var
  i: Integer;
  L, T, R, B: Double;
begin
  // Iterate through the characters of the spoken word
  for i := CharIndex to CharIndex + CharCount - 1 do
  begin
    // Get the physical bounding box on the PDF page
    FPDFText_GetCharBox(TextPage, i, @L, @R, @B, @T);
    // Transform PDF coordinates to screen coordinates and draw highlighting rect...
  end;
end;

Budowanie aplikacji inkluzywnej

Łącząc przestrzenną ekstrakcję tekstu z PDFium oraz silnik mowy SAPI, programiści Delphi mogą tworzyć potężne narzędzia dla użytkowników niedowidzących lub preferujących naukę ze słuchu. Prawidłowe wdrożenie tych funkcji gwarantuje, że Twoja aplikacja jest zgodna z surowymi korporacyjnymi i rządowymi standardami dostępności.

Uwaga: Zintegrowana ekstrakcja tekstu i mapowanie współrzędnych są w pełni obsługiwane przez PDFium Component.