Technical Article

בניית מציגי PDF נגישים עם טקסט לדיבור (TTS) ב-Delphi

נגישות אינה עוד תכונה אופציונלית בפיתוח תוכנה מודרני. תחת תקנות כמו ה-ADA (חוק האמריקאים עם מוגבלויות) והנחיית הנגישות לאינטרנט של האיחוד האירופי (EU Web Accessibility Directive), תוכנות המטפלות במסמכים חייבות לספק טכנולוגיות מסייעות. עבור מציגי PDF, המשמעות היא יישום שילוב רובסטי של טקסט לדיבור (TTS) וקוראי מסך.

במאמר זה, נבחן כיצד לחלץ זרמי טקסט סמנטיים מ-PDF באמצעות PDFium ב-Delphi, ולאחר מכן להזין את הטקסט הזה ל-Windows Speech API (SAPI) כדי לבנות מציג PDF נגיש לחלוטין.

האתגר בחילוץ טקסט מ-PDF

במהותו, PDF הוא קנבס של הוראות ציור. הוא אינו יודע באופן מובנה מהי "פסקה" או "עמודה"; הוא רק ממקם גליפים (glyphs) בקואורדינטות X/Y ספציפיות. כדי להקריא מסמך בקול רם בסדר הגיוני, המנתח (parser) שלכם חייב לשחזר את סדר הקריאה.

PDFium מטפל בכך באמצעות משפחת ממשקי ה-API שנקראת FPDFText_*, המנתחת את היחסים המרחביים של הגליפים כדי להפיק זרמי טקסט קוהרנטיים.

שלב 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 Speech API (SAPI)

לאחר שיש בידינו את הטקסט הסמנטי, נוכל להעביר אותו ל-Windows Speech API. SAPI מספק את ממשק ה-COM שנקרא SpVoice, המאפשר סינתזת דיבור אסינכרונית, בחירת קול ושליטה בקצב הדיבור.

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: סנכרון הדיבור עם הדגשה (Highlighting)

מציג נגיש באמת אינו קורא את הטקסט בצורה עיוורת בלבד; הוא מדגיש את המילים על המסך תוך כדי השמעתן. SAPI מספק אירועים (דרך connection points) המופעלים כאשר מגיעים לגבול של מילה.

על ידי מיפוי אינדקס התווים שמוחזר מאירוע גבול המילה של SAPI בחזרה לאינדקס התווים ב-PDFium באמצעות FPDFText_GetCharBox(), תוכלו לאחזר את מלבן התוחם של המילה המדוברת כעת ולצייר שכבת הדגשה על גבי קנבס המציג שלכם.

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;

בניית יישום מכליל (Inclusive Application)

באמצעות שילוב חילוץ הטקסט המרחבי של PDFium עם מנוע הדיבור של SAPI, מפתחי Delphi יכולים ליצור כלים עוצמתיים עבור משתמשים לקויי ראייה או אלה המעדיפים למידה שמיעתית. יישום נכון של תכונות אלו מבטיח שהיישום שלכם יעמוד בתקני נגישות ממשלתיים וארגוניים מחמירים.

הערה: חילוץ טקסט משולב ומיפוי קואורדינטות נתמכים באופן מלא על ידי PDFium Component.