Egy szó szerinti RGB-vel kitöltött chartsorozat nem követi a munkafüzet témáját. Ha megváltoztatod a témát, a sorozat megtartja a régi színt. A HotXLS ezt bináris XLS-ben theme-színű chart fillként kezeli: egy GelFrame rekorddal, a 4198-as vagy $1066-os értékkel, amely közvetlenül az AreaFormat után, a series blokkon belül jelenik meg, és egy OfficeArt scheme indexet, valamint tintet hordoz. Az Excel ezután ugyanúgy rendereli a sorozatot, mint a saját maga által kiírt theme fillt
Honnan származik a GelFrame rekord száma?
A GelFrame rekord száma 4198 ($1066), és nem a rekord saját specifikációs szakaszában találod. Az [MS-XLS] 2.4.131 leírja, mit tartalmaz egy GelFrame, de a legtöbb rekord-szakasszal ellentétben nem mondja meg az rt értékét. A chart-alstream ABNF-je sem segít: csak ezt a produkciót adja: GELFRAME = 1*2GelFrame *Continue, a rekordot megnevezi, a számát nem. A szám a rekord-számok enumerációs táblájában él, több oldallal távolabb a payloadot dokumentáló fejezettől. Ezt a produkciót érdemes újra megnézni mindenkinek, aki readert ír: egy vagy két GelFrame rekordot enged, mindegyiket opcionálisan Continue rekordok követhetik, ezért egy parser, amely produkciónként egyetlen rekordot feltételez, rosszul kezeli a nem általa írt fájlt. A HotXLS pontosan egy GelFrameet ír theme filles sorozatonként, ahogy az Excel teszi egyszerű solid theme fill esetén, a dekódere pedig önálló payloadként kezeli a rekordot, nem rögzített darabszámot feltételez
A GelFrame payload belseje: két OfficeArt property table
A GelFrame payloadja két egymás mögötti OfficeArt property table: egy OfficeArtFOPT (OPT1 néven) és egy OfficeArtTertiaryFOPT (OPT2). Mindkét tábla kétbájtos property counttal indul, majd ennyi hatbájtos FOPTE bejegyzés jön, minden bejegyzésben kétbájtos opid és négybájtos op. Az opid 15. bitje az fComplex: ha be van állítva, az op értéke byte-hossz, és a fix bejegyzéseket változó farok követi. Az a dekóder, amely figyelmen kívül hagyja ezeket a farkakat, deszinkronizál, és az első komplex property után szemét opidokat olvas
A theme fill három, a két tábla között szétosztott propertyvel fejezhető ki, plusz egy negyedikkel, amely a fill típusát deklarálja. A HotXLS 28 bájtban ír ki négy propertyt, komplex farok nélkül:
fillType$0180 az OPT1-ben, 1-re (msofillSolid) állítvafillColor$0181 az OPT1-ben, a lapított RGB, amelyet egy régebbi vagy theme-et nem ismerő consumer rajzolnafillColorExt$019E az OPT2-ben, az alapul szolgáló theme colorfillColorExtMod$01A0 az OPT2-ben, az alapra alkalmazott tint vagy shade
Ez a felosztás a formátum tudatos része, nem az implementáció véletlenje: az [MS-ODRAW] 2.2.2 a theme-hármast lapos színként, alapszínként és módosításként írja le, így a theme-et értő consumer újraszámolja a fillt, az azt nem értő pedig továbbra is elfogadhatót fest. A környező opidok is ugyanezt a mintát követik, és azonos számozást hordoznak a régi és a jelenlegi [MS-ODRAW] kiadásban, ami kényelmes, amikor két revíziót olvasol össze: fillOpacity $0182, fillBackColor $0183, fillShadeType $019C, fillBackColorExt $01A2 és fillBackColorExtMod $01A4
Miért a red byte-ban ül a scheme index?
Azért, mert az OfficeArtCOLORREF byte-offset alapján definiált, nem numerikus érték alapján: a red a 0. byte, a green az 1., a blue a 2., a flag a 3. Olvasd ezt a struktúrát little-endian DWORD-ként, ahogy minden FOPTE op értékét kell, és a red lesz a legkisebb helyiértékű byte. Az [MS-ODRAW] kidolgozott lineColor példája is ezt igazolja. Így az fSchemeIndex, amely az E flag bitje, numerikusan $08000000, maga a scheme index pedig a red byte-ba kerül, a greennek és bluenak nullának kell maradnia. Az Accent1 ezért $08000004 op-érték, nem $00000004, és különösen nem $04000000
A theme-index sorrendje, amelyet a specifikáció nem hajlandó definiálni
A specifikáció a scheme index sorrendjét host-definedként kezeli, táblát nem ad, ezért a byte-elrendezés önmagában nem elég az Excellel való interoperabilitáshoz. A HotXLS a spreadsheet theme sorrendjét használja, amely valódi Excel-fájlokkal round-trippel:
- 0 = lt1, 1 = dk1, 2 = lt2, 3 = dk2
- 4–9 = accent1–accent6
- 10 = hlink, 11 = folHlink
Tint és shade: az MSOTINTSHADE payload
A fillColorExtMod op egy MSOTINTSHADE érték, amely az irányt és a mértéket egyetlen DWORD-ben kódolja, nem előjeles törtben. A $20000000 érték módosítatlan állapotot jelent. A világosító tint alakja $02F4 shl 16 or amount shl 8 or $10 (MSOTINT), a sötétítő tinté ugyanez $01F4-gyel a magas szóban (MSOSHADE). Az amount byte az intuícióval ellentétesen fut: a $FF változatlant, a $00 teljes módosítást jelent. A HotXLS ezt egyetlen DrawingML-szerű double értékre normalizálja, ahol a pozitív világosít, a negatív sötétít, plusz vagy mínusz (255 - amount) / 255 segítségével. A leképezés pontos az Excel UI által ténylegesen kínált értékekre, ezért a round-trip veszteségmentes, nem csak közelítő: az ismert „Lighter 40%” amountja 153, és (255 - 153) / 255 mindkét irányban kerekítési hiba nélkül 0,4, egy 191-es amountú shade pedig -64/255-ként jön vissza. Íme a legális tartományra korlátozott encoder:
if Tint > 0 then // MSOTINT - világosabb
TintOp := LongWord($02F4) shl 16 or
(LongWord(Round(255 * (1 - Tint))) shl 8) or $10
else if Tint < 0 then // MSOSHADE - sötétebb
TintOp := LongWord($01F4) shl 16 or
(LongWord(Round(255 * (1 + Tint))) shl 8) or $10
else
TintOp := $20000000; // MSOCOLORMODUNDEFINED
Theme fill beállítása és olvasása Delphiből
Íráskor a theme fill két extra mező a sorozatonkénti style rekordon. A TXLSChartSeriesStyleInfo megkapta a HasFillTheme, FillThemeColor és FillThemeTint mezőt, a builder pedig csak akkor ír GelFrameet, ha a HasStyle és a HasFillTheme egyszerre igaz. Ha explicit FillRgb-t is beállítasz, ez az érték változtatás nélkül kerül az OPT1 fillColor-ába; ha nem, a HotXLS maga lapítja a színt egy beépített alapértelmezett Office theme table-ön át, a tintet alkalmazva, így egy csak theme-et használó sorozatnak is marad ésszerű lapos színe azok számára, akik figyelmen kívül hagyják az OPT2-t. Figyeld a Default() inicializálást, mert a TXLSChartSeriesInfo managed mezőket tartalmaz, a sima Boolean tagjai pedig különben stack-szemetek lennének:
var
Wb: TXLSWorkbook;
Series: array [0..1] of TXLSChartSeriesInfo;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Sheets.Add.Name := 'Data';
Series[0] := Default(TXLSChartSeriesInfo); // ezt a rekordot soha ne FillChar-old
Series[0].Name := 'Explicit';
Series[0].Categories := 'Data!$A$1:$A$2';
Series[0].Values := 'Data!$B$1:$B$2';
Series[0].HasStyle := True;
Series[0].Style.HasFill := True;
Series[0].Style.FillRgb := $C47244; // accent1, red az alsó byte-ban
Series[0].Style.HasFillTheme := True;
Series[0].Style.FillThemeColor := 4; // accent1
Series[0].Style.FillThemeTint := 0.4; // Lighter 40%
Series[1] := Default(TXLSChartSeriesInfo);
Series[1].Name := 'ThemeOnly';
Series[1].Categories := 'Data!$A$1:$A$2';
Series[1].Values := 'Data!$C$1:$C$2';
Series[1].HasStyle := True;
Series[1].Style.HasFillTheme := True; // nincs explicit RGB: lapítva
Series[1].Style.FillThemeColor := 8; // accent5
Wb.Sheets.AddChartSheet('Themed', xlsChartTypeColumn, '', '', '', Series);
Wb.SaveAs('themed.xls');
finally
Wb.Free;
end;
end;
A visszaolvasás ugyanazon chart modellen keresztül történik, amelyet a HotXLS többi chart inspectionje is használ. A GetChartModel saját tulajdonú TXLSChartModel-t ad, amelyet fel kell szabadítanod, minden TXLSChartSeries pedig a FillRgb mellett kódolt HasFillTheme, FillThemeColor és FillThemeTint értéket tesz elérhetővé, az OPT1 fillColor-ából dekódolva, amely elsőbbséget élvez az adott series AreaFormat-színével szemben. Ugyanez a három érték eljut a kanonikus szemantikus snapshotba is, SolidFillThemeSet, SolidFillThemeColor és SolidFillThemeTint néven, így egy workbook diff a theme változását theme-változásként látja, nem megmagyarázhatatlan RGB-sodródásként. Ha az XLSX-oldal felől érkezel, ez a HotXLS Excel-chartok, képek és rajzok Delphiben útmutatójában leírt stílus bináris formátumú párja:
Wb := TXLSWorkbook.Create;
try
Wb.Open('themed.xls');
Model := Wb.Sheets[2]._Chart.GetChartModel;
try
Ser := Model.GetSeries(0);
if Ser.HasFillTheme then
begin
WriteLn(Ser.FillThemeColor); // 4 = accent1
WriteLn(Ser.FillThemeTint:0:3); // 0.400
WriteLn(IntToHex(Ser.FillRgb, 6)); // C47244, az OPT1 fillColor
end;
finally
Model.Free;
end;
finally
Wb.Free;
end;
Mit nem ígér egy theme fill bináris XLS-ben?
Három őszinte korlát. Az első, és az ezt a kódot auditálók számára a legfontosabb: a helyi korpusz egyetlen minta-fájlja sem tartalmaz GelFrame rekordot. A conditional-formatting mintában lévő 66 10 byte-pár tizenegy előfordulása nem rekordhatáron áll, a teljes stream dump pedig nulla találatot ad. Az itt leírt bitelrendezést a specifikációból vezettük le, majd három irányból rögzítettük: decode symmetryvel a builder kimenetén, kézzel felépített byte-tesztekkel, amelyek közvetlenül szintetikus $1066 payloadot adnak a dekódernek, és a pontos lapított RGB assertionjével. Ez gyengébb bizonyíték, mint egy rögzített Excel-fájl, és érdemes ezt kimondani, nem az ellenkezőjét sugallni. A második, hogy a csak theme-et tartalmazó fill lapítása beépített alapértelmezett Office theme table-t használ, nem a munkafüzetből kiolvasott theme partot, mert a bináris XLS-nek nincs olyan theme partja, mint a csomagolt XLSX-nek — ha a munkafüzet saját témájának kell meghatároznia a lapos színt, add meg magad a FillRgb-t. A harmadik, hogy a dekóder csak series blokkon belül fogad el GelFrameet; ugyanez a rekord megjelenhet a chart area vagy egy axis frame szintjén is, de ott elfogadva csendben sorozatszínnek tulajdonítaná a háttér filljét, ezért ezeket figyelmen kívül hagyja. A $08000000 flag nélküli fillColorExt ugyanígy sima kiterjesztett színként kezelődik, és soha nem állítja be a HasFillTheme-et. Olyan munkafüzetnél, amelynek chartja az XLSX-világban készült, és csak átmegy a formátumon, a ChartML elvesztése nélküli Excel-chart szerkesztési útvonal a biztonságosabb, a rekordok konténere pedig az OLE2 compound file-ok COM IStorage nélküli olvasásáról szóló Delphi-cikkben szerepel
A theme-színű chart filleket, a GelFrame encodert és dekódert, valamint a teljes BIFF8 chart-alstream buildert a HotXLS Delphi spreadsheet component szállítja Delphi és C++Builder számára, XLS, XLSX és ODS olvasásával és írásával telepített Excel nélkül