Τεχνικό Άρθρο

Εξαγωγή εύρους κελιών Excel ως μία εικόνα με HotXLS

Μερικές φορές το παραδοτέο δεν είναι έγγραφο, είναι μια εικόνα ενός πίνακα. Ένα μπλοκ σύνοψης σε email κατάστασης, ένα αποδιδόμενο πάνελ KPI σε dashboard, μια μικρογραφία δίπλα σε αποτέλεσμα αναζήτησης: όλα θέλουν τα κελιά και κανένα δεν θέλει χαρτί. Η TXLSCellImageExporter στο HotXLS παίρνει ένα κλασικό ή XLSX ορθογώνιο κελιών και παράγει ένα συμπαγές PNG ή JPEG χωρίς μέγεθος σελίδας, χωρίς περιθώρια, χωρίς κεφαλίδες ή υποσέλιδα, χωρίς τίτλους εκτύπωσης και χωρίς αλλαγές σελίδας. Ανάλυση, κλίμακα, μορφή και ποιότητα JPEG είναι ρυθμίσιμα, objects, γραμμές πλέγματος και περιγράμματα κελιών έχουν ανεξάρτητους διακόπτες, το φόντο μπορεί να είναι χρώμα ή διαφάνεια, και η εγγραφή αρχείου περνά από ατομική αντικατάσταση στον ίδιο φάκελο που αφήνει υπάρχοντα στόχο ανέγγιχτο αν κάτι αποτύχει

Ο λόγος που αυτό χρειάζεται τον δικό του εξαγωγέα αντί για μια σημαία στη διαδρομή εκτύπωσης είναι ότι η σελιδοποίηση δεν είναι προαιρετικό στρώμα που μπορείτε να κλείσετε. Είναι το πράγμα για το οποίο υπάρχει ο αγωγός σελίδων

Γιατί όχι απόδοση του εύρους μέσω του αγωγού εκτύπωσης;

Επειδή ο αγωγός εκτύπωσης βάζει μια σελίδα ανάμεσα σε εσάς και τα κελιά. Το μέγεθος χαρτιού αποφασίζει πόσο χωράει, τα περιθώρια σπρώχνουν το περιεχόμενο προς τα μέσα, οι κεφαλίδες και τα υποσέλιδα καταλαμβάνουν ζώνες που δεν ζητήσατε, οι τίτλοι εκτύπωσης επαναλαμβάνουν γραμμές που έχετε ήδη, και οι αλλαγές σελίδας σχίζουν το εύρος. Ένα μπλοκ σύνοψης που τυχαίνει να απλώνεται πάνω σε αλλαγή βγαίνει ως δύο εικόνες με την ενδιαφέρουσα γραμμή κομμένη στα δύο. Μπορείτε να αντισταθμίσετε όλα αυτά ρυθμίζοντας προσαρμοσμένο μέγεθος σελίδας που ταιριάζει ακριβώς με το εύρος, και κόσμος το κάνει, αλλά αυτό σημαίνει επανυπολογισμό της γεωμετρίας χαρτιού κάθε φορά που αλλάζει το εύρος και αφήνει ούτως ή άλλως τη ζώνη κεφαλίδας και τη λογική τίτλων εκτύπωσης στη διαδρομή

Ο εξαγωγέας κελιών μετρά το ορθογώνιο, δεσμεύει ένα bitmap ακριβώς εκείνου του μεγέθους, ζωγραφίζει τα κελιά μέσα σε αυτό, και κωδικοποιεί. Δεν υπάρχει σελίδα, οπότε δεν υπάρχει τίποτα προς ρύθμιση εκτός. Για τις περιπτώσεις που θέλετε πράγματι χαρτί, η διαδρομή εξαγωγής PDF είναι το σωστό εργαλείο και καλύπτεται στο άρθρο για την εξαγωγή φύλλου σε PDF

Η TXLSCellImageExporter μετρά, ζωγραφίζει και κωδικοποιεί μία εικόνα ανά εύρος κελιών ενώ ο αγωγός εκτύπωσης σχίζει το εύρος στις αλλαγές σελίδας
Ο αγωγός σελίδων βάζει γεωμετρία χαρτιού ανάμεσα σε εσάς και τα κελιά· ο εξαγωγέας κελιών δεν έχει σελίδα πουθενά στη διαδρομή

Μετρήστε πριν αποδώσετε

