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

Γεωμετρία εικόνων HotXLS στο Delphi: EMU, cm και κλιμάκωση

Ρίχνετε ένα λογότυπο 600×400 pixel στην κεφαλίδα ενός τιμολογίου που δημιουργείται, φαίνεται σωστό στην οθόνη ανάπτυξης 96 DPI, και μια εβδομάδα αργότερα ένας πελάτης με laptop υψηλού DPI αναφέρει ότι τυπώνεται στο μέγεθος γραμματοσήμου. Τα pixels δεν άλλαξαν ποτέ. Αυτό που άλλαξε είναι η υπόθεση ότι ο αριθμός pixels σημαίνει φυσικό μέγεθος, και στο OOXML δεν σημαίνει. Μια εικόνα φύλλου εργασίας αποθηκεύει τις διαστάσεις της σε EMU, και μέχρι να σκέφτεστε σε EMU ή στις πραγματικές μονάδες που αντιστοιχούν καθαρά σε αυτό, η διάταξή σας εξαρτάται από το DPI που υποθέτει όποια μηχανή απόδοσης την ανοίγει

Το HotXLS είναι ένα εγγενές στοιχείο VCL για υπολογιστικά φύλλα σε Delphi και C++Builder που διαβάζει και γράφει XLS και XLSX χωρίς το Excel ή κάποια εξάρτηση COM. Από την έκδοση v2.91.0 το αντικείμενο εικόνας XLSX σταματά να σας βάζει να κάνετε τη μαθηματική πράξη των μονάδων με το χέρι: μαζί με το ακατέργαστο EMU εκθέτει πλάτος και ύψος σε εκατοστά, ίντσες και σημεία, καθώς και μια Scale μέθοδο που αλλάζει το μέγεθος κατά ποσοστό με προαιρετικό κλείδωμα λόγου διαστάσεων. Αυτό το άρθρο εξηγεί τι είναι πραγματικά το EMU, γιατί το DrawingML το επέλεξε και πώς να χρησιμοποιήσετε τη νέα επιφάνεια γεωμετρίας για να τοποθετείτε εικόνες με βάση το φυσικό τους μέγεθος και όχι έναν αριθμό pixels που δεν μπορείτε να εμπιστευτείτε

Τι είναι το EMU και γιατί το χρησιμοποιεί το DrawingML

Το EMU σημαίνει English Metric Unit και είναι η βασική μονάδα μήκους του DrawingML, του επιπέδου σχεδίασης που μοιράζεται ολόκληρη η οικογένεια Office Open XML (ECMA-376, Part 1, §20). Ένα EMU ορίζεται έτσι ώστε να υπάρχουν ακριβώς 914400 EMU ανά ίντσα και 360000 EMU ανά εκατοστό. Αυτές οι δύο σταθερές είναι ολόκληρος ο λόγος ύπαρξης της μονάδας. Το 914400 διαιρείται με 2, 3, 4, 5, 6, 8, 9, 10, 12 και πολλά ακόμη· παραγοντοποιείται ως 26 × 32 × 52 × 127. Επειδή 1 ίντσα = 2,54 cm ακριβώς, η επιλογή μιας μονάδας που διαιρείται τόσο με το 360000 όσο και με ένα καθαρό κλάσμα του 914400 επιτρέπει στη μορφή να εκφράζει ίντσες, εκατοστά και σημεία ως ακέραιους αριθμούς χωρίς στρογγυλοποίηση στο όριο της μονάδας. Εκεί όπου ένας αριθμός κινητής υποδιαστολής "1.27 cm" θα αποκλίνει, το EMU αποθηκεύει 457200 και παραμένει ακριβές

