Files
xSaherelmWorkspace/Documents/Docs/Developer/7_2.html
T
2026-10-03 18:04:36 +03:30

737 lines
28 KiB
HTML

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>بازنویسی PDF Fallback با PdfiumViewer - فن آوران ساحر علم</title>
<style>
:root {
--primary: #1e3a8a;
--secondary: #3b82f6;
--accent: #f59e0b;
--success: #10b981;
--danger: #ef4444;
--warning: #f97316;
--bg-light: #f8fafc;
--bg-code: #1e293b;
--text-dark: #0f172a;
--text-muted: #64748b;
--border: #e2e8f0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Tahoma', 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 100%);
color: var(--text-dark);
line-height: 1.8;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: white;
padding: 40px;
text-align: center;
}
.header h1 { font-size: 2.1em; margin-bottom: 10px; }
.header .subtitle { font-size: 1.1em; opacity: 0.95; }
.meta-bar {
display: flex;
justify-content: space-between;
background: var(--bg-light);
padding: 15px 30px;
border-bottom: 2px solid var(--border);
flex-wrap: wrap;
gap: 15px;
}
.meta-item { display: flex; align-items: center; gap: 8px; font-size: 0.9em; color: var(--text-muted); }
.meta-item strong { color: var(--primary); }
.content { padding: 40px; }
.section {
margin-bottom: 35px;
padding: 25px;
background: var(--bg-light);
border-radius: 12px;
border-right: 5px solid var(--secondary);
}
.section h2 {
color: var(--primary);
font-size: 1.5em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
pre {
background: var(--bg-code);
color: #e2e8f0;
padding: 18px;
border-radius: 8px;
overflow-x: auto;
direction: ltr;
text-align: left;
font-family: 'Consolas', monospace;
font-size: 0.85em;
margin: 15px 0;
border-right: 4px solid var(--accent);
}
code {
background: #fef3c7;
color: #92400e;
padding: 2px 8px;
border-radius: 4px;
font-family: 'Consolas', monospace;
font-size: 0.9em;
direction: ltr;
display: inline-block;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
background: white;
border-radius: 8px;
overflow: hidden;
}
th { background: var(--primary); color: white; padding: 12px; text-align: right; }
td { padding: 12px; border-bottom: 1px solid var(--border); }
.alert { padding: 15px 20px; border-radius: 8px; margin: 15px 0; border-right: 4px solid; }
.alert-info { background: #dbeafe; border-color: var(--secondary); color: #1e40af; }
.alert-success { background: #d1fae5; border-color: var(--success); color: #065f46; }
.alert-warning { background: #fef3c7; border-color: var(--accent); color: #92400e; }
.alert-danger { background: #fee2e2; border-color: var(--danger); color: #991b1b; }
.footer { background: var(--primary); color: white; padding: 25px; text-align: center; }
.file-change { background: #f0f9ff; border-right: 4px solid var(--secondary); padding: 15px; margin: 10px 0; border-radius: 8px; }
.file-change .path { font-family: 'Consolas', monospace; color: var(--primary); font-weight: bold; direction: ltr; display: inline-block; }
.badge-modify { background: var(--warning); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.badge-new { background: var(--success); color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; margin-right: 8px; }
.arch-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin: 20px 0;
}
.arch-card {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
border-top: 4px solid var(--secondary);
}
.arch-card h4 { color: var(--primary); margin-bottom: 12px; }
.arch-card ul { list-style: none; padding-right: 0; }
.arch-card li { padding: 6px 0; padding-right: 20px; position: relative; }
.arch-card li::before { content: '▸'; position: absolute; right: 0; color: var(--accent); font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📄 بازنویسی PDF Fallback با PdfiumViewer</h1>
<div class="subtitle">جایگزینی PdfPigRenderer با PdfiumViewer برای تبدیل PDF به تصویر</div>
</div>
<div class="meta-bar">
<div class="meta-item">👨‍💻 <strong>توسعه‌دهنده:</strong> هادی خزاعی اصل</div>
<div class="meta-item">🏢 <strong>شرکت:</strong> فن آوران ساحر علم</div>
<div class="meta-item">📅 <strong>تاریخ:</strong> یکشنبه ۱۳ مهر ۱۴۰۵</div>
<div class="meta-item">📦 <strong>پروژه:</strong> xAiApi</div>
</div>
<div class="content">
<!-- Section 1: Overview -->
<div class="section">
<h2>🎯 معرفی PdfiumViewer</h2>
<p>
<span style="font-weight: bold;">PdfiumViewer</span> یک کتابخانه قدرتمند و سریع برای کار با فایل‌های PDF در .NET است
که بر پایه موتور <strong>PDFium</strong> گوگل (همان موتوری که Chrome استفاده می‌کند) ساخته شده است.
</p>
<div class="arch-grid">
<div class="arch-card">
<h4>⚡ مزایای PdfiumViewer</h4>
<ul>
<li>سرعت بالا در رندر صفحات</li>
<li>پشتیبانی کامل از PDF های پیچیده</li>
<li>کیفیت رندر عالی</li>
<li>کنترل دقیق DPI و اندازه خروجی</li>
<li>پشتیبانی از فرمت‌های مختلف تصویر</li>
</ul>
</div>
<div class="arch-card">
<h4>⚠️ ملاحظات مهم</h4>
<ul>
<li>نیاز به Native DLL های PDFium</li>
<li>حجم فایل DLL ها حدود 10-20 مگابایت</li>
<li>نیاز به کپی DLL ها در output directory</li>
<li>Thread-safe نیست (نیاز به قفل)</li>
</ul>
</div>
</div>
</div>
<!-- Section 2: Installation -->
<div class="section">
<h2>📦 گام ۱: نصب پکیج‌های NuGet</h2>
<pre># نصب پکیج اصلی PdfiumViewer
Install-Package PdfiumViewer
# نصب Native DLL های PDFium (برای پلتفرم x64)
Install-Package PdfiumViewer.Native
# یا برای هر دو پلتفرم x86 و x64
Install-Package PdfiumViewer.Native.x86
Install-Package PdfiumViewer.Native.x64</pre>
<div class="alert alert-info">
<strong>💡 نکته:</strong> پکیج <code>PdfiumViewer.Native</code> به صورت خودکار DLL های <code>pdfium.dll</code>
را در پوشه <code>x86</code> و <code>x64</code> پروژه کپی می‌کند و در runtime بر اساس معماری سیستم،
DLL مناسب را لود می‌کند.
</div>
</div>
<!-- Section 3: Rewrite Code -->
<div class="section">
<h2>🔧 گام ۲: بازنویسی تابع FallbackToImageExtractionAsync</h2>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Providers/Extractors/XPdfFileContentExtractor.cs</span>
</div>
<pre>using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Threading.Tasks;
using PdfiumViewer;
using xAiApi.Models;
namespace xAiApi.Providers.Extractors
{
public class XPdfFileContentExtractor : IXPdfFileContentExtractor
{
private const int MaxPagesForImageFallback = 10;
private const int MinTextLengthThreshold = 50;
/// &lt;summary&gt;
/// DPI پیش‌فرض برای رندر تصاویر (72 = استاندارد، 150 = کیفیت بالا)
/// &lt;/summary&gt;
private const int DefaultDpi = 150;
// ... (سایر متدها)
/// &lt;summary&gt;
/// Fallback: تبدیل صفحات PDF به تصویر با استفاده از PdfiumViewer ...
/// &lt;/summary&gt;
private async Task&lt;XFileExtractionResult&gt; FallbackToImageExtractionAsync(
Stream pdfStream,
string fileName,
string mimeType,
CancellationToken cancellationToken
)
{
var result = new XFileExtractionResult
{
FileName = fileName,
MimeType = mimeType,
UsedImageFallback = true
};
// کپی Stream به MemoryStream برای PdfiumViewer
// (PdfiumViewer به Stream قابل Seek نیاز دارد)
using var memoryStream = new MemoryStream();
await pdfStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
await Task.Run(() =&gt;
{
try
{
// بارگذاری PDF با PdfiumViewer
using var pdfDocument = PdfDocument.Load(memoryStream);
// محاسبه تعداد صفحات برای پردازش
var pageCount = Math.Min(
pdfDocument.PageCount,
MaxPagesForImageFallback
);
// حلقه روی صفحات و تبدیل به تصویر
for (int pageIndex = 0; pageIndex &lt; pageCount; pageIndex++)
{
// بررسی CancellationToken
cancellationToken.ThrowIfCancellationRequested();
// رندر صفحه به Bitmap
// متد RenderPage پارامترهای زیر را می‌پذیرد:
// - pageIndex: شماره صفحه
// - width: عرض تصویر خروجی (0 = اندازه اصلی)
// - height: ارتفاع تصویر خروجی (0 = اندازه اصلی)
// - dpi: کیفیت رندر
using var bitmap = pdfDocument.RenderPage(
pageIndex: pageIndex,
width: 0, // 0 = استفاده از اندازه اصلی
height: 0, // 0 = استفاده از اندازه اصلی
dpiX: DefaultDpi,
dpiY: DefaultDpi
);
if (bitmap != null)
{
// تبدیل Bitmap به PNG
using var imageStream = new MemoryStream();
bitmap.Save(imageStream, ImageFormat.Png);
var imageBytes = imageStream.ToArray();
// افزودن به لیست تصاویر
result.Images.Add(new XExtractedImage
{
Bytes = imageBytes,
PageNumber = pageIndex + 1,
MimeType = "image/png",
Description = $"Page {pageIndex + 1} of {fileName}"
});
}
}
}
catch (OperationCanceledException)
{
// عملیات لغو شد
throw;
}
catch (Exception ex)
{
// خطا در پردازش PDF
result.ErrorMessage = $"Failed to render PDF pages to images: {ex.Message}";
}
}, cancellationToken);
return result;
}
// ... (سایر متدها)
}
}</pre>
</div>
<!-- Section 4: Alternative with DPI Control -->
<div class="section">
<h2>🎨 گام ۳: نسخه پیشرفته با کنترل دقیق DPI</h2>
<p>اگر نیاز به کنترل دقیق‌تر روی کیفیت و اندازه تصاویر دارید، از این نسخه استفاده کنید:</p>
<div class="file-change">
<span class="badge-new">ADVANCED</span>
<span class="path">نسخه پیشرفته با کنترل DPI</span>
</div>
<pre>/// &lt;summary&gt;
/// Fallback پیشرفته با کنترل دقیق DPI و کیفیت ...
/// &lt;/summary&gt;
private async Task&lt;XFileExtractionResult&gt; FallbackToImageExtractionAdvancedAsync(
Stream pdfStream,
string fileName,
string mimeType,
int targetDpi = 150,
ImageFormat outputFormat = null,
CancellationToken cancellationToken = default
)
{
outputFormat ??= ImageFormat.Png;
var result = new XFileExtractionResult
{
FileName = fileName,
MimeType = mimeType,
UsedImageFallback = true
};
using var memoryStream = new MemoryStream();
await pdfStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
await Task.Run(() =&gt;
{
using var pdfDocument = PdfDocument.Load(memoryStream);
var pageCount = Math.Min(pdfDocument.PageCount, MaxPagesForImageFallback);
for (int pageIndex = 0; pageIndex &lt; pageCount; pageIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
// دریافت اندازه اصلی صفحه
var pageSize = pdfDocument.PageSizes[pageIndex];
// محاسبه اندازه تصویر بر اساس DPI
var width = (int)(pageSize.Width * targetDpi / 72.0);
var height = (int)(pageSize.Height * targetDpi / 72.0);
// رندر با اندازه مشخص
using var bitmap = pdfDocument.RenderPage(
pageIndex: pageIndex,
width: width,
height: height,
dpiX: targetDpi,
dpiY: targetDpi
);
if (bitmap != null)
{
using var imageStream = new MemoryStream();
bitmap.Save(imageStream, outputFormat);
var imageBytes = imageStream.ToArray();
result.Images.Add(new XExtractedImage
{
Bytes = imageBytes,
PageNumber = pageIndex + 1,
MimeType = outputFormat == ImageFormat.Jpeg ? "image/jpeg" : "image/png",
Description = $"Page {pageIndex + 1} of {fileName} ({width}x{height}px @ {targetDpi}dpi)"
});
}
}
}, cancellationToken);
return result;
}</pre>
</div>
<!-- Section 5: Configuration -->
<div class="section">
<h2>⚙️ گام ۴: پیکربندی DPI در appsettings.json</h2>
<div class="file-change">
<span class="badge-new">CONFIG</span>
<span class="path">appsettings.json</span>
</div>
<pre>{
"AiApiConfiguration": {
"FileExtraction": {
"PdfImageFallback": {
"Enabled": true,
"MaxPages": 10,
"Dpi": 150,
"OutputFormat": "png",
"MinTextLengthThreshold": 50
}
}
}
}</pre>
<div class="file-change">
<span class="badge-new">CLASS</span>
<span class="path">xAiApi/Configurations/XPdfImageFallbackConfiguration.cs</span>
</div>
<pre>namespace xAiApi.Configurations
{
public class XPdfImageFallbackConfiguration
{
public bool Enabled { get; set; } = true;
public int MaxPages { get; set; } = 10;
public int Dpi { get; set; } = 150;
public string OutputFormat { get; set; } = "png";
public int MinTextLengthThreshold { get; set; } = 50;
}
}</pre>
</div>
<!-- Section 6: Usage with Configuration -->
<div class="section">
<h2>🔗 گام ۵: استفاده از پیکربندی در Extractor</h2>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">XPdfFileContentExtractor.cs</span>
</div>
<pre>public class XPdfFileContentExtractor : IXPdfFileContentExtractor
{
private readonly XPdfImageFallbackConfiguration _config;
public XPdfFileContentExtractor(XPdfImageFallbackConfiguration config)
{
_config = config ?? new XPdfImageFallbackConfiguration();
}
private async Task&lt;XFileExtractionResult&gt; FallbackToImageExtractionAsync(
Stream pdfStream,
string fileName,
string mimeType,
CancellationToken cancellationToken
)
{
var result = new XFileExtractionResult
{
FileName = fileName,
MimeType = mimeType,
UsedImageFallback = true
};
using var memoryStream = new MemoryStream();
await pdfStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
await Task.Run(() =&gt;
{
using var pdfDocument = PdfDocument.Load(memoryStream);
var pageCount = Math.Min(pdfDocument.PageCount, _config.MaxPages);
for (int pageIndex = 0; pageIndex &lt; pageCount; pageIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
using var bitmap = pdfDocument.RenderPage(
pageIndex: pageIndex,
width: 0,
height: 0,
dpiX: _config.Dpi,
dpiY: _config.Dpi
);
if (bitmap != null)
{
using var imageStream = new MemoryStream();
// انتخاب فرمت خروجی بر اساس پیکربندی
var format = _config.OutputFormat?.ToLowerInvariant() switch
{
"jpeg" or "jpg" =&gt; ImageFormat.Jpeg,
"bmp" =&gt; ImageFormat.Bmp,
_ =&gt; ImageFormat.Png
};
bitmap.Save(imageStream, format);
var imageBytes = imageStream.ToArray();
result.Images.Add(new XExtractedImage
{
Bytes = imageBytes,
PageNumber = pageIndex + 1,
MimeType = format == ImageFormat.Jpeg ? "image/jpeg" : "image/png",
Description = $"Page {pageIndex + 1} of {fileName}"
});
}
}
}, cancellationToken);
return result;
}
}</pre>
</div>
<!-- Section 7: DI Registration -->
<div class="section">
<h2>🔌 گام ۶: ثبت در Dependency Injection</h2>
<div class="file-change">
<span class="badge-modify">MODIFY</span>
<span class="path">xAiApi/Startup.cs</span>
</div>
<pre>public void ConfigureServices(IServiceCollection services)
{
// ... (سایر ثبت‌ها)
// ✅ ثبت پیکربندی PDF Image Fallback
services.Configure&lt;XPdfImageFallbackConfiguration&gt;(
Configuration.GetSection("AiApiConfiguration:FileExtraction:PdfImageFallback")
);
// ✅ ثبت PDF Extractor با پیکربندی
services.AddSingleton&lt;IXFileContentExtractor&gt;(sp =&gt;
{
var config = sp.GetRequiredService&lt;IOptions&lt;XPdfImageFallbackConfiguration&gt;&gt;().Value;
return new XPdfFileContentExtractor(config);
});
// ... (بقیه کدها)
}</pre>
</div>
<!-- Section 8: Troubleshooting -->
<div class="section">
<h2>🐛 گام ۷: عیب‌یابی و نکات مهم</h2>
<table>
<tr>
<th>مشکل</th>
<th>علت</th>
<th>راه‌حل</th>
</tr>
<tr>
<td><code>DllNotFoundException</code></td>
<td>pdfium.dll یافت نشد</td>
<td>نصب <code>PdfiumViewer.Native</code> و rebuild پروژه</td>
</tr>
<tr>
<td><code>BadImageFormatException</code></td>
<td>عدم تطابق معماری (x86/x64)</td>
<td>استفاده از پکیج Native مناسب یا تنظیم Platform Target</td>
</tr>
<tr>
<td>خطا در <code>PdfDocument.Load</code></td>
<td>Stream غیرقابل Seek</td>
<td>کپی به <code>MemoryStream</code> قبل از Load</td>
</tr>
<tr>
<td>کیفیت پایین تصاویر</td>
<td>DPI خیلی کم</td>
<td>افزایش DPI به 200 یا 300</td>
</tr>
<tr>
<td><code>OutOfMemoryException</code></td>
<td>تعداد صفحات زیاد یا DPI بالا</td>
<td>کاهش <code>MaxPages</code> یا <code>Dpi</code></td>
</tr>
</table>
<div class="alert alert-warning">
<strong>⚠️ Thread Safety:</strong>
<p>
PdfiumViewer <strong>Thread-safe نیست</strong>. اگر چندین درخواست همزمان PDF را پردازش می‌کنند،
باید از <code>lock</code> یا <code>SemaphoreSlim</code> استفاده کنید:
</p>
<pre>private static readonly SemaphoreSlim _pdfLock = new SemaphoreSlim(2, 2); // حداکثر 2 همزمان
await _pdfLock.WaitAsync(cancellationToken);
try
{
// پردازش PDF
}
finally
{
_pdfLock.Release();
}</pre>
</div>
</div>
<!-- Section 9: Comparison -->
<div class="section">
<h2>📊 گام ۸: مقایسه PdfPigRenderer و PdfiumViewer</h2>
<table>
<tr>
<th>ویژگی</th>
<th>PdfPigRenderer</th>
<th>PdfiumViewer</th>
</tr>
<tr>
<td>سرعت رندر</td>
<td>⭐⭐⭐ متوسط</td>
<td>⭐⭐⭐⭐⭐ بسیار سریع</td>
</tr>
<tr>
<td>کیفیت خروجی</td>
<td>⭐⭐⭐ خوب</td>
<td>⭐⭐⭐⭐⭐ عالی</td>
</tr>
<tr>
<td>حجم DLL</td>
<td>کوچک (فقط .NET)</td>
<td>بزرگ (10-20 MB Native)</td>
</tr>
<tr>
<td>پشتیبانی از PDF های پیچیده</td>
<td>⭐⭐⭐ محدود</td>
<td>⭐⭐⭐⭐⭐ کامل</td>
</tr>
<tr>
<td>کنترل DPI</td>
<td>⭐⭐ محدود</td>
<td>⭐⭐⭐⭐⭐ دقیق</td>
</tr>
<tr>
<td>Thread Safety</td>
<td>✅ Thread-safe</td>
<td>❌ نیاز به قفل</td>
</tr>
<tr>
<td>نیاز به Native DLL</td>
<td>❌ خیر</td>
<td>✅ بله</td>
</tr>
</table>
<div class="alert alert-success">
<strong>✅ نتیجه‌گیری:</strong>
<p>
<strong>PdfiumViewer</strong> انتخاب بهتری برای سناریوهای Production است، به ویژه اگر:
</p>
<ul style="padding-right: 25px; margin-top: 10px;">
<li>کیفیت رندر برایتان مهم است</li>
<li>با PDF های پیچیده (فونت‌های خاص، تصاویر، فرم‌ها) کار می‌کنید</li>
<li>سرعت پردازش اولویت دارد</li>
<li>حجم DLL ها مسئله‌ای نیست</li>
</ul>
</div>
</div>
<!-- Section 10: Summary -->
<div class="section">
<h2>📋 گام ۹: خلاصه تغییرات</h2>
<table>
<tr>
<th>فایل</th>
<th>تغییر</th>
</tr>
<tr>
<td><code>XPdfFileContentExtractor.cs</code></td>
<td>بازنویسی <code>FallbackToImageExtractionAsync</code> با PdfiumViewer</td>
</tr>
<tr>
<td><code>XPdfImageFallbackConfiguration.cs</code></td>
<td>کلاس جدید برای پیکربندی</td>
</tr>
<tr>
<td><code>appsettings.json</code></td>
<td>افزودن بخش <code>PdfImageFallback</code></td>
</tr>
<tr>
<td><code>Startup.cs</code></td>
<td>ثبت پیکربندی و Extractor در DI</td>
</tr>
<tr>
<td>NuGet Packages</td>
<td>نصب <code>PdfiumViewer</code> و <code>PdfiumViewer.Native</code></td>
</tr>
</table>
<div class="alert alert-info">
<strong>💡 نکات کلیدی:</strong>
<ul style="padding-right: 25px; margin-top: 10px;">
<li>✅ PdfiumViewer سریع‌تر و باکیفیت‌تر از PdfPigRenderer است</li>
<li>✅ کنترل دقیق DPI و اندازه خروجی</li>
<li>⚠️ نیاز به Native DLL ها (حدود 10-20 MB)</li>
<li>⚠️ Thread-safe نیست و نیاز به قفل دارد</li>
<li>✅ پیکربندی‌پذیر از طریق appsettings.json</li>
</ul>
</div>
</div>
</div>
<div class="footer">
<p><strong>👨‍💻 توسعه‌دهنده:</strong> هادی خزاعی اصل</p>
<p><strong>🏢 شرکت:</strong> فن آوران ساحر علم</p>
<p><strong>📅 تاریخ:</strong> یکشنبه ۱۳ مهر ۱۴۰۵</p>
<p style="margin-top: 15px; opacity: 0.8; font-size: 0.9em;">
📄 بازنویسی PDF Fallback با PdfiumViewer - تمامی حقوق محفوظ است
</p>
</div>
</div>
</body>
</html>