737 lines
28 KiB
HTML
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;
|
|
|
|
/// <summary>
|
|
/// DPI پیشفرض برای رندر تصاویر (72 = استاندارد، 150 = کیفیت بالا)
|
|
/// </summary>
|
|
private const int DefaultDpi = 150;
|
|
|
|
// ... (سایر متدها)
|
|
|
|
/// <summary>
|
|
/// Fallback: تبدیل صفحات PDF به تصویر با استفاده از PdfiumViewer ...
|
|
/// </summary>
|
|
private async Task<XFileExtractionResult> 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(() =>
|
|
{
|
|
try
|
|
{
|
|
// بارگذاری PDF با PdfiumViewer
|
|
using var pdfDocument = PdfDocument.Load(memoryStream);
|
|
|
|
// محاسبه تعداد صفحات برای پردازش
|
|
var pageCount = Math.Min(
|
|
pdfDocument.PageCount,
|
|
MaxPagesForImageFallback
|
|
);
|
|
|
|
// حلقه روی صفحات و تبدیل به تصویر
|
|
for (int pageIndex = 0; pageIndex < 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>/// <summary>
|
|
/// Fallback پیشرفته با کنترل دقیق DPI و کیفیت ...
|
|
/// </summary>
|
|
private async Task<XFileExtractionResult> 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(() =>
|
|
{
|
|
using var pdfDocument = PdfDocument.Load(memoryStream);
|
|
var pageCount = Math.Min(pdfDocument.PageCount, MaxPagesForImageFallback);
|
|
|
|
for (int pageIndex = 0; pageIndex < 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<XFileExtractionResult> 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(() =>
|
|
{
|
|
using var pdfDocument = PdfDocument.Load(memoryStream);
|
|
var pageCount = Math.Min(pdfDocument.PageCount, _config.MaxPages);
|
|
|
|
for (int pageIndex = 0; pageIndex < 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" => ImageFormat.Jpeg,
|
|
"bmp" => ImageFormat.Bmp,
|
|
_ => 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<XPdfImageFallbackConfiguration>(
|
|
Configuration.GetSection("AiApiConfiguration:FileExtraction:PdfImageFallback")
|
|
);
|
|
|
|
// ✅ ثبت PDF Extractor با پیکربندی
|
|
services.AddSingleton<IXFileContentExtractor>(sp =>
|
|
{
|
|
var config = sp.GetRequiredService<IOptions<XPdfImageFallbackConfiguration>>().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> |