Technical Article

在 Delphi 中建置具備文字轉語音功能的無障礙 PDF 檢視器

無障礙功能 (Accessibility) 在現代軟體開發中已不再是可有可無的功能。在 ADA (美國身心障礙者法案) 和歐盟 Web 無障礙指令等法規下,處理文件的軟體必須提供輔助技術。對於 PDF 檢視器來說,這意味著要實作穩健的文字轉語音 (Text-to-Speech, TTS) 與螢幕閱讀器整合。

在本文中,我們將探討如何使用 Delphi 中的 PDFium 從 PDF 擷取語意文字串流,然後將該文字輸入至 Windows 語音 API (SAPI),以建置完全無障礙的 PDF 檢視器。

PDF 文字擷取的挑戰

PDF 基本上是一塊繪圖指令畫布。它本身並不知道「段落」或「欄位」是什麼;它只是將字形 (glyphs) 放置在特定的 X/Y 座標上。為了以合乎邏輯的順序大聲朗讀文件,您的解析器必須重建閱讀順序。

PDFium 透過 FPDFText_* API 系列處理這個問題,它會分析字形的空間關係,輸出連貫的文字串流。

第 1 步:使用 PDFium 擷取文字

在我們能朗讀文字之前,我們必須先將它擷取出來。以下 Delphi 程式碼示範如何初始化文字頁面,並將其內容擷取至標準字串中。

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;

第 2 步:整合 Windows 語音 API (SAPI)

一旦我們有了語意文字,我們就可以將它傳遞給 Windows 語音 API。SAPI 提供了 SpVoice COM 介面,允許進行非同步語音合成、語音選擇與語速控制。

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;

第 3 步:將語音與反白顯示同步

一個真正無障礙的檢視器不僅會盲目地朗讀文字;它還會在螢幕上朗讀時將單字反白。SAPI 提供事件 (透過連線點 connection points),這些事件會在到達單字邊界時觸發。

藉由將 SAPI 單字邊界事件傳回的字元索引對映回使用 FPDFText_GetCharBox() 的 PDFium 字元索引,您可以擷取目前朗讀單字的邊界矩形,並在檢視器畫布上繪製反白覆蓋層。

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;

建置具包容性的應用程式

透過結合 PDFium 的空間文字擷取與 SAPI 的語音引擎,Delphi 開發人員可以為視障使用者或偏好聽覺學習的使用者建立強大的工具。正確地實作這些功能可確保您的應用程式符合嚴格的企業與政府無障礙標準。

備註:PDFium Component VCL 元件 完全支援整合文字擷取與座標對映。