vendor: import KillerPDF 1.7.5 source (GPL-3.0) as the base for MMD PDF
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using UglyToad.PdfPig;
|
||||
|
||||
namespace KillerPDF.Services
|
||||
{
|
||||
internal sealed class SearchResult
|
||||
{
|
||||
public Dictionary<int, List<(double Left, double Bottom, double Right, double Top)>> PageRects { get; } = [];
|
||||
public List<int> ResultPages { get; } = [];
|
||||
public int TotalHits { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class SearchService
|
||||
{
|
||||
/// <summary>
|
||||
/// Scans every page of <paramref name="filePath"/> for <paramref name="query"/> (case-insensitive).
|
||||
/// Returns an empty result when query is blank or the file cannot be opened.
|
||||
/// </summary>
|
||||
public SearchResult Search(string filePath, string query)
|
||||
{
|
||||
var result = new SearchResult();
|
||||
if (string.IsNullOrWhiteSpace(query) || string.IsNullOrWhiteSpace(filePath))
|
||||
return result;
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = PdfDocument.Open(filePath);
|
||||
for (int pi = 0; pi < doc.NumberOfPages; pi++)
|
||||
{
|
||||
var page = doc.GetPage(pi + 1);
|
||||
var hits = FindMatchesOnPage(page, query);
|
||||
if (hits.Count > 0)
|
||||
{
|
||||
result.PageRects[pi] = hits;
|
||||
result.ResultPages.Add(pi);
|
||||
result.TotalHits += hits.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* return whatever was collected so far */ }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static List<(double Left, double Bottom, double Right, double Top)> FindMatchesOnPage(
|
||||
UglyToad.PdfPig.Content.Page page, string query)
|
||||
{
|
||||
var result = new List<(double, double, double, double)>();
|
||||
var words = page.GetWords().ToList();
|
||||
|
||||
// A multi-word phrase can span adjacent words; a single token never does, so for single-token
|
||||
// queries we only do the per-word substring match (the cross-word union box below would
|
||||
// otherwise highlight whole runs of words leading up to the matching one).
|
||||
bool isPhrase = query.Trim().IndexOf(' ') >= 0;
|
||||
|
||||
for (int i = 0; i < words.Count; i++)
|
||||
{
|
||||
if (words[i].Text.IndexOf(query, System.StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
var bb = words[i].BoundingBox;
|
||||
result.Add((bb.Left, bb.Bottom, bb.Right, bb.Top));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isPhrase) continue;
|
||||
|
||||
// Multi-word match
|
||||
string combined = words[i].Text;
|
||||
for (int j = i + 1; j < words.Count && combined.Length < query.Length + 20; j++)
|
||||
{
|
||||
combined += " " + words[j].Text;
|
||||
if (combined.IndexOf(query, System.StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
double minX = double.MaxValue, minY = double.MaxValue;
|
||||
double maxX = double.MinValue, maxY = double.MinValue;
|
||||
for (int k = i; k <= j; k++)
|
||||
{
|
||||
var wbb = words[k].BoundingBox;
|
||||
minX = Math.Min(minX, wbb.Left);
|
||||
minY = Math.Min(minY, wbb.Bottom);
|
||||
maxX = Math.Max(maxX, wbb.Right);
|
||||
maxY = Math.Max(maxY, wbb.Top);
|
||||
}
|
||||
result.Add((minX, minY, maxX, maxY));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user