Printed views and the badge sheet leave the app through the platform. Under Electron a gtt:imprimer channel prints with the paper and orientation asked for and reports sent, cancelled, failed or unknown; an exception is logged to the console and never shown raw to the operator. Every file system, real or simulated, writes bytes atomically from a copy taken at the call. The sheet name carries event, date and imposition, and a failed print becomes IMPRESSION_ECHEC. Checked: red first, twelve mutations caught; 3502 node, 659 browser and 79 long tests green from the index alone. --- FR --- [ADD] plateforme : pont d'impression, octets atomiques, nom de planche Les vues imprimables et la planche sortent de l'application par la plateforme. Sous Electron, un canal gtt:imprimer imprime au papier et à l'orientation demandés et rend envoyé, annulé, échec ou inconnu ; une exception va à la console, jamais brute à l'opérateur. Chaque système de fichiers, réel ou simulé, écrit des octets de façon atomique, depuis une copie prise à l'appel. Le nom de la planche porte événement, date et imposition, et une impression échouée devient IMPRESSION_ECHEC. Vérifié : rouge d'abord, douze mutations attrapées ; 3502 node, 659 navigateur et 79 longues vertes depuis l'index seul. Assisted-by: Claude Opus 5.5
106 lines
4.9 KiB
JavaScript
106 lines
4.9 KiB
JavaScript
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
// L'impression de la coquille (§ 11.7, § 13.4), dans le processus
|
|
// principal : le gestionnaire du canal gtt:imprimer, que
|
|
// electron/preload.cjs expose à la page sous window.gtt.imprimer. La page
|
|
// n'y demande qu'un papier et une orientation ; le gestionnaire en compose
|
|
// lui-même les options de webContents.print — boîte de dialogue du système
|
|
// montrée, fonds imprimés, format de page du papier, paysage ou non — et
|
|
// refuse toute autre demande sans rien imprimer : une option de print
|
|
// brute venue de la page, une impression silencieuse vers une imprimante de
|
|
// son choix par exemple, ne passe pas, comme electron/fichiers.js n'admet
|
|
// qu'un chemin relatif à une racine connue.
|
|
//
|
|
// Le paquet n'emporte de src/ que la page construite (§ 14.15) : la table
|
|
// des papiers se recopie ici, et test/coquille.test.js éprouve que ses clés
|
|
// sont celles de PAPIERS (src/geometrie/papiers.js).
|
|
//
|
|
// L'issue rendue à la page est celle du rappel de print : envoyé quand il
|
|
// réussit, annulé quand sa raison est « cancelled », celle que donne une
|
|
// boîte de dialogue que l'opérateur ferme, échoué sinon, la raison pour
|
|
// cause. Un print qui lève — un contenu détruit entre la demande et
|
|
// l'impression — est un échec sans cause : son message, une exception du
|
|
// moteur, va à la console du processus principal, jamais à l'opérateur.
|
|
|
|
/** Le canal de l'impression, qu'invoque le préchargement. */
|
|
export const CANAL_IMPRESSION = 'gtt:imprimer';
|
|
|
|
/** Le format de page de webContents.print, par papier : les clés de PAPIERS. */
|
|
export const FORMATS_DE_PAGE = Object.freeze({ A4: 'A4', Lettre: 'Letter' });
|
|
|
|
// Les orientations d'une demande, et la valeur de landscape de chacune.
|
|
const PAYSAGE = new Map([
|
|
['portrait', false],
|
|
['paysage', true],
|
|
]);
|
|
|
|
// Les clés d'une demande, triées : papier et orientation, rien d'autre.
|
|
const CLES_DE_LA_DEMANDE = ['orientation', 'papier'];
|
|
|
|
// La raison qu'Electron donne au rappel d'une impression que l'opérateur
|
|
// annule.
|
|
const RAISON_ANNULEE = 'cancelled';
|
|
|
|
/**
|
|
* Les options de webContents.print d'une demande de la page.
|
|
*
|
|
* @param {unknown} demande {papier, orientation}, telle que l'IPC l'a copiée
|
|
* @returns {{silent: false, printBackground: true, pageSize: string, landscape: boolean}}
|
|
* @throws {TypeError} une demande qui n'est pas un objet aux seules clés
|
|
* papier et orientation, un papier hors de FORMATS_DE_PAGE, une
|
|
* orientation autre que portrait ou paysage
|
|
*/
|
|
export function optionsImpression(demande) {
|
|
if (demande === null || typeof demande !== 'object' || Array.isArray(demande)) {
|
|
throw new TypeError("impression : la demande n'est pas un objet");
|
|
}
|
|
const cles = Object.keys(demande).sort();
|
|
if (cles.length !== CLES_DE_LA_DEMANDE.length || cles.some((cle, rang) => cle !== CLES_DE_LA_DEMANDE[rang])) {
|
|
throw new TypeError(`impression : options refusées ${JSON.stringify(cles)}, seuls papier et orientation admis`);
|
|
}
|
|
const { papier, orientation } = demande;
|
|
if (typeof papier !== 'string' || !Object.hasOwn(FORMATS_DE_PAGE, papier)) {
|
|
throw new TypeError(`impression : papier inconnu ${JSON.stringify(papier)}`);
|
|
}
|
|
if (!PAYSAGE.has(orientation)) throw new TypeError(`impression : orientation inconnue ${JSON.stringify(orientation)}`);
|
|
return { silent: false, printBackground: true, pageSize: FORMATS_DE_PAGE[papier], landscape: PAYSAGE.get(orientation) };
|
|
}
|
|
|
|
/**
|
|
* Imprime un contenu de fenêtre selon une demande de la page.
|
|
*
|
|
* @param {{print: Function}} contenu le webContents de la page qui demande
|
|
* @param {unknown} demande
|
|
* @returns {Promise<{issue: 'envoye'|'annule'|'echec', cause: string|null}>}
|
|
* cause : la raison d'un échec, null sinon, et null pour un échec
|
|
* sans raison
|
|
* @throws {TypeError} la demande que refuse optionsImpression, en rejet, sans
|
|
* appeler print
|
|
*/
|
|
export async function imprimer(contenu, demande) {
|
|
const options = optionsImpression(demande);
|
|
return new Promise((resoudre) => {
|
|
try {
|
|
contenu.print(options, (succes, raison) => {
|
|
if (succes) resoudre({ issue: 'envoye', cause: null });
|
|
else if (raison === RAISON_ANNULEE) resoudre({ issue: 'annule', cause: null });
|
|
else resoudre({ issue: 'echec', cause: typeof raison === 'string' && raison !== '' ? raison : null });
|
|
});
|
|
} catch (erreur) {
|
|
console.error('gtt:imprimer', erreur);
|
|
resoudre({ issue: 'echec', cause: null });
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pose le gestionnaire du canal de l'impression : poser(canal,
|
|
* gestionnaire), ipcMain.handle dans le processus principal. Le
|
|
* gestionnaire imprime le contenu qui a envoyé la demande.
|
|
*
|
|
* @param {(canal: string, gestionnaire: Function) => void} poser
|
|
*/
|
|
export function servirImpression(poser) {
|
|
poser(CANAL_IMPRESSION, (evenement, demande) => imprimer(evenement.sender, demande));
|
|
}
|