Intégration de la visualisation de documents PDF en Go

Cet article décrit comment implémenter un visualiseur de documents en ligne dans une application Go, en se concentrant sur la gestion des fichiers PDF. Il détaille les étapes de configuration d'un environnement Ubuntu, l'installation des dépendances nécessaires comme LibreOffice et un environnement Java, et la gestion des polices pour un affichage correct des caractères.

Prérequis et Installation

Pour un fonctionnement optimal, les éléments suivants sont requis :

  • LibreOffice : Nécessaire pour la conversion de divers formats de documents (doc, docx, xlsx, ppt, pptx) vers des formats intermédiaires ou finaux comme le PDF ou le HTML. ```

    sudo apt-get update sudo apt-get upgrade sudo apt-get install libreoffice-common

  • Environnement Java : Indispensable pour que LibreOffice puisse effectuer les conversions. ```

    apt install openjdk-8-jre-headless

  • Composants LibreOffice : Pour prendre en charge des types de fichiers spécifiques, les modules correspondants doivent être installés. ```

    sudo apt-get install libreoffice-writer sudo apt-get install libreoffice-calc sudo apt-get install libreoffice-impress

  • Polices de caractères : Pour un affichage correct des caractères chinois, il est nécessaire d'ajouter des paquets de polices au répertoire /usr/share/fonts/, incluant au minimum des polices comme "Microsoft YaHei" (微软雅黑).

Implémentation Serveur Go

Le serveur Go utilise le framework Gin pour gérer les requêtes HTTP. Il expose deux points d'accès principaux :

  • /pdf : Ce endpoint sert le fichier HTML qui contient le code pour afficher le PDF. Il lit le template HTML, remplace un placeholder par l'URL du service de visualisation du PDF, puis renvoie le contenu HTML au client.
  • /view/img : Ce endpoint est responsable de la lecture et de l'envoi du fichier PDF demandé. Il prend l'URL du fichier PDF en tant que paramètre de requête, lit le fichier depuis le système de fichiers et le renvoie. En cas d'erreur de lecture, un message d'erreur est retourné.

package main

import (
	"io/ioutil"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// Endpoint pour servir le visualiseur HTML du PDF
	router.GET("/pdf", func(c *gin.Context) {
		// Lecture du template HTML
		htmlBytes, err := ioutil.ReadFile("./view-pdf/pdf/pdf.html")
		if err != nil {
			c.AbortWithStatusJSON(500, gin.H{"error": "Failed to read HTML template"})
			return
		}
		htmlContent := string(htmlBytes)

		// Préparation de l'URL pour le service de visualisation du PDF
		pdfViewerURL := "/view/img?url=testpdf.pdf" // Exemple: chemin vers le PDF
		htmlContent = strings.Replace(htmlContent, "{{url}}", pdfViewerURL, -1)

		// Envoi de la réponse HTML
		c.Header("Content-Length", strconv.Itoa(len(htmlContent)))
		c.Header("Content-Type", "text/html;charset=UTF-8")
		c.Writer.Write([]byte(htmlContent))
	})

	// Endpoint pour servir le fichier PDF
	router.GET("/view/img", func(c *gin.Context) {
		pdfPath := c.Query("url")
		if pdfPath == "" {
			c.AbortWithStatusJSON(400, gin.H{"error": "URL parameter is missing"})
			return
		}

		// Construction du chemin complet du fichier
		fullPath := "./" + pdfPath // Assurez-vous que ce chemin est sûr et contrôlé

		// Lecture du fichier PDF
		pdfData, err := ioutil.ReadFile(fullPath)
		if err != nil {
			c.Header("Content-Length", strconv.Itoa(len("404")))
			c.Header("Content-Type", "text/html;charset=UTF-8")
			c.Writer.Write([]byte("Une erreur est survenue lors de la récupération du fichier PDF."))
			return
		}

		// Envoi du contenu du fichier PDF
		c.Header("Content-Length", strconv.Itoa(len(pdfData)))
		c.Writer.Write(pdfData)
	})

	// Lancement du serveur sur le port 8080
	router.Run(":8080")
}

Template HTML pour la Visualisation

Le fichier pdf.html utilise la bibliothèque PDFObject pour intégrer le PDF dans la page web. Ce script gère la détection des capacités du navigateur et utilise soit un élément <embed> natif, soit une iframe avec PDF.js comme solution de repli pour les navigateurs moins compatibles.


<html>
<head>
    <title>Visualiseur de PDF Go</title>
    <style>
        .pdf {
            height: 30rem;
            border: 1rem solid rgba(0, 0, 0, .1);
        }
    </style>
</head>
<body>
    <div id="pdf"></div>
</body>
<script src="/js/pdfobject.js"></script>
<script>
/*
    PDFObject v2.1.1
    Licence MIT
*/

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.PDFObject = factory();
  }
}(this, function () {

    "use strict";

    if(typeof window === "undefined" || typeof navigator === "undefined"){ return false; }

    var pdfobjectversion = "2.1.1",
        ua = window.navigator.userAgent,
        supportsPDFs,
        isIE,
        supportsPdfMimeType = (typeof navigator.mimeTypes['application/pdf'] !== "undefined"),
        supportsPdfActiveX,
        isModernBrowser = (typeof window.Promise !== "undefined"),
        isFirefox = (/firefox/i.test(ua)),
        isFirefoxWithPDFJS = (isFirefox && parseInt(ua.split("rv:")[1].split(".")[0], 10) > 18),
        isIOS = (/iphone|ipad|ipod/i.test(ua.toLowerCase())),
        createAXO,
        buildFragmentString,
        log,
        embedError,
        embed,
        getTargetElement,
        generatePDFJSiframe,
        generateEmbedElement;

    createAXO = function (type){
        var ax;
        try { ax = new ActiveXObject(type); } catch (e) { ax = null; }
        return ax;
    };

    isIE = function (){ return !!(window.ActiveXObject || "ActiveXObject" in window); };
    supportsPdfActiveX = function (){ return !!(createAXO("AcroPDF.PDF") || createAXO("PDF.PdfCtrl")); };

    supportsPDFs = (!isIOS && (isFirefoxWithPDFJS || supportsPdfMimeType || (isIE() && supportsPdfActiveX())));

    buildFragmentString = function(pdfParams){
        var string = "";
        if(pdfParams){
            for (var prop in pdfParams) {
                if (pdfParams.hasOwnProperty(prop)) {
                    string += encodeURIComponent(prop) + "=" + encodeURIComponent(pdfParams[prop]) + "&";
                }
            }
            if(string){ string = "#" + string.slice(0, -1); }
        }
        return string;
    };

    log = function (msg){ if(typeof console !== "undefined" && console.log){ console.log("[PDFObject] " + msg); } };
    embedError = function (msg){ log(msg); return false; };

    getTargetElement = function (targetSelector){
        var targetNode = document.body;
        if(typeof targetSelector === "string") { targetNode = document.querySelector(targetSelector); } 
        else if (typeof jQuery !== "undefined" && targetSelector instanceof jQuery && targetSelector.length) { targetNode = targetSelector.get(0); } 
        else if (typeof targetSelector.nodeType !== "undefined" && targetSelector.nodeType === 1) { targetNode = targetSelector; }
        return targetNode;
    };

    generatePDFJSiframe = function (targetNode, url, pdfOpenFragment, PDFJS_URL, id){
        var fullURL = PDFJS_URL + "?file=" + encodeURIComponent(url) + pdfOpenFragment;
        var scrollfix = (isIOS) ? "overflow-y: scroll; " : "overflow: hidden; ";
        var iframe = "<div style="" + scrollfix + "position: absolute; top: 0; right: 0; bottom: 0; left: 0;"><iframe frameborder="0" id="" src="" + fullURL + "" style="border: none; width: 100%; height: 100%;"></iframe></div>";
        targetNode.className += " pdfobject-container";
        targetNode.style.position = "relative";
        targetNode.style.overflow = "auto";
        targetNode.innerHTML = iframe;
        return targetNode.getElementsByTagName("iframe")[0];
    };

    generateEmbedElement = function (targetNode, targetSelector, url, pdfOpenFragment, width, height, id){
        var style = "";
        if(targetSelector && targetSelector !== document.body) { style = "width: " + width + "; height: " + height + ";"; } 
        else { style = "position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%;"; }
        targetNode.className += " pdfobject-container";
        targetNode.innerHTML = "<embed class="pdfobject" id="" src="" + url + pdfOpenFragment + "" style="overflow: auto; " + style + "" type="application/pdf"></embed>";
        return targetNode.getElementsByTagName("embed")[0];
    };

    embed = function(url, targetSelector, options){
        if(typeof url !== "string"){ return embedError("URL is not valid"); }
        targetSelector = (typeof targetSelector !== "undefined") ? targetSelector : false;
        options = (typeof options !== "undefined") ? options : {};

        var id = (options.id && typeof options.id === "string") ? "id='" + options.id + "'" : "",
            page = (options.page) ? options.page : false,
            pdfOpenParams = (options.pdfOpenParams) ? options.pdfOpenParams : {},
            fallbackLink = (typeof options.fallbackLink !== "undefined") ? options.fallbackLink : true,
            width = (options.width) ? options.width : "100%",
            height = (options.height) ? options.height : "100%",
            assumptionMode = (typeof options.assumptionMode === "boolean") ? options.assumptionMode : true,
            forcePDFJS = (typeof options.forcePDFJS === "boolean") ? options.forcePDFJS : false,
            PDFJS_URL = (options.PDFJS_URL) ? options.PDFJS_URL : false,
            targetNode = getTargetElement(targetSelector),
            fallbackHTML = "",
            pdfOpenFragment = "",
            fallbackHTML_default = "<p>Ce navigateur ne supporte pas les PDF en ligne. Veuillez télécharger le PDF pour le consulter : <a href="[url]">Télécharger le PDF</a></p>";

        if(!targetNode){ return embedError("L'élément cible ne peut être déterminé"); }
        if(page){ pdfOpenParams.page = page; }
        pdfOpenFragment = buildFragmentString(pdfOpenParams);

        if(forcePDFJS && PDFJS_URL){
            return generatePDFJSiframe(targetNode, url, pdfOpenFragment, PDFJS_URL, id);
        } else if(supportsPDFs || (assumptionMode && isModernBrowser && !isIOS)){
            return generateEmbedElement(targetNode, targetSelector, url, pdfOpenFragment, width, height, id);
        } else if(PDFJS_URL){
            return generatePDFJSiframe(targetNode, url, pdfOpenFragment, PDFJS_URL, id);
        } else {
            if(fallbackLink){
                fallbackHTML = (typeof fallbackLink === "string") ? fallbackLink : fallbackHTML_default;
                targetNode.innerHTML = fallbackHTML.replace(/\[url\]/g, url);
            }
            return embedError("Ce navigateur ne supporte pas les PDF intégrés");
        }
    };

    return {
        embed: function (a,b,c){ return embed(a,b,c); },
        pdfobjectversion: pdfobjectversion,
        supportsPDFs: supportsPDFs
    };

}));
</script>
<script>PDFObject.embed("{{url}}", "#pdf");</script>
</html>

Ce script est appelé par le serveur Go pour intégrer dynamiquement le PDF spécifié dans l'élément div avec l'ID "pdf".

Étiquettes: Go gin libreoffice PDF document-viewer

Publié le 11 juin à 21h45