From ac75cc0f64b85f49be34a3ab61216794e29bf7cf Mon Sep 17 00:00:00 2001 From: Ger Hobbelt Date: Tue, 14 Apr 2020 12:05:54 +0200 Subject: [PATCH] - fix for using ImageMagick 7.x installs where the compare tool has been migrated into the magick tool itself: https://imagemagick.org/script/porting.php - augment ImageMagick and GhostScript init code to auto-detect the latest viable install of both when no environment values for `gsExec` and/or `compareExec` have been set. + auto-detect both 64-bit and 32-bit installed binaries for these applications. --- .../itext/kernel/utils/CompareTool.cs | 143 ++++++++++++++++-- 1 file changed, 129 insertions(+), 14 deletions(-) diff --git a/itext/itext.kernel/itext/kernel/utils/CompareTool.cs b/itext/itext.kernel/itext/kernel/utils/CompareTool.cs index ee4808b011..c577a16f17 100644 --- a/itext/itext.kernel/itext/kernel/utils/CompareTool.cs +++ b/itext/itext.kernel/itext/kernel/utils/CompareTool.cs @@ -42,10 +42,12 @@ source product. address: sales@itextpdf.com */ using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Xml; using Common.Logging; using iText.IO.Font; @@ -112,6 +114,7 @@ public class CompareTool { private String gsExec; private String compareExec; + private bool compareExecIsLegacyIM; private String cmpPdf; @@ -143,10 +146,122 @@ public class CompareTool { private IMetaInfo metaInfo; + private class VersionInNameComparer : IComparer + { + public int Compare(string a, string b) + { + string na = System.IO.Path.GetFileName(a); + na = Regex.Replace(na, @"[^\d]+", " "); + string[] p = na.Split(' '); + + string nb = System.IO.Path.GetFileName(b); + nb = Regex.Replace(nb, @"[^\d]+", " "); + string[] q = nb.Split(' '); + + for (int i = 0, len = Math.Max(p.Length, q.Length); i < len; i++) + { + int va, vb; + // Edge cases: + // - evaluate "1" to be LESS than "1.0" + // - evaluate any non-number-carrying string as LESS than any number carrying one, even when it's only "0" + if (i < p.Length) + int.TryParse(p[i], out va); + else + va = -1; + if (i < q.Length) + int.TryParse(q[i], out vb); + else + vb = -1; + int diff = vb - va; + if (diff != 0) + { + return diff; + } + } + return String.Compare(a, b); + } + } + /// Creates an instance of the CompareTool. public CompareTool() { + // Set up common code for both 'when not specified via environment variables, do try to find it on our own' code sections below: + // + Func GetDirs = (path, wildcard) => + { + try + { + return Directory.GetDirectories(path, wildcard, SearchOption.TopDirectoryOnly); + } + catch (Exception ex) + { + string[] rv = { }; + return rv; + } + }; + Func, string, string, string> CheckForSuitableExe = (paths, exe1, exe2) => + { + for (int i = 0, len = paths.Count(); i < len; i++) + { + string p = System.IO.Path.Combine(paths[i], exe1); + p = Regex.Replace(p, @"\\", @"/"); + string match = null; + if (File.Exists(p) && new FileInfo(p).CanExecute()) + { + // we've got a potential match, which can only be superceded by the second (optional!) exe being available as well: + match = p; + } + if (exe2 != null) + { + // check whether the (optional!) second exe is available: we're fine with that and consider is equal to exe1 + p = System.IO.Path.Combine(paths[i], exe2); + p = Regex.Replace(p, @"\\", @"/"); + if (File.Exists(p) && new FileInfo(p).CanExecute()) + { + match = p; + } + } + if (match != null) + { + return match; + } + } + return null; + }; + + string dir = SystemUtil.GetEnvironmentVariable("ProgramFiles") ?? @"C:/Program Files"; + dir = dir.Replace(" (x86)", ""); // make sure we always point at both 64 and 32 program directories + // + // --- done --- + gsExec = SystemUtil.GetEnvironmentVariable("gsExec"); + if (gsExec == null) { + // attempt to locate GhostScript on this machine by ourselves: search both 64 and 32bit install directories + List files = new List(GetDirs(System.IO.Path.Combine(dir, "gs"), "gs*.*")); + files.AddRange(GetDirs(System.IO.Path.Combine(dir + " (x86)", "gs"), "gs*.*")); + // Now sort the paths so that the latest GhostScript version install directory ends up on top. + // When it exists, but has no suitable GhostScript binary inside, we consider it a b0rked/uninstalled remainder and try the next entry. + files.Sort(new VersionInNameComparer()); + gsExec = CheckForSuitableExe(files, "bin/gswin32c.exe", "bin/gswin64c.exe"); + } + compareExec = SystemUtil.GetEnvironmentVariable("compareExec"); + if (compareExec == null) + { + // attempt to locate ImageMagick on this machine by ourselves: search both 64 and 32bit install directories + List files = new List(GetDirs(dir, "ImageMagick*")); + files.AddRange(GetDirs(dir + " (x86)", "ImageMagick*.*")); + // Now sort the paths so that the latest ImageMagick version install directory ends up on top. + // When it exists, but has no suitable ImageMagick binary inside, we consider it a b0rked/uninstalled remainder and try the next entry. + files.Sort(new VersionInNameComparer()); + compareExec = CheckForSuitableExe(files, "magick.exe", "compare.exe"); + } + + if (compareExec != null) + { + // Now detect whether this is a ImageMagick 7+ modern or 6- Legacy install. + // See also: https://imagemagick.org/script/porting.php + compareExecIsLegacyIM = !compareExec.ToLower().EndsWith("magick.exe"); // it's either 'magick compare' or 'compare', depending on IM version being used. + } } /// @@ -321,7 +436,7 @@ public virtual iText.Kernel.Utils.CompareTool EnableEncryptionCompare() { /// class description. /// /// - /// + /// /// /// instance to be passed later to the /// @@ -357,7 +472,7 @@ public virtual ReaderProperties GetOutReaderProperties() { /// class description. /// /// - /// + /// /// /// instance to be passed later to the /// @@ -644,7 +759,7 @@ public virtual bool CompareDictionaries(PdfDictionary outDict, PdfDictionary cmp /// from the cmp-file file, which is to be compared to output file dictionary. /// /// - /// + /// /// /// instance containing differences between the two dictionaries, /// or @@ -695,7 +810,7 @@ public virtual CompareTool.CompareResult CompareDictionariesStructure(PdfDiction /// which are to be skipped during comparison. /// /// - /// + /// /// /// instance containing differences between the two dictionaries, /// or @@ -739,7 +854,7 @@ public virtual CompareTool.CompareResult CompareDictionariesStructure(PdfDiction /// from the cmp-file file, which is to be compared to output file stream. /// /// - /// + /// /// /// instance containing differences between the two streams, /// or @@ -1159,8 +1274,8 @@ private String CompareImagesOfPdfs(String outPath, String differenceImagePrefix, String currCompareParams = compareParams.Replace("", imageFiles[i].FullName).Replace("", cmpImageFiles [i].FullName).Replace("", outPath + differenceImagePrefix + JavaUtil.IntegerToString(i + 1 ) + ".png"); - if (!SystemUtil.RunProcessAndWait(compareExec, currCompareParams)) { - differentPagesFail += "\nPlease, examine " + outPath + differenceImagePrefix + JavaUtil.IntegerToString(i + if (!SystemUtil.RunProcessAndWait(compareExec, (compareExecIsLegacyIM ? "" : "compare ") + currCompareParams)) { + differentPagesFail += "\nPlease, examine " + outPath + differenceImagePrefix + JavaUtil.IntegerToString(i + 1) + ".png for more details."; } } @@ -1174,7 +1289,7 @@ private String CompareImagesOfPdfs(String outPath, String differenceImagePrefix, String errorMessage = differentPages.Replace("", UrlUtil.ToNormalizedURI(outPdf).AbsolutePath).Replace ("", ListDiffPagesAsString(diffPages)); if (!compareExecIsOk) { - errorMessage += "\nYou can optionally specify path to ImageMagick compare tool (e.g. -DcompareExec=\"C:/Program Files/ImageMagick-6.5.4-2/compare.exe\") to visualize differences."; + errorMessage += "\nYou can optionally specify path to ImageMagick compare tool (e.g. -DcompareExec=\"C:/Program Files/ImageMagick-6.5.4-2/compare.exe\" or -DcompareExec=\"C:/Program Files/ImageMagick-7.0.10.Q16-HDRI/magick.exe\") to visualize differences."; } return errorMessage; } @@ -1254,7 +1369,7 @@ private void PrepareOutputDirs(String outPath, String differenceImagePrefix) { /// Path to the output folder. private void RunGhostScriptImageGeneration(String outPath) { if (!FileUtil.DirectoryExists(outPath)) { - throw new CompareTool.CompareToolExecutionException(this, cannotOpenOutputDirectory.Replace("", + throw new CompareTool.CompareToolExecutionException(this, cannotOpenOutputDirectory.Replace("", outPdf)); } String currGsParams = gsParams.Replace("", outPath + cmpImage).Replace("", cmpPdf); @@ -1348,7 +1463,7 @@ private String CompareByContent(String outPath, String differenceImagePrefix, ID } } - private String CompareVisuallyAndCombineReports(String compareByFailContentReason, String outPath, String + private String CompareVisuallyAndCombineReports(String compareByFailContentReason, String outPath, String differenceImagePrefix, IDictionary> ignoredAreas, IList equalPages) { System.Console.Out.WriteLine("Fail"); System.Console.Out.Flush(); @@ -1362,7 +1477,7 @@ private String CompareVisuallyAndCombineReports(String compareByFailContentReaso return message; } - private void LoadPagesFromReader(PdfDocument doc, IList pages, IList + private void LoadPagesFromReader(PdfDocument doc, IList pages, IList pagesRef) { int numOfPages = doc.GetNumberOfPages(); for (int i = 0; i < numOfPages; ++i) { @@ -1677,7 +1792,7 @@ private bool CompareObjects(PdfObject outObj, PdfObject cmpObj, CompareTool.Obje } } if (cmpDirectObj.IsDictionary()) { - return CompareDictionariesExtended((PdfDictionary)outDirectObj, (PdfDictionary)cmpDirectObj, currentPath, + return CompareDictionariesExtended((PdfDictionary)outDirectObj, (PdfDictionary)cmpDirectObj, currentPath, compareResult); } else { @@ -1936,7 +2051,7 @@ private byte[] ConvertPdfStringToBytes(PdfString pdfString) { return bytes; } - private bool CompareBooleansExtended(PdfBoolean outBoolean, PdfBoolean cmpBoolean, CompareTool.ObjectPath + private bool CompareBooleansExtended(PdfBoolean outBoolean, PdfBoolean cmpBoolean, CompareTool.ObjectPath currentPath, CompareTool.CompareResult compareResult) { if (cmpBoolean.GetValue() == outBoolean.GetValue()) { return true; @@ -2406,7 +2521,7 @@ public override int GetHashCode() { } public override bool Equals(Object obj) { - return obj.GetType() == GetType() && baseCmpObject.Equals(((CompareTool.ObjectPath)obj).baseCmpObject) && + return obj.GetType() == GetType() && baseCmpObject.Equals(((CompareTool.ObjectPath)obj).baseCmpObject) && baseOutObject.Equals(((CompareTool.ObjectPath)obj).baseOutObject) && Enumerable.SequenceEqual(path, (( CompareTool.ObjectPath)obj).path); }