Tehnični članak

Barve teme grafikonov Excel v Delphiju: HotXLS GelFrame

Serija grafikona, zapolnjena z dobesednim RGB, ne sledi temi delovnega zvezka. Spremenite temo in serija ohrani staro barvo. HotXLS to v binarnem XLS obravnava s polnili serij grafikona v barvah teme: zapisom GelFrame, 4198 oziroma $1066, zapisanim takoj za AreaFormat znotraj bloka serije, ki nosi indeks sheme OfficeArt in odtenek. Excel nato serijo izriše tako, kot izriše tematsko polnilo, ki ga je zapisal sam

Od kod pride številka zapisa GelFrame?

Številka zapisa GelFrame je 4198 ($1066) in v lastnem razdelku specifikacije zapisa je ne boste našli. [MS-XLS] 2.4.131 opisuje, kaj vsebuje GelFrame, vendar za razliko od večine razdelkov zapisov ne navaja vrednosti rt. Tudi ABNF podtoka grafikona ne pomaga: poda samo produkcijo GELFRAME = 1*2GelFrame *Continue, ki zapis poimenuje, ne pa oštevilči. Številka živi v tabeli naštevanja številk zapisov, več strani stran od razdelka, ki dokumentira tovor. Ta produkcija si zasluži drugi pogled vsakega, ki piše bralnik: dovoljuje en ali dva zapisa GelFrame, vsakemu pa lahko sledijo zapisi Continue, zato bo razčlenjevalnik, ki za produkcijo predpostavi en sam zapis, napačno obdelal datoteko, ki je ni zapisal sam. HotXLS za vsako serijo s temo odda natanko en GelFrame, kar Excel ustvari pri preprostem polnem polnilu teme, njegov dekodirnik pa zapis obravnava kot samostojen tovor in ne predpostavlja fiksnega števila

Znotraj tovora GelFrame: dve tabeli lastnosti OfficeArt

Tovor GelFrame sta dve tabeli lastnosti OfficeArt druga za drugo: OfficeArtFOPT (imenovan OPT1), ki mu sledi OfficeArtTertiaryFOPT (OPT2). Vsaka tabela je dvobajtno število lastnosti, ki mu sledi toliko šestbajtnih vnosov FOPTE, vsak vnos pa je sestavljen iz dvobajtnega opid in štiribajtnega op. Bit 15 v opid je fComplex: ko je nastavljen, je vrednost op dolžina v bajtih, za fiksnimi vnosi pa sledi spremenljiv rep. Dekodirnik, ki te repe prezre, izgubi sinhronizacijo in za prvo kompleksno lastnostjo bere smeti kot opid

Polnilo teme je izraženo s tremi lastnostmi, razporejenimi čez obe tabeli, in še eno, ki določi vrsto polnila. HotXLS zapiše štiri lastnosti v 28 bajtih brez kompleksnih repov:

  • fillType $0180 v OPT1, nastavljen na 1 (msofillSolid)
  • fillColor $0181 v OPT1, sploščeni RGB, ki ga bo izrisal starejši porabnik ali porabnik brez podpore temam
  • fillColorExt $019E v OPT2, osnovna barva teme
  • fillColorExtMod $01A0 v OPT2, odtenek ali senčenje, uporabljeno na osnovi

Ta delitev je v formatu namerna, ne naključje izvedbe: [MS-ODRAW] 2.2.2 trojico teme opisuje kot plosko barvo, osnovno barvo in spremembo, zato porabnik, ki razume teme, polnilo preračuna, tisti, ki jih ne, pa še vedno nariše nekaj razumnega. Sosednji opid sledijo istemu vzorcu in nosijo enako oštevilčenje v starih in trenutnih izdajah [MS-ODRAW], kar je priročno pri branju dveh revizij: fillOpacity $0182, fillBackColor $0183, fillShadeType $019C, fillBackColorExt $01A2 in fillBackColorExtMod $01A4

Zakaj je indeks sheme v rdečem bajtu?

