using System.Diagnostics;
namespace Rmsys.Infrastructure;
///
/// Word/OFD → PDF 转换(数据包内 PAD 只能显示 PDF,不能显示 docx)。
///
/// 依赖外部转换器:服务器上装 LibreOffice(soffice)即可;可用环境变量
/// RMSYS_SOFFICE 指定 soffice 可执行文件全路径。
/// 转换结果按「材料Id_版本号」缓存在 App_Data/_pdfcache,同一份材料只转一次。
///
public static class PdfConverter
{
private static readonly object _lock = new();
private static bool _checked;
private static string? _soffice;
/// 转换器是否可用(不可用时不阻断出包,只是包里没有 PDF 正文)
public static bool Available
{
get { EnsureProbe(); return _soffice != null; }
}
public static string? ExePath { get { EnsureProbe(); return _soffice; } }
private static void EnsureProbe()
{
if (_checked) return;
lock (_lock)
{
if (_checked) return;
_checked = true;
var custom = Environment.GetEnvironmentVariable("RMSYS_SOFFICE");
if (!string.IsNullOrWhiteSpace(custom) && File.Exists(custom)) { _soffice = custom; return; }
var candidates = new[]
{
"soffice", "soffice.exe", "soffice.com",
@"C:\Program Files\LibreOffice\program\soffice.exe",
@"C:\Program Files (x86)\LibreOffice\program\soffice.exe",
"/usr/bin/soffice", "/usr/lib/libreoffice/program/soffice"
};
foreach (var c in candidates)
{
try
{
if (c.Contains('/') || c.Contains('\\'))
{
if (File.Exists(c)) { _soffice = c; return; }
}
else
{
var psi = new ProcessStartInfo(c) { Arguments = "--version", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true };
using var p = Process.Start(psi);
if (p != null && p.WaitForExit(8000) && p.ExitCode == 0) { _soffice = c; return; }
try { p?.Kill(true); } catch { }
}
}
catch { /* 试下一个 */ }
}
}
}
///
/// 把源文件转成 PDF,返回 PDF 全路径;转不了返回 null。
/// PDF 直接返回源文件;docx/doc 走 LibreOffice;其余类型(.lrmx/.ofd 等)返回 null。
///
public static string? ToPdf(string srcFull, string cacheDir, string cacheKey)
{
try
{
if (!File.Exists(srcFull)) return null;
var ext = Path.GetExtension(srcFull).ToLowerInvariant();
// 已经是 PDF:直接用源文件
if (ext == ".pdf") return srcFull;
// 只对 Word 家族做转换
if (ext != ".docx" && ext != ".doc") return null;
if (!Available || ExePath == null) return null;
Directory.CreateDirectory(cacheDir);
var cache = Path.Combine(cacheDir, cacheKey + ".pdf");
if (File.Exists(cache) && File.GetLastWriteTimeUtc(cache) >= File.GetLastWriteTimeUtc(srcFull))
return cache;
var outDir = Path.Combine(Path.GetTempPath(), "rmsyspdf_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(outDir);
var profile = Path.Combine(Path.GetTempPath(), "rmsyslo_" + Guid.NewGuid().ToString("N"));
try
{
// 每次用独立 UserInstallation,避免并发调用时 LibreOffice 单实例锁冲突
var args = $"--headless --norestore --nolockcheck --nodefault --nofirststartwizard " +
$"-env:UserInstallation=file:///{profile.Replace('\\', '/')} " +
$"--convert-to pdf:writer_pdf_Export --outdir \"{outDir}\" \"{srcFull}\"";
var psi = new ProcessStartInfo(ExePath!)
{
Arguments = args,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var proc = Process.Start(psi);
if (proc == null) return null;
if (!proc.WaitForExit(180_000))
{
try { proc.Kill(true); } catch { }
return null;
}
var produced = Directory.GetFiles(outDir, "*.pdf").FirstOrDefault();
if (produced == null || new FileInfo(produced).Length < 1024) return null;
File.Copy(produced, cache, overwrite: true);
return cache;
}
finally
{
try { if (Directory.Exists(outDir)) Directory.Delete(outDir, true); } catch { }
try { if (Directory.Exists(profile)) Directory.Delete(profile, true); } catch { }
}
}
catch
{
return null; // 转换失败不阻断出包
}
}
}