Η άλλη μονάδα που μετρά εδώ είναι το σημείο. Ένα τυπογραφικό σημείο είναι 1/72 της ίντσας, άρα υπάρχουν 12700 EMU ανά σημείο (914400 / 72). Τα σημεία είναι ο τρόπος με τον οποίο το ίδιο το Excel σκέφτεται το ύψος γραμμών, τα μεγέθη γραμματοσειρών και τα περιθώρια στο εσωτερικό του, γι' αυτό η έκθεση της γεωμετρίας εικόνας σε σημεία είναι χρήσιμη όταν θέλετε μια εικόνα να ευθυγραμμιστεί με τις μετρικές του κειμένου και όχι με έναν τυπωμένο χάρακα. Το HotXLS κωδικοποιεί όλες τις τέσσερις σχέσεις ως σταθερές μονάδων στη βιβλιοθήκη:

const
  XlsxEmuPerInch  = 914400;  // 1 inch
  XlsxEmuPerCm    = 360000;  // 1 centimetre
  XlsxEmuPerPoint = 12700;   // 1 point (1/72 inch)
  XlsxEmuPerPixel = 9525;    // 1 pixel at 96 DPI (914400 / 96)

Αυτή η τελευταία γραμμή είναι η ουσία του σφάλματος με το γραμματόσημο. Ένα pixel αποκτά φυσικό μέγεθος μόνο όταν ορίσετε ένα DPI, και τα 9525 EMU είναι το μέγεθος ενός pixel στα 96 DPI συγκεκριμένα. Το προεπιλεγμένο DPI απόδοσης του Excel είναι 96, άρα μια εικόνα 100 pixels καταλήγει στα 100 × 9525 = 952500 EMU ≈ 2,54 cm σε προεπιλεγμένη ρύθμιση, αλλά τίποτα στο αρχείο δεν εγγυάται ότι ο καταναλωτής χρησιμοποιεί 96. Αν γράφετε σε πραγματικές μονάδες, αυτή η ασάφεια εξαφανίζεται: 4 cm είναι 4 cm είτε η οθόνη είναι 96 είτε 220 DPI

Η επιφάνεια γεωμετρίας του TXLSXImage

Μια ενσωματωμένη εικόνα στο HotXLS είναι ένα TXLSXImage. Η κανονική αποθήκευσή της είναι δύο ακέραια πεδία, WidthEMU και HeightEMU, αγκυρωμένο σε ένα one-based Row και Col (το επάνω αριστερά κελί από το οποίο κρέμεται η εικόνα). Οι ιδιότητες σε πραγματικές μονάδες είναι υπολογιζόμενες προβολές πάνω σε αυτά τα πεδία EMU, όχι ξεχωριστή κατάσταση, η ανάγνωση WidthCM διαιρεί το EMU με το 360000, και η εγγραφή του το πολλαπλασιάζει και το στρογγυλοποιεί ξανά. Άρα κάθε διάσταση που ορίζετε είναι απλώς μια διαφορετική διατύπωση της ίδιας υποκείμενης τιμής EMU:

  • WidthInch / HeightInch - EMU ÷ 914400
  • WidthCM / HeightCM - EMU ÷ 360000
  • WidthPt / HeightPt - EMU ÷ 12700
  • WidthEMU / HeightEMU - η πηγή αλήθειας των ακεραίων

Προσθέτετε μια εικόνα με AddImage(ARow, ACol, AData, AFormat), περνώντας τα ακατέργαστα κωδικοποιημένα bytes και ένα TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, or xlsxImageBmp); επιστρέφει τον δείκτη με μηδενική βάση στη συλλογή του φύλλου εργασίαςImages. Υπάρχει επίσης AddImageFromFile(ARow, ACol, AFileName), η οποία εξάγει τη μορφή από την επέκταση του αρχείου. Προσέξτε τη βάση του δείκτη: AddImage επιστρέφει με μηδενική βάση και Images[] είναι με μηδενική βάση, κάτι που αποτελεί εσκεμμένη αντίθεση με το πλέγμα Cells[Row, Col] με βάση το 1, οπότε μην υποθέτετε ότι τα δύο συμφωνούν

var
  Sheet: TXLSXWorksheet;
  Img: TXLSXImage;
  Idx: Integer;
