Teknisk artikkel

Excel-chartfarger fra tema i Delphi: HotXLS GelFrame

En chart-serie fylt med en bokstavelig RGB-verdi følger ikke arbeidsboktemaet. Endre temaet, og serien beholder den gamle fargen. HotXLS håndterer dette i binær XLS med temafargede chartfyll: en GelFrame-record, 4198 eller $1066, skrevet rett etter AreaFormat inne i serieblokken og med en OfficeArt scheme index pluss en tint. Excel rendrer deretter serien slik det rendrer en temafyll det selv skrev

Hvor kommer GelFrame-recordnummeret fra?

GelFrame-recordnummeret er 4198 ($1066), og du finner det ikke i record-ens egen spesifikasjonsseksjon. [MS-XLS] 2.4.131 beskriver hva en GelFrame inneholder, men i motsetning til de fleste record-seksjoner sier den ikke rt-verdien. ABNF-en for chart-understrømmen hjelper heller ikke: Den gir bare produksjonen GELFRAME = 1*2GelFrame *Continue, som navngir record-en uten å nummerere den. Nummeret ligger i oppramsingstabellen for record-numre, flere sider unna seksjonen som dokumenterer payloaden. Den produksjonen fortjener et ekstra blikk for alle som skriver en reader: Den tillater én eller to GelFrame-recorder, hver eventuelt fulgt av Continue-recorder, så en parser som antar én record per produksjon, håndterer en fil den ikke skrev feil. HotXLS emitterer nøyaktig én GelFrame per temaserie, som er det Excel produserer for en enkel solid temafyll, og dekoderen behandler record-en som en selvstendig payload i stedet for å anta et fast antall

Inne i GelFrame-payloaden: to OfficeArt-propertytabeller

GelFrame-payloaden er to OfficeArt-propertytabeller etter hverandre: en OfficeArtFOPT (kalt OPT1) fulgt av en OfficeArtTertiaryFOPT (OPT2). Hver tabell er et 2-byte property-antall fulgt av så mange seks-byte FOPTE-oppføringer, og hver oppføring er en to-byte opid pluss en fire-byte op. Bit 15 i opid er fComplex: Når den er satt, er op-verdien en bytelengde, og en variabel hale følger etter de faste oppføringene. En dekoder som ignorerer disse halene, desynkroniserer og leser søppel-opid-er for alt etter den første komplekse egenskapen

Temafyllen uttrykkes av tre egenskaper fordelt over begge tabellene, pluss én som deklarerer fylltypen. HotXLS skriver fire egenskaper på 28 byte uten komplekse haler:

  • fillType $0180 i OPT1, satt til 1 (msofillSolid)
  • fillColor $0181 i OPT1, den flate RGB-en en eldre eller temauvitende konsument vil tegne
  • fillColorExt $019E i OPT2, temafargen i bunnen
  • fillColorExtMod $01A0 i OPT2, tint eller shade som er lagt på basen

Dette skillet er bevisst i formatet, ikke en tilfeldighet i implementasjonen: [MS-ODRAW] 2.2.2 beskriver tematriade som en flat farge pluss en basefarge pluss en modifikasjon, så en konsument som forstår temaer, beregner fyllen på nytt, mens en som ikke gjør det, fortsatt maler noe fornuftig. De omkringliggende opid-ene følger samme mønster og har identisk nummerering i gamle og nåværende [MS-ODRAW]-utgaver, noe som er praktisk når du leser to revisjoner på tvers: fillOpacity $0182, fillBackColor $0183, fillShadeType $019C, fillBackColorExt $01A2 og fillBackColorExtMod $01A4

Hvorfor ligger scheme index i den røde byte-en?

Fordi en OfficeArtCOLORREF defineres etter byte-offset, ikke etter numerisk verdi: rød på byte 0, grønn på byte 1, blå på byte 2 og flagg på byte 3. Les den strukturen som en little-endian DWORD, som er det hver FOPTE op er, og rød blir den minst signifikante byte-en. Det gjennomarbeidede lineColor-eksempelet i [MS-ODRAW] bekrefter det. Dermed har fSchemeIndex, som er flaggbiten E, tallverdien $08000000, og selve scheme index går i den røde byte-en, mens grønn og blå må være null. Accent1 er derfor op-verdien $08000004, ikke $00000004 og bestemt ikke $04000000

Temafølgeindeksen spesifikasjonen nekter å definere

Spesifikasjonen kaller rekkefølgen for scheme index vert-definert og gir ingen tabell, noe som betyr at byte-layouten alene ikke er nok til interoperabilitet med Excel. HotXLS bruker regnearkets temarekkefølge, som er den som går tur-retur mot ekte Excel-filer:

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

Tint og shade: MSOTINTSHADE-payloaden

fillColorExtMod-op-en er en MSOTINTSHADE-verdi, og den koder retning og mengde i én DWORD i stedet for som en fortegnet brøk. Verdien $20000000 betyr uendret. En lysnende tint er $02F4 shl 16 or amount shl 8 or $10 (MSOTINT); en mørknende tint har samme form med $01F4 i høyordet (MSOSHADE). amount-byte-en går motsatt vei av intuisjonen: $FF betyr uendret og $00 betyr full modifikasjon. HotXLS normaliserer dette til én DrawingML-lignende double der positiv verdi lysner og negativ verdi mørkner, med pluss eller minus (255 - amount) / 255. Mappingen er eksakt for verdiene Excel faktisk tilbyr i brukergrensesnittet, og derfor er tur-returen tapsfri i stedet for omtrentlig tapsfri: Den velkjente "Lighter 40%" er amount 153, og (255 - 153) / 255 er 0,4 uten avrundingsfeil i noen retning. En shade med amount 191 kommer tilbake som -64/255. Her er koderen, begrenset til det lovlige området:

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

Sette og lese en temafyll fra Delphi

På skrivesiden er en temafyll to ekstra felt på stil-record-en per serie. TXLSChartSeriesStyleInfo fikk HasFillTheme, FillThemeColor og FillThemeTint, og builderen emitterer GelFrame bare når både HasStyle og HasFillTheme er satt. Hvis du også setter en eksplisitt FillRgb, går den verdien ordrett inn i OPT1 fillColor; hvis du ikke gjør det, flater HotXLS ut fargen selv gjennom en innebygd standardtabell for Office-temaer med tint påført, slik at en serie som bare har tema, fortsatt har en fornuftig flat farge for konsumenter som ignorerer OPT2. Legg merke til Default()-initialiseringen, som betyr noe fordi TXLSChartSeriesInfo inneholder managed-felter, og de vanlige Boolean-medlemmene ellers er søppel fra stacken:

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

    Series[0] := Default(TXLSChartSeriesInfo);   // bruk aldri FillChar på denne record-en
    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, rød i 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;        // ingen eksplisitt RGB: flatet
    Series[1].Style.FillThemeColor := 8;         // accent5

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

Lesing tilbake går gjennom den samme chart-modellen som resten av HotXLS-chartinspeksjonen bruker. GetChartModel returnerer en eid TXLSChartModel som du frigjør, og hver TXLSChartSeries eksponerer HasFillTheme, FillThemeColor og FillThemeTint sammen med FillRgb dekodet fra OPT1 fillColor, som går foran AreaFormat-fargen for den serien. De samme tre verdiene når også det kanoniske semantiske øyeblikksbildet som SolidFillThemeSet, SolidFillThemeColor og SolidFillThemeTint, så en arbeidsbokdiff ser en temaendring som en temaendring i stedet for som en uforklart RGB-drift. Hvis du kommer fra XLSX-siden, er dette den binære formatmotparten til stylingen som beskrives i HotXLS-veiledningen til Excel-chart, bilder og tegninger i 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;

Hva lover ikke en temafyll i binær XLS?

Tre ærlige grenser. Først, og viktigst for alle som reviderer denne koden: Ingen eksempelfil i det lokale korpuset inneholder en GelFrame-record i det hele tatt. De elleve forekomstene av byteparet 66 10 i eksempelet med betinget formatering ligger ved ikke-record-grenser, og en full record-dump av strømmen finner null treff. Bitlayouten som beskrives her, ble utledet fra spesifikasjonen og deretter bundet på tre måter: gjennom dekodesymmetri på builder-output, gjennom håndbygde byt-tester som mater en syntetisk $1066-payload rett inn i dekoderen og gjennom en assertion av nøyaktig flatet RGB. Det er en svakere evidensform enn en fanget Excel-fil, og det er verdt å si det i stedet for å antyde noe annet. For det andre bruker flatsettingen for en temafyll uten egen farge en innebygd standardtabell for Office-tema, ikke en temadel lest fra arbeidsboken, fordi binær XLS ikke har en temadel i samme betydning som en pakket XLSX har — hvis du trenger at arbeidsbokens eget tema skal styre den flate fargen, må du oppgi FillRgb selv. For det tredje aksepterer dekoderen bare en GelFrame inne i en serieblokk; samme record kan dukke opp på chart-området eller en akseramme, og å akseptere den der ville stille tilordnet en bakgrunnsfyll til en serie, så slike forekomster ignoreres. En fillColorExt uten flagget $08000000 behandles på samme måte som en vanlig utvidet farge og setter aldri HasFillTheme. For arbeidsbøker der chartet er laget i XLSX-verdenen og bare passerer gjennom, er bevaringsstien i redigering av Excel-chart uten å miste ChartML den sikrere ruten, og beholderen disse record-ene ligger i, er dekket i lesing av OLE2 compound-filer i Delphi uten COM IStorage

Temafargede chartfyll, GelFrame-koderen og dekoderen og den komplette BIFF8-chart-understrømsbyggeren leveres i HotXLS Delphi spreadsheet-komponenten for Delphi og C++Builder, som leser og skriver XLS, XLSX og ODS uten Excel installert