From de76c1776c7f8db3926bffb2fe650c24c6c98706 Mon Sep 17 00:00:00 2001 From: Jules Date: Sun, 21 Jun 2026 22:16:24 +0200 Subject: [PATCH] Ajoute le plugin Acrobat Pro d'imposition (acrobat-plugin/) Suite d'imposition professionnelle en Folder-Level Scripts JS (7 modules + verification de licence Vision), avec un plugin natif hybride optionnel (WinINet + Registre) pour deplacer la verification de licence en code compile. Installeur install.bat sur le meme modele que celui du projet desktop (git portable, detection automatique des dossiers Acrobat). --- acrobat-plugin/install.bat | 258 ++++++++++++++ acrobat-plugin/native/LicenceVerifier.cpp | 225 ++++++++++++ acrobat-plugin/native/LicenceVerifier.h | 54 +++ acrobat-plugin/native/PluginMain.cpp | 109 ++++++ acrobat-plugin/native/TestConsole.cpp | 82 +++++ acrobat-plugin/src/Imposition-00-Core.js | 231 +++++++++++++ acrobat-plugin/src/Imposition-00b-Licence.js | 342 +++++++++++++++++++ acrobat-plugin/src/Imposition-01-UI.js | 154 +++++++++ acrobat-plugin/src/Imposition-02-Livret.js | 318 +++++++++++++++++ acrobat-plugin/src/Imposition-03-NUp.js | 197 +++++++++++ acrobat-plugin/src/Imposition-04-Decoupe.js | 222 ++++++++++++ acrobat-plugin/src/Imposition-05-Marges.js | 233 +++++++++++++ acrobat-plugin/src/Imposition-06-Reperes.js | 242 +++++++++++++ acrobat-plugin/src/Imposition-07-Auto.js | 189 ++++++++++ 14 files changed, 2856 insertions(+) create mode 100644 acrobat-plugin/install.bat create mode 100755 acrobat-plugin/native/LicenceVerifier.cpp create mode 100755 acrobat-plugin/native/LicenceVerifier.h create mode 100755 acrobat-plugin/native/PluginMain.cpp create mode 100755 acrobat-plugin/native/TestConsole.cpp create mode 100755 acrobat-plugin/src/Imposition-00-Core.js create mode 100755 acrobat-plugin/src/Imposition-00b-Licence.js create mode 100755 acrobat-plugin/src/Imposition-01-UI.js create mode 100755 acrobat-plugin/src/Imposition-02-Livret.js create mode 100755 acrobat-plugin/src/Imposition-03-NUp.js create mode 100755 acrobat-plugin/src/Imposition-04-Decoupe.js create mode 100755 acrobat-plugin/src/Imposition-05-Marges.js create mode 100755 acrobat-plugin/src/Imposition-06-Reperes.js create mode 100755 acrobat-plugin/src/Imposition-07-Auto.js diff --git a/acrobat-plugin/install.bat b/acrobat-plugin/install.bat new file mode 100644 index 0000000..42b466c --- /dev/null +++ b/acrobat-plugin/install.bat @@ -0,0 +1,258 @@ +@echo off +chcp 65001 >nul 2>&1 +setlocal EnableDelayedExpansion +title Imposition Professionnelle (Acrobat) — Installation + +set "APP_NAME=Imposition Professionnelle (plugin Acrobat)" +set "INSTALL_DIR=%LOCALAPPDATA%\CopydevImposingAcrobat" +set "REPO_DIR=%INSTALL_DIR%\repo" +set "GITEA_REPO=https://git.copydev.fr/jules/copydev-imposing-tool.git" +set "GIT_URL=https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.2/MinGit-2.47.1.2-64-bit.zip" + +:: ============================================================ +:MENU +cls +echo ============================================================ +echo %APP_NAME% +echo ============================================================ +echo. +echo 1. Installer +echo 2. Mettre a jour +echo 3. Lancer Acrobat +echo 4. Desinstaller +echo 5. Quitter +echo. +set "CHOIX=" +set /p "CHOIX=Votre choix [1-5] : " + +if "%CHOIX%"=="1" goto INSTALL +if "%CHOIX%"=="2" goto UPDATE +if "%CHOIX%"=="3" goto LAUNCH +if "%CHOIX%"=="4" goto UNINSTALL +if "%CHOIX%"=="5" goto FIN +echo Choix invalide. +timeout /t 2 >nul +goto MENU + +:: ============================================================ +:: GIT (portable si absent du systeme) +:: ============================================================ +:TROUVER_GIT +set "GIT_CMD=" +where git >nul 2>&1 +if %ERRORLEVEL%==0 ( + set "GIT_CMD=git" + exit /b 0 +) +if exist "%INSTALL_DIR%\git\cmd\git.exe" ( + set "GIT_CMD=%INSTALL_DIR%\git\cmd\git.exe" + exit /b 0 +) +echo Telechargement de Git portable... +if not exist "%INSTALL_DIR%\temp" mkdir "%INSTALL_DIR%\temp" +if not exist "%INSTALL_DIR%\git" mkdir "%INSTALL_DIR%\git" +powershell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%GIT_URL%' -OutFile '%INSTALL_DIR%\temp\git.zip' -UseBasicParsing" +if not exist "%INSTALL_DIR%\temp\git.zip" ( + echo [ERREUR] Echec telechargement Git + exit /b 1 +) +powershell -Command "Expand-Archive -Force '%INSTALL_DIR%\temp\git.zip' '%INSTALL_DIR%\git'" +del "%INSTALL_DIR%\temp\git.zip" 2>nul +if not exist "%INSTALL_DIR%\git\cmd\git.exe" ( + echo [ERREUR] git.exe introuvable apres extraction + exit /b 1 +) +set "GIT_CMD=%INSTALL_DIR%\git\cmd\git.exe" +exit /b 0 + +:: ============================================================ +:: Detection des dossiers JavaScripts d'Acrobat (un par version +:: installee, ex: DC, 2020, 2024...). Cree le dossier si absent +:: (ne necessite pas de droits admin : c'est sous %APPDATA%). +:: ============================================================ +:TROUVER_DOSSIERS_ACROBAT +set "NB_DOSSIERS=0" +if not exist "%APPDATA%\Adobe\Acrobat" ( + exit /b 1 +) +for /d %%D in ("%APPDATA%\Adobe\Acrobat\*") do ( + if not exist "%%D\JavaScripts" mkdir "%%D\JavaScripts" + set /a NB_DOSSIERS+=1 + set "DOSSIER_JS_!NB_DOSSIERS!=%%D\JavaScripts" +) +if "%NB_DOSSIERS%"=="0" exit /b 1 +exit /b 0 + +:: ============================================================ +:: INSTALLATION / MISE A JOUR (meme logique : on clone si absent, +:: on pull sinon, puis on recopie les fichiers a jour) +:: ============================================================ +:INSTALL +cls +echo ============================================================ +echo INSTALLATION de %APP_NAME% +echo ============================================================ +goto DEPLOIEMENT + +:UPDATE +cls +echo ============================================================ +echo MISE A JOUR de %APP_NAME% +echo ============================================================ +goto DEPLOIEMENT + +:DEPLOIEMENT +echo. +if not exist "%INSTALL_DIR%" mkdir "%INSTALL_DIR%" + +echo [1/4] Git... +call :TROUVER_GIT +if %ERRORLEVEL% neq 0 ( + pause + goto MENU +) +echo [OK] + +echo. +echo [2/4] Telechargement du code (acrobat-plugin/)... +if exist "%REPO_DIR%\.git" ( + pushd "%REPO_DIR%" + "%GIT_CMD%" fetch origin + "%GIT_CMD%" pull origin master + popd +) else ( + if exist "%REPO_DIR%" rmdir /s /q "%REPO_DIR%" + "%GIT_CMD%" clone "%GITEA_REPO%" "%REPO_DIR%" +) +if not exist "%REPO_DIR%\acrobat-plugin\src\Imposition-00-Core.js" ( + echo [ERREUR] Code source introuvable apres clonage/mise a jour + pause + goto MENU +) +echo [OK] + +echo. +echo [3/4] Detection d'Acrobat et copie des scripts... +call :TROUVER_DOSSIERS_ACROBAT +if %ERRORLEVEL% neq 0 ( + echo [ERREUR] Aucune installation d'Acrobat trouvee sous %APPDATA%\Adobe\Acrobat + echo Verifiez qu'Acrobat Pro (pas seulement Reader) est installe. + pause + goto MENU +) +for /l %%i in (1,1,%NB_DOSSIERS%) do ( + echo -^> !DOSSIER_JS_%%i! + copy /y "%REPO_DIR%\acrobat-plugin\src\*.js" "!DOSSIER_JS_%%i!\" >nul +) +echo [OK] Scripts copies dans %NB_DOSSIERS% dossier^(s^) Acrobat + +echo. +echo [4/4] Plugin natif (verification de licence)... +if exist "%REPO_DIR%\acrobat-plugin\native\dist\ImposingLicence.api" ( + set "PLUGIN_DEST=%APPDATA%\Adobe\Acrobat" + for /d %%D in ("%APPDATA%\Adobe\Acrobat\*") do ( + if not exist "%%D\Plug_ins3" mkdir "%%D\Plug_ins3" + copy /y "%REPO_DIR%\acrobat-plugin\native\dist\ImposingLicence.api" "%%D\Plug_ins3\" >nul + echo -^> %%D\Plug_ins3\ImposingLicence.api + ) + echo [OK] Plugin natif copie + echo [!] Dossier "Plug_ins3" a verifier : c'est l'emplacement usuel des + echo plugins utilisateur sans droits admin, mais a confirmer selon + echo votre version d'Acrobat. +) else ( + echo [INFO] Aucun .api precompile trouve dans native\dist\ + echo Le plugin natif est optionnel : la suite fonctionne en JS seul, + echo avec une verification de licence plus simple ^(voir documentation^). + echo Pour l'activer, compilez native\PluginMain.cpp ^(SDK Acrobat + VS^) + echo et placez le .api resultant dans native\dist\ImposingLicence.api + echo avant de relancer cet installeur. +) + +echo. +echo ============================================================ +echo TERMINE +echo ============================================================ +echo. +echo Redemarrez Acrobat Pro pour charger les scripts. +echo Menu attendu : Edition ^> Imposition Pro +echo. +set /p "LAUNCH=Lancer Acrobat maintenant ? [O/N] : " +if /i "!LAUNCH!"=="O" call :LANCER_ACROBAT +pause +goto MENU + +:: ============================================================ +:: LANCER ACROBAT +:: ============================================================ +:LAUNCH +cls +echo ============================================================ +echo LANCEMENT D'ACROBAT +echo ============================================================ +echo. +call :LANCER_ACROBAT +timeout /t 2 >nul +goto MENU + +:LANCER_ACROBAT +where acrobat >nul 2>&1 +if %ERRORLEVEL%==0 ( + start "" acrobat + exit /b 0 +) +if exist "%ProgramFiles(x86)%\Adobe\Acrobat DC\Acrobat\Acrobat.exe" ( + start "" "%ProgramFiles(x86)%\Adobe\Acrobat DC\Acrobat\Acrobat.exe" + exit /b 0 +) +if exist "%ProgramFiles%\Adobe\Acrobat DC\Acrobat\Acrobat.exe" ( + start "" "%ProgramFiles%\Adobe\Acrobat DC\Acrobat\Acrobat.exe" + exit /b 0 +) +echo [!] Executable Acrobat introuvable automatiquement. Lancez-le manuellement. +exit /b 1 + +:: ============================================================ +:: DESINSTALLATION +:: ============================================================ +:UNINSTALL +cls +echo ============================================================ +echo DESINSTALLATION de %APP_NAME% +echo ============================================================ +echo. +echo Seront supprimes : +echo - Les scripts CDImp dans chaque dossier Acrobat\^\JavaScripts +echo - Le plugin natif ImposingLicence.api ^(s'il a ete installe^) +echo - %INSTALL_DIR% ^(code source local, cle de licence en registre conservee^) +echo. +set /p "CONFIRM=Confirmer ? [O/N] : " +if /i "%CONFIRM%" neq "O" goto MENU + +echo. +if exist "%APPDATA%\Adobe\Acrobat" ( + for /d %%D in ("%APPDATA%\Adobe\Acrobat\*") do ( + del /q "%%D\JavaScripts\Imposition-*.js" 2>nul + del /q "%%D\Plug_ins3\ImposingLicence.api" 2>nul + ) +) +echo [OK] Scripts et plugin supprimes + +rmdir /s /q "%INSTALL_DIR%" 2>nul +echo [OK] %INSTALL_DIR% supprime + +echo. +echo Pour retirer aussi la cle de licence enregistree : +echo supprimez la cle de registre HKCU\Software\Copydev\ImposingAcrobat +echo. +echo ============================================================ +echo DESINSTALLATION TERMINEE +echo ============================================================ +echo. +pause +goto MENU + +:: ============================================================ +:FIN +echo Au revoir ! +timeout /t 2 >nul +exit /b 0 diff --git a/acrobat-plugin/native/LicenceVerifier.cpp b/acrobat-plugin/native/LicenceVerifier.cpp new file mode 100755 index 0000000..4684c68 --- /dev/null +++ b/acrobat-plugin/native/LicenceVerifier.cpp @@ -0,0 +1,225 @@ +// LicenceVerifier.cpp +// --------------------------------------------------------------------------- +// Voir LicenceVerifier.h pour le contexte general. +// +// Le parsing JSON est volontairement minimal (recherche de sous-chaines) car +// le SDK Acrobat ne fournit pas de bibliotheque JSON et on souhaite eviter +// toute dependance externe pour un plugin aussi cible. Le format attendu de +// license.php est celui deja documente pour le projet "impose" : +// { "active": true/false, "offline_timeout_hours": 24, ... } +// Si le serveur utilise plutot un champ "valid" au lieu de "active", les +// deux sont acceptes. A AJUSTER si le format reel differe (voir TestConsole +// pour verifier la reponse brute sans avoir a passer par Acrobat). +// --------------------------------------------------------------------------- + +#include "LicenceVerifier.h" + +#include +#include +#include +#include +#include + +#pragma comment(lib, "Wininet.lib") +#pragma comment(lib, "Advapi32.lib") +#pragma comment(lib, "Shlwapi.lib") + +namespace CDImpLicence { + + static const wchar_t* CLE_REGISTRE = L"Software\\Copydev\\ImposingAcrobat"; + + // ------------------------------------------------------------------- + // Utilitaires de parsing minimal (pas de dependance JSON externe) + // ------------------------------------------------------------------- + static bool ContientChampBooleenVrai(const std::string& json, const char* champ) { + std::string motif = std::string("\"") + champ + "\""; + size_t pos = json.find(motif); + if (pos == std::string::npos) { + return false; + } + pos = json.find(':', pos); + if (pos == std::string::npos) { + return false; + } + // Ignore les espaces apres ':' + pos++; + while (pos < json.size() && isspace((unsigned char)json[pos])) { + pos++; + } + return json.compare(pos, 4, "true") == 0; + } + + static int LireChampEntier(const std::string& json, const char* champ, int defaut) { + std::string motif = std::string("\"") + champ + "\""; + size_t pos = json.find(motif); + if (pos == std::string::npos) { + return defaut; + } + pos = json.find(':', pos); + if (pos == std::string::npos) { + return defaut; + } + pos++; + while (pos < json.size() && isspace((unsigned char)json[pos])) { + pos++; + } + int valeur = atoi(json.c_str() + pos); + return (valeur > 0) ? valeur : defaut; + } + + // ------------------------------------------------------------------- + // Identifiants machine + // ------------------------------------------------------------------- + std::wstring ObtenirHostname() { + wchar_t nom[256] = { 0 }; + DWORD taille = 256; + if (GetComputerNameW(nom, &taille)) { + return std::wstring(nom); + } + return L"Inconnu"; + } + + std::wstring ObtenirMachineId() { + wchar_t nomVolume[MAX_PATH] = { 0 }; + DWORD numeroSerie = 0; + // Volume serial number du lecteur systeme : stable pour une + // installation Windows donnee, suffisant pour le comptage + // d'appareils cote serveur (pas un UUID materiel, mais standard + // Win32 fiable, sans dependance WMI/COM). + wchar_t racineSysteme[4] = L"C:\\"; + GetWindowsDirectoryW(nomVolume, MAX_PATH); + if (wcslen(nomVolume) >= 2) { + racineSysteme[0] = nomVolume[0]; + } + GetVolumeInformationW(racineSysteme, nullptr, 0, &numeroSerie, nullptr, nullptr, nullptr, 0); + + wchar_t buffer[64]; + swprintf_s(buffer, 64, L"%08X-%s", numeroSerie, ObtenirHostname().c_str()); + return std::wstring(buffer); + } + + // ------------------------------------------------------------------- + // Requete HTTP (WinINet) + // ------------------------------------------------------------------- + static std::wstring EncoderURL(const std::wstring& valeur) { + wchar_t tampon[1024]; + DWORD taille = 1024; + if (UrlEscapeW(valeur.c_str(), tampon, &taille, URL_ESCAPE_PERCENT)) { + return std::wstring(tampon); + } + return valeur; + } + + ResultatVerification VerifierLicence( + const std::wstring& serveurUrl, + const std::wstring& cleLicence, + const std::wstring& machineId, + const std::wstring& hostname, + const std::wstring& produit + ) { + ResultatVerification resultat; + + std::wstring url = serveurUrl + + L"?key=" + EncoderURL(cleLicence) + + L"&machine_id=" + EncoderURL(machineId) + + L"&hostname=" + EncoderURL(hostname) + + L"&product=" + EncoderURL(produit); + + HINTERNET hInternet = InternetOpenW(L"CopydevImposingAcrobat/1.0", + INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); + if (!hInternet) { + resultat.erreur = "InternetOpenW a echoue"; + return resultat; + } + + // Timeout raisonnable : ne pas bloquer le demarrage d'Acrobat + // indefiniment si le serveur ne repond pas. + DWORD timeoutMs = 5000; + InternetSetOptionW(hInternet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeoutMs, sizeof(timeoutMs)); + InternetSetOptionW(hInternet, INTERNET_OPTION_RECEIVE_TIMEOUT, &timeoutMs, sizeof(timeoutMs)); + + HINTERNET hUrl = InternetOpenUrlW(hInternet, url.c_str(), nullptr, 0, + INTERNET_FLAG_RELOAD | INTERNET_FLAG_SECURE, 0); + if (!hUrl) { + resultat.erreur = "InternetOpenUrlW a echoue (reseau indisponible ou serveur inaccessible)"; + InternetCloseHandle(hInternet); + return resultat; + } + + char tampon[2048]; + DWORD octetsLus = 0; + std::string corps; + while (InternetReadFile(hUrl, tampon, sizeof(tampon) - 1, &octetsLus) && octetsLus > 0) { + tampon[octetsLus] = '\0'; + corps += tampon; + } + + InternetCloseHandle(hUrl); + InternetCloseHandle(hInternet); + + if (corps.empty()) { + resultat.erreur = "Reponse vide du serveur de licence"; + return resultat; + } + + resultat.succesReseau = true; + resultat.reponseBrute = corps; + resultat.valide = ContientChampBooleenVrai(corps, "active") || ContientChampBooleenVrai(corps, "valid"); + resultat.offlineTimeoutHeures = LireChampEntier(corps, "offline_timeout_hours", 24); + + return resultat; + } + + // ------------------------------------------------------------------- + // Registre Windows + // ------------------------------------------------------------------- + void EcrireVerdictRegistre(bool valide, int dureeToleranceHeures) { + HKEY hCle; + if (RegCreateKeyExW(HKEY_CURRENT_USER, CLE_REGISTRE, 0, nullptr, + REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &hCle, nullptr) != ERROR_SUCCESS) { + return; + } + + long long expiration = (long long)time(nullptr) + (long long)dureeToleranceHeures * 3600LL; + + wchar_t valeurOK[2]; + swprintf_s(valeurOK, 2, L"%d", valide ? 1 : 0); + wchar_t valeurExpiration[32]; + swprintf_s(valeurExpiration, 32, L"%lld", expiration); + + RegSetValueExW(hCle, L"LicenceOK", 0, REG_SZ, + (const BYTE*)valeurOK, (DWORD)((wcslen(valeurOK) + 1) * sizeof(wchar_t))); + RegSetValueExW(hCle, L"LicenceValideJusqua", 0, REG_SZ, + (const BYTE*)valeurExpiration, (DWORD)((wcslen(valeurExpiration) + 1) * sizeof(wchar_t))); + + RegCloseKey(hCle); + } + + bool LireVerdictRegistre(bool& valideOut, long long& expirationUnixOut) { + HKEY hCle; + if (RegOpenKeyExW(HKEY_CURRENT_USER, CLE_REGISTRE, 0, KEY_READ, &hCle) != ERROR_SUCCESS) { + return false; + } + + wchar_t valeurOK[16] = { 0 }; + DWORD tailleOK = sizeof(valeurOK); + wchar_t valeurExpiration[32] = { 0 }; + DWORD tailleExpiration = sizeof(valeurExpiration); + + bool ok1 = RegQueryValueExW(hCle, L"LicenceOK", nullptr, nullptr, + (LPBYTE)valeurOK, &tailleOK) == ERROR_SUCCESS; + bool ok2 = RegQueryValueExW(hCle, L"LicenceValideJusqua", nullptr, nullptr, + (LPBYTE)valeurExpiration, &tailleExpiration) == ERROR_SUCCESS; + + RegCloseKey(hCle); + + if (!ok1 || !ok2) { + return false; + } + + valideOut = (_wtoi(valeurOK) == 1); + expirationUnixOut = _wtoi64(valeurExpiration); + return true; + } + +} // namespace CDImpLicence diff --git a/acrobat-plugin/native/LicenceVerifier.h b/acrobat-plugin/native/LicenceVerifier.h new file mode 100755 index 0000000..0ef1d66 --- /dev/null +++ b/acrobat-plugin/native/LicenceVerifier.h @@ -0,0 +1,54 @@ +// LicenceVerifier.h +// --------------------------------------------------------------------------- +// Verification de licence Vision (meme serveur/API que le projet "impose") - +// implementation Win32 pure (WinINet + Registre), independante du SDK +// Acrobat. Peut etre compilee et testee comme executable console autonome +// via TestConsole.cpp avant d'etre integree au plugin Acrobat (PluginMain.cpp). +// +// Dependances : Wininet.lib (WinINet), Advapi32.lib (Registre) - standard +// Win32, disponibles avec n'importe quelle installation de Visual Studio. +// --------------------------------------------------------------------------- +#pragma once + +#include + +namespace CDImpLicence { + + struct ResultatVerification { + bool succesReseau = false; // la requete HTTP a reussi (reponse recue) + bool valide = false; // la licence est active selon le serveur + int offlineTimeoutHeures = 24; + std::string erreur; // message d'erreur eventuel + std::string reponseBrute; // corps de la reponse, pour diagnostic + }; + + // Construit l'URL de verification et effectue la requete GET. + // serveurUrl : ex. "https://vision.copydev.fr/api/license.php" + ResultatVerification VerifierLicence( + const std::wstring& serveurUrl, + const std::wstring& cleLicence, + const std::wstring& machineId, + const std::wstring& hostname, + const std::wstring& produit + ); + + // Identifiant machine stable : tente d'utiliser le nom de la machine + // Windows (GetComputerNameW) combine au volume serial number du disque + // systeme (GetVolumeInformationW), deux API Win32 standard et fiables - + // contrairement a l'approche JS qui devait generer un identifiant + // aleatoire faute d'acces materiel direct. + std::wstring ObtenirMachineId(); + std::wstring ObtenirHostname(); + + // Ecrit le verdict de licence dans HKCU\Software\Copydev\ImposingAcrobat + // sous forme de chaines REG_SZ simples (lisibles depuis le JS via + // WScript.Shell.RegRead, qui gere mal certains types REG_DWORD/QWORD) : + // LicenceOK "1" ou "0" + // LicenceValideJusqua timestamp Unix (secondes, en texte) + void EcrireVerdictRegistre(bool valide, int dureeToleranceHeures); + + // Relit le verdict actuellement stocke (utilise par TestConsole.cpp pour + // verifier l'ecriture, et a titre de reference pour l'equivalent JS). + bool LireVerdictRegistre(bool& valideOut, long long& expirationUnixOut); + +} // namespace CDImpLicence diff --git a/acrobat-plugin/native/PluginMain.cpp b/acrobat-plugin/native/PluginMain.cpp new file mode 100755 index 0000000..b59dc8c --- /dev/null +++ b/acrobat-plugin/native/PluginMain.cpp @@ -0,0 +1,109 @@ +// PluginMain.cpp +// --------------------------------------------------------------------------- +// Squelette de plugin Acrobat (.api) dont l'unique role est d'appeler +// CDImpLicence::VerifierLicence() au demarrage d'Acrobat et d'ecrire le +// verdict dans le Registre (voir LicenceVerifier.h/.cpp). Ce fichier ne +// touche a aucune fonctionnalite d'imposition : tout le reste de la suite +// reste dans les Folder-Level Scripts JS deja en place +// (/mnt/homeshare/Documents/Projet/imposing/src/), qui lisent ce verdict. +// +// ============================================================================ +// AVERTISSEMENT MAJEUR - A LIRE AVANT DE COMPILER +// ============================================================================ +// Le code ci-dessous suit le squelette de plugin le plus communement +// documente dans les exemples du Plug-in SDK Acrobat ("HelloWorld" et +// derives), MAIS : +// - cet environnement de developpement n'a ni le SDK Acrobat, ni Windows, +// ni Visual Studio : ce fichier n'a PU ETRE NI COMPILE NI TESTE ; +// - les macros/signatures exactes de handshake (PIHandshake, gExtensionID, +// PIPlugInInit, l'enregistrement de l'export HFT) varient legerement +// entre versions du SDK (et notamment entre les SDK 32 bits historiques +// et les SDK modernes 64 bits) ; +// - il FAUT remplacer ce squelette par le vrai boilerplate fourni dans le +// dossier "Sample Plug-ins" du SDK que vous installez (generalement +// PIMain.cpp dans chaque exemple), et n'y inserer que l'appel a +// CDImpLicence::VerifierLicence() + EcrireVerdictRegistre() dans la +// fonction d'initialisation du plugin. +// La partie fiable et reutilisable telle quelle est LicenceVerifier.cpp/.h +// (Win32 pur, deja validable independamment via TestConsole.cpp). +// ============================================================================ + +#include "PIHeaders.h" // a fournir par le SDK Acrobat (non present ici) +#include +#include "LicenceVerifier.h" + +// Identifiants a definir selon votre enregistrement developpeur Adobe +// (Tools > JavaScript Debugger n'est pas concerne ici : c'est l'ID +// d'extension attribue lors de l'inscription au programme Acrobat +// Solutions/Integration Partner, requis pour distribuer un plugin signe). +extern "C" const char* gExtensionID = "com.copydev.imposingacrobat.licence"; + +static const wchar_t* SERVEUR_LICENCE = L"https://vision.copydev.fr/api/license.php"; +static const wchar_t* PRODUIT = L"imposing-acrobat"; + +// ----------------------------------------------------------------------- +// Verification effectuee une fois, a l'initialisation du plugin (donc une +// fois par session Acrobat). Suffisant pour la semantique de tolerance +// hors-ligne deja en place cote serveur (offline_timeout_hours). +// ----------------------------------------------------------------------- +static void EffectuerVerificationLicence() { + // La cle de licence est lue depuis le Registre si deja saisie via le + // dialogue JS "Gerer la licence..." (Imposition-00b-Licence.js stocke + // global.cdImpLicenceCle via global.setPersistent, qui est in fine + // persiste par Acrobat dans son propre stockage de preferences - PAS le + // meme emplacement que celui utilise ici cote natif). Pour partager la + // cle entre JS et natif de facon fiable, le plus simple est de la + // stocker aussi dans le Registre depuis le JS (a ajouter dans + // Imposition-00b-Licence.js : ecrire la cle saisie sous + // HKCU\Software\Copydev\ImposingAcrobat\CleLicence via + // WScript.Shell.RegWrite, en plus de global.setPersistent). + wchar_t cleLicence[256] = { 0 }; + HKEY hCle; + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Copydev\\ImposingAcrobat", + 0, KEY_READ, &hCle) == ERROR_SUCCESS) { + DWORD taille = sizeof(cleLicence); + RegQueryValueExW(hCle, L"CleLicence", nullptr, nullptr, (LPBYTE)cleLicence, &taille); + RegCloseKey(hCle); + } + + if (wcslen(cleLicence) == 0) { + // Aucune cle connue cote natif : on n'ecrit aucun verdict, le JS + // retombera sur sa propre verification (voir Imposition-00b-Licence.js). + return; + } + + std::wstring machineId = CDImpLicence::ObtenirMachineId(); + std::wstring hostname = CDImpLicence::ObtenirHostname(); + + CDImpLicence::ResultatVerification resultat = CDImpLicence::VerifierLicence( + SERVEUR_LICENCE, cleLicence, machineId, hostname, PRODUIT); + + if (resultat.succesReseau) { + CDImpLicence::EcrireVerdictRegistre(resultat.valide, resultat.offlineTimeoutHeures); + } + // Si la requete reseau echoue, on n'ecrit rien : le verdict precedent + // (s'il existe, avec son timestamp d'expiration) reste en place et sera + // evalue tel quel par le JS (logique de tolerance hors-ligne). +} + +// ----------------------------------------------------------------------- +// Point d'entree d'initialisation du plugin. Le nom exact et la signature +// de cette fonction DOIVENT etre repris du boilerplate PIMain.cpp de votre +// SDK (le nom "PluginInit" ci-dessous est indicatif). +// ----------------------------------------------------------------------- +ACCB1 ASBool ACCB2 PluginInit(void) { + EffectuerVerificationLicence(); + return true; // true = le plugin s'est initialise correctement +} + +// ----------------------------------------------------------------------- +// Handshake / export table : A REMPLACER INTEGRALEMENT par le contenu du +// boilerplate de votre version du SDK (PIMain.cpp, PIRequir.h, etc.). Conserve +// ici uniquement comme rappel de la structure generale attendue. +// ----------------------------------------------------------------------- +/* +ACCB1 ASBool ACCB2 PIHandshake(...) { + // ... initialisation standard du SDK ... + return PluginInit(); +} +*/ diff --git a/acrobat-plugin/native/TestConsole.cpp b/acrobat-plugin/native/TestConsole.cpp new file mode 100755 index 0000000..ea6413a --- /dev/null +++ b/acrobat-plugin/native/TestConsole.cpp @@ -0,0 +1,82 @@ +// TestConsole.cpp +// --------------------------------------------------------------------------- +// Executable console autonome pour valider LicenceVerifier.cpp SANS le SDK +// Acrobat (pas besoin d'Acrobat installe ni du Plug-in SDK pour ce test). +// +// Build rapide (Visual Studio Developer Command Prompt) : +// cl /EHsc /std:c++14 TestConsole.cpp LicenceVerifier.cpp ^ +// wininet.lib advapi32.lib shlwapi.lib +// +// Ou : creer un projet "Application console" Visual Studio classique avec +// ces deux fichiers .cpp + LicenceVerifier.h. +// +// A FAIRE AVANT D'INTEGRER AU PLUGIN ACROBAT : +// 1. Lancer ce programme avec une cle de licence de test valide +// (ex: COPYDEV-IMPOS-TEST-2026, comme pour le projet impose) et +// verifier que la reponse brute du serveur correspond au parsing +// attendu (champ "active" ou "valid", "offline_timeout_hours"). +// 2. Si le parsing echoue, ajuster ContientChampBooleenVrai/LireChampEntier +// dans LicenceVerifier.cpp selon le format reel observe. +// 3. Verifier que le Registre est bien ecrit (HKCU\Software\Copydev\ +// ImposingAcrobat) avec regedit, ou en relancant ce programme avec +// l'option "lire" (voir ci-dessous). +// --------------------------------------------------------------------------- + +#include "LicenceVerifier.h" +#include +#include + +// Conversion simple (les reponses JSON attendues sont en ASCII). +static std::wstring VersWstring(const std::string& s) { + return std::wstring(s.begin(), s.end()); +} + +int wmain(int argc, wchar_t* argv[]) { + if (argc >= 2 && std::wstring(argv[1]) == L"lire") { + bool valide = false; + long long expiration = 0; + if (CDImpLicence::LireVerdictRegistre(valide, expiration)) { + wprintf(L"Verdict en registre : valide=%d, expire le (unix)=%lld\n", valide ? 1 : 0, expiration); + } else { + wprintf(L"Aucun verdict trouve dans le registre.\n"); + } + return 0; + } + + if (argc < 2) { + wprintf(L"Usage: TestConsole.exe [serveur_url]\n"); + wprintf(L" TestConsole.exe lire (relit le verdict deja ecrit en registre)\n"); + return 1; + } + + std::wstring cle = argv[1]; + std::wstring serveur = (argc >= 3) ? argv[2] : L"https://vision.copydev.fr/api/license.php"; + std::wstring produit = L"imposing-acrobat"; + + std::wstring machineId = CDImpLicence::ObtenirMachineId(); + std::wstring hostname = CDImpLicence::ObtenirHostname(); + + wprintf(L"Machine ID : %s\n", machineId.c_str()); + wprintf(L"Hostname : %s\n", hostname.c_str()); + wprintf(L"Serveur : %s\n", serveur.c_str()); + wprintf(L"Produit : %s\n\n", produit.c_str()); + + CDImpLicence::ResultatVerification resultat = CDImpLicence::VerifierLicence( + serveur, cle, machineId, hostname, produit); + + wprintf(L"Succes reseau : %d\n", resultat.succesReseau ? 1 : 0); + wprintf(L"Licence valide : %d\n", resultat.valide ? 1 : 0); + wprintf(L"Tolerance hors-ligne: %d heures\n", resultat.offlineTimeoutHeures); + if (!resultat.erreur.empty()) { + wprintf(L"Erreur : %s\n", VersWstring(resultat.erreur).c_str()); + } + wprintf(L"Reponse brute : %s\n\n", VersWstring(resultat.reponseBrute).c_str()); + + if (resultat.succesReseau) { + CDImpLicence::EcrireVerdictRegistre(resultat.valide, resultat.offlineTimeoutHeures); + wprintf(L"Verdict ecrit dans HKCU\\Software\\Copydev\\ImposingAcrobat.\n"); + wprintf(L"Relancez avec l'argument \"lire\" pour le verifier.\n"); + } + + return 0; +} diff --git a/acrobat-plugin/src/Imposition-00-Core.js b/acrobat-plugin/src/Imposition-00-Core.js new file mode 100755 index 0000000..a9e2dac --- /dev/null +++ b/acrobat-plugin/src/Imposition-00-Core.js @@ -0,0 +1,231 @@ +// Imposition-00-Core.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 0 : Noyau (namespace, utilitaires, menu racine) +// +// Ce fichier doit etre charge en premier (prefixe "00") car il definit le +// namespace global CDImp (Copydev Imposition) utilise par tous les autres +// modules. Acrobat charge les scripts du dossier JavaScripts par ordre +// alphabetique, donc le prefixe numerique garantit l'ordre d'initialisation. +// +// Emplacement attendu sous Windows : +// %APPDATA%\Adobe\Acrobat\\JavaScripts\ (utilisateur courant) +// ou \Acrobat\Javascripts\ (tous les utilisateurs) +// --------------------------------------------------------------------------- + +// Namespace global partage par tous les modules. "var" au niveau racine d'un +// folder-level script devient une variable globale persistante pour la +// session Acrobat (et non une variable "global" partagee entre documents, +// qui necessiterait l'objet `global`). +if (typeof CDImp === "undefined") { + var CDImp = {}; +} + +// ----------------------------------------------------------------------- +// Constantes +// ----------------------------------------------------------------------- +CDImp.PT_PAR_MM = 72 / 25.4; // 1 mm en points PDF (~2.8346456693) + +// ----------------------------------------------------------------------- +// Utilitaires generaux +// ----------------------------------------------------------------------- +CDImp.Util = { + + /** + * Convertit des millimetres en points PDF. + */ + mmToPt: function (mm) { + return mm * CDImp.PT_PAR_MM; + }, + + /** + * Convertit des points PDF en millimetres. + */ + ptToMm: function (pt) { + return pt / CDImp.PT_PAR_MM; + }, + + /** + * Execute une fonction avec les privileges eleves necessaires aux + * operations sur les fichiers / documents (creation, sauvegarde, + * fermeture, ajout de menus...). Restaure toujours les privileges + * standards via app.endPriv(), meme en cas d'erreur. + * + * @param {Function} fn fonction a executer en contexte privilegie + * @returns la valeur retournee par fn, ou undefined en cas d'erreur + */ + avecPrivileges: function (fn) { + var resultat; + try { + app.beginPriv(); + resultat = fn(); + } catch (e) { + CDImp.Util.erreur("Erreur privilegiee : " + e.toString()); + } finally { + app.endPriv(); + } + return resultat; + }, + + /** + * Affiche une boite d'alerte d'erreur standardisee. + */ + erreur: function (message) { + app.alert({ + cMsg: message, + nIcon: 0, // erreur + nType: 0 // OK + }); + }, + + /** + * Affiche une boite d'information standardisee. + */ + info: function (message) { + app.alert({ + cMsg: message, + nIcon: 3, // information + nType: 0 // OK + }); + }, + + /** + * Retourne le document actif passe en parametre, ou a defaut le premier + * document ouvert dans Acrobat. Leve une erreur explicite si aucun + * document n'est ouvert. + */ + getDocActif: function (docPotentiel) { + var doc = docPotentiel; + if (!doc) { + if (app.activeDocs && app.activeDocs.length > 0) { + doc = app.activeDocs[0]; + } + } + if (!doc) { + throw new Error("Aucun document PDF ouvert."); + } + return doc; + }, + + /** + * Demande un nombre a l'utilisateur via app.response, avec valeur par + * defaut et validation. Retourne null si l'utilisateur annule. + */ + demanderNombre: function (question, titre, defaut) { + var saisie = app.response({ cQuestion: question, cTitle: titre || "Imposition Professionnelle", cDefault: String(defaut) }); + if (saisie === null) { + return null; + } + var n = parseFloat(saisie); + return isNaN(n) ? defaut : n; + }, + + /** + * Place le contenu integral de la page source nSrcPage (1-based) du + * document srcDoc a l'interieur du rectangle rectDest (en points PDF, + * [llx, lly, urx, ury]) de la page nDestPage (0-based) du document + * destDoc, avec mise a l'echelle automatique si rectDest a une taille + * differente de la page source. + * + * Technique : on cree un template a partir de la page source, on force + * temporairement la CropBox de la page de destination a rectDest (ce qui + * determine la zone et donc l'echelle d'affichage du contenu "spawn"), + * on spawn le template, puis on restaure la CropBox d'origine de la + * page de destination. C'est la technique standard utilisee en + * JavaScript pur pour Acrobat (sans plugin natif) ; a valider dans une + * vraie session Acrobat Pro (voir notes dans chaque module appelant). + * + * @param {Doc} srcDoc + * @param {Number|null} nSrcPage numero 1-based, ou null/0 pour ne rien placer + * @param {Doc} destDoc + * @param {Number} nDestPage index 0-based + * @param {Array} rectDest [llx, lly, urx, ury] en points + */ + placerPageDansRect: function (srcDoc, nSrcPage, destDoc, nDestPage, rectDest) { + if (!nSrcPage) { + return; + } + CDImp.Util.avecPrivileges(function () { + var nomTpl = "CDImp_tpl_" + nSrcPage + "_" + nDestPage + "_" + (new Date()).getTime(); + var tpl = srcDoc.createTemplate({ cName: nomTpl, nPage: nSrcPage - 1 }); + var cropOrigineDest = destDoc.getPageBox({ nPage: nDestPage, cBox: "Crop" }); + try { + destDoc.setPageBoxes({ nPage: nDestPage, cBox: "Crop", rBox: rectDest }); + tpl.spawn({ cDocName: destDoc, nPage: nDestPage, bRename: false, bOverlay: true }); + } finally { + destDoc.setPageBoxes({ nPage: nDestPage, cBox: "Crop", rBox: cropOrigineDest }); + tpl.remove(); + } + }); + }, + + /** + * Extrait une sous-region rectSource (en points PDF, [llx, lly, urx, ury] + * dans le repere de la page source) de la page nSrcPage (1-based) de + * srcDoc, et la place a l'echelle 1:1 sur la page nDestPage (0-based) de + * destDoc (qui doit deja avoir la taille de rectSource). + * + * Technique symetrique de placerPageDansRect : ici on recadre + * temporairement la CropBox de la page SOURCE (pour n'exposer que la + * region voulue), on cree le template, on restaure la CropBox source, + * puis on spawn sur la page de destination. Utilise pour le decoupage + * de pages (Module 4) et la delivretisation (Module 2). + * + * @param {Doc} srcDoc + * @param {Number} nSrcPage numero 1-based + * @param {Array} rectSource [llx, lly, urx, ury] dans le repere de la page source + * @param {Doc} destDoc + * @param {Number} nDestPage index 0-based + */ + extraireRegionPage: function (srcDoc, nSrcPage, rectSource, destDoc, nDestPage) { + CDImp.Util.avecPrivileges(function () { + var cropOrigineSrc = srcDoc.getPageBox({ nPage: nSrcPage - 1, cBox: "Crop" }); + try { + srcDoc.setPageBoxes({ nPage: nSrcPage - 1, cBox: "Crop", rBox: rectSource }); + var nomTpl = "CDImp_ext_" + nSrcPage + "_" + nDestPage + "_" + (new Date()).getTime(); + var tpl = srcDoc.createTemplate({ cName: nomTpl, nPage: nSrcPage - 1 }); + try { + tpl.spawn({ cDocName: destDoc, nPage: nDestPage, bRename: false, bOverlay: true }); + } finally { + tpl.remove(); + } + } finally { + srcDoc.setPageBoxes({ nPage: nSrcPage - 1, cBox: "Crop", rBox: cropOrigineSrc }); + } + }); + } +}; + +// ----------------------------------------------------------------------- +// Formats de feuille standards (largeur x hauteur, en mm, portrait) +// ----------------------------------------------------------------------- +CDImp.Util.FORMATS_MM = { + A3: [297, 420], + A4: [210, 297], + A5: [148, 210], + SRA3: [320, 450], + SRA4: [225, 320], + LETTER: [215.9, 279.4] +}; + +// ----------------------------------------------------------------------- +// Menu racine "Imposition Pro" +// ----------------------------------------------------------------------- +// Cree un menu de premier niveau avec un seul point d'entree qui ouvre le +// panneau de controle principal (boite de dialogue maitresse, voir Module 1). +// La creation/modification de menus est une operation privilegiee. +// ----------------------------------------------------------------------- +CDImp.Util.avecPrivileges(function () { + try { + app.addMenuItem({ + cName: "CDImpRacine", + cParent: "Edit", // accroche dans le menu Edition (peut etre adapte) + cExec: "CDImp.UI.ouvrirPanneauPrincipal(this);", + cEnable: "event.rc = (CDImp.UI != undefined);", + nPos: -1 + }); + } catch (e) { + // Si le menu existe deja (rechargement de script), on ignore + // silencieusement l'erreur de doublon. + } +}); diff --git a/acrobat-plugin/src/Imposition-00b-Licence.js b/acrobat-plugin/src/Imposition-00b-Licence.js new file mode 100755 index 0000000..15435e5 --- /dev/null +++ b/acrobat-plugin/src/Imposition-00b-Licence.js @@ -0,0 +1,342 @@ +// Imposition-00b-Licence.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 0b : Verification de licence (serveur Vision) +// +// Depend de Imposition-00-Core.js (doit etre charge avant). Doit etre charge +// AVANT Imposition-01-UI.js (le prefixe "00b" garantit cet ordre). +// +// Reutilise le serveur de licence Vision deja en place pour le projet +// "Copydev Imposing Tool" (impose) : meme API license.php, meme logique de +// cache hors-ligne avec duree de tolerance, meme principe de plafond +// d'appareils (max_devices) gere cote serveur. Seul le champ "product" +// change pour distinguer ce plugin Acrobat des autres produits Copydev. +// +// AVERTISSEMENTS TECHNIQUES - a tester dans Acrobat Pro reel : +// - Net.HTTP.request() est l'API documentee pour les requetes HTTP depuis +// un script Acrobat privilegie, mais sa disponibilite depend des +// reglages de securite reseau d'Acrobat (Preferences > Securite +// (avancee) > Gestionnaire de confiance > acces internet pour les +// scripts non-PDF). A activer/whitelister si necessaire. +// - La lecture du flux de reponse (Stream.read()) est implementee selon le +// schema le plus communement documente, mais n'a pas pu etre executee +// dans cet environnement de developpement (pas d'Acrobat installe) : +// ajuster _lireFluxReponse() si le parsing JSON echoue en conditions +// reelles. +// - L'identifiant machine (machine_id) repose sur ActiveXObject (COM), +// disponible uniquement sous Windows pour des scripts de dossier +// (folder-level) de confiance. En cas d'indisponibilite, on retombe sur +// un identifiant genere une fois et persiste localement (moins robuste +// contre la copie de licence, mais fonctionnel). +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-00b-Licence.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.Licence = CDImp.Licence || {}; + +CDImp.Licence.CONFIG = { + serveurUrl: "https://vision.copydev.fr/api/license.php", + produit: "imposing-acrobat", + dureeCacheHeuresParDefaut: 24 // utilisee si le serveur ne renvoie pas offline_timeout_hours +}; + +// ----------------------------------------------------------------------- +// Identifiants machine +// ----------------------------------------------------------------------- +/** + * Nom de la machine, via WSH (Windows uniquement, scripts de dossier + * privilegies). Retombe sur "Inconnu" si indisponible. + */ +CDImp.Licence._obtenirHostname = function () { + try { + var reseau = new ActiveXObject("WScript.Network"); + return String(reseau.ComputerName); + } catch (e) { + return "Inconnu"; + } +}; + +/** + * Identifiant machine stable, persiste dans les preferences globales + * d'Acrobat (genere une seule fois). Pas un identifiant materiel (UUID + * BIOS), mais suffisant pour le comptage d'appareils cote serveur. + */ +CDImp.Licence._obtenirMachineId = function () { + if (typeof global.cdImpMachineId !== "undefined" && global.cdImpMachineId) { + return global.cdImpMachineId; + } + var id = ""; + for (var i = 0; i < 32; i++) { + id += Math.floor(Math.random() * 16).toString(16); + } + CDImp.Util.avecPrivileges(function () { + global.cdImpMachineId = id; + global.setPersistent("cdImpMachineId", true); + }); + return id; +}; + +// ----------------------------------------------------------------------- +// Requete HTTP synchrone vers le serveur de licence +// ----------------------------------------------------------------------- +/** + * Convertit le flux de reponse Net.HTTP en chaine de caracteres. + */ +CDImp.Licence._lireFluxReponse = function (msgStream) { + try { + var nTaille = msgStream.length; + var aOctets = msgStream.read(nTaille); + var texte = ""; + for (var i = 0; i < aOctets.length; i++) { + texte += String.fromCharCode(aOctets[i]); + } + return texte; + } catch (e) { + return ""; + } +}; + +/** + * Effectue une requete GET synchrone et retourne { erreur, texte }. + */ +CDImp.Licence._requeteHTTPSync = function (url) { + var resultat = { erreur: null, texte: null }; + try { + Net.HTTP.request({ + cVerb: "GET", + cURL: url, + oHandler: { + response: function (msgStream, status) { + if (status && status >= 400) { + resultat.erreur = "HTTP " + status; + } else { + resultat.texte = CDImp.Licence._lireFluxReponse(msgStream); + } + }, + error: function (msg, err) { + resultat.erreur = (err || msg || "Erreur reseau inconnue").toString(); + } + }, + bAsync: false + }); + } catch (e) { + resultat.erreur = e.toString(); + } + return resultat; +}; + +// ----------------------------------------------------------------------- +// Saisie / stockage de la cle de licence +// ----------------------------------------------------------------------- +CDImp.Licence._demanderCle = function () { + var cle = app.response({ + cQuestion: "Cle de licence Copydev Imposing (Acrobat) :", + cTitle: "Activation de la licence", + cDefault: "" + }); + return (cle && cle.length > 0) ? cle : null; +}; + +/** + * Recopie la cle dans le Registre Windows (HKCU\Software\Copydev\ + * ImposingAcrobat\CleLicence), seul emplacement que le plugin natif + * (voir /native/PluginMain.cpp) sait lire : global.setPersistent() stocke + * la valeur dans le mecanisme de preferences interne d'Acrobat, pas dans + * le Registre, donc cette ecriture explicite est necessaire pour que le + * plugin natif et le JS partagent la meme cle. Echec silencieux si + * ActiveXObject est indisponible (plugin natif alors simplement inutilisable, + * le JS continue de fonctionner seul). + */ +CDImp.Licence._ecrireCleRegistre = function (cle) { + try { + var shell = new ActiveXObject("WScript.Shell"); + shell.RegWrite("HKCU\\Software\\Copydev\\ImposingAcrobat\\CleLicence", cle, "REG_SZ"); + } catch (e) { + // pas grave : le plugin natif (s'il est installe) ne pourra pas + // verifier la licence de son cote, le JS reste fonctionnel seul. + } +}; + +CDImp.Licence._obtenirCle = function () { + if (typeof global.cdImpLicenceCle !== "undefined" && global.cdImpLicenceCle) { + return global.cdImpLicenceCle; + } + var cle = CDImp.Licence._demanderCle(); + if (!cle) { + return null; + } + CDImp.Util.avecPrivileges(function () { + global.cdImpLicenceCle = cle; + global.setPersistent("cdImpLicenceCle", true); + }); + CDImp.Licence._ecrireCleRegistre(cle); + return cle; +}; + +// ----------------------------------------------------------------------- +// Verdict du plugin natif (voir /native/), lu en priorite s'il est present +// et frais. Permet de s'appuyer sur une verification effectuee en code +// compile (plus dur a lire/patcher qu'un .js) plutot que sur le seul check +// JS ci-dessous. Si le plugin natif n'est pas installe ou n'a pas encore +// ecrit de verdict, on retombe naturellement sur la verification JS pure +// (aucune regression). +// ----------------------------------------------------------------------- +CDImp.Licence._lireVerdictNatif = function () { + try { + var shell = new ActiveXObject("WScript.Shell"); + var ok = shell.RegRead("HKCU\\Software\\Copydev\\ImposingAcrobat\\LicenceOK"); + var expirationStr = shell.RegRead("HKCU\\Software\\Copydev\\ImposingAcrobat\\LicenceValideJusqua"); + var expiration = parseInt(expirationStr, 10); + var maintenant = Math.floor((new Date()).getTime() / 1000); + + if (String(ok) === "1" && expiration > maintenant) { + return { valide: true, source: "plugin_natif" }; + } + return { valide: false, source: "plugin_natif", raison: "Verdict du plugin natif absent ou expire." }; + } catch (e) { + return null; // plugin natif non installe / registre absent : pas d'info + } +}; + +// ----------------------------------------------------------------------- +// Verification (serveur, avec repli sur cache hors-ligne) +// ----------------------------------------------------------------------- +/** + * Verifie la licence auprès du serveur Vision. En cas d'echec reseau, + * retombe sur le dernier resultat valide mis en cache, dans la limite de la + * duree de tolerance hors-ligne (offline_timeout_hours renvoye par le + * serveur, ou valeur par defaut). + * + * @returns {Object} { valide, source: "serveur"|"cache", donnees, raison } + */ +CDImp.Licence.verifier = function () { + var verdictNatif = CDImp.Licence._lireVerdictNatif(); + if (verdictNatif && verdictNatif.valide) { + return verdictNatif; + } + // Verdict natif absent, expire ou plugin non installe : on continue + // avec la verification JS habituelle (HTTP + cache hors-ligne). + + var cle = CDImp.Licence._obtenirCle(); + if (!cle) { + return { valide: false, raison: "Aucune cle de licence saisie." }; + } + + var machineId = CDImp.Licence._obtenirMachineId(); + var hostname = CDImp.Licence._obtenirHostname(); + var url = CDImp.Licence.CONFIG.serveurUrl + + "?key=" + encodeURIComponent(cle) + + "&machine_id=" + encodeURIComponent(machineId) + + "&hostname=" + encodeURIComponent(hostname) + + "&product=" + encodeURIComponent(CDImp.Licence.CONFIG.produit); + + var reponse = CDImp.Licence._requeteHTTPSync(url); + + if (reponse.erreur || !reponse.texte) { + return CDImp.Licence._verifierCache(reponse.erreur); + } + + var data; + try { + data = JSON.parse(reponse.texte); + } catch (e) { + return CDImp.Licence._verifierCache("Reponse serveur illisible."); + } + + var valide = !!(data.active || data.valid); + + CDImp.Util.avecPrivileges(function () { + global.cdImpLicenceCache = { valide: valide, donnees: data, dateVerif: (new Date()).getTime() }; + global.setPersistent("cdImpLicenceCache", true); + }); + + return { valide: valide, donnees: data, source: "serveur" }; +}; + +CDImp.Licence._verifierCache = function (raisonReseau) { + var cache = global.cdImpLicenceCache; + if (!cache) { + return { valide: false, raison: "Serveur de licence inaccessible (" + (raisonReseau || "?") + ") et aucun cache local." }; + } + var heuresEcoulees = ((new Date()).getTime() - cache.dateVerif) / 3600000; + var tolerance = (cache.donnees && cache.donnees.offline_timeout_hours) || CDImp.Licence.CONFIG.dureeCacheHeuresParDefaut; + + if (cache.valide && heuresEcoulees <= tolerance) { + return { valide: true, donnees: cache.donnees, source: "cache", heuresEcoulees: heuresEcoulees }; + } + return { + valide: false, + raison: "Serveur de licence inaccessible (" + (raisonReseau || "?") + ") et cache hors-ligne expire ou invalide\n(hors-ligne depuis ~" + Math.round(heuresEcoulees) + "h, tolerance " + tolerance + "h)." + }; +}; + +/** + * Verifie la licence et affiche une alerte bloquante si elle est invalide. + * A appeler avant toute action des modules d'imposition. + * + * @returns {Boolean} true si l'utilisation peut continuer + */ +CDImp.Licence.verifierEtBloquerSiInvalide = function () { + var resultat; + try { + resultat = CDImp.Licence.verifier(); + } catch (e) { + CDImp.Util.erreur("Erreur lors de la verification de la licence : " + e.toString()); + return false; + } + if (!resultat.valide) { + CDImp.Util.erreur("Licence invalide ou expiree :\n" + (resultat.raison || "") + + "\n\nLa suite d'imposition professionnelle est desactivee.\nUtilisez \"Imposition Pro > Gerer la licence...\" pour saisir une cle valide."); + return false; + } + return true; +}; + +/** + * Boite de dialogue simple permettant de saisir/remplacer la cle de licence + * et de relancer immediatement une verification. + */ +CDImp.Licence.gererCle = function () { + var cleActuelle = global.cdImpLicenceCle || ""; + var nouvelleCle = app.response({ + cQuestion: "Cle de licence Copydev Imposing (Acrobat) :", + cTitle: "Gerer la licence", + cDefault: cleActuelle + }); + if (nouvelleCle === null || nouvelleCle === cleActuelle) { + return; + } + CDImp.Util.avecPrivileges(function () { + global.cdImpLicenceCle = nouvelleCle; + global.setPersistent("cdImpLicenceCle", true); + global.cdImpLicenceCache = null; // force une nouvelle verification serveur + global.setPersistent("cdImpLicenceCache", true); + }); + CDImp.Licence._ecrireCleRegistre(nouvelleCle); + CDImp.Util.info("Cle enregistree. Redemarrez Acrobat pour que le plugin natif\n(s'il est installe) la prenne en compte au prochain demarrage."); + var resultat = CDImp.Licence.verifier(); + if (resultat.valide) { + CDImp.Util.info("Licence valide. Source : " + resultat.source + "."); + } else { + CDImp.Util.erreur("Licence invalide : " + (resultat.raison || "")); + } +}; + +// ----------------------------------------------------------------------- +// Entree de menu "Gerer la licence..." +// ----------------------------------------------------------------------- +CDImp.Util.avecPrivileges(function () { + try { + app.addMenuItem({ + cName: "CDImpGererLicence", + cParent: "Edit", + cExec: "CDImp.Licence.gererCle();", + cEnable: "event.rc = (CDImp.Licence != undefined);", + nPos: -1 + }); + } catch (e) { + // menu deja present (rechargement de script) : ignore + } +}); diff --git a/acrobat-plugin/src/Imposition-01-UI.js b/acrobat-plugin/src/Imposition-01-UI.js new file mode 100755 index 0000000..b43888d --- /dev/null +++ b/acrobat-plugin/src/Imposition-01-UI.js @@ -0,0 +1,154 @@ +// Imposition-01-UI.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 1 : Panneau de controle principal (UI Dialog) +// +// Depend de Imposition-00-Core.js (doit etre charge avant). +// +// IMPORTANT : le format de description utilise par app.execDialog() est tres +// specifique et difficile a valider hors d'une session Acrobat reelle (cet +// environnement de developpement n'a pas Acrobat installe). La structure +// ci-dessous suit la convention documentee dans l'Annexe "Creating Dialogs" +// du Acrobat DC SDK JavaScript Reference. A tester et ajuster dans Acrobat +// Pro (les noms de "type" et d'evenements sont les points les plus fragiles). +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-01-UI.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.UI = CDImp.UI || {}; + +// Codes de retour utilises pour savoir quel bouton a ete actionne dans le +// panneau principal. +CDImp.UI.ACTIONS = { + LIVRET: 1, + NUP: 2, + DECOUPE: 3, + MARGES: 4, + REPERES: 5, + AUTO: 6 +}; + +/** + * Construit l'objet de description du panneau principal pour app.execDialog. + * Les boutons referencent chacun un item_id ; le clic ferme le dialogue en + * renvoyant le code d'action correspondant via dialog.end(). + */ +CDImp.UI._construireDialoguePrincipal = function () { + var A = CDImp.UI.ACTIONS; + + return { + description: { + name: "Imposition Professionnelle", + elements: [ + { + type: "view", + name: "Imposition Professionnelle", + elements: [ + { + type: "panel", + align_children: "align_top", + elements: [ + { type: "static_text", name: "Choisissez un module d'imposition :" }, + { + type: "view", + align_children: "align_row", + elements: [ + { + type: "view", + align_children: "align_column", + elements: [ + { type: "button", item_id: "btnLivret", name: "1. Creer un livret" }, + { type: "button", item_id: "btnNUp", name: "2. N-Up / Step & Repeat" }, + { type: "button", item_id: "btnDecoupe", name: "3. Decoupe & reassemblage" } + ] + }, + { + type: "view", + align_children: "align_column", + elements: [ + { type: "button", item_id: "btnMarges", name: "4. Marges & cadrages" }, + { type: "button", item_id: "btnReperes", name: "5. Reperes, textes, numerotation" }, + { type: "button", item_id: "btnAuto", name: "6. Automatisation" } + ] + } + ] + }, + { type: "cancel", item_id: "btnFermer", name: "Fermer" } + ] + } + ] + } + ] + }, + + // -- Gestionnaires d'evenements, un par item_id de bouton -------- + btnLivret: { mouse_down: function (dialog) { dialog.end(A.LIVRET); } }, + btnNUp: { mouse_down: function (dialog) { dialog.end(A.NUP); } }, + btnDecoupe: { mouse_down: function (dialog) { dialog.end(A.DECOUPE); } }, + btnMarges: { mouse_down: function (dialog) { dialog.end(A.MARGES); } }, + btnReperes: { mouse_down: function (dialog) { dialog.end(A.REPERES); } }, + btnAuto: { mouse_down: function (dialog) { dialog.end(A.AUTO); } } + }; +}; + +/** + * Affiche un message indiquant qu'un module n'est pas encore implemente. + */ +CDImp.UI._moduleAVenir = function (nomModule) { + CDImp.Util.info("Module \"" + nomModule + "\" : en cours de developpement.\nDisponible dans une prochaine version de la suite d'imposition."); +}; + +/** + * Point d'entree principal : ouvre le panneau de controle, puis route vers + * le module choisi par l'utilisateur. Le document actif (celui depuis lequel + * le menu a ete invoque) est transmis aux modules afin qu'ils n'aient pas a + * deviner quel document cibler. + * + * @param {Doc} [docCourant] document actif, transmis via "this" par le menu + */ +CDImp.UI.ouvrirPanneauPrincipal = function (docCourant) { + var doc; + try { + doc = CDImp.Util.getDocActif(docCourant); + } catch (e) { + CDImp.Util.erreur("Veuillez ouvrir un document PDF avant de lancer l'imposition."); + return; + } + + if (CDImp.Licence && !CDImp.Licence.verifierEtBloquerSiInvalide()) { + return; + } + + var oDialogue = CDImp.UI._construireDialoguePrincipal(); + var resultat; + try { + resultat = app.execDialog(oDialogue); + } catch (e) { + CDImp.Util.erreur("Impossible d'ouvrir le panneau d'imposition : " + e.toString()); + return; + } + + var routes = [ + { action: CDImp.UI.ACTIONS.LIVRET, module: "Livret", nom: "Creer un livret" }, + { action: CDImp.UI.ACTIONS.NUP, module: "NUp", nom: "N-Up / Step & Repeat" }, + { action: CDImp.UI.ACTIONS.DECOUPE, module: "Decoupe", nom: "Decoupe & reassemblage" }, + { action: CDImp.UI.ACTIONS.MARGES, module: "Marges", nom: "Marges & cadrages" }, + { action: CDImp.UI.ACTIONS.REPERES, module: "Reperes", nom: "Reperes, textes, numerotation" }, + { action: CDImp.UI.ACTIONS.AUTO, module: "Auto", nom: "Automatisation" } + ]; + + for (var i = 0; i < routes.length; i++) { + if (routes[i].action === resultat) { + var mod = CDImp[routes[i].module]; + if (mod && mod.lancer) { + mod.lancer(doc); + } else { + CDImp.UI._moduleAVenir(routes[i].nom); + } + return; + } + } + // Fermeture / Annulation : rien a faire. +}; diff --git a/acrobat-plugin/src/Imposition-02-Livret.js b/acrobat-plugin/src/Imposition-02-Livret.js new file mode 100755 index 0000000..5cca0c6 --- /dev/null +++ b/acrobat-plugin/src/Imposition-02-Livret.js @@ -0,0 +1,318 @@ +// Imposition-02-Livret.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 2 : Creer un livret / Delivretiser (signature de cahier, piqure a cheval) +// +// Depend de Imposition-00-Core.js (doit etre charge avant). +// +// ATTENTION TECHNIQUE - a lire avant utilisation en production : +// Le placement des pages source sur les planches composites repose sur +// CDImp.Util.placerPageDansRect()/extraireRegionPage() (voir Core.js), qui +// utilisent la technique "template + recadrage de CropBox" standard en +// JavaScript pur pour Acrobat (sans plugin natif). Cette technique n'a pas +// pu etre executee/verifiee dans une vraie session Acrobat depuis cet +// environnement de developpement (Acrobat n'y est pas installe) : a tester +// sur un PDF reel avant tout usage en production. +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-02-Livret.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.Livret = CDImp.Livret || {}; + +// ----------------------------------------------------------------------- +// Calcul de l'ordre des pages (imposition en cahier unique, piqure a cheval) +// ----------------------------------------------------------------------- +/** + * Calcule l'imbrication des pages pour un cahier unique (selle/pliage central). + * Fonction symetrique : utilisee aussi bien pour imposer (Page logique -> + * position sur planche) que pour delivretiser (position sur planche -> page + * logique), puisque l'imposition en cahier simple est sa propre inverse au + * niveau de la formule (seul le sens d'utilisation change). + * + * Pour N pages (multiple de 4), chaque feuille porte 4 pages : 2 au recto, + * 2 au verso. Exemple pour N=8 : + * Feuille 1 - recto : (8 | 1) verso : (2 | 7) + * Feuille 2 - recto : (6 | 3) verso : (4 | 5) + * + * @param {Number} nPages nombre total de pages (doit etre un multiple de 4) + * @returns {Array} liste de planches, dans l'ordre d'impression : + * [{ feuille: 1, face: "recto", gauche: 8, droite: 1 }, ...] + * "gauche"/"droite" sont des numeros de page 1-based du document source. + */ +CDImp.Livret.calculerOrdre = function (nPages) { + if (nPages % 4 !== 0) { + throw new Error("calculerOrdre : le nombre de pages (" + nPages + ") doit etre un multiple de 4."); + } + var nFeuilles = nPages / 4; + var planches = []; + for (var s = 0; s < nFeuilles; s++) { + planches.push({ feuille: s + 1, face: "recto", gauche: nPages - 2 * s, droite: 2 * s + 1 }); + planches.push({ feuille: s + 1, face: "verso", gauche: 2 * s + 2, droite: nPages - 2 * s - 1 }); + } + return planches; +}; + +// ----------------------------------------------------------------------- +// Ajout de pages blanches pour atteindre un multiple de 4 +// ----------------------------------------------------------------------- +/** + * Insere nManque pages blanches dans doc, a la position demandee. + * Les pages blanches reprennent le format (largeur/hauteur) de la premiere + * page du document. + * + * @param {Doc} doc document a modifier (modification en place) + * @param {Number} nManque nombre de pages blanches a ajouter + * @param {String} position "debut" | "fin" | "apres_couverture" + */ +CDImp.Livret.ajouterPagesBlanches = function (doc, nManque, position) { + if (nManque <= 0) { + return; + } + var boxRef = doc.getPageBox({ nPage: 0, cBox: "Crop" }); + var largeur = Math.abs(boxRef[2] - boxRef[0]); + var hauteur = Math.abs(boxRef[3] - boxRef[1]); + + CDImp.Util.avecPrivileges(function () { + for (var k = 0; k < nManque; k++) { + var idxInsertion; + switch (position) { + case "debut": + idxInsertion = 0; + break; + case "apres_couverture": + idxInsertion = 1; + break; + case "fin": + default: + idxInsertion = doc.numPages; // toujours recalcule : ajoute en fin + break; + } + doc.newPage({ nPage: idxInsertion, width: largeur, height: hauteur }); + } + }); +}; + +/** + * Demande a l'utilisateur ou inserer les pages blanches manquantes, via un + * menu contextuel simple (app.popUpMenu, API stable et bien documentee, + * preferee ici a un second execDialog pour limiter les risques sur un choix + * aussi simple). + * + * @returns {String|null} "debut" | "fin" | "apres_couverture", ou null si + * l'utilisateur annule. + */ +CDImp.Livret._demanderPositionPagesBlanches = function (nManque) { + var choix = app.popUpMenu( + { cName: "A la fin du document (" + nManque + " page(s))", cReturn: "fin" }, + { cName: "Au debut du document (" + nManque + " page(s))", cReturn: "debut" }, + { cName: "Juste apres la couverture (page 1)", cReturn: "apres_couverture" } + ); + return choix || null; +}; + +// ----------------------------------------------------------------------- +// Construction du document imposes (sens direct : pages -> livret) +// ----------------------------------------------------------------------- +/** + * Construit le document de sortie : une page composite (format double, en + * paysage) par planche recto/verso, avec les deux pages source positionnees + * cote a cote. + * + * @param {Doc} srcDoc document source (deja complete a un multiple de 4 pages) + * @param {Array} planches resultat de CDImp.Livret.calculerOrdre() + * @param {Number} creepMm compensation de chasse en mm, appliquee + * progressivement vers l'interieur du cahier (0 = desactive) + * @returns {Doc} le nouveau document compose + */ +CDImp.Livret.construireDocument = function (srcDoc, planches, creepMm) { + var boxSrc = srcDoc.getPageBox({ nPage: 0, cBox: "Crop" }); + var largeurSrc = Math.abs(boxSrc[2] - boxSrc[0]); + var hauteurSrc = Math.abs(boxSrc[3] - boxSrc[1]); + var largeurDest = largeurSrc * 2; + var hauteurDest = hauteurSrc; + var nFeuilles = planches.length / 2; + + var destDoc; + CDImp.Util.avecPrivileges(function () { + destDoc = new Doc(); + destDoc.newPage({ nPage: 0, width: largeurDest, height: hauteurDest }); + for (var k = 1; k < planches.length; k++) { + destDoc.newPage({ nPage: k, width: largeurDest, height: hauteurDest }); + } + }); + + for (var i = 0; i < planches.length; i++) { + var planche = planches[i]; + + // Decalage de chasse (creep) : croissant en s'eloignant du centre du + // cahier vers l'exterieur, applique symetriquement gauche/droite. + var decalageCreepPt = creepMm + ? CDImp.Util.mmToPt(creepMm) * (nFeuilles - planche.feuille) + : 0; + + CDImp.Util.placerPageDansRect(srcDoc, planche.gauche, destDoc, i, + [decalageCreepPt, 0, decalageCreepPt + largeurSrc, hauteurSrc]); + CDImp.Util.placerPageDansRect(srcDoc, planche.droite, destDoc, i, + [largeurSrc - decalageCreepPt, 0, largeurDest - decalageCreepPt, hauteurSrc]); + } + + return destDoc; +}; + +// ----------------------------------------------------------------------- +// Delivretisation (sens inverse : livret imposes -> ordre de lecture normal) +// ----------------------------------------------------------------------- +/** + * Reconstitue l'ordre de lecture normal d'un PDF imposé en cahier piqûre a + * cheval : chaque page du document source est une planche double (recto ou + * verso) contenant deux pages logiques cote a cote, dans l'ordre produit par + * CDImp.Livret.construireDocument(). On reutilise calculerOrdre() pour + * retrouver, pour chaque planche, les numeros de page logique a replacer a + * gauche et a droite, puis on extrait chaque moitie vers sa position finale. + * + * Limite connue : ne compense pas une eventuelle chasse (creep) appliquee + * lors de l'imposition d'origine (les pages reconstituees seront legerement + * decalees si un creep non nul avait ete utilise). + * + * @param {Doc} doc document imposes (planches doubles), document actif si omis + */ +CDImp.Livret.delivretiser = function (doc) { + try { + doc = CDImp.Util.getDocActif(doc); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var nPlanches = doc.numPages; + if (nPlanches % 2 !== 0) { + CDImp.Util.erreur("Delivretiser : le document doit contenir un nombre pair de planches\n(une planche recto + une planche verso par feuille)."); + return; + } + var nPagesLogiques = nPlanches * 2; + + var planches; + try { + planches = CDImp.Livret.calculerOrdre(nPagesLogiques); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var boxRef = doc.getPageBox({ nPage: 0, cBox: "Crop" }); + var largeurPlanche = Math.abs(boxRef[2] - boxRef[0]); + var hauteur = Math.abs(boxRef[3] - boxRef[1]); + var largeurDemiPage = largeurPlanche / 2; + + var destDoc; + try { + CDImp.Util.avecPrivileges(function () { + destDoc = new Doc(); + destDoc.newPage({ nPage: 0, width: largeurDemiPage, height: hauteur }); + for (var k = 1; k < nPagesLogiques; k++) { + destDoc.newPage({ nPage: k, width: largeurDemiPage, height: hauteur }); + } + }); + + for (var i = 0; i < planches.length; i++) { + var planche = planches[i]; + var rectGauche = [0, 0, largeurDemiPage, hauteur]; + var rectDroite = [largeurDemiPage, 0, largeurPlanche, hauteur]; + + CDImp.Util.extraireRegionPage(doc, i + 1, rectGauche, destDoc, planche.gauche - 1); + CDImp.Util.extraireRegionPage(doc, i + 1, rectDroite, destDoc, planche.droite - 1); + } + } catch (e) { + CDImp.Util.erreur("Erreur lors de la delivretisation : " + e.toString()); + return; + } + + if (CDImp.Auto && CDImp.Auto.enregistrerDerniereAction) { + CDImp.Auto.enregistrerDerniereAction("Livret.delivretiser", {}); + } + + CDImp.Util.info("Delivretisation terminee (" + nPagesLogiques + " pages reconstituees).\nPensez a l'enregistrer (Ctrl+S) sous un nouveau nom."); +}; + +// ----------------------------------------------------------------------- +// Point d'entree du module (appele depuis le panneau principal, Module 1) +// ----------------------------------------------------------------------- +CDImp.Livret.lancer = function (doc) { + try { + doc = CDImp.Util.getDocActif(doc); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var sens = app.popUpMenu( + { cName: "Creer un livret (imposition)", cReturn: "imposer" }, + { cName: "Delivretiser (revenir a l'ordre de lecture)", cReturn: "delivretiser" } + ); + if (!sens) { + return; // annulation + } + + if (sens === "delivretiser") { + CDImp.Livret.delivretiser(doc); + return; + } + + var nPages = doc.numPages; + var reste = nPages % 4; + + if (reste !== 0) { + var nManque = 4 - reste; + var position = CDImp.Livret._demanderPositionPagesBlanches(nManque); + if (!position) { + return; // annulation utilisateur + } + CDImp.Livret.ajouterPagesBlanches(doc, nManque, position); + nPages = doc.numPages; + } + + var creepMm = CDImp.Util.demanderNombre( + "Compensation de chasse (creep) en mm par feuille (0 = aucune) :", + "Creer un livret", + 0 + ); + if (creepMm === null) { + return; // annulation utilisateur + } + + var planches = CDImp.Livret.calculerOrdre(nPages); + + var destDoc; + try { + destDoc = CDImp.Livret.construireDocument(doc, planches, creepMm); + } catch (e) { + CDImp.Util.erreur("Erreur lors de la construction du livret : " + e.toString()); + return; + } + + if (CDImp.Auto && CDImp.Auto.enregistrerDerniereAction) { + CDImp.Auto.enregistrerDerniereAction("Livret.imposer", { creepMm: creepMm }); + } + + CDImp.Util.info("Livret cree avec succes (" + planches.length + " planches).\nPensez a l'enregistrer (Ctrl+S) sous un nouveau nom."); +}; + +/** + * Variante directe sans dialogues, utilisee par le Module 7 (Automatisation) + * pour rejouer une action enregistree sur un nouveau document. Ne gere pas + * l'ajout de pages blanches (le document doit deja etre un multiple de 4). + */ +CDImp.Livret.executerAvecParams = function (doc, params) { + doc = CDImp.Util.getDocActif(doc); + var nPages = doc.numPages; + if (nPages % 4 !== 0) { + CDImp.Util.erreur("Repeter l'action : le document (" + nPages + " pages) n'est pas un multiple de 4.\nUtilisez le module Livret manuellement pour ajouter les pages blanches."); + return; + } + var planches = CDImp.Livret.calculerOrdre(nPages); + var destDoc = CDImp.Livret.construireDocument(doc, planches, params.creepMm || 0); + CDImp.Util.info("Livret recree avec succes (" + planches.length + " planches)."); + return destDoc; +}; diff --git a/acrobat-plugin/src/Imposition-03-NUp.js b/acrobat-plugin/src/Imposition-03-NUp.js new file mode 100755 index 0000000..46ed292 --- /dev/null +++ b/acrobat-plugin/src/Imposition-03-NUp.js @@ -0,0 +1,197 @@ +// Imposition-03-NUp.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 3 : N-Up & Step and Repeat (amalgame et multi-poses) +// +// Depend de Imposition-00-Core.js (doit etre charge avant). +// +// Repose sur CDImp.Util.placerPageDansRect() (technique template + CropBox, +// voir avertissement dans Imposition-00-Core.js) pour positionner ET mettre +// a l'echelle chaque page source dans sa cellule de grille. A tester dans +// Acrobat Pro reel avant usage en production. +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-03-NUp.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.NUp = CDImp.NUp || {}; + +/** + * Construit une grille de nLignes x nColonnes sur des feuilles au format + * (largeurFeuilleMm x hauteurFeuilleMm), en mode "n-up" (pages successives + * du document source) ou "repeat" (une seule page dupliquee sur la grille). + * + * @param {Doc} srcDoc document source + * @param {Object} p parametres : + * nLignes, nColonnes {Number} + * largeurFeuilleMm, hauteurFeuilleMm {Number} + * gouttiereMm {Number} espace entre les poses + * margeMm {Number} marge technique autour de la feuille + * mode {String} "nup" | "repeat" + * nPageRepeat {Number} page 1-based a dupliquer (mode "repeat" uniquement) + * @returns {Doc} le nouveau document compose + */ +CDImp.NUp.construireGrille = function (srcDoc, p) { + var feuilleW = CDImp.Util.mmToPt(p.largeurFeuilleMm); + var feuilleH = CDImp.Util.mmToPt(p.hauteurFeuilleMm); + var margePt = CDImp.Util.mmToPt(p.margeMm); + var goutPt = CDImp.Util.mmToPt(p.gouttiereMm); + + var cellW = (feuilleW - 2 * margePt - (p.nColonnes - 1) * goutPt) / p.nColonnes; + var cellH = (feuilleH - 2 * margePt - (p.nLignes - 1) * goutPt) / p.nLignes; + if (cellW <= 0 || cellH <= 0) { + throw new Error("Grille N-Up : la feuille est trop petite pour " + p.nLignes + " x " + p.nColonnes + " avec ces marges/gouttieres."); + } + + // Page de reference pour le ratio d'echelle (premiere page, ou page a + // repeter en mode Step & Repeat). + var nPageRef = (p.mode === "repeat") ? p.nPageRepeat : 1; + var boxRef = srcDoc.getPageBox({ nPage: nPageRef - 1, cBox: "Crop" }); + var largeurSrc = Math.abs(boxRef[2] - boxRef[0]); + var hauteurSrc = Math.abs(boxRef[3] - boxRef[1]); + var echelle = Math.min(cellW / largeurSrc, cellH / hauteurSrc); + var largeurMise = largeurSrc * echelle; + var hauteurMise = hauteurSrc * echelle; + + var nParFeuille = p.nLignes * p.nColonnes; + var nFeuilles = (p.mode === "repeat") ? 1 : Math.ceil(srcDoc.numPages / nParFeuille); + + var destDoc; + CDImp.Util.avecPrivileges(function () { + destDoc = new Doc(); + destDoc.newPage({ nPage: 0, width: feuilleW, height: feuilleH }); + for (var k = 1; k < nFeuilles; k++) { + destDoc.newPage({ nPage: k, width: feuilleW, height: feuilleH }); + } + }); + + for (var s = 0; s < nFeuilles; s++) { + for (var r = 0; r < p.nLignes; r++) { + for (var c = 0; c < p.nColonnes; c++) { + var idxGrille = r * p.nColonnes + c; + var nSrcPage; + if (p.mode === "repeat") { + nSrcPage = p.nPageRepeat; + } else { + var idxGlobal = s * nParFeuille + idxGrille; + if (idxGlobal >= srcDoc.numPages) { + continue; // cellule vide en fin de grille + } + nSrcPage = idxGlobal + 1; + } + + var cellX = margePt + c * (cellW + goutPt); + // r=0 correspond a la rangee du haut visuellement. + var cellYBas = feuilleH - margePt - (r + 1) * cellH - r * goutPt; + var offsetX = cellX + (cellW - largeurMise) / 2; + var offsetY = cellYBas + (cellH - hauteurMise) / 2; + + CDImp.Util.placerPageDansRect(srcDoc, nSrcPage, destDoc, s, + [offsetX, offsetY, offsetX + largeurMise, offsetY + hauteurMise]); + } + } + } + + return destDoc; +}; + +/** + * Demande le format de feuille cible (standard ou personnalise), en mm. + * @returns {Object|null} { largeurMm, hauteurMm }, ou null si annule + */ +CDImp.NUp._demanderFormatFeuille = function () { + var choix = app.popUpMenu( + { cName: "A4 (210 x 297 mm)", cReturn: "A4" }, + { cName: "A3 (297 x 420 mm)", cReturn: "A3" }, + { cName: "SRA3 (320 x 450 mm)", cReturn: "SRA3" }, + { cName: "SRA4 (225 x 320 mm)", cReturn: "SRA4" }, + { cName: "Letter (215.9 x 279.4 mm)", cReturn: "LETTER" }, + { cName: "Personnalise...", cReturn: "PERSO" } + ); + if (!choix) { + return null; + } + if (choix !== "PERSO") { + var f = CDImp.Util.FORMATS_MM[choix]; + return { largeurMm: f[0], hauteurMm: f[1] }; + } + var largeurMm = CDImp.Util.demanderNombre("Largeur de la feuille (mm) :", "Format personnalise", 320); + if (largeurMm === null) { return null; } + var hauteurMm = CDImp.Util.demanderNombre("Hauteur de la feuille (mm) :", "Format personnalise", 450); + if (hauteurMm === null) { return null; } + return { largeurMm: largeurMm, hauteurMm: hauteurMm }; +}; + +CDImp.NUp.lancer = function (doc) { + try { + doc = CDImp.Util.getDocActif(doc); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var mode = app.popUpMenu( + { cName: "Grille N-Up (pages successives du document)", cReturn: "nup" }, + { cName: "Repetition - Step & Repeat (dupliquer une page)", cReturn: "repeat" } + ); + if (!mode) { return; } + + var nLignes = CDImp.Util.demanderNombre("Nombre de lignes :", "N-Up / Step & Repeat", 2); + if (nLignes === null) { return; } + var nColonnes = CDImp.Util.demanderNombre("Nombre de colonnes :", "N-Up / Step & Repeat", 2); + if (nColonnes === null) { return; } + + var formatFeuille = CDImp.NUp._demanderFormatFeuille(); + if (!formatFeuille) { return; } + + var gouttiereMm = CDImp.Util.demanderNombre("Gouttiere entre les poses (mm) :", "N-Up / Step & Repeat", 4); + if (gouttiereMm === null) { return; } + var margeMm = CDImp.Util.demanderNombre("Marge technique autour de la feuille (mm) :", "N-Up / Step & Repeat", 5); + if (margeMm === null) { return; } + + var nPageRepeat = 1; + if (mode === "repeat") { + nPageRepeat = CDImp.Util.demanderNombre("Numero de la page a dupliquer :", "Step & Repeat", doc.pageNum + 1); + if (nPageRepeat === null) { return; } + if (nPageRepeat < 1 || nPageRepeat > doc.numPages) { + CDImp.Util.erreur("Page invalide : " + nPageRepeat); + return; + } + } + + var params = { + nLignes: nLignes, + nColonnes: nColonnes, + largeurFeuilleMm: formatFeuille.largeurMm, + hauteurFeuilleMm: formatFeuille.hauteurMm, + gouttiereMm: gouttiereMm, + margeMm: margeMm, + mode: mode, + nPageRepeat: nPageRepeat + }; + + var destDoc; + try { + destDoc = CDImp.NUp.construireGrille(doc, params); + } catch (e) { + CDImp.Util.erreur("Erreur lors de la construction de la grille : " + e.toString()); + return; + } + + if (CDImp.Auto && CDImp.Auto.enregistrerDerniereAction) { + CDImp.Auto.enregistrerDerniereAction("NUp.executer", params); + } + + CDImp.Util.info("Mise en page terminee (" + destDoc.numPages + " feuille(s)).\nPensez a l'enregistrer (Ctrl+S) sous un nouveau nom."); +}; + +/** + * Variante directe sans dialogues (Module 7 - Automatisation). + */ +CDImp.NUp.executerAvecParams = function (doc, params) { + doc = CDImp.Util.getDocActif(doc); + var destDoc = CDImp.NUp.construireGrille(doc, params); + CDImp.Util.info("Mise en page recreee avec succes (" + destDoc.numPages + " feuille(s))."); + return destDoc; +}; diff --git a/acrobat-plugin/src/Imposition-04-Decoupe.js b/acrobat-plugin/src/Imposition-04-Decoupe.js new file mode 100755 index 0000000..9d703c9 --- /dev/null +++ b/acrobat-plugin/src/Imposition-04-Decoupe.js @@ -0,0 +1,222 @@ +// Imposition-04-Decoupe.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 4 : Decoupe et reassemblage de pages +// +// Depend de Imposition-00-Core.js (doit etre charge avant). +// +// Repose sur CDImp.Util.extraireRegionPage() / placerPageDansRect() (voir +// avertissement dans Imposition-00-Core.js). A tester dans Acrobat Pro reel +// avant usage en production. +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-04-Decoupe.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.Decoupe = CDImp.Decoupe || {}; + +// ----------------------------------------------------------------------- +// Couper les pages en deux (Split) +// ----------------------------------------------------------------------- +/** + * Coupe chaque page de srcDoc en deux moities independantes et retourne un + * nouveau document contenant 2x plus de pages. + * + * @param {Doc} srcDoc + * @param {String} axe "vertical" (gauche/droite) | "horizontal" (haut/bas) + * @returns {Doc} nouveau document + */ +CDImp.Decoupe.couperPages = function (srcDoc, axe) { + var destDoc; + CDImp.Util.avecPrivileges(function () { + destDoc = new Doc(); + }); + + var nDestPage = 0; + for (var s = 0; s < srcDoc.numPages; s++) { + var box = srcDoc.getPageBox({ nPage: s, cBox: "Crop" }); + var w = Math.abs(box[2] - box[0]); + var h = Math.abs(box[3] - box[1]); + + var demis; // [{rect, largeur, hauteur}, ...] dans l'ordre de lecture + if (axe === "horizontal") { + demis = [ + { rect: [0, h / 2, w, h], largeur: w, hauteur: h / 2 }, // haut + { rect: [0, 0, w, h / 2], largeur: w, hauteur: h / 2 } // bas + ]; + } else { + demis = [ + { rect: [0, 0, w / 2, h], largeur: w / 2, hauteur: h }, // gauche + { rect: [w / 2, 0, w, h], largeur: w / 2, hauteur: h } // droite + ]; + } + + for (var d = 0; d < demis.length; d++) { + CDImp.Util.avecPrivileges(function () { + destDoc.newPage({ nPage: nDestPage, width: demis[d].largeur, height: demis[d].hauteur }); + }); + CDImp.Util.extraireRegionPage(srcDoc, s + 1, demis[d].rect, destDoc, nDestPage); + nDestPage++; + } + } + + return destDoc; +}; + +// ----------------------------------------------------------------------- +// Inversion de l'ordre des pages +// ----------------------------------------------------------------------- +/** + * Retourne un nouveau document avec les pages de srcDoc dans l'ordre inverse. + */ +CDImp.Decoupe.inverserOrdre = function (srcDoc) { + var n = srcDoc.numPages; + var destDoc; + CDImp.Util.avecPrivileges(function () { + destDoc = new Doc(); + }); + + for (var i = 0; i < n; i++) { + var nSrcPage = n - i; // 1-based, en partant de la derniere page + var box = srcDoc.getPageBox({ nPage: nSrcPage - 1, cBox: "Crop" }); + var w = Math.abs(box[2] - box[0]); + var h = Math.abs(box[3] - box[1]); + + CDImp.Util.avecPrivileges(function () { + destDoc.newPage({ nPage: i, width: w, height: h }); + }); + CDImp.Util.placerPageDansRect(srcDoc, nSrcPage, destDoc, i, [0, 0, w, h]); + } + + return destDoc; +}; + +// ----------------------------------------------------------------------- +// Reassemblage par motif (mélange par blocs) +// ----------------------------------------------------------------------- +/** + * Reordonne les pages de srcDoc par blocs, selon un motif comme "1,3,2,4" + * (1-based, a l'interieur de chaque bloc). La taille de bloc est la longueur + * du motif ; le nombre de pages doit en etre un multiple exact. + * + * @param {Doc} srcDoc + * @param {String} motifStr ex: "1,3,2,4" + * @returns {Doc} nouveau document reordonne + */ +CDImp.Decoupe.reassembler = function (srcDoc, motifStr) { + var motif = motifStr.split(",").map(function (s) { return parseInt(s.trim(), 10); }); + var taille = motif.length; + if (taille === 0 || motif.some(function (m) { return isNaN(m) || m < 1 || m > taille; })) { + throw new Error("Motif invalide : \"" + motifStr + "\". Attendu une liste de positions 1-based, ex: 1,3,2,4"); + } + if (srcDoc.numPages % taille !== 0) { + throw new Error("Le document (" + srcDoc.numPages + " pages) n'est pas un multiple de la taille de bloc (" + taille + ")."); + } + + var nBlocs = srcDoc.numPages / taille; + var destDoc; + CDImp.Util.avecPrivileges(function () { + destDoc = new Doc(); + }); + + var nDestPage = 0; + for (var b = 0; b < nBlocs; b++) { + for (var k = 0; k < taille; k++) { + var nSrcPage = b * taille + motif[k]; // 1-based + var box = srcDoc.getPageBox({ nPage: nSrcPage - 1, cBox: "Crop" }); + var w = Math.abs(box[2] - box[0]); + var h = Math.abs(box[3] - box[1]); + + CDImp.Util.avecPrivileges(function () { + destDoc.newPage({ nPage: nDestPage, width: w, height: h }); + }); + CDImp.Util.placerPageDansRect(srcDoc, nSrcPage, destDoc, nDestPage, [0, 0, w, h]); + nDestPage++; + } + } + + return destDoc; +}; + +// ----------------------------------------------------------------------- +// Point d'entree du module +// ----------------------------------------------------------------------- +CDImp.Decoupe.lancer = function (doc) { + try { + doc = CDImp.Util.getDocActif(doc); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var action = app.popUpMenu( + { cName: "Couper les pages en deux (Split)", cReturn: "split" }, + { cName: "Inverser l'ordre des pages", cReturn: "reverse" }, + { cName: "Reassembler par motif (Shuffle)", cReturn: "shuffle" } + ); + if (!action) { return; } + + var destDoc, params; + try { + switch (action) { + case "split": + var axe = app.popUpMenu( + { cName: "Vertical (gauche / droite)", cReturn: "vertical" }, + { cName: "Horizontal (haut / bas)", cReturn: "horizontal" } + ); + if (!axe) { return; } + params = { action: "split", axe: axe }; + destDoc = CDImp.Decoupe.couperPages(doc, axe); + break; + + case "reverse": + params = { action: "reverse" }; + destDoc = CDImp.Decoupe.inverserOrdre(doc); + break; + + case "shuffle": + var motifStr = app.response({ + cQuestion: "Motif de reassemblage par bloc (positions 1-based separees par des virgules) :", + cTitle: "Reassembler par motif", + cDefault: "1,3,2,4" + }); + if (motifStr === null) { return; } + params = { action: "shuffle", motif: motifStr }; + destDoc = CDImp.Decoupe.reassembler(doc, motifStr); + break; + } + } catch (e) { + CDImp.Util.erreur("Erreur : " + e.toString()); + return; + } + + if (CDImp.Auto && CDImp.Auto.enregistrerDerniereAction) { + CDImp.Auto.enregistrerDerniereAction("Decoupe.executer", params); + } + + CDImp.Util.info("Operation terminee (" + destDoc.numPages + " page(s)).\nPensez a l'enregistrer (Ctrl+S) sous un nouveau nom."); +}; + +/** + * Variante directe sans dialogues (Module 7 - Automatisation). + */ +CDImp.Decoupe.executerAvecParams = function (doc, params) { + doc = CDImp.Util.getDocActif(doc); + var destDoc; + switch (params.action) { + case "split": + destDoc = CDImp.Decoupe.couperPages(doc, params.axe); + break; + case "reverse": + destDoc = CDImp.Decoupe.inverserOrdre(doc); + break; + case "shuffle": + destDoc = CDImp.Decoupe.reassembler(doc, params.motif); + break; + default: + throw new Error("Action inconnue : " + params.action); + } + CDImp.Util.info("Operation recreee avec succes (" + destDoc.numPages + " page(s))."); + return destDoc; +}; diff --git a/acrobat-plugin/src/Imposition-05-Marges.js b/acrobat-plugin/src/Imposition-05-Marges.js new file mode 100755 index 0000000..091e92d --- /dev/null +++ b/acrobat-plugin/src/Imposition-05-Marges.js @@ -0,0 +1,233 @@ +// Imposition-05-Marges.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 5 : Gestion des marges et cadrages (Page Boxes) +// +// Depend de Imposition-00-Core.js (doit etre charge avant). +// +// ajouterEspaceBlanc() et harmoniserBoites() modifient le document EN PLACE +// (simple edition de boites, operation legere et fiable). deplacerContenu() +// doit en revanche reconstruire les pages via la technique template+CropBox +// (voir avertissement dans Imposition-00-Core.js) car deplacer du contenu +// existant n'est pas possible par simple edition de boite. +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-05-Marges.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.Marges = CDImp.Marges || {}; + +/** + * Resout la liste d'indices de page (0-based) a traiter. + * @param {Doc} doc + * @param {String} portee "toutes" | "courante" + */ +CDImp.Marges._resoudrePortee = function (doc, portee) { + if (portee === "courante") { + return [doc.pageNum]; + } + var indices = []; + for (var i = 0; i < doc.numPages; i++) { + indices.push(i); + } + return indices; +}; + +// ----------------------------------------------------------------------- +// Ajouter de l'espace blanc autour du contenu +// ----------------------------------------------------------------------- +/** + * Agrandit la MediaBox et la CropBox de chaque page concernee sans toucher + * au contenu existant, en ajoutant les marges donnees (en mm) sur chaque + * cote. + * + * @param {Doc} doc modifie en place + * @param {Object} margesMm { haut, bas, gauche, droite } en mm + * @param {String} portee "toutes" | "courante" + */ +CDImp.Marges.ajouterEspaceBlanc = function (doc, margesMm, portee) { + var hautPt = CDImp.Util.mmToPt(margesMm.haut); + var basPt = CDImp.Util.mmToPt(margesMm.bas); + var gauchePt = CDImp.Util.mmToPt(margesMm.gauche); + var droitePt = CDImp.Util.mmToPt(margesMm.droite); + var indices = CDImp.Marges._resoudrePortee(doc, portee); + + CDImp.Util.avecPrivileges(function () { + for (var k = 0; k < indices.length; k++) { + var i = indices[k]; + var crop = doc.getPageBox({ nPage: i, cBox: "Crop" }); + var nouveauCrop = [crop[0] - gauchePt, crop[1] - basPt, crop[2] + droitePt, crop[3] + hautPt]; + doc.setPageBoxes({ nPage: i, cBox: "Crop", rBox: nouveauCrop }); + doc.setPageBoxes({ nPage: i, cBox: "Media", rBox: nouveauCrop }); + } + }); +}; + +// ----------------------------------------------------------------------- +// Deplacer le contenu d'une page +// ----------------------------------------------------------------------- +/** + * Construit un nouveau document ou le contenu de chaque page concernee est + * decale de (dxMm, dyMm) par rapport a sa position d'origine (les pages non + * concernees sont recopiees telles quelles). + * + * @param {Doc} srcDoc + * @param {Number} dxMm decalage horizontal en mm (positif = vers la droite) + * @param {Number} dyMm decalage vertical en mm (positif = vers le haut) + * @param {String} portee "toutes" | "courante" + * @returns {Doc} nouveau document + */ +CDImp.Marges.deplacerContenu = function (srcDoc, dxMm, dyMm, portee) { + var dxPt = CDImp.Util.mmToPt(dxMm); + var dyPt = CDImp.Util.mmToPt(dyMm); + var indicesConcernes = CDImp.Marges._resoudrePortee(srcDoc, portee); + var ensembleConcernes = {}; + for (var k = 0; k < indicesConcernes.length; k++) { + ensembleConcernes[indicesConcernes[k]] = true; + } + + var destDoc; + CDImp.Util.avecPrivileges(function () { + destDoc = new Doc(); + }); + + for (var i = 0; i < srcDoc.numPages; i++) { + var box = srcDoc.getPageBox({ nPage: i, cBox: "Crop" }); + var w = Math.abs(box[2] - box[0]); + var h = Math.abs(box[3] - box[1]); + + CDImp.Util.avecPrivileges(function () { + destDoc.newPage({ nPage: i, width: w, height: h }); + }); + + var decalX = ensembleConcernes[i] ? dxPt : 0; + var decalY = ensembleConcernes[i] ? dyPt : 0; + CDImp.Util.placerPageDansRect(srcDoc, i + 1, destDoc, i, [decalX, decalY, decalX + w, decalY + h]); + } + + return destDoc; +}; + +// ----------------------------------------------------------------------- +// Harmonisation des boites (Media/Crop/Bleed/Trim) +// ----------------------------------------------------------------------- +/** + * Pour chaque page concernee, force les 4 boites (Media, Crop, Bleed, Trim) + * a coincider avec la valeur actuelle de la boite de reference cBoxRef. + * + * @param {Doc} doc modifie en place + * @param {String} cBoxRef "Media" | "Crop" | "Bleed" | "Trim" + * @param {String} portee "toutes" | "courante" + */ +CDImp.Marges.harmoniserBoites = function (doc, cBoxRef, portee) { + var indices = CDImp.Marges._resoudrePortee(doc, portee); + var toutesBoites = ["Media", "Crop", "Bleed", "Trim"]; + + CDImp.Util.avecPrivileges(function () { + for (var k = 0; k < indices.length; k++) { + var i = indices[k]; + var rectRef = doc.getPageBox({ nPage: i, cBox: cBoxRef }); + for (var b = 0; b < toutesBoites.length; b++) { + doc.setPageBoxes({ nPage: i, cBox: toutesBoites[b], rBox: rectRef }); + } + } + }); +}; + +// ----------------------------------------------------------------------- +// Point d'entree du module +// ----------------------------------------------------------------------- +CDImp.Marges.lancer = function (doc) { + try { + doc = CDImp.Util.getDocActif(doc); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var action = app.popUpMenu( + { cName: "Ajouter de l'espace blanc autour du contenu", cReturn: "espace" }, + { cName: "Deplacer le contenu (decalage)", cReturn: "deplacer" }, + { cName: "Harmoniser les boites (Media/Crop/Bleed/Trim)", cReturn: "harmoniser" } + ); + if (!action) { return; } + + var portee = app.popUpMenu( + { cName: "Toutes les pages", cReturn: "toutes" }, + { cName: "Page courante uniquement", cReturn: "courante" } + ); + if (!portee) { return; } + + var params; + try { + switch (action) { + case "espace": { + var haut = CDImp.Util.demanderNombre("Marge en haut (mm) :", "Ajouter de l'espace blanc", 0); + if (haut === null) { return; } + var bas = CDImp.Util.demanderNombre("Marge en bas (mm) :", "Ajouter de l'espace blanc", 0); + if (bas === null) { return; } + var gauche = CDImp.Util.demanderNombre("Marge a gauche (mm) :", "Ajouter de l'espace blanc", 0); + if (gauche === null) { return; } + var droite = CDImp.Util.demanderNombre("Marge a droite (mm) :", "Ajouter de l'espace blanc", 0); + if (droite === null) { return; } + params = { action: "espace", marges: { haut: haut, bas: bas, gauche: gauche, droite: droite }, portee: portee }; + CDImp.Marges.ajouterEspaceBlanc(doc, params.marges, portee); + break; + } + case "deplacer": { + var dx = CDImp.Util.demanderNombre("Decalage horizontal (mm, + = vers la droite) :", "Deplacer le contenu", 0); + if (dx === null) { return; } + var dy = CDImp.Util.demanderNombre("Decalage vertical (mm, + = vers le haut) :", "Deplacer le contenu", 0); + if (dy === null) { return; } + params = { action: "deplacer", dxMm: dx, dyMm: dy, portee: portee }; + var destDoc = CDImp.Marges.deplacerContenu(doc, dx, dy, portee); + if (CDImp.Auto && CDImp.Auto.enregistrerDerniereAction) { + CDImp.Auto.enregistrerDerniereAction("Marges.executer", params); + } + CDImp.Util.info("Contenu deplace dans un nouveau document.\nPensez a l'enregistrer (Ctrl+S) sous un nouveau nom."); + return; + } + case "harmoniser": { + var refBoite = app.popUpMenu( + { cName: "Aligner sur la TrimBox", cReturn: "Trim" }, + { cName: "Aligner sur la CropBox", cReturn: "Crop" }, + { cName: "Aligner sur la BleedBox", cReturn: "Bleed" }, + { cName: "Aligner sur la MediaBox", cReturn: "Media" } + ); + if (!refBoite) { return; } + params = { action: "harmoniser", refBoite: refBoite, portee: portee }; + CDImp.Marges.harmoniserBoites(doc, refBoite, portee); + break; + } + } + } catch (e) { + CDImp.Util.erreur("Erreur : " + e.toString()); + return; + } + + if (CDImp.Auto && CDImp.Auto.enregistrerDerniereAction) { + CDImp.Auto.enregistrerDerniereAction("Marges.executer", params); + } + + CDImp.Util.info("Operation terminee avec succes."); +}; + +/** + * Variante directe sans dialogues (Module 7 - Automatisation). + */ +CDImp.Marges.executerAvecParams = function (doc, params) { + doc = CDImp.Util.getDocActif(doc); + switch (params.action) { + case "espace": + CDImp.Marges.ajouterEspaceBlanc(doc, params.marges, params.portee); + return doc; + case "deplacer": + return CDImp.Marges.deplacerContenu(doc, params.dxMm, params.dyMm, params.portee); + case "harmoniser": + CDImp.Marges.harmoniserBoites(doc, params.refBoite, params.portee); + return doc; + default: + throw new Error("Action inconnue : " + params.action); + } +}; diff --git a/acrobat-plugin/src/Imposition-06-Reperes.js b/acrobat-plugin/src/Imposition-06-Reperes.js new file mode 100755 index 0000000..026a480 --- /dev/null +++ b/acrobat-plugin/src/Imposition-06-Reperes.js @@ -0,0 +1,242 @@ +// Imposition-06-Reperes.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 6 : Reperes de coupe, textes et numerotation +// +// Depend de Imposition-00-Core.js (doit etre charge avant). +// +// Les traits de coupe utilisent des annotations de type "Line" (API stable +// et bien documentee : doc.addAnnot). La numerotation utilise +// doc.addWatermarkFromText, egalement standard. Ces deux API sont plus +// fiables que la technique template+CropBox utilisee dans les autres +// modules, mais les constantes exactes de app.constants.align doivent etre +// confirmees dans Acrobat Pro reel (non installe dans cet environnement de +// developpement). +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-06-Reperes.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.Reperes = CDImp.Reperes || {}; + +// ----------------------------------------------------------------------- +// Traits de coupe (8 reperes aux 4 coins, bases sur la TrimBox) +// ----------------------------------------------------------------------- +/** + * Ajoute 8 traits de coupe (2 par coin) autour de la zone de delimitation + * (TrimBox par defaut) de chaque page. + * + * @param {Doc} doc modifie en place + * @param {Object} opts + * decalageMm {Number} retrait entre le bord du format fini et le trait + * longueurMm {Number} longueur de chaque trait + * epaisseurPt {Number} epaisseur du trait en points + * cBoxRef {String} boite de reference, "Trim" par defaut + */ +CDImp.Reperes.ajouterTraitsCoupe = function (doc, opts) { + var decalage = CDImp.Util.mmToPt(opts.decalageMm); + var longueur = CDImp.Util.mmToPt(opts.longueurMm); + var epaisseur = opts.epaisseurPt || 0.25; + var cBoxRef = opts.cBoxRef || "Trim"; + + CDImp.Util.avecPrivileges(function () { + for (var i = 0; i < doc.numPages; i++) { + var box = doc.getPageBox({ nPage: i, cBox: cBoxRef }); + var llx = Math.min(box[0], box[2]); + var lly = Math.min(box[1], box[3]); + var urx = Math.max(box[0], box[2]); + var ury = Math.max(box[1], box[3]); + + var traits = [ + // bas-gauche + [llx - decalage - longueur, lly, llx - decalage, lly], + [llx, lly - decalage - longueur, llx, lly - decalage], + // bas-droite + [urx + decalage, lly, urx + decalage + longueur, lly], + [urx, lly - decalage - longueur, urx, lly - decalage], + // haut-gauche + [llx - decalage - longueur, ury, llx - decalage, ury], + [llx, ury + decalage, llx, ury + decalage + longueur], + // haut-droite + [urx + decalage, ury, urx + decalage + longueur, ury], + [urx, ury + decalage, urx, ury + decalage + longueur] + ]; + + for (var t = 0; t < traits.length; t++) { + var pts = traits[t]; + var rect = [ + Math.min(pts[0], pts[2]) - epaisseur, Math.min(pts[1], pts[3]) - epaisseur, + Math.max(pts[0], pts[2]) + epaisseur, Math.max(pts[1], pts[3]) + epaisseur + ]; + doc.addAnnot({ + page: i, + type: "Line", + rect: rect, + linePoints: pts, + width: epaisseur, + color: color.black, + print: true, + name: "CDImp_repere_" + i + "_" + t + }); + } + } + }); +}; + +// ----------------------------------------------------------------------- +// Numerotation sequentielle avancee +// ----------------------------------------------------------------------- +/** + * Remplace les variables dynamiques dans un modele de texte. + * Variables : [Page], [Total], [NomDuFichier], [Date] + */ +CDImp.Reperes.remplacerVariables = function (modele, doc, numPage, total) { + var date = util.printd("dd/mm/yyyy", new Date()); + return modele + .replace(/\[Page\]/g, String(numPage)) + .replace(/\[Total\]/g, String(total)) + .replace(/\[NomDuFichier\]/g, doc.documentFileName || "") + .replace(/\[Date\]/g, date); +}; + +/** + * Ajoute une numerotation / mention variable sur chaque page (sauf la + * couverture si demande). + * + * @param {Doc} doc modifie en place + * @param {Object} opts + * modele {String} ex: "Page [Page] / [Total]" + * police {String} ex: "Helvetica" + * taillePt {Number} + * positionXmm, positionYmm {Number} distance depuis le bord (selon coin) + * coin {String} "bas_droite" | "bas_gauche" | "haut_droite" | "haut_gauche" + * masquerPremierePage {Boolean} + */ +CDImp.Reperes.ajouterNumerotation = function (doc, opts) { + var total = doc.numPages; + var A = app.constants.align; + var coins = { + bas_droite: { h: A.right, v: A.bottom }, + bas_gauche: { h: A.left, v: A.bottom }, + haut_droite: { h: A.right, v: A.top }, + haut_gauche: { h: A.left, v: A.top } + }; + var coin = coins[opts.coin] || coins.bas_droite; + var offsetXPt = CDImp.Util.mmToPt(opts.positionXmm); + var offsetYPt = CDImp.Util.mmToPt(opts.positionYmm); + + CDImp.Util.avecPrivileges(function () { + for (var i = 0; i < doc.numPages; i++) { + var numPage = i + 1; + if (opts.masquerPremierePage && numPage === 1) { + continue; + } + var texte = CDImp.Reperes.remplacerVariables(opts.modele, doc, numPage, total); + + doc.addWatermarkFromText({ + cText: texte, + cFont: opts.police || "Helvetica", + nFontSize: opts.taillePt || 9, + nHorizAlign: coin.h, + nVertAlign: coin.v, + nHorizValue: offsetXPt, + nVertValue: offsetYPt, + nRotation: 0, + nOpacity: 1.0, + bOnTop: true, + bOnscreen: true, + bPrintable: true, + nStart: i, + nEnd: i + }); + } + }); +}; + +// ----------------------------------------------------------------------- +// Point d'entree du module +// ----------------------------------------------------------------------- +CDImp.Reperes.lancer = function (doc) { + try { + doc = CDImp.Util.getDocActif(doc); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var action = app.popUpMenu( + { cName: "Ajouter les traits de coupe", cReturn: "traits" }, + { cName: "Ajouter une numerotation / mention variable", cReturn: "numero" } + ); + if (!action) { return; } + + var params; + try { + if (action === "traits") { + var decalageMm = CDImp.Util.demanderNombre("Retrait par rapport au format fini (mm) :", "Traits de coupe", 3); + if (decalageMm === null) { return; } + var longueurMm = CDImp.Util.demanderNombre("Longueur du trait (mm) :", "Traits de coupe", 5); + if (longueurMm === null) { return; } + params = { action: "traits", decalageMm: decalageMm, longueurMm: longueurMm, epaisseurPt: 0.25 }; + CDImp.Reperes.ajouterTraitsCoupe(doc, params); + } else { + var modele = app.response({ cQuestion: "Modele de texte ([Page], [Total], [NomDuFichier], [Date]) :", cTitle: "Numerotation", cDefault: "[Page] / [Total]" }); + if (modele === null) { return; } + var taillePt = CDImp.Util.demanderNombre("Taille de police (pt) :", "Numerotation", 9); + if (taillePt === null) { return; } + var coin = app.popUpMenu( + { cName: "Bas droite", cReturn: "bas_droite" }, + { cName: "Bas gauche", cReturn: "bas_gauche" }, + { cName: "Haut droite", cReturn: "haut_droite" }, + { cName: "Haut gauche", cReturn: "haut_gauche" } + ); + if (!coin) { return; } + var positionXmm = CDImp.Util.demanderNombre("Distance horizontale depuis le bord (mm) :", "Numerotation", 10); + if (positionXmm === null) { return; } + var positionYmm = CDImp.Util.demanderNombre("Distance verticale depuis le bord (mm) :", "Numerotation", 10); + if (positionYmm === null) { return; } + var masquerStr = app.popUpMenu( + { cName: "Afficher sur toutes les pages", cReturn: "non" }, + { cName: "Masquer sur la premiere page (couverture)", cReturn: "oui" } + ); + if (!masquerStr) { return; } + + params = { + action: "numero", + modele: modele, + police: "Helvetica", + taillePt: taillePt, + coin: coin, + positionXmm: positionXmm, + positionYmm: positionYmm, + masquerPremierePage: masquerStr === "oui" + }; + CDImp.Reperes.ajouterNumerotation(doc, params); + } + } catch (e) { + CDImp.Util.erreur("Erreur : " + e.toString()); + return; + } + + if (CDImp.Auto && CDImp.Auto.enregistrerDerniereAction) { + CDImp.Auto.enregistrerDerniereAction("Reperes.executer", params); + } + + CDImp.Util.info("Operation terminee avec succes."); +}; + +/** + * Variante directe sans dialogues (Module 7 - Automatisation). + */ +CDImp.Reperes.executerAvecParams = function (doc, params) { + doc = CDImp.Util.getDocActif(doc); + if (params.action === "traits") { + CDImp.Reperes.ajouterTraitsCoupe(doc, params); + } else if (params.action === "numero") { + CDImp.Reperes.ajouterNumerotation(doc, params); + } else { + throw new Error("Action inconnue : " + params.action); + } + return doc; +}; diff --git a/acrobat-plugin/src/Imposition-07-Auto.js b/acrobat-plugin/src/Imposition-07-Auto.js new file mode 100755 index 0000000..3783634 --- /dev/null +++ b/acrobat-plugin/src/Imposition-07-Auto.js @@ -0,0 +1,189 @@ +// Imposition-07-Auto.js +// --------------------------------------------------------------------------- +// Suite d'imposition professionnelle - Folder-Level Script pour Adobe Acrobat Pro +// Module 7 : Automatisation (sequences de commandes / presets) +// +// Depend de Imposition-00-Core.js (doit etre charge avant) ainsi que des +// modules 2 a 6 pour le rejeu d'actions (chacun expose une fonction +// "executerAvecParams(doc, params)" sans dialogue, appelee ici). +// +// Les presets sont stockes dans l'objet global persistant +// global.impositionSettings (survit aux fermetures d'Acrobat grace a +// global.setPersistent). La "derniere action" n'est gardee qu'en memoire +// pour la session courante. +// --------------------------------------------------------------------------- + +if (typeof CDImp === "undefined") { + throw new Error("Imposition-07-Auto.js : CDImp.Core doit etre charge avant ce fichier."); +} + +CDImp.Auto = CDImp.Auto || {}; + +// Initialise l'espace de stockage persistant des presets. +CDImp.Util.avecPrivileges(function () { + if (typeof global.impositionSettings === "undefined") { + global.impositionSettings = {}; + } + global.setPersistent("impositionSettings", true); +}); + +// Memoire de la derniere action executee (module + parametres), pour le +// bouton "Repeter la derniere action". Reinitialisee a chaque session. +CDImp.Auto.derniereAction = null; + +// Table de correspondance "cle d'action" -> fonction de rejeu sans dialogue. +// Chaque entree appelle la variante "executerAvecParams" du module concerne. +CDImp.Auto._dispatch = { + "Livret.imposer": function (doc, params) { return CDImp.Livret.executerAvecParams(doc, params); }, + "Livret.delivretiser": function (doc) { return CDImp.Livret.delivretiser(doc); }, + "NUp.executer": function (doc, params) { return CDImp.NUp.executerAvecParams(doc, params); }, + "Decoupe.executer": function (doc, params) { return CDImp.Decoupe.executerAvecParams(doc, params); }, + "Marges.executer": function (doc, params) { return CDImp.Marges.executerAvecParams(doc, params); }, + "Reperes.executer": function (doc, params) { return CDImp.Reperes.executerAvecParams(doc, params); } +}; + +/** + * Appelee par chaque module a l'issue d'une execution reussie, pour + * permettre "Repeter la derniere action" et la sauvegarde en preset. + * + * @param {String} cle une des cles de CDImp.Auto._dispatch + * @param {Object} params parametres necessaires au rejeu + */ +CDImp.Auto.enregistrerDerniereAction = function (cle, params) { + if (!CDImp.Auto._dispatch[cle]) { + return; // cle inconnue : on ignore silencieusement (pas bloquant) + } + CDImp.Auto.derniereAction = { cle: cle, params: params || {} }; +}; + +/** + * Rejoue la derniere action enregistree sur le document fourni (typiquement + * un nouveau document different de celui d'origine), sans repasser par les + * boites de dialogue. + */ +CDImp.Auto.repeterDerniereAction = function (doc) { + if (!CDImp.Auto.derniereAction) { + CDImp.Util.erreur("Aucune action a repeter : lancez d'abord un module d'imposition."); + return; + } + try { + doc = CDImp.Util.getDocActif(doc); + var fn = CDImp.Auto._dispatch[CDImp.Auto.derniereAction.cle]; + var destDoc = fn(doc, CDImp.Auto.derniereAction.params); + CDImp.Util.info("Derniere action (" + CDImp.Auto.derniereAction.cle + ") rejouee avec succes."); + return destDoc; + } catch (e) { + CDImp.Util.erreur("Erreur lors de la repetition de l'action : " + e.toString()); + } +}; + +/** + * Enregistre la derniere action sous un nom de preset persistant. + */ +CDImp.Auto.sauvegarderPreset = function (nomPreset) { + if (!CDImp.Auto.derniereAction) { + CDImp.Util.erreur("Aucune action a enregistrer : lancez d'abord un module d'imposition."); + return; + } + CDImp.Util.avecPrivileges(function () { + global.impositionSettings[nomPreset] = CDImp.Auto.derniereAction; + }); + CDImp.Util.info("Preset \"" + nomPreset + "\" enregistre."); +}; + +/** + * Retourne la liste des noms de presets enregistres. + */ +CDImp.Auto.listerPresets = function () { + var noms = []; + for (var nom in global.impositionSettings) { + if (global.impositionSettings.hasOwnProperty(nom)) { + noms.push(nom); + } + } + return noms; +}; + +/** + * Charge un preset et l'execute directement sur le document fourni. + */ +CDImp.Auto.chargerEtExecuterPreset = function (doc, nomPreset) { + var action = global.impositionSettings[nomPreset]; + if (!action) { + CDImp.Util.erreur("Preset introuvable : " + nomPreset); + return; + } + try { + doc = CDImp.Util.getDocActif(doc); + var fn = CDImp.Auto._dispatch[action.cle]; + var destDoc = fn(doc, action.params); + CDImp.Util.info("Preset \"" + nomPreset + "\" execute avec succes."); + return destDoc; + } catch (e) { + CDImp.Util.erreur("Erreur lors de l'execution du preset : " + e.toString()); + } +}; + +/** + * Supprime un preset enregistre. + */ +CDImp.Auto.supprimerPreset = function (nomPreset) { + CDImp.Util.avecPrivileges(function () { + delete global.impositionSettings[nomPreset]; + }); +}; + +// ----------------------------------------------------------------------- +// Point d'entree du module +// ----------------------------------------------------------------------- +CDImp.Auto.lancer = function (doc) { + try { + doc = CDImp.Util.getDocActif(doc); + } catch (e) { + CDImp.Util.erreur(e.toString()); + return; + } + + var presets = CDImp.Auto.listerPresets(); + var options = [ + { cName: "Repeter la derniere action sur ce document", cReturn: "repeter" }, + { cName: "Enregistrer la configuration actuelle comme preset", cReturn: "sauvegarder" } + ]; + if (presets.length > 0) { + options.push({ cName: "Charger et executer un preset...", cReturn: "charger" }); + options.push({ cName: "Supprimer un preset...", cReturn: "supprimer" }); + } + + var choix = app.popUpMenu.apply(app, options); + if (!choix) { return; } + + switch (choix) { + case "repeter": + CDImp.Auto.repeterDerniereAction(doc); + break; + + case "sauvegarder": { + var nom = app.response({ cQuestion: "Nom du preset :", cTitle: "Enregistrer la configuration", cDefault: "MonPreset" }); + if (nom === null || nom === "") { return; } + CDImp.Auto.sauvegarderPreset(nom); + break; + } + + case "charger": { + var presetsOptions = presets.map(function (n) { return { cName: n, cReturn: n }; }); + var nomChoisi = app.popUpMenu.apply(app, presetsOptions); + if (!nomChoisi) { return; } + CDImp.Auto.chargerEtExecuterPreset(doc, nomChoisi); + break; + } + + case "supprimer": { + var presetsOptionsSupp = presets.map(function (n) { return { cName: n, cReturn: n }; }); + var nomASupprimer = app.popUpMenu.apply(app, presetsOptionsSupp); + if (!nomASupprimer) { return; } + CDImp.Auto.supprimerPreset(nomASupprimer); + CDImp.Util.info("Preset \"" + nomASupprimer + "\" supprime."); + break; + } + } +};