begin
  Sheet := Workbook.Sheets.Add('Images');

  // Anchor a PNG at row 3, column 2; AddImage returns a 0-based index.
  Idx := Sheet.AddImage(3, 2, LogoBytes, xlsxImagePng);

  Img := Sheet.Images[Idx];
  Img.WidthCM := 4.0;    // 4 cm wide  -> 1440000 EMU
  Img.HeightCM := 3.0;   // 3 cm tall  -> 1080000 EMU

  // Same geometry, read back in other units.
  // Img.WidthPt  is now 113.39 pt, Img.WidthInch is 1.5748 in.
end;

Μια εικόνα που δημιουργείται μόλις τώρα έχει προεπιλογή 100×100 pixels, δηλαδή τετράγωνο 952500 EMU, περίπου ένα πλαίσιο 2.54 cm στα 96 DPI. Αυτή η προεπιλογή υπάρχει ώστε μια εικόνα να είναι ορατή ακόμα κι αν ξεχάσετε να ορίσετε μέγεθος, αλλά για οποιαδήποτε πραγματική διάταξη θα πρέπει να ορίσετε ρητό φυσικό μέγεθος αντί να βασιστείτε στην προεπιλογή που προκύπτει από τα pixels

Κλιμάκωση και η σημαία αναλογίας διαστάσεων

Όταν θέλετε να αλλάξετε μέγεθος σε σχέση με τις τρέχουσες διαστάσεις αντί για έναν απόλυτο στόχο, για παράδειγμα να συρρικνώσετε μια εικόνα γραφήματος στο 60% του μεγέθους με το οποίο εισήχθη, χρησιμοποιήστε Scale:

procedure Scale(APercent: Double; AKeepAspect: Boolean = True);

APercent είναι ποσοστό όπου το 100 σημαίνει χωρίς αλλαγή, το 150 μεγαλώνει κατά το ήμισυ, το 50 το μειώνει στο μισό. Με AKeepAspect στην προεπιλογή του True, τόσο το πλάτος όσο και το ύψος πολλαπλασιάζονται με τον ίδιο συντελεστή, οπότε οι αναλογίες διατηρούνται και μια εικόνα 4×3 cm γίνεται 6×4.5 cm μετά το Scale(150). Δώστε False και μόνο το πλάτος κλιμακώνεται - το ύψος μένει ακριβώς όπως ήταν. Αυτή η ασυμμετρία είναι σκόπιμη: όταν θέλετε να τεντώσετε έναν άξονα ανεξάρτητα, το σωστό εργαλείο είναι οι ρητοί WidthCM/HeightCM ρυθμιστές, ενώ ο κλάδος χωρίς aspect του Scale υπάρχει για την πιο στενή περίπτωση της προσαρμογής μόνο του πλάτους. Είναι εύκολο να διαβάσει κανείς το Scale(150, False) ως «τεντώστε και τα δύο ελεύθερα» και να εκπλαγεί, οπότε χρησιμοποιήστε τους ρυθμιστές όταν εννοείτε πραγματικά δύο ανεξάρτητες διαστάσεις

Img.WidthCM := 4.0;
Img.HeightCM := 3.0;

Img.Scale(150);          // aspect locked: now 6.0 x 4.5 cm
Img.Scale(100);          // no-op, returns immediately

Img.Scale(50, False);    // width only: 3.0 cm wide, height unchanged at 4.5 cm

Μια μικρή συμπεριφορά που αξίζει να γνωρίζετε: Scale(100) τερματίζει νωρίς και επιστρέφει χωρίς να αγγίξει κανένα από τα δύο πεδία, οπότε είναι ασφαλές να το καλείτε χωρίς όρο σε έναν βρόχο όπου το ποσοστό μπορεί να είναι 100. Και επειδή η γεωμετρία αποθηκεύεται ως ακέραιος EMU, κάθε ρυθμιστής στρογγυλοποιεί. Η διαδοχική μετατροπή μέσω κλασματικών εκατοστών μπορεί επομένως να αποκλίνει κατά ένα κλάσμα του EMU - πολύ κάτω από οτιδήποτε ορατό, αλλά αξίζει να το γνωρίζετε αν ποτέ ελέγχετε ακριβή ισότητα σε ένα τεστ. Για έλεγχο ακριβείας σε pixel, ορίστε WidthEMU και HeightEMU απευθείας και παραλείψτε εντελώς τη μετατροπή μονάδων