Ker je OfficeArtCOLORREF določen z odmikom bajta, ne s številčno vrednostjo: rdeča je v bajtu 0, zelena v bajtu 1, modra v bajtu 2 in zastavice v bajtu 3. Preberite to strukturo kot DWORD little-endian, kar velja za vsak op FOPTE, in rdeča postane najmanj pomemben bajt. Praktični primer lineColor v [MS-ODRAW] to potrdi. Zato ima fSchemeIndex, ki je bit zastavic E, številsko vrednost $08000000, sam indeks sheme pa gre v rdeči bajt, pri čemer morata biti zelena in modra nič. Accent1 je zato vrednost op $08000004, ne $00000004 in nikakor ne $04000000

Vrstni red indeksov tem, ki ga specifikacija noče določiti

Specifikacija vrstni red indeksa sheme označi kot določen s strani gostitelja in ne poda nobene tabele, kar pomeni, da sama postavitev bajtov za interoperabilnost z Excelom ni dovolj. HotXLS uporablja vrstni red teme preglednice, ki se pri povratnem zapisu ujema z resničnimi datotekami Excel:

  • 0 = lt1, 1 = dk1, 2 = lt2, 3 = dk2
  • 4 do 9 = accent1 do accent6
  • 10 = hlink, 11 = folHlink

Odtenek in senčenje: tovor MSOTINTSHADE

fillColorExtMod je vrednost MSOTINTSHADE in smer ter količino kodira v enem DWORD-u, ne kot predznačeni ulomek. Vrednost $20000000 pomeni nespremenjeno. Posvetlitveni odtenek je $02F4 shl 16 or amount shl 8 or $10 (MSOTINT), potemnitveni odtenek pa ima enako obliko z $01F4 v višji besedi (MSOSHADE). Bajt amount teče v nasprotni smeri od intuicije: $FF pomeni nespremenjeno, $00 pa celotno spremembo. HotXLS to normalizira v eno samo dvojno vrednost v slogu DrawingML, pri kateri pozitivna vrednost posvetli in negativna potemni, z uporabo plus ali minus (255 - amount) / 255. Preslikava je natančna za vrednosti, ki jih Excel dejansko ponuja v uporabniškem vmesniku, zato je povratni zapis brez izgub, ne samo približno brez izgub: znani »Lighter 40%« je amount 153, (255 - 153) / 255 pa je 0.4 brez napake zaokroževanja v nobeni smeri. Senčenje z amount 191 se vrne kot -64/255. Tukaj je kodirnik, omejen na zakoniti razpon:

if Tint > 0 then                       // MSOTINT - svetlejše
  TintOp := LongWord($02F4) shl 16 or
    (LongWord(Round(255 * (1 - Tint))) shl 8) or $10
else if Tint < 0 then                  // MSOSHADE - temnejše
  TintOp := LongWord($01F4) shl 16 or
    (LongWord(Round(255 * (1 + Tint))) shl 8) or $10
else
  TintOp := $20000000;                 // MSOCOLORMODUNDEFINED

Nastavljanje in branje polnila teme iz Delphija

Na strani zapisovanja sta polnilo teme dve dodatni polji v zapisu sloga posamezne serije. TXLSChartSeriesStyleInfo je dobil HasFillTheme, FillThemeColor in FillThemeTint, graditelj pa GelFrame odda samo, ko sta nastavljena HasStyle in HasFillTheme. Če nastavite tudi izrecni FillRgb, gre ta vrednost v OPT1 fillColor dobesedno; če ga ne, HotXLS barvo sam splošči skozi vgrajeno privzeto tabelo teme Office z uporabljenim odtenkom, zato ima serija samo s temo še vedno razumno plosko barvo za porabnike, ki ignorirajo OPT2. Upoštevajte inicializacijo Default(), ki je pomembna, ker TXLSChartSeriesInfo vsebuje upravljana polja, njegovi navadni člani Boolean pa so sicer smeti sklada:

var
  Wb: TXLSWorkbook;
  Series: array [0..1] of TXLSChartSeriesInfo;
begin
  Wb := TXLSWorkbook.Create;
  try
    Wb.Sheets.Add.Name := 'Data';

    Series[0] := Default(TXLSChartSeriesInfo);   // tega zapisa nikoli ne 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, rdeča v nizkem 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;        // brez izrecnega RGB: sploščeno
    Series[1].Style.FillThemeColor := 8;         // accent5

    Wb.Sheets.AddChartSheet('Themed', xlsChartTypeColumn, '', '', '', Series);
    Wb.SaveAs('themed.xls');
  finally
    Wb.Free;
  end;
end;

Branje nazaj gre skozi isti model grafikona, ki ga uporablja preostali pregled grafikonov HotXLS. GetChartModel vrne lastniški TXLSChartModel, ki ga sprostite, vsak TXLSChartSeries pa poleg FillRgb, dekodiranega iz OPT1 fillColor, izpostavi HasFillTheme, FillThemeColor in FillThemeTint; ta ima prednost pred barvo AreaFormat za to serijo. Iste tri vrednosti dosežejo tudi kanonični semantični posnetek kot SolidFillThemeSet, SolidFillThemeColor in SolidFillThemeTint, zato primerjava delovnih zvezkov spremembo teme vidi kot spremembo teme, ne kot nepojasnjen zdrs RGB. Če prihajate s strani XLSX, je to binarni dvojnik formata sloga, opisanega v vodiču HotXLS za grafikone, slike in risbe Excel v Delphiju:

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;

Česa polnilo teme v binarnem XLS ne obljublja?

Tri poštene omejitve. Prva in najpomembnejša za vsakega, ki revidira to kodo: nobena vzorčna datoteka v lokalnem korpusu sploh ne vsebuje zapisa GelFrame. Enajst pojavitev para bajtov 66 10 v vzorcu pogojnega oblikovanja leži na mejah, ki niso meje zapisov, celoten izpis toka zapisov pa najde nič zadetkov. Tukaj opisana postavitev bitov je bila izpeljana iz specifikacije in nato pritrjena na tri načine: s simetrijo dekodiranja nad izhodom graditelja, z ročno zgrajenimi bajtnimi testi, ki sintetiziran tovor $1066 neposredno podajo dekodirniku, ter s preverjanjem natančnega sploščenega RGB. To je šibkejša oblika dokaza kot zajeta datoteka Excel in to je vredno povedati, ne pa namigovati nasprotno. Druga: sploščitev polnila samo s temo uporablja vgrajeno privzeto tabelo teme Office, ne dela teme, prebrane iz delovnega zvezka, ker binarni XLS nima dela teme v smislu zapakiranega XLSX — če potrebujete, da lastna tema delovnega zvezka določi plosko barvo, sami podajte FillRgb. Tretja: dekodirnik sprejme GelFrame samo znotraj bloka serije; isti zapis se lahko pojavi na območju grafikona ali v okvirju osi, njegovo sprejetje tam pa bi tiho pripisalo ozadno polnilo seriji, zato sta ta primera prezrta. fillColorExt brez zastavice $08000000 se prav tako obravnava kot navadna razširjena barva in nikoli ne nastavi HasFillTheme. Pri delovnih zvezkih, katerih grafikon je ustvarjen v svetu XLSX in samo prehaja skozi, je varnejša pot ohranjanja v urejanju grafikonov Excel brez izgube ChartML, vsebnik, v katerem ti zapisi sedijo, pa je opisan v branju sestavljenih datotek OLE2 v Delphiju brez COM IStorage

Polnila grafikonov v barvah teme, kodirnik in dekodirnik GelFrame ter celoten graditelj podtoka grafikona BIFF8 so del komponente HotXLS Delphi spreadsheet za Delphi in C++Builder, ki bere in zapisuje XLS, XLSX in ODS brez nameščenega Excela