Technical Article

在 Delphi 中构建带有文本转语音功能的可访问 PDF 阅读器

在现代软件开发中,可访问性(Accessibility)已不再是可选功能。根据 ADA(美国残疾人法案)和欧盟 Web 可访问性指令等法规,处理文档的软件必须提供辅助技术。对于 PDF 阅读器而言,这意味着要实现稳健的文本转语音(TTS)和屏幕阅读器集成。

在本文中,我们将探讨如何在 Delphi 中使用 PDFium 从 PDF 提取语义文本流,然后将该文本输入到 Windows 语音 API(SAPI)中,以构建一个完全可访问的 PDF 阅读器。

PDF 文本提取的挑战

从根本上说,PDF 是一个包含绘制指令的画布。它本身并不知道什么是“段落”或“列”;它仅仅是在特定的 X/Y 坐标处放置字形(glyphs)。要按逻辑顺序朗读文档,您的解析器必须重建阅读顺序。

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 提供了一些事件(通过连接点),当到达单词边界时就会触发这些事件。

通过使用 FPDFText_GetCharBox() 将 SAPI 单词边界事件返回的字符索引映射回 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 完全支持集成的文本提取和坐标映射。