using System;
using System.IO;
using System.Linq;
using System.Text;
using PdfiumViewer;
using System.Threading;
using xAiModels.Models;
using System.Threading.Tasks;
using xAiApi.Interfaces.Extractors;
namespace xAiApi.Providers.Extractors
{
///
/// Extracts content from PDF files using PdfPig ...
///
public class XPdfFileContentExtractor : IXPdfFileContentExtractor
{
///
/// Supported MIME Types ...
///
private static readonly string[] SupportedMimeTypes =
[
"application/pdf"
];
///
/// Minimum Length for Fallback Using Images ...
///
private const int MinTextLengthThreshold = 50;
///
/// Max Pages for Falling Back Using Images ...
///
private const int MaxPagesForImageFallback = 10;
///
/// Default DPI for Rendering Images ...
///
private const int RenderDpi = 150;
///
/// Max Rendered Image Width ...
///
private const int MaxImageWidth = 2000;
///
/// Check if this extractor supports the specified MIME type ...
///
public bool CanExtract(string mimeType)
{
//
return SupportedMimeTypes.Contains(
mimeType?.ToLowerInvariant() ?? string.Empty
);
}
///
/// Extract text content from file stream ...
///
public async Task ExtractAsync(
Stream fileStream,
string mimeType,
CancellationToken cancellationToken = default
)
{
//
var result = await ExtractRichAsync(
fileStream,
"document.pdf",
mimeType,
cancellationToken
);
//
return result.Text;
}
///
/// Extract content from stream as Rich Result ...
///
///
///
///
///
///
public async Task ExtractRichAsync(
Stream fileStream,
string fileName,
string mimeType,
CancellationToken cancellationToken = default
)
{
//
var result = new XFileExtractionResult
{
FileName = fileName,
MimeType = mimeType
};
//
using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
//
var extractedText = await ExtractTextAsync(
memoryStream,
cancellationToken
);
if (IsTextSufficient(extractedText))
{
// PDF متنی است
result.Text = extractedText;
}
else
{
// PDF اسکنشده است - Fallback به تصویر
memoryStream.Position = 0;
result = await FallbackToImageExtractionAsync(
memoryStream,
fileName,
mimeType,
cancellationToken
);
result.UsedImageFallback = true;
}
//
return result;
}
///
/// Extract Text From PDF Files using Common way ...
///
///
///
///
private async Task ExtractTextAsync(
Stream pdfStream,
CancellationToken cancellationToken
)
{
//
var result = await Task.Run(() =>
{
//
var sb = new StringBuilder();
using var document = UglyToad.PdfPig.PdfDocument.Open(pdfStream);
foreach (var page in document.GetPages())
{
//
var text = page.Text?.Trim();
if (!string.IsNullOrEmpty(text))
{
//
sb.AppendLine(text);
sb.AppendLine();
}
}
//
return sb.ToString();
}, cancellationToken);
//
return result;
}
///
/// Check Extracted Content is Sufficient or not ...
/// if not try to Falling Back using Images ...
///
private bool IsTextSufficient(string text)
{
//
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
//
// حذف فاصلهها و بررسی طول
var cleanText = new string(text.Where(c => !char.IsWhiteSpace(c)).ToArray());
//
return cleanText.Length >= MinTextLengthThreshold;
}
///
/// Fallback Converts PDF Pages to Images ...
///
private async Task FallbackToImageExtractionAsync(
Stream pdfStream,
string fileName,
string mimeType,
CancellationToken cancellationToken
)
{
//
var result = new XFileExtractionResult
{
FileName = fileName,
MimeType = mimeType,
UsedImageFallback = true
};
//
await Task.Run(() =>
{
//
try
{
//
using var document = PdfDocument.Load(pdfStream);
//
var pageCount = Math.Min(
document.PageCount,
MaxPagesForImageFallback
);
//
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++)
{
//
if (cancellationToken.IsCancellationRequested)
{
break;
}
//
try
{
//
var pageSize = document.PageSizes[pageIndex];
//
var scaleFactor = RenderDpi / 72.0;
var renderWidth = (int)Math.Ceiling(pageSize.Width * scaleFactor);
var renderHeight = (int)Math.Ceiling(pageSize.Height * scaleFactor);
//
if (renderWidth > MaxImageWidth)
{
//
var ratio = (double)MaxImageWidth / renderWidth;
renderWidth = MaxImageWidth;
renderHeight = (int)(renderHeight * ratio);
}
//
using var bitmap = document.Render(
dpiX: RenderDpi,
dpiY: RenderDpi,
page: pageIndex,
width: renderWidth,
forPrinting: false,
height: renderHeight
);
//
byte[] imageBytes;
using (var memoryStream = new MemoryStream())
{
//
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
imageBytes = memoryStream.ToArray();
}
//
result.Images.Add(new XExtractedImage
{
Bytes = imageBytes,
MimeType = "image/png",
PageNumber = pageIndex + 1,
Description = $"Page {pageIndex + 1} of {fileName} ({renderWidth}x{renderHeight})"
});
}
catch (Exception)
{ }
}
}
catch (Exception ex)
{
result.ErrorMessage = $"PDF image extraction failed: {ex.Message}";
}
}, cancellationToken);
//
return result;
}
}
}