Η Measure επιστρέφει τις διαστάσεις pixels που θα παρήγαν οι τρέχουσες ρυθμίσεις χωρίς να κωδικοποιήσει τίποτα. Αυτό μετράει για δύο λόγους. Ένα πρότυπο HTML ή email συνήθως χρειάζεται τις διαστάσεις της εικόνας πριν υπάρξει η εικόνα, ώστε να δεσμεύσει το κουτί και να αποφύγει μετατόπιση διάταξης. Και μια υπηρεσία που αποδίδει εύρη επιλεγμένα από χρήστες χρειάζεται τρόπο να απορρίψει παράλογο αίτημα πριν δεσμεύσει πόρους για αυτό

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;   // Το PNG κρατά λεπτές πινελιές καθαρές
      Exporter.DPI := 96;
      Exporter.Scale := 2.0;             // έξοδος πυκνότητας retina
      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 και H είναι πλέον γνωστά· δεσμεύστε το κουτί διάταξης
      // πριν την κωδικοποίηση
      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;

Budgets, επειδή η κλίμακα πολλαπλασιάζει

Τα MaxPixels και MaxBytes δεν είναι αμυντική διακόσμηση. Ο αριθμός pixels μεγαλώνει με το τετράγωνο του συντελεστή κλίμακας και με το τετράγωνο του λόγου ανάλυσης, οπότε ένα εύρος που είναι λογικά 1200 επί 800 στα 96 DPI γίνεται περίπου 47 megapixels στα 600 DPI, και ένας χρήστης που επιλέγει ολόκληρη χρησιμοποιούμενη περιοχή αντί για μπλοκ σύνοψης προσθέτει άλλη μια τάξη μεγέθους από πάνω. Χωρίς όριο η λειτουργία αστοχίας είναι μια δέσμευση που η διεργασία δεν μπορεί να ικανοποιήσει, που ρίχνει κάτω ό,τι άλλο έκανε εκείνη η διεργασία

Με όριο το αίτημα αποτυγχάνει και ο καλών διαλέγει: απόρριψη, μείωση κλίμακας, ή στένεμα του εύρους. Αυτή είναι πολύ καλύτερη θέση για έναν διακομιστή αναφορών, και είναι ο ίδιος συλλογισμός πίσω από τα ρητά budgets στον αποκωδικοποιητή metafile που περιγράφεται στο άρθρο για τον bounded αποκωδικοποιητή EMF και WMF

Ροή budget για TXLSCellImageExporter στο HotXLS: η Measure επιστρέφει πρώτα το μέγεθος pixels, και μετά MaxPixels και MaxBytes οριοθετούν δέσμευση και μέγεθος εξόδου
Η απόρριψη συμβαίνει πριν τη δέσμευση, και μια αστοχία budget bytes αφήνει την προηγούμενη εικόνα ανέγγιχτη για τον καλούντα

Ατομική αντικατάσταση, και γιατί μετράει ο φάκελος

Η Save σε όνομα αρχείου δεν γράφει μέσα στον στόχο. Γράφει ένα προσωρινό αρχείο στον ίδιο φάκελο, κωδικοποιεί μέσα σε αυτό, και μόνο τότε αντικαθιστά τον στόχο. Αν η κωδικοποίηση αποτύχει, αν το budget ξεπεραστεί στη μέση, ή αν η διεργασία σκοτωθεί, η προηγούμενη εικόνα είναι ακόμη εκεί και ακόμη έγκυρη. Ένα dashboard που αναπαράγει τα tiles του σε πρόγραμμα επομένως δεν δείχνει ποτέ περικομμένο PNG, που είναι το συνηθισμένο σύμπτωμα μιας αφελούς εγγραφής που ανοίγει τον προορισμό και αρχίζει να στέλνει ροή

Η λεπτομέρεια του ίδιου φακέλου δεν είναι τυχαία. Μια ατομική αντικατάσταση είναι ατομική μόνο μέσα σε έναν τόμο, επειδή μεταξύ τόμων το λειτουργικό σύστημα πρέπει να αντιγράψει και μετά να διαγράψει, που επανεισάγει το παράθυρο που προσπαθούσατε να κλείσετε. Κάθε υλοποίηση αυτού του μοτίβου που βάζει το προσωρινό αρχείο της στον προσωρινό κατάλογο συστήματος δεν είναι ατομική σε ένα μηχάνημα όπου η έξοδος ζει σε διαφορετικό δίσκο

