Série grafu vyplněná literálem RGB nesleduje theme workbooku. Změňte theme a série si ponechá starou barvu. HotXLS to v binárním XLS řeší theme-colored výplněmi sérií grafu: rekordem GelFrame, 4198 nebo $1066, zapsaným hned za AreaFormat uvnitř bloku série a nesoucím index schématu OfficeArt plus tint. Excel pak sérii vykreslí stejně jako theme výplň, kterou zapsal sám
Odkud pochází číslo rekordu GelFrame
Číslo rekordu GelFrame je 4198 ($1066) a ve vlastní specifikační sekci rekordu ho nenajdete. [MS-XLS] 2.4.131 popisuje, co GelFrame obsahuje, ale na rozdíl od většiny sekcí rekordů neuvádí hodnotu rt. ABNF chart substreamu také nepomůže: dává pouze produkci GELFRAME = 1*2GelFrame *Continue, která rekord pojmenuje, ale nečísluje. Číslo žije v tabulce enumerace čísel rekordů několik stran od sekce dokumentující payload. Na tuto produkci je dobré se podívat podruhé při psaní readeru: dovoluje jeden nebo dva rekordy GelFrame, za každým mohou volitelně následovat Continue rekordy, takže parser předpokládající jeden rekord na produkci špatně zpracuje soubor, který sám nezapsal. HotXLS emituje přesně jeden GelFrame na theme sérii, což je to, co Excel vytváří pro jednoduchou solid theme výplň, a decoder s rekordem zachází jako se samostatným payloadem místo předpokladu pevného počtu
Uvnitř payloadu GelFrame: dvě property tabulky OfficeArt
Payload GelFrame tvoří dvě property tabulky OfficeArt za sebou: OfficeArtFOPT (nazývaná OPT1) následovaná OfficeArtTertiaryFOPT (OPT2). Každá tabulka obsahuje dvoubajtový počet properties následovaný tolika šestibajtovými položkami FOPTE, přičemž každá položka má dvoubajtový opid a čtyřbajtový op. Bit 15 v opid je fComplex: je-li nastavený, hodnota op je délka v bajtech a za pevnými položkami následuje proměnný tail. Decoder, který tyto taily ignoruje, se desynchronizuje a od první komplexní property dál čte nesmyslné opidy
Theme výplň vyjadřují tři properties rozložené přes obě tabulky plus jedna, která deklaruje druh výplně. HotXLS zapisuje čtyři properties v 28 bajtech bez komplexních tailů:
fillType$0180 v OPT1, nastavené na 1 (msofillSolid)fillColor$0181 v OPT1, zploštěná RGB, kterou vykreslí starší konzument nebo konzument bez theme podporyfillColorExt$019E v OPT2, základní theme barvafillColorExtMod$01A0 v OPT2, tint nebo shade aplikovaný na základ
Toto rozdělení je záměr formátu, nikoli náhoda implementace: [MS-ODRAW] 2.2.2 popisuje theme triple jako plochou barvu plus základní barvu plus modifikaci, takže konzument, který themes rozumí, výplň znovu spočítá a ten, který jim nerozumí, stále vykreslí něco rozumného. Sousední opidy sledují stejný vzor a nesou shodné číslování ve starém i aktuálním vydání [MS-ODRAW], což je pohodlné při porovnávání dvou revizí: fillOpacity $0182, fillBackColor $0183, fillShadeType $019C, fillBackColorExt $01A2 a fillBackColorExtMod $01A4
Proč index schématu sedí v červeném bajtu
Protože OfficeArtCOLORREF je definován offsetem bajtu, nikoli číselnou hodnotou: červená na bajtu 0, zelená na bajtu 1, modrá na bajtu 2 a flags na bajtu 3. Přečtěte tuto strukturu jako little-endian DWORD, což každý op FOPTE je, a červená se stane nejméně významným bajtem. Potvrzuje to zpracovaný příklad lineColor v [MS-ODRAW]. fSchemeIndex, což je bit flags E, má tedy číselnou hodnotu $08000000 a samotný index schématu patří do červeného bajtu, přičemž zelená a modrá musí být nula. Accent1 je proto hodnota op $08000004, nikoli $00000004 a už vůbec ne $04000000
Pořadí theme indexu, které specifikace odmítá definovat
Specifikace označuje pořadí scheme indexu za host-defined a žádnou tabulku nedává, což znamená, že samotné rozložení bajtů nestačí pro interop s Excelem. HotXLS používá pořadí spreadsheet theme, které round-trippuje proti skutečným souborům Excelu:
- 0 = lt1, 1 = dk1, 2 = lt2, 3 = dk2
- 4 až 9 = accent1 až accent6
- 10 = hlink, 11 = folHlink
Tint a shade: payload MSOTINTSHADE
fillColorExtMod op je hodnota MSOTINTSHADE a kóduje směr i množství do jediného DWORD místo signed fraction. Hodnota $20000000 znamená beze změny. Lightening tint je $02F4 shl 16 or amount shl 8 or $10 (MSOTINT); darkening tint má stejný tvar s $01F4 ve vysokém slově (MSOSHADE). Bajt amount jde opačným směrem, než napovídá intuice: $FF znamená beze změny a $00 plnou modifikaci. HotXLS to normalizuje na jediný double ve stylu DrawingML, kde kladná hodnota zesvětluje a záporná ztmavuje, pomocí plus nebo minus (255 - amount) / 255. Mapování je přesné pro hodnoty, které Excel skutečně nabízí v UI, proto je round-trip bezeztrátový místo přibližně bezeztrátového: známé „Lighter 40%“ je amount 153 a (255 - 153) / 255 je 0,4 bez zaokrouhlovací chyby v žádném směru. Shade s amount 191 se vrátí jako -64/255. Tady je encoder omezený na legální rozsah:
if Tint > 0 then // MSOTINT - světlejší
TintOp := LongWord($02F4) shl 16 or
(LongWord(Round(255 * (1 - Tint))) shl 8) or $10
else if Tint < 0 then // MSOSHADE - tmavší
TintOp := LongWord($01F4) shl 16 or
(LongWord(Round(255 * (1 + Tint))) shl 8) or $10
else
TintOp := $20000000; // MSOCOLORMODUNDEFINED
Nastavení a čtení theme výplně z Delphi
Na straně zápisu je theme výplň dvojicí nových polí v recordu stylu jednotlivé série. TXLSChartSeriesStyleInfo získal HasFillTheme, FillThemeColor a FillThemeTint a builder emituje GelFrame pouze tehdy, když jsou současně nastaveny HasStyle a HasFillTheme. Když nastavíte také explicitní FillRgb, hodnota jde do OPT1 fillColor beze změny; když ji nenastavíte, HotXLS barvu zploští sám přes vestavěnou výchozí tabulku Office theme s aplikovaným tintem, takže i theme-only série má rozumnou plochou barvu pro konzumenty, které OPT2 ignorují. Všimněte si inicializace Default(), která je důležitá, protože TXLSChartSeriesInfo obsahuje managed fields a jeho obyčejné Boolean členy by jinak byly odpadem ze stacku:
var
Wb: TXLSWorkbook;
Series: array [0..1] of TXLSChartSeriesInfo;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Sheets.Add.Name := 'Data';
Series[0] := Default(TXLSChartSeriesInfo); // tento record nikdy neplň FillChar
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 v low byte
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; // bez explicitního RGB: zploštěno
Series[1].Style.FillThemeColor := 8; // accent5
Wb.Sheets.AddChartSheet('Themed', xlsChartTypeColumn, '', '', '', Series);
Wb.SaveAs('themed.xls');
finally
Wb.Free;
end;
end;
Čtení zpět prochází stejným chart modelem, který používá zbytek inspekce grafů HotXLS. GetChartModel vrací vlastněný TXLSChartModel, který uvolníte, a každý TXLSChartSeries vystavuje HasFillTheme, FillThemeColor a FillThemeTint vedle FillRgb dekódovaného z OPT1 fillColor, který má pro tuto sérii přednost před barvou AreaFormat. Stejné tři hodnoty se dostanou i do kanonického sémantického snapshotu jako SolidFillThemeSet, SolidFillThemeColor a SolidFillThemeTint, takže diff workbooku uvidí změnu theme jako změnu theme místo nevysvětleného RGB driftu. Pokud přicházíte ze strany XLSX, toto je protějšek stylování v binárním formátu popsaného v průvodci HotXLS grafy, obrázky a drawingy v Delphi:
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, OPT1 fillColor
end;
finally
Model.Free;
end;
finally
Wb.Free;
end;
Co theme výplň v binárním XLS neslibuje
Tři poctivá omezení. První a nejdůležitější pro každého, kdo tento kód audituje: žádný ukázkový soubor v lokálním korpusu neobsahuje rekord GelFrame. Jedenáct výskytů dvojice bajtů 66 10 ve vzorku conditional formatting leží mimo hranice rekordů a dump celého streamu nalezne nulový počet shod. Layout bitů popsaný zde byl odvozen ze specifikace a potom zafixován třemi způsoby: symetrií dekódování nad výstupem builderu, ručně vytvořenými byte testy, které pošlou syntetický payload $1066 přímo decoderu, a assertion přesné zploštěné RGB. Je to slabší důkaz než zachycený soubor z Excelu a je lepší to říct, než vytvářet opačný dojem. Za druhé, zploštění theme-only výplně používá vestavěnou výchozí tabulku Office theme, nikoli theme part načtenou z workbooku, protože binární XLS nemá theme part ve smyslu zabaleného XLSX — pokud potřebujete, aby plochou barvu řídila vlastní theme workbooku, dodejte FillRgb sami. Za třetí, decoder přijímá GelFrame pouze uvnitř bloku série; stejný rekord se může objevit v oblasti grafu nebo ve frame osy a jeho přijetí tam by potichu přiřadilo výplň pozadí sérii, proto se tyto výskyty ignorují. fillColorExt bez příznaku $08000000 se také chápe jako běžná rozšířená barva a nikdy nenastaví HasFillTheme. Pro workbooky, jejichž graf vznikl ve světě XLSX a jen jím prochází, je bezpečnější preservation path v úpravě grafů Excelu bez ztráty ChartML a kontejner, v němž tyto rekordy sedí, pokrývá čtení OLE2 compound files v Delphi bez COM IStorage
Theme-colored výplně grafů, encoder a decoder GelFrame i úplný builder chart substreamu BIFF8 se dodávají v HotXLS Delphi spreadsheet component pro Delphi a C++Builder, který čte a zapisuje XLS, XLSX a ODS bez instalovaného Excelu