Ανάγνωση της γεωμετρίας πίσω

Η συλλογή εικόνων μπορεί να ερωτηθεί, κάτι που έχει σημασία όταν φορτώνετε ένα υπάρχον βιβλίο εργασίας και πρέπει να επιθεωρήσετε ή να προσαρμόσετε ό,τι ήδη υπάρχει αντί για ό,τι μόλις προσθέσατε. Images.Count απαριθμεί κάθε εικόνα στο φύλλο, Images[i] τα ευρετηριάζει με αρίθμηση από το μηδέν, και FindAt(ARow, ACol) επιστρέφει την εικόνα αγκυρωμένη σε ένα συγκεκριμένο κελί, ή nil αν δεν υπάρχει καμία. Υπάρχει επίσης IndexOfCell για το ευρετήριο αντί για το αντικείμενο, και DeleteAt / DeleteInRange για αφαίρεση

var
  i: Integer;
  Img: TXLSXImage;
begin
  for i := 0 to Sheet.Images.Count - 1 do
  begin
    Img := Sheet.Images[i];
    Writeln(Format('[%d] R%dC%d  %.2f x %.2f cm  (%d x %d EMU)',
      [i, Img.Row, Img.Col, Img.WidthCM, Img.HeightCM,
       Img.WidthEMU, Img.HeightEMU]));
  end;

  Img := Sheet.Images.FindAt(3, 2);   // nil-check before use
  if Img <> nil then
    Img.Scale(80);
end;

Επειδή οι ιδιότητες των πραγματικών μονάδων είναι ζωντανές προβολές, μια εικόνα που εισάγεται σε κάποιο μέγεθος EMU από άλλο εργαλείο εμφανίζει αμέσως τη γεωμετρία της σε εκατοστά, χωρίς να χρειάζεται καμία μετατροπή από την πλευρά σας. Αυτό ταιριάζει φυσικά με το ευρύτερο μοντέλο σχεδίασης· αν τοποθετείτε επίσης γραφήματα και σχήματα μαζί με raster εικόνες, ο συνοδευτικός οδηγός για Γραφήματα HotXLS, εικόνες και σχέδια Excel στο Delphi καλύπτει το μοντέλο αγκύρωσης που μοιράζονται αυτά τα αντικείμενα

Μετρικά περιθώρια ρύθμισης σελίδας

Η ίδια ένταση ανάμεσα σε EMU και πραγματικές μονάδες εμφανίζεται ένα επίπεδο πιο έξω, στη σελίδα. Το OOXML και το Excel αποθηκεύουν τα περιθώρια εκτύπωσης σε ίντσες, κάτι που είναι άβολο αν τα πρότυπα αναφορών σας ορίζονται σε χιλιοστά, όπως σε μεγάλο μέρος του κόσμου εκτός ΗΠΑ. Η v2.91.0 προσθέτει μετατροπείς σε εκατοστά πάνω από τα περιθώρια σε ίντσες: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM, και MarginFooterCM. Κάθε μία είναι μια λεπτή ευκολία πάνω από την αντίστοιχη ιδιότητα σε ίντσες, με μετατροπή στην ακριβή αναλογία 1 inch = 2.54 cm

Sheet.MarginLeftCM := 2.0;     // 2 cm  == 0.7874 inch
Sheet.MarginRightCM := 2.0;
Sheet.MarginTopCM := 2.5;
Sheet.MarginBottomCM := 2.5;
Sheet.MarginHeaderCM := 1.0;
Sheet.MarginFooterCM := 1.0;

Οι ιδιότητες σε ίντσες (MarginLeft και οι υπόλοιπες) παραμένουν η κανονική αποθήκευση, έτσι μπορείτε να χρησιμοποιείτε και τα δύο, να ορίσετε το πάνω περιθώριο σε εκατοστά και να το διαβάσετε πίσω σε ίντσες, ή το αντίστροφο, και το αρχείο που γράφεται στον δίσκο είναι ίδιο και στις δύο περιπτώσεις. Η μετατροπή είναι απλός πολλαπλασιασμός με 2.54, χωρίς στρογγυλοποίηση σε χονδρό πλέγμα, οπότε τα 2 cm μένουν 2 cm με πλήρη ακρίβεια double. Αυτή είναι η ίδια φιλοσοφία ευκολίας μετρικών μονάδων όπως και στη γεωμετρία της εικόνας: η μορφή μιλά αυτοκρατορικές μονάδες από κάτω, και η βιβλιοθήκη σάς επιτρέπει να συντάσσετε ό,τι καθορίζεται στη μονάδα που χρησιμοποιεί η προδιαγραφή σας. Για τη διάταξη της γύρω αναφοράς, τίτλοι, μπλοκ μεταδεδομένων, σύνολα, δείτε συνενωμένα κελιά και διάταξη προτύπου αναφοράς στο HotXLS, το οποίο χρησιμοποιεί αυτά τα περιθώρια μαζί με συνενωμένες περιοχές και μια περιοχή εκτύπωσης

Μια σημείωση για το τι εγγυάται και τι δεν εγγυάται η γεωμετρία

Οι ιδιότητες γεωμετρίας ελέγχουν το δηλωμένο μέγεθος της εικόνας στο αρχείο, δηλαδή το μέγεθος με το οποίο θα την αποδώσει ένας συμβατός καταναλωτής. Δεν επαναδειγματοληπτούν τα δεδομένα της εικόνας· ένα PNG 50×50 pixel με μέγεθος 8 cm θα μεγεθυνθεί και θα φαίνεται τετραγωνισμένο, ακριβώς όπως θα συνέβαινε στο Excel. Η αλλαγή μεγέθους είναι λειτουργία διάταξης, όχι επεξεργασίας εικόνας, οπότε δώστε στην εικόνα αρκετή πηγαία ανάλυση για το φυσικό μέγεθος που σκοπεύετε να χρησιμοποιήσετε. Η βιβλιοθήκη επίσης δεν επανακωδικοποιεί μορφές: τα bytes που περνάτε στο AddImage αποθηκεύονται και γράφονται αυτούσια, μαζί με το TXLSXImageFormat που δηλώνετε. Αν περάσετε bytes JPEG αλλά τα επισημάνετε ως xlsxImagePng θα δημιουργήσετε αρχείο που το Excel δεν μπορεί να ανοίξει, οπότε αφήστε το AddImageFromFile να συμπεράνει τη μορφή από την επέκταση όταν μπορείτε

Τίποτα από αυτά δεν είναι εξωτικό μόλις εσωτερικεύσετε τη μία ιδέα που βρίσκεται από κάτω: στο OOXML, το φυσικό μέγεθος είναι η πραγματική ποσότητα και τα pixel είναι μια παραγόμενη, εξαρτώμενη από το DPI σκιά του. Δημιουργήστε εικόνες και περιθώρια σε εκατοστά, ίντσες ή points, αφήστε το HotXLS να τα χαρτογραφήσει σε ακριβή EMU, και τα τιμολόγια και οι αναφορές σας θα τυπώνονται στο ίδιο μέγεθος σε κάθε μηχάνημα που τα ανοίγει

Οι διεπαφές API για τη γεωμετρία εικόνας, την κλιμάκωση και τα μετρικά περιθώρια που περιγράφονται εδώ διατίθενται με το HotXLS Delphi spreadsheet component, το οποίο διαβάζει και γράφει XLS και XLSX από Delphi και C++Builder χωρίς να απαιτείται εγκατάσταση Excel