Η Save της TXLSCellImageExporter κωδικοποιεί σε προσωρινό αρχείο στον ίδιο φάκελο και μετά αντικαθιστά τον στόχο ατομικά· οι αστοχίες αφήνουν την προηγούμενη εικόνα έγκυρη
Το προσωρινό αρχείο πρέπει να ζει δίπλα στον στόχο επειδή μια ατομική αντικατάσταση δουλεύει μόνο μέσα σε έναν τόμο

Τα συμβάντα paint ζωγραφίζουν στο πραγματικό canvas

Και ο εξαγωγέας εύρους και ο εξαγωγέας σελίδων εκθέτουν συμβάντα paint leading και trailing, και αυτά λαμβάνουν ένα πλήρες context μόνο για ανάγνωση και όχι απλώς handle canvas. Το TXLSPagePaintContext κουβαλά το ζωντανό canvas, τα όρια pixels, το μέγεθος σελίδας σε points, την ανάλυση και την κλίμακα που χρησιμοποιούνται πραγματικά, τον αριθμό σελίδας εγγράφου, τον αριθμό σελίδας εντός φύλλου, το σύνολο σελίδων, το όνομα φύλλου και το φύλλο προέλευσης τόσο σε κλασική όσο και σε XLSX εκδοχή. Αυτό φτάνει για να ζωγραφίσει κανείς ένα υδατογράφημα που κλιμακώνεται σωστά, ή μια σφραγίδα σελίδας που ξέρει πού βρίσκεται στην εκτέλεση

procedure TReportJob.StampDraft(Sender: TObject;
  const AContext: TXLSPagePaintContext);
begin
  // Ευαίσθητο σε κλίμακα, ώστε η σφραγίδα να μοιάζει ίδια σε 1x και 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;

Τρεις συμπεριφορές αξίζει να βασιστείτε. Τα συμβάντα ενεργοποιούνται ακριβώς μία φορά ανά αποδιδόμενο καρέ, συμπεριλαμβανομένου κάθε καρέ ενός multipage TIFF, οπότε ένας μετρητής που αυξάνεται στον χειριστή είναι αξιόπιστος. Μένουν σιωπηλοί κατά τη μέτρηση, οπότε ένας χειριστής με παρενέργεια δεν τρέχει δύο φορές για μία έξοδο. Και αν το leading συμβάν προκαλέσει εξαίρεση, το trailing δεν ενεργοποιείται και δεν γράφονται μερικά bytes εικόνας, οπότε μια εξαίρεση στον δικό σας κώδικα ζωγραφικής δεν μπορεί να παράγει μισό-σφραγισμένο αρχείο

Επιλογή της μορφής

PNG για οτιδήποτε πλούσιο σε κείμενο. Το JPEG εφαρμόζει μετασχηματισμό block που παράγει ορατό ringing γύρω από λεπτές πινελιές υψηλής αντίθεσης, που είναι ακριβώς τα περιγράμματα κελιών και το μικρό κείμενο, και τα τεχνουργήματα επιβιώνουν σε ρυθμίσεις ποιότητας όπου μια φωτογραφία φαίνεται τέλεια. Το JPEG κερδίζει τη θέση του όταν το εύρος κυριαρχείται από ενσωματωμένες φωτογραφίες και το μέγεθος αρχείου μετράει περισσότερο από την πιστότητα ακμών. Τα διαφανή φόντα απαιτούν PNG, αφού το JPEG δεν έχει κανάλι alpha, οπότε ένα tile που προορίζεται να καθίσει σε χρωματιστή επιφάνεια έχει κάνει την επιλογή για εσάς

Αν το εύρος σας περιέχει συγχωνευμένα κελιά, ελέγξτε την έξοδο απέναντι στο φύλλο: οι συγχωνευμένες περιοχές αλληλεπιδρούν με τα πλάτη στηλών με τρόπους που εκπλήσσουν κόσμο, και οι κανόνες διάταξης καλύπτονται στο άρθρο για συγχωνευμένα κελιά και πρότυπα αναφορών. Το HotXLS διαβάζει και γράφει XLS, XLSX, ODS και CSV από Delphi και C++Builder χωρίς εξάρτηση Excel, και η πλήρης επιφάνεια του εξαγωγέα τεκμηριώνεται στη σελίδα προϊόντος HotXLS Delphi spreadsheet component