Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix for using ImageMagick 7.x installs + autodetect ImageMagick+GhostScript #13

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 129 additions & 14 deletions itext/itext.kernel/itext/kernel/utils/CompareTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ source product.
address: [email protected]
*/
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;
Expand Down Expand Up @@ -112,6 +114,7 @@ public class CompareTool {
private String gsExec;

private String compareExec;
private bool compareExecIsLegacyIM;

private String cmpPdf;

Expand Down Expand Up @@ -143,10 +146,122 @@ public class CompareTool {

private IMetaInfo metaInfo;

private class VersionInNameComparer : IComparer<string>
{
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);
}
}

/// <summary>Creates an instance of the CompareTool.</summary>
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<string, string, string[]> GetDirs = (path, wildcard) =>
{
try
{
return Directory.GetDirectories(path, wildcard, SearchOption.TopDirectoryOnly);
}
catch (Exception ex)
{
string[] rv = { };
return rv;
}
};
Func<List<string>, 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<string> files = new List<string>(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<string> files = new List<string>(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.
}
}

/// <summary>
Expand Down Expand Up @@ -321,7 +436,7 @@ public virtual iText.Kernel.Utils.CompareTool EnableEncryptionCompare() {
/// class description.
/// </remarks>
/// <returns>
///
///
/// <see cref="iText.Kernel.Pdf.ReaderProperties"/>
/// instance to be passed later to the
/// <see cref="iText.Kernel.Pdf.PdfReader"/>
Expand Down Expand Up @@ -357,7 +472,7 @@ public virtual ReaderProperties GetOutReaderProperties() {
/// class description.
/// </remarks>
/// <returns>
///
///
/// <see cref="iText.Kernel.Pdf.ReaderProperties"/>
/// instance to be passed later to the
/// <see cref="iText.Kernel.Pdf.PdfReader"/>
Expand Down Expand Up @@ -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.
/// </param>
/// <returns>
///
///
/// <see cref="CompareResult"/>
/// instance containing differences between the two dictionaries,
/// or
Expand Down Expand Up @@ -695,7 +810,7 @@ public virtual CompareTool.CompareResult CompareDictionariesStructure(PdfDiction
/// which are to be skipped during comparison.
/// </param>
/// <returns>
///
///
/// <see cref="CompareResult"/>
/// instance containing differences between the two dictionaries,
/// or
Expand Down Expand Up @@ -739,7 +854,7 @@ public virtual CompareTool.CompareResult CompareDictionariesStructure(PdfDiction
/// from the cmp-file file, which is to be compared to output file stream.
/// </param>
/// <returns>
///
///
/// <see cref="CompareResult"/>
/// instance containing differences between the two streams,
/// or
Expand Down Expand Up @@ -1159,8 +1274,8 @@ private String CompareImagesOfPdfs(String outPath, String differenceImagePrefix,
String currCompareParams = compareParams.Replace("<image1>", imageFiles[i].FullName).Replace("<image2>", cmpImageFiles
[i].FullName).Replace("<difference>", 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.";
}
}
Expand All @@ -1174,7 +1289,7 @@ private String CompareImagesOfPdfs(String outPath, String differenceImagePrefix,
String errorMessage = differentPages.Replace("<filename>", UrlUtil.ToNormalizedURI(outPdf).AbsolutePath).Replace
("<pagenumber>", 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;
}
Expand Down Expand Up @@ -1254,7 +1369,7 @@ private void PrepareOutputDirs(String outPath, String differenceImagePrefix) {
/// <param name="outPath">Path to the output folder.</param>
private void RunGhostScriptImageGeneration(String outPath) {
if (!FileUtil.DirectoryExists(outPath)) {
throw new CompareTool.CompareToolExecutionException(this, cannotOpenOutputDirectory.Replace("<filename>",
throw new CompareTool.CompareToolExecutionException(this, cannotOpenOutputDirectory.Replace("<filename>",
outPdf));
}
String currGsParams = gsParams.Replace("<outputfile>", outPath + cmpImage).Replace("<inputfile>", cmpPdf);
Expand Down Expand Up @@ -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<int, IList<Rectangle>> ignoredAreas, IList<int> equalPages) {
System.Console.Out.WriteLine("Fail");
System.Console.Out.Flush();
Expand All @@ -1362,7 +1477,7 @@ private String CompareVisuallyAndCombineReports(String compareByFailContentReaso
return message;
}

private void LoadPagesFromReader(PdfDocument doc, IList<PdfDictionary> pages, IList<PdfIndirectReference>
private void LoadPagesFromReader(PdfDocument doc, IList<PdfDictionary> pages, IList<PdfIndirectReference>
pagesRef) {
int numOfPages = doc.GetNumberOfPages();
for (int i = 0; i < numOfPages; ++i) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down