Popuna serije grafikona literalnim RGB-om ne prati theme radne sveske. Promenite theme i serija zadržava staru boju. HotXLS ovo u binarnom XLS-u rešava theme-bojenim popunama serija grafikona: GelFrame zapis, 4198 ili $1066, upisan odmah posle AreaFormat-a unutar bloka serije i sa OfficeArt scheme indeksom plus tint-om. Excel tada renderuje seriju onako kako renderuje theme popunu koju je sam upisao
Odakle dolazi broj GelFrame zapisa?
Broj GelFrame zapisa je 4198 ($1066) i nećete ga naći u sopstvenom odeljku specifikacije zapisa. [MS-XLS] 2.4.131 opisuje šta GelFrame sadrži, ali za razliku od većine odeljaka zapisa ne navodi vrednost rt. Ni ABNF chart podstream-a ne pomaže: daje samo produkciju GELFRAME = 1*2GelFrame *Continue, koja imenuje zapis bez njegovog broja. Broj živi u tabeli enumeracije brojeva zapisa, nekoliko strana dalje od odeljka koji dokumentuje payload. Tu produkciju vredi drugi put pogledati ako pišete reader: ona dopušta jedan ili dva GelFrame zapisa, od kojih svaki opciono može da bude praćen Continue zapisima, pa parser koji pretpostavi jedan zapis po produkciji pogrešno će obraditi fajl koji nije sam upisao. HotXLS emituje tačno jedan GelFrame po theme seriji, što Excel proizvodi za jednostavnu solid theme popunu, a decoder zapis tretira kao samostalan payload umesto da pretpostavi fiksan broj
Unutar GelFrame payload-a: dve OfficeArt property tabele
GelFrame payload su dve OfficeArt property tabele jedna za drugom: OfficeArtFOPT (zvana OPT1), pa OfficeArtTertiaryFOPT (OPT2). Svaka tabela jeste dvobajtni broj property stavki, za kojim sledi toliko šestobajtnih FOPTE unosa, a svaki unos je dvobajtni opid plus četvorobajtni op. Bit 15 u opid jeste fComplex: kada je postavljen, vrednost op predstavlja dužinu u bajtovima i za fiksnim unosima sledi promenljivi rep. Decoder koji ignoriše te repove gubi poravnanje i posle prvog kompleksnog property-ja čita pogrešne opid-e za sve ostalo
Theme popuna izražava se kroz tri property-ja raspoređena po obe tabele, plus jedan koji deklariše vrstu popune. HotXLS upisuje četiri property-ja u 28 bajtova bez kompleksnih repova:
fillType$0180 u OPT1, postavljen na 1 (msofillSolid)fillColor$0181 u OPT1, spljošteni RGB koji će stariji ili theme-neupućen potrošač iscrtatifillColorExt$019E u OPT2, osnovna theme bojafillColorExtMod$01A0 u OPT2, tint ili shade primenjen na osnovu
Ta podela je namerna u formatu, a ne slučajnost implementacije: [MS-ODRAW] 2.2.2 opisuje theme trojku kao ravnu boju plus osnovnu boju plus modifikaciju, pa potrošač koji razume theme ponovo računa popunu, dok onaj koji ih ne razume i dalje slika nešto razumno. Okolni opid-i prate isti obrazac i nose isto numerisanje u starim i aktuelnim izdanjima [MS-ODRAW], što je zgodno kada upoređujete dve revizije: fillOpacity $0182, fillBackColor $0183, fillShadeType $019C, fillBackColorExt $01A2 i fillBackColorExtMod $01A4
Zašto scheme indeks stoji u crvenom bajtu?
Zato što je OfficeArtCOLORREF definisan pomakom bajta, a ne numeričkom vrednošću: crvena je na bajtu 0, zelena na bajtu 1, plava na bajtu 2, a zastavice na bajtu 3. Pročitajte tu strukturu kao little-endian DWORD, što je način na koji je svaki FOPTE op zapisan, i crvena postaje najmanje značajan bajt. Radni primer lineColor u [MS-ODRAW] to potvrđuje. Zato fSchemeIndex, koji je flags bit E, ima numeričku vrednost $08000000, a sam scheme indeks ide u crveni bajt, dok zelena i plava moraju biti nula. Accent1 je zato op vrednost $08000004, a ne $00000004 i nipošto $04000000
Redosled theme indeksa koji specifikacija odbija da definiše
Specifikacija redosled scheme indeksa naziva host-defined i ne daje tabelu, što znači da sam raspored bajtova nije dovoljan za interop sa Excel-om. HotXLS koristi redosled spreadsheet theme-a, koji radi round-trip sa stvarnim Excel fajlovima:
- 0 = lt1, 1 = dk1, 2 = lt2, 3 = dk2
- 4 do 9 = accent1 do accent6
- 10 = hlink, 11 = folHlink
Tint i shade: MSOTINTSHADE payload
fillColorExtMod op je vrednost MSOTINTSHADE i u jednom DWORD-u kodira smer i količinu, a ne kao signed fraction. Vrednost $20000000 znači bez izmene. Posvetljujući tint jeste $02F4 shl 16 or amount shl 8 or $10 (MSOTINT); zatamnjujući tint ima isti oblik, sa $01F4 u višoj reči (MSOSHADE). Bajt amount ide suprotno intuiciji: $FF znači nepromenjeno, a $00 znači punu modifikaciju. HotXLS to normalizuje u jedan double u stilu DrawingML-a, gde pozitivno posvetljuje, a negativno zatamnjuje, koristeći plus ili minus (255 - amount) / 255. Mapiranje je tačno za vrednosti koje Excel stvarno nudi u UI-ju, zbog čega je round-trip bez gubitka, a ne približno bez gubitka: poznato „Lighter 40%“ jeste amount 153, a (255 - 153) / 255 jeste 0,4 bez greške zaokruživanja u bilo kom smeru. Shade sa amount 191 vraća se kao -64/255. Evo encoder-a, ograničenog na legalni opseg:
if Tint > 0 then // MSOTINT - svetlije
TintOp := LongWord($02F4) shl 16 or
(LongWord(Round(255 * (1 - Tint))) shl 8) or $10
else if Tint < 0 then // MSOSHADE - tamnije
TintOp := LongWord($01F4) shl 16 or
(LongWord(Round(255 * (1 + Tint))) shl 8) or $10
else
TintOp := $20000000; // MSOCOLORMODUNDEFINED
Postavljanje i čitanje theme popune iz Delphi-ja
Na strani upisa theme popuna predstavlja dva dodatna polja u style zapisu po seriji. TXLSChartSeriesStyleInfo dobio je HasFillTheme, FillThemeColor i FillThemeTint, a builder emituje GelFrame samo kada su istovremeno postavljeni HasStyle i HasFillTheme. Ako postavite i eksplicitni FillRgb, ta vrednost ide u OPT1 fillColor verbatim; ako je ne postavite, HotXLS sam spljoštava boju kroz ugrađenu podrazumevanu Office theme tabelu sa primenjenim tint-om, pa serija koja ima samo theme i dalje dobija razumnu ravnu boju za potrošače koji ignorišu OPT2. Obratite pažnju na inicijalizaciju Default(), koja je važna zato što TXLSChartSeriesInfo sadrži managed polja, a njegovi obični Boolean članovi bi inače bili đubre sa steka:
var
Wb: TXLSWorkbook;
Series: array [0..1] of TXLSChartSeriesInfo;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Sheets.Add.Name := 'Data';
Series[0] := Default(TXLSChartSeriesInfo); // nikada ne koristi FillChar za ovaj zapis
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, crvena u niskom bajtu
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 eksplicitnog RGB-a: spljošteno
Series[1].Style.FillThemeColor := 8; // accent5
Wb.Sheets.AddChartSheet('Themed', xlsChartTypeColumn, '', '', '', Series);
Wb.SaveAs('themed.xls');
finally
Wb.Free;
end;
end;
Čitanje nazad prolazi kroz isti model grafikona koji koristi ostatak HotXLS chart inspekcije. GetChartModel vraća vlasnički TXLSChartModel koji morate osloboditi, a svaki TXLSChartSeries izlaže HasFillTheme, FillThemeColor i FillThemeTint uz FillRgb dekodiran iz OPT1 fillColor, koji ima prednost nad AreaFormat bojom za tu seriju. Iste tri vrednosti dospevaju i u kanonski semantički snapshot kao SolidFillThemeSet, SolidFillThemeColor i SolidFillThemeTint, pa diff radne sveske vidi promenu theme-a kao promenu theme-a, a ne kao neobjašnjivi RGB drift. Ako dolazite sa XLSX strane, ovo je binarni format pandan stilu opisanom u HotXLS vodiču za Excel grafikone, slike i crteže u Delphi-ju:
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;
Šta theme popuna u binarnom XLS-u ne obećava?
Tri poštena ograničenja. Prvo, i najvažnije za svakoga ko audit-uje ovaj kod: nijedan sample fajl u lokalnom korpusu uopšte ne sadrži GelFrame zapis. Jedanaest pojavljivanja para bajtova 66 10 u sample-u uslovnog formatiranja nalaze se na granicama koje nisu granice zapisa, a dump celog stream-a nalazi nula pogodaka. Ovde opisan layout bitova izveden je iz specifikacije, a zatim potvrđen na tri načina, simetrijom dekodiranja na izlazu builder-a, ručno građenim byte testovima koji sintetički $1066 payload šalju pravo u decoder i proverom tačnog spljoštenog RGB-a. To je slabiji oblik dokaza od uhvaćenog Excel fajla i vredi to reći umesto ostavljanja suprotnog utiska. Drugo, spljoštavanje popune koja ima samo theme koristi ugrađenu podrazumevanu Office theme tabelu, a ne theme deo pročitan iz radne sveske, jer binarni XLS nema theme deo u smislu zapakovanog XLSX-a — ako vam treba theme same radne sveske za ravnu boju, sami prosledite FillRgb. Treće, decoder prihvata GelFrame samo unutar bloka serije; isti zapis može da se pojavi na chart oblasti ili okviru ose, a prihvatanje tamo tiho bi pripisalo pozadinsku popunu seriji, pa se takvi slučajevi ignorišu. fillColorExt bez zastavice $08000000 takođe se tretira kao obična proširena boja i nikada ne postavlja HasFillTheme. Za radne sveske gde je grafikon nastao u XLSX svetu i samo prolazi kroz njega, preservation putanja u tekstu o uređivanju Excel grafikona bez gubitka ChartML-a bezbedniji je put, a kontejner u kojem ti zapisi žive obrađen je u tekstu o čitanju OLE2 compound fajlova u Delphi-ju bez COM IStorage
Theme-bojene popune grafikona, GelFrame encoder i decoder i ceo BIFF8 chart substream builder isporučuju se u HotXLS Delphi spreadsheet komponenti za Delphi i C++Builder, koja čita i upisuje XLS, XLSX i ODS bez instaliranog Excel-a