Ghostscript32.cs

Classe C# pour encapsuler l’API Ghostscript 32-bit (via P/Invoke).

using System;
using System.IO;
using System.Runtime.InteropServices;

namespace HeraklesFacturX.Util
{
    public class Ghostscript32
    {
        private static readonly object _lockObject = new object();

        // Déclarations DllImport inchangées
        [DllImport("gsdll32.dll", EntryPoint = "gsapi_new_instance")]
        internal static extern int CreateAPIInstance(out IntPtr pinstance, IntPtr caller_handle);

        [DllImport("gsdll32.dll", EntryPoint = "gsapi_init_with_args")]
        internal static extern int InitAPI(IntPtr instance, int argc, string[] argv);

        [DllImport("gsdll32.dll", EntryPoint = "gsapi_exit")]
        internal static extern int ExitAPI(IntPtr instance);

        [DllImport("gsdll32.dll", EntryPoint = "gsapi_delete_instance")]
        internal static extern void DeleteAPIInstance(IntPtr instance);

        // --- Nouvelle méthode générique avec Verrouillage ---

        /// <summary>
        /// Exécute une commande Ghostscript en utilisant les arguments spécifiés.
        /// Un verrouillage est utilisé pour garantir l'atomicité, car l'API Ghostscript est généralement non thread-safe.
        /// </summary>
        /// <param name="args">Tableau de chaînes de caractères représentant les arguments de la ligne de commande Ghostscript.</param>
        /// <returns>Le code de retour de l'exécution de l'API Ghostscript (0 pour succès).</returns>
        /// <exception cref="ArgumentException">Lance une exception si le tableau d'arguments est null ou vide.</exception>
        /// <exception cref="InvalidOperationException">Lance une exception si l'instance Ghostscript ne peut pas être créée ou si l'exécution échoue.</exception>
        public static int ExecuteGhostscript(string[] args)
        {
            if (args == null || args.Length == 0)
            {
                throw new ArgumentException("Le tableau d'arguments ne peut pas être null ou vide.", nameof(args));
            }

            // 1. Verrouillage de l'accès à l'API Ghostscript
            lock (_lockObject) 
            {
                IntPtr gsInstance = IntPtr.Zero;
                int finalCode = -1; 

                try
                {
                    // 2. Création de l'instance API
                    int code = CreateAPIInstance(out gsInstance, IntPtr.Zero);
                    if (code != 0)
                    {
                        throw new InvalidOperationException($"Échec de création de l'instance Ghostscript (code: {code})");
                    }

                    // 3. Initialisation et exécution de l'API
                    finalCode = InitAPI(gsInstance, args.Length, args);
                    
                    // Remarque : Même si l'exécution réussit, il est important d'exécuter le nettoyage.
                    if (finalCode != 0)
                    {
                        // L'API a retourné un code d'erreur lors de l'exécution
                        throw new InvalidOperationException($"Ghostscript a échoué (code: {finalCode})");
                    }

                    return finalCode; // Retourne 0 en cas de succès
                }
                finally
                {
                    // 4. Nettoyage, même en cas d'erreur
                    if (gsInstance != IntPtr.Zero)
                    {
                        ExitAPI(gsInstance);
                        DeleteAPIInstance(gsInstance);
                    }
                }
                GC.Collect();
                GC.WaitForPendingFinalizers();
            } // Le verrou est libéré ici
        }

        // --- Exemple de fonction pour l'incrustation de polices utilisant la nouvelle méthode ---
        
        /// <summary>
        /// Incorpore toutes les polices d'un fichier PDF source dans un fichier de sortie.
        /// </summary>
        /// <param name="inputFile">Chemin du fichier PDF source.</param>
        /// <param name="outputFile">Chemin du fichier PDF de sortie.</param>
        /// <returns>Vrai si l'exécution de Ghostscript a réussi et que le fichier de sortie existe.</returns>
        /// <exception cref="FileNotFoundException">Lance une exception si le fichier PDF source est introuvable.</exception>
        public static bool EmbedingFonts(string inputFile, string outputFile)
        {
            if (!File.Exists(inputFile))
                throw new FileNotFoundException($"Le fichier PDF source est introuvable : {inputFile}");

            // Arguments spécifiques pour l'incrustation de polices
            string[] gsArgs =
            {
                "-dBATCH", "-dNOPAUSE", "-sDEVICE=pdfwrite",
                "-dEmbedAllFonts=true", "-dSubsetFonts=true",
                "-dCompatibilityLevel=1.4",
                $"-sOutputFile={outputFile}",
                inputFile
            };

            int code = ExecuteGhostscript(gsArgs);

            // Vérifie le code de retour et l'existence du fichier
            return code == 0 && File.Exists(outputFile);
        }
    }
}