320 lines
13 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|