Article technique

Exporter une plage de cellules Excel en image avec HotXLS

Parfois le livrable n'est pas un document, c'est l'image d'un tableau. Un bloc récapitulatif dans un courriel d'état, un panneau KPI rendu dans un tableau de bord, une vignette à côté d'un résultat de recherche : tous veulent les cellules et aucun ne veut du papier. TXLSCellImageExporter dans HotXLS prend un rectangle de cellules classique ou XLSX et produit un PNG ou JPEG compact unique sans taille de page, sans marges, sans en-têtes ni pieds de page, sans titres d'impression et sans sauts de page. La résolution, l'échelle, le format et la qualité JPEG sont configurables, les objets, les filets de grille et les bordures de cellules ont des interrupteurs indépendants, l'arrière-plan peut être une couleur ou transparent, et l'écriture du fichier passe par un remplacement atomique dans le même dossier qui laisse une cible existante intacte si quelque chose échoue

La raison pour laquelle cela exige son propre exportateur plutôt qu'un drapeau sur la voie d'impression est que la pagination n'est pas une couche optionnelle que l'on peut désactiver. C'est la raison d'être du pipeline de pages

Pourquoi ne pas rendre la plage par le pipeline d'impression ?

Parce que le pipeline d'impression insère une page entre vous et les cellules. La taille de papier décide de ce qui tient, les marges poussent le contenu vers l'intérieur, les en-têtes et pieds de page occupent des bandes que vous n'avez pas demandées, les titres d'impression répètent des lignes que vous avez déjà, et les sauts de page coupent la plage. Un bloc récapitulatif qui se trouve à cheval sur un saut sort en deux images avec la ligne intéressante coupée en deux. Vous pouvez compenser tout cela en configurant une taille de page personnalisée qui correspond exactement à la plage, et des gens le font, mais cela signifie recalculer la géométrie de papier à chaque changement de plage et cela laisse quand même la bande d'en-tête et la logique de titres d'impression dans le chemin

L'exportateur de cellules mesure le rectangle, alloue un bitmap d'exactement cette taille, dessine les cellules dedans, et encode. Il n'y a pas de page, donc il n'y a rien à configurer pour faire disparaître. Pour les cas où vous voulez vraiment du papier, la voie d'export PDF est le bon outil et est couverte dans l'article sur l'export PDF de feuille de calcul

TXLSCellImageExporter mesure, dessine et encode une image par plage de cellules tandis que le pipeline d'impression coupe la plage aux sauts de page
Le pipeline de pages insère une géométrie de papier entre vous et les cellules ; l'exportateur de cellules n'a aucune page nulle part dans le chemin

Mesurez avant de rendre

Measure renvoie les dimensions en pixels que les réglages courants produiraient sans rien encoder. Cela compte pour deux raisons. Un modèle HTML ou de courriel a généralement besoin des dimensions de l'image avant que l'image existe, pour pouvoir réserver la boîte et éviter le décalage de mise en page. Et un service qui rend des plages sélectionnées par l'utilisateur a besoin d'un moyen de refuser une requête absurde avant d'allouer pour elle

uses
  lxHandleX, lxPagination;

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Exporter: TXLSCellImageExporter;
  Summary: TXLSXRange;
  W, H, Bytes: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarter.xlsx');
    Sheet := Book.Sheets.ByPos[0];
    Summary := Sheet.Range['A1:F20'];

    Exporter := TXLSCellImageExporter.Create;
    try
      Exporter.ImageFormat := xpifPng;   // le PNG garde les traits fins nets
      Exporter.DPI := 96;
      Exporter.Scale := 2.0;             // sortie de densité rétine
      Exporter.IncludeGridlines := False;
      Exporter.IncludeCellBorders := True;
      Exporter.TransparentBackground := True;
      Exporter.MaxPixels := 40 * 1000 * 1000;
      Exporter.MaxBytes := 8 * 1024 * 1024;

      if not Exporter.Measure(Summary, W, H) then
        raise Exception.Create('range exceeds the configured budget');
      // W et H sont maintenant connus ; réservez la boîte de mise en page avant d'encoder
      Bytes := Exporter.Save(Summary, 'summary.png');
      if Bytes <= 0 then
        raise Exception.Create('image export failed, previous file kept');
    finally
      Exporter.Free;
    end;
  finally
    Book.Free;
  end;
end;

Des budgets, parce que l'échelle multiplie

MaxPixels et MaxBytes ne sont pas de la décoration défensive. Le compte de pixels croît avec le carré du facteur d'échelle et avec le carré du rapport de résolution, donc une plage raisonnable de 1200 sur 800 à 96 DPI devient environ 47 mégapixels à 600 DPI, et un utilisateur qui sélectionne toute la plage utilisée au lieu d'un bloc récapitulatif ajoute encore un ordre de grandeur par-dessus. Sans plafond, le mode de défaillance est une allocation que le processus ne peut pas satisfaire, ce qui fait tomber tout le reste de ce que faisait ce processus

Avec un plafond, la requête échoue et l'appelant peut choisir : refuser, réduire l'échelle, ou restreindre la plage. C'est une bien meilleure position pour un serveur de rapports, et c'est le même raisonnement derrière les budgets explicites du décodeur de métafichier décrit dans l'article sur le décodeur borné EMF et WMF

Flux de budgets pour TXLSCellImageExporter dans HotXLS : Measure renvoie d'abord la taille en pixels, puis MaxPixels et MaxBytes bornent l'allocation et la taille de sortie
Le refus a lieu avant l'allocation, et un échec de budget en octets laisse l'image précédente intacte pour l'appelant

Remplacement atomique, et pourquoi le dossier compte

Save vers un nom de fichier n'écrit pas dans la cible. Il écrit un fichier temporaire dans le même dossier, encode dedans, et seulement ensuite remplace la cible. Si l'encodage échoue, si le budget est dépassé en cours de route, ou si le processus est tué, l'image précédente est toujours là et toujours valide. Un tableau de bord qui régénère ses tuiles sur un calendrier ne montre donc jamais un PNG tronqué, qui est le symptôme habituel d'une écriture naïve qui ouvre la destination et commence à diffuser

Le détail du même dossier n'est pas fortuit. Un remplacement atomique n'est atomique qu'au sein d'un seul volume, car entre volumes le système d'exploitation doit copier puis supprimer, ce qui réintroduit la fenêtre que vous essayiez de fermer. Toute implémentation de ce schéma qui place son fichier temporaire dans le répertoire temporaire système n'est pas atomique sur une machine où la sortie vit sur un autre disque

Le Save de TXLSCellImageExporter encode dans un fichier temporaire du même dossier, puis remplace la cible atomiquement ; les échecs laissent l'image précédente valide
Le fichier temporaire doit vivre à côté de la cible car un remplacement atomique ne fonctionne qu'au sein d'un seul volume

Les événements de peinture dessinent sur le vrai canvas

L'exportateur de plages comme l'exportateur de pages exposent des événements de peinture de début et de fin, et ils reçoivent un contexte complet en lecture seule plutôt qu'un simple handle de canvas. TXLSPagePaintContext porte le canvas vivant, les bornes en pixels, la taille de page en points, la résolution et l'échelle réellement utilisées, le numéro de page du document, le numéro de page dans la feuille, le compte total de pages, le nom de la feuille et la feuille de calcul d'origine en saveurs classique et XLSX. C'est assez pour dessiner un filigrane qui s'échelle correctement, ou un tampon de page qui sait où il en est dans la série

procedure TReportJob.StampDraft(Sender: TObject;
  const AContext: TXLSPagePaintContext);
begin
  // Conscient de l'échelle, donc le tampon se ressemble à 1x et 3x
  AContext.Canvas.Font.Height := Round(-48 * AContext.Scale);
  AContext.Canvas.Font.Color := clSilver;
  AContext.Canvas.Brush.Style := bsClear;
  AContext.Canvas.TextOut(AContext.Bounds.Left + Round(24 * AContext.Scale),
    AContext.Bounds.Top + Round(24 * AContext.Scale), 'DRAFT');
end;

Exporter.AfterPaint := Job.StampDraft;

Trois comportements valent la peine d'être exploités. Les événements se déclenchent exactement une fois par trame rendue, y compris chaque trame d'un TIFF multipage, donc un compteur incrémenté dans le gestionnaire est fiable. Ils restent silencieux pendant la mesure, donc un gestionnaire avec effet de bord ne tourne pas deux fois pour une sortie. Et si l'événement de début lève, l'événement de fin ne se déclenche pas et aucun octet partiel d'image n'est écrit, donc une exception dans votre propre code de dessin ne peut pas produire un fichier à moitié tamponné

Choisir le format

PNG pour tout ce qui est riche en texte. Le JPEG applique une transformation par blocs qui produit un artefact de franges visible autour des traits fins à fort contraste, qui est exactement ce que sont les bordures de cellules et le petit texte, et les artefacts survivent à des réglages de qualité où une photographie est parfaite. Le JPEG mérite sa place quand la plage est dominée par des photographies intégrées et que la taille de fichier compte plus que la fidélité des bords. Les arrière-plans transparents exigent le PNG, puisque le JPEG n'a pas de canal alpha, donc une tuile destinée à poser sur une surface colorée a fait le choix pour vous

Si votre plage contient des cellules fusionnées, vérifiez la sortie contre la feuille : les régions fusionnées interagissent avec les largeurs de colonnes de manières qui surprennent, et les règles de mise en page sont couvertes dans l'article sur les cellules fusionnées et les modèles de rapport. HotXLS lit et écrit XLS, XLSX, ODS et CSV depuis Delphi et C++Builder sans aucune dépendance Excel, et la surface complète de l'exportateur est documentée sur la page produit du HotXLS Delphi spreadsheet component