Convert-it/Areas/DocumentConverters/Controllers/PdfBarcodeLineController.cs

320 lines
13 KiB
C#

using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using iText.Kernel.Exceptions;
namespace Convert_It_Online.Areas.DocumentConverters.Controllers
{
[Area("DocumentConverters")]
public class PdfBarcodeLineController : Controller
{
private readonly IStringLocalizer<SharedResource> _localizer;
private readonly ILogger<PdfBarcodeLineController> _logger;
private const long MaxPreviewSize = 5 * 1024 * 1024; // 5MB
private static readonly Regex CandidatePattern = new(@"[0-9\s\.-]{44,70}", RegexOptions.Compiled);
public PdfBarcodeLineController(IStringLocalizer<SharedResource> localizer, ILogger<PdfBarcodeLineController> logger)
{
_localizer = localizer;
_logger = logger;
}
private void SetCommonViewBagProperties()
{
ViewBag.HomeLink = _localizer["HomeLink"];
ViewBag.TextMenuTitle = _localizer["TextMenuTitle"];
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
ViewBag.FooterText = _localizer["FooterText"];
ViewBag.About = _localizer["About"];
ViewBag.Contact = _localizer["Contact"];
ViewBag.Terms = _localizer["Terms"];
}
private void PrepareIndexView()
{
SetCommonViewBagProperties();
ViewBag.PageTitle = _localizer["PdfBarcodeConverterPageTitle"];
ViewBag.PageDescription = _localizer["PdfBarcodeConverterPageDescription"];
ViewBag.PdfBarcodeDetectTabTitle = _localizer["PdfBarcodeDetectTabTitle"];
ViewBag.PdfBarcodeDownloadTabTitle = _localizer["PdfBarcodeDownloadTabTitle"];
ViewBag.PdfBarcodeFileInputLabel = _localizer["PdfBarcodeFileInputLabel"];
ViewBag.PdfPasswordLabel = _localizer["PdfPasswordLabel"];
ViewBag.PdfPasswordPlaceholder = _localizer["PdfPasswordPlaceholder"];
ViewBag.PdfBarcodePasswordHint = _localizer["PdfBarcodePasswordHint"];
ViewBag.DetectBarcodeButton = _localizer["DetectBarcodeButton"];
ViewBag.DownloadBarcodeButton = _localizer["DownloadBarcodeButton"];
ViewBag.PdfBarcodePreviewTitle = _localizer["PdfBarcodePreviewTitle"];
ViewBag.SelectFileError = _localizer["SelectFileError"];
ViewBag.CopyButtonLabel = _localizer["CopyButtonLabel"];
ViewBag.CopyButtonSuccess = _localizer["CopyButtonSuccess"];
ViewBag.FaqWhatTitle = _localizer["PdfBarcodeFaqWhatTitle"];
ViewBag.FaqWhatContent = _localizer["PdfBarcodeFaqWhatContent"];
ViewBag.FaqHowTitle = _localizer["PdfBarcodeFaqHowTitle"];
ViewBag.FaqHowContent = _localizer["PdfBarcodeFaqHowContent"];
ViewBag.FaqWhyTitle = _localizer["PdfBarcodeFaqWhyTitle"];
ViewBag.FaqWhyContent = _localizer["PdfBarcodeFaqWhyContent"];
ViewBag.FaqSecurityTitle = _localizer["PdfBarcodeFaqSecurityTitle"];
ViewBag.FaqSecurityContent = _localizer["PdfBarcodeFaqSecurityContent"];
ViewBag.FaqLimitsTitle = _localizer["PdfBarcodeFaqLimitsTitle"];
ViewBag.FaqLimitsContent = _localizer["PdfBarcodeFaqLimitsContent"];
ViewBag.MetaDescription = ViewBag.PageDescription;
}
public IActionResult Index()
{
PrepareIndexView();
return View();
}
[HttpPost]
public async Task<IActionResult> DetectLine(IFormFile pdfFile, string? password, bool preview = false)
{
return await HandleDetection(pdfFile, password, preview, forDownload: false);
}
[HttpPost]
public async Task<IActionResult> DownloadLine(IFormFile pdfFile, string? password, bool preview = false)
{
return await HandleDetection(pdfFile, password, preview, forDownload: true);
}
private async Task<IActionResult> HandleDetection(IFormFile? pdfFile, string? password, bool preview, bool forDownload)
{
if (pdfFile == null || pdfFile.Length == 0)
{
_logger.LogWarning("[PDF-BARCODE] Attempt without file");
if (preview)
{
return Json(new { success = false, message = _localizer["SelectFileError"].Value });
}
ModelState.AddModelError("pdfFile", _localizer["SelectFileError"]);
PrepareIndexView();
return View("Index");
}
if (!IsValidPdf(pdfFile))
{
_logger.LogWarning("[PDF-BARCODE] Invalid file type: {ContentType}", pdfFile.ContentType);
if (preview)
{
return Json(new { success = false, message = _localizer["InvalidPdfFileError"].Value });
}
ModelState.AddModelError("pdfFile", _localizer["InvalidPdfFileError"]);
PrepareIndexView();
return View("Index");
}
if (preview && pdfFile.Length > MaxPreviewSize)
{
return Json(new { success = false, message = _localizer["PdfPreviewTooLarge"].Value });
}
var extraction = await TryExtractTextAsync(pdfFile, password);
if (!extraction.Success)
{
var message = _localizer[extraction.ErrorKey].Value;
if (preview)
{
return Json(new { success = false, message });
}
ModelState.AddModelError("pdfFile", _localizer[extraction.ErrorKey]);
ViewBag.ConversionError = message;
PrepareIndexView();
return View("Index");
}
var lineResult = FindBarcodeLine(extraction.Content ?? string.Empty);
if (lineResult == null)
{
var message = _localizer["PdfNoBarcodeFound"].Value;
if (preview)
{
return Json(new { success = false, message });
}
ModelState.AddModelError("pdfFile", _localizer["PdfNoBarcodeFound"]);
ViewBag.ConversionError = message;
PrepareIndexView();
return View("Index");
}
if (preview)
{
return Json(new
{
success = true,
content = lineResult,
format = "digit-line"
});
}
var fileBaseName = Path.GetFileNameWithoutExtension(pdfFile.FileName);
var downloadFileName = string.IsNullOrWhiteSpace(fileBaseName) ? "linha-digitavel.txt" : fileBaseName + "-linha-digitavel.txt";
var payload = Encoding.UTF8.GetBytes(lineResult + Environment.NewLine);
return File(payload, "text/plain", downloadFileName);
}
private async Task<(bool Success, string? Content, string ErrorKey)> TryExtractTextAsync(IFormFile pdfFile, string? password)
{
try
{
await using var memoryStream = new MemoryStream();
await pdfFile.CopyToAsync(memoryStream);
var pdfBytes = memoryStream.ToArray();
// Try multiple password encodings
var passwords = new List<byte[]?> { null }; // Start with no password
if (!string.IsNullOrEmpty(password))
{
passwords.Add(System.Text.Encoding.UTF8.GetBytes(password));
passwords.Add(System.Text.Encoding.ASCII.GetBytes(password));
passwords.Add(System.Text.Encoding.Latin1.GetBytes(password));
}
foreach (var pwd in passwords)
{
try
{
ReaderProperties readerProperties = new ReaderProperties();
if (pwd != null)
{
readerProperties.SetPassword(pwd);
}
using var pdfReader = new PdfReader(new MemoryStream(pdfBytes), readerProperties);
using var pdfDocument = new PdfDocument(pdfReader);
var builder = new StringBuilder();
int numberOfPages = pdfDocument.GetNumberOfPages();
for (int i = 1; i <= numberOfPages; i++)
{
var page = pdfDocument.GetPage(i);
// Try multiple extraction strategies for barcode detection
var strategies = new ITextExtractionStrategy[]
{
new LocationTextExtractionStrategy(),
new SimpleTextExtractionStrategy()
};
foreach (var strategy in strategies)
{
try
{
var pageText = PdfTextExtractor.GetTextFromPage(page, strategy);
if (!string.IsNullOrWhiteSpace(pageText))
{
builder.AppendLine(pageText);
break;
}
}
catch
{
continue;
}
}
}
var result = builder.ToString();
if (!string.IsNullOrWhiteSpace(result))
{
return (true, result, string.Empty);
}
}
catch (BadPasswordException) when (pwd != null)
{
continue; // Try next password encoding
}
catch (BadPasswordException) when (pwd == null && !string.IsNullOrEmpty(password))
{
break;
}
}
if (!string.IsNullOrEmpty(password))
{
return (false, null, "PdfInvalidPassword");
}
return (false, null, "InvalidPdfFileError");
}
catch (BadPasswordException)
{
return (false, null, string.IsNullOrEmpty(password) ? "PdfPasswordRequired" : "PdfInvalidPassword");
}
catch (Exception ex)
{
_logger.LogError(ex, "[PDF-BARCODE] Failed to extract text from {FileName}", pdfFile.FileName);
return (false, null, "InvalidPdfFileError");
}
}
private static bool IsValidPdf(IFormFile file)
{
if (file == null)
{
return false;
}
var contentType = file.ContentType?.ToLowerInvariant();
if (contentType == "application/pdf" || contentType == "application/x-pdf")
{
return true;
}
return Path.GetExtension(file.FileName).Equals(".pdf", StringComparison.OrdinalIgnoreCase);
}
private static string? FindBarcodeLine(string rawText)
{
if (string.IsNullOrWhiteSpace(rawText))
{
return null;
}
foreach (Match match in CandidatePattern.Matches(rawText))
{
var digits = Regex.Replace(match.Value, "[^0-9]", string.Empty);
if (digits.Length == 47 || digits.Length == 48 || digits.Length == 44)
{
return digits;
}
}
// fallback: check full text digits
var fullDigits = Regex.Replace(rawText, "[^0-9]", string.Empty);
if (fullDigits.Length >= 44)
{
var candidates = new[] { 48, 47, 44 };
foreach (var length in candidates)
{
if (fullDigits.Length >= length)
{
return fullDigits.Substring(0, length);
}
}
}
return null;
}
}
}