مقال تقني

ألوان سمة مخططات Excel في Delphi: GelFrame في HotXLS

لا تتبع سلسلة مخطط مملوءة بقيمة RGB حرفية سمة المصنف. غيّر السمة، وتحتفظ السلسلة باللون القديم. وتعالج HotXLS هذا في XLS الثنائي بتعبئات سلاسل المخطط ذات ألوان السمة: سجل GelFrame، رقمه 4198 أو $1066، يُكتب مباشرة بعد AreaFormat داخل كتلة السلسلة ويحمل فهرس مخطط OfficeArt مع tint. ثم يصيّر Excel السلسلة بالطريقة نفسها التي يصيّر بها تعبئة ذات سمة كتبها هو

من أين يأتي رقم سجل GelFrame؟

رقم سجل GelFrame هو 4198 ($1066)، ولن تجده في قسم مواصفة السجل نفسه. تصف [MS-XLS] 2.4.131 ما يحتويه GelFrame، لكنها، خلاف معظم أقسام السجلات، لا تذكر قيمة rt. كما أن ABNF الخاص بتدفق المخطط لا يساعد: فهو يعطي فقط الإنتاج GELFRAME = 1*2GelFrame *Continue الذي يسمي السجل من دون ترقيمه. ويعيش الرقم في جدول تعداد أرقام السجلات، على بعد صفحات عدة من القسم الذي يوثق الحمولة. ويستحق هذا الإنتاج نظرة ثانية من كل من يكتب قارئًا: فهو يسمح بسجل GelFrame واحد أو اثنين، يتبعهما اختياريًا سجلات Continue، ولذلك سيعالج المحلل الذي يفترض سجلًا واحدًا لكل إنتاج ملفًا لم يكتبه بطريقة خاطئة. وتصدر HotXLS سجل GelFrame واحدًا بالضبط لكل سلسلة ذات سمة، وهو ما ينتجه Excel لتعبئة سمة صلبة بسيطة، ويتعامل مفككها مع السجل كحمولة مكتفية بذاتها بدل افتراض عدد ثابت

داخل حمولة GelFrame: جدولان لخصائص OfficeArt

حمولة GelFrame هي جدولان لخصائص OfficeArt متتابعان: OfficeArtFOPT، المسمى OPT1، يليه OfficeArtTertiaryFOPT، المسمى OPT2. وكل جدول عبارة عن عدد خصائص من بايتين يليه ذلك العدد من إدخالات FOPTE ذات الستة بايتات، وكل إدخال هو opid من بايتين وop من أربعة بايتات. والبت 15 من opid هو fComplex: وعند ضبطه تكون قيمة op طولًا بالبايتات، وتتبع الإدخالات الثابتة ذيلًا متغيرًا. والمفكك الذي يتجاهل هذه الذيول يفقد تزامنه ويقرأ opids تالفة لكل شيء بعد أول خاصية معقدة

يُعبّر عن تعبئة السمة بثلاث خصائص موزعة عبر الجدولين، إضافة إلى خاصية تعلن نوع التعبئة. وتكتب HotXLS أربع خصائص في 28 بايتًا من دون ذيول معقدة:

  • fillType بقيمة $0180 في OPT1، مضبوطة على 1 (msofillSolid)
  • fillColor بقيمة $0181 في OPT1، وهو RGB مسطح سيرسمه مستهلك أقدم أو غير واعٍ بالسمات
  • fillColorExt بقيمة $019E في OPT2، وهو لون السمة الأساسي
  • fillColorExtMod بقيمة $01A0 في OPT2، وهو tint أو shade المطبق على ذلك الأساس

وهذا الفصل مقصود في الصيغة، لا مصادفة في التنفيذ: تصف [MS-ODRAW] 2.2.2 الثلاثية ذات السمة كـلون مسطح مع لون أساس وتعديل، ولذلك يعيد المستهلك الذي يفهم السمات حساب التعبئة، بينما يرسم من لا يفهمها شيئًا معقولًا على أي حال. وتتبع opids المحيطة النمط نفسه وتحمل الترقيم نفسه في إصداري [MS-ODRAW] القديم والحالي، وهو أمر مريح عند القراءة المتقاطعة بين المراجعتين: fillOpacity $0182 وfillBackColor $0183 وfillShadeType $019C وfillBackColorExt $01A2 وfillBackColorExtMod $01A4

لماذا يقع فهرس المخطط في البايت الأحمر؟

لأن OfficeArtCOLORREF يُعرّف بإزاحة البايت لا بالقيمة العددية: الأحمر عند البايت 0، والأخضر عند البايت 1، والأزرق عند البايت 2، والأعلام عند البايت 3. اقرأ البنية كـDWORD ذي ترتيب little-endian، وهو ما تمثله كل قيمة op في FOPTE، فيصبح الأحمر البايت الأقل أهمية. ويؤكد مثال lineColor التطبيقي في [MS-ODRAW] ذلك. ولذلك تكون قيمة fSchemeIndex، وهو بت flags رقم E، هي $08000000، بينما يذهب فهرس المخطط نفسه إلى البايت الأحمر مع اشتراط أن يكون الأخضر والأزرق صفرًا. ومن ثم يكون Accent1 هو قيمة op $08000004، لا $00000004 وبالتأكيد ليس $04000000

ترتيب فهارس السمات الذي ترفض المواصفة تعريفه

تسمي المواصفة ترتيب فهرس المخطط محددًا من قبل المضيف ولا تقدم جدولًا له، ما يعني أن تخطيط البايتات وحده لا يكفي للتوافق مع Excel. وتستخدم HotXLS ترتيب سمة جدول البيانات، وهو ما يعيد المرور مع ملفات Excel الحقيقية:

  • 0 = lt1 و1 = dk1 و2 = lt2 و3 = dk2
  • من 4 إلى 9 = accent1 إلى accent6
  • 10 = hlink و11 = folHlink

Tint وshade: حمولة MSOTINTSHADE

قيمة op الخاصة بـfillColorExtMod هي قيمة MSOTINTSHADE، وتشفّر الاتجاه والكمية في DWORD واحد بدل كسر موقّع. وتعني القيمة $20000000 عدم التعديل. ويكون tint التفتيح بصيغة $02F4 shl 16 or amount shl 8 or $10 (MSOTINT)، بينما يكون tint التغميق بالشكل نفسه مع $01F4 في الكلمة العليا (MSOSHADE). ويسير بايت amount عكس الحدس: فـ$FF تعني من دون تغيير، و$00 تعني التعديل الكامل. وتطبّع HotXLS ذلك إلى double واحدة على نمط DrawingML، حيث الموجب يفتح والسالب يغمق، باستخدام زائد أو ناقص (255 - amount) / 255. والتخطيط دقيق للقيم التي يعرضها Excel فعلًا في واجهته، ولذلك تكون إعادة المرور بلا فقد لا تقريبية فقط: فقيمة «Lighter 40%» المألوفة هي amount بقيمة 153، و(255 - 153) / 255 تساوي 0.4 من دون خطأ تقريب في أي اتجاه. وتعود shade ذات amount بقيمة 191 كـ-64/255. إليك المرمّز، مع حصره في النطاق القانوني:

if Tint > 0 then                       // MSOTINT - تفتيح
  TintOp := LongWord($02F4) shl 16 or
    (LongWord(Round(255 * (1 - Tint))) shl 8) or $10
else if Tint < 0 then                  // MSOSHADE - تغميق
  TintOp := LongWord($01F4) shl 16 or
    (LongWord(Round(255 * (1 + Tint))) shl 8) or $10
else
  TintOp := $20000000;                 // MSOCOLORMODUNDEFINED

ضبط تعبئة ذات سمة وقراءتها من Delphi

في جانب الكتابة تكون تعبئة السمة حقلين إضافيين في سجل النمط الخاص بكل سلسلة. فقد أضاف TXLSChartSeriesStyleInfo الحقول HasFillTheme وFillThemeColor وFillThemeTint، ولا يصدر المنشئ GelFrame إلا عندما يكون كل من HasStyle وHasFillTheme مضبوطًا. وإذا ضبطت أيضًا FillRgb صريحًا، تذهب القيمة حرفيًا إلى fillColor في OPT1؛ وإذا لم تفعل، تقوم HotXLS بتسطيح اللون بنفسها عبر جدول سمة Office افتراضي مضمّن مع تطبيق tint، ولذلك تظل السلسلة ذات السمة فقط مالكة للون مسطح معقول لدى المستهلكين الذين يتجاهلون OPT2. لاحظ تهيئة Default() التي تهم هنا لأن TXLSChartSeriesInfo يحتوي حقولًا مدارة وأعضاء Boolean عاديين، وإلا أصبحت قمامة مكدس:

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

    Series[0] := Default(TXLSChartSeriesInfo);   // لا تستخدم 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، الأحمر في البايت الأقل أهمية
    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;        // لا RGB صريح: سيُسطح اللون
    Series[1].Style.FillThemeColor := 8;         // accent5

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

وتتم القراءة عبر نموذج المخطط نفسه الذي تستخدمه بقية عمليات فحص مخططات HotXLS. إذ يعيد GetChartModel كائن TXLSChartModel مملوكًا عليك تحريره، وتكشف كل TXLSChartSeries عن HasFillTheme وFillThemeColor وFillThemeTint إلى جانب FillRgb المفكوك من fillColor في OPT1، وهو الذي يتقدم على لون AreaFormat لهذه السلسلة. وتصل القيم الثلاث نفسها أيضًا إلى اللقطة الدلالية المعيارية كـSolidFillThemeSet وSolidFillThemeColor وSolidFillThemeTint، ولذلك ترى مقارنة المصنف تغير السمة كتغير سمة لا كانحراف RGB غير مفسر. وإذا كنت قادمًا من جانب XLSX، فهذا هو النظير الثنائي للتنسيق الموصوف في دليل HotXLS إلى مخططات Excel وصورها ورسوماتها في 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، تعبئة fillColor في OPT1
    end;
  finally
    Model.Free;
  end;
finally
  Wb.Free;
end;

ما الذي لا تعد به تعبئة سمة في XLS الثنائي؟

ثلاثة حدود صادقة. الأول، وهو الأهم لكل من يدقق هذا الكود: لا يحتوي أي ملف عينة في corpus المحلي سجل GelFrame أصلًا. وتقع التكرارات الإحدى عشرة لزوج البايت 66 10 في عينة التنسيق الشرطي عند حدود ليست حدود سجلات، كما أن تفريغًا كاملًا للتدفق يعثر على صفر من المطابقات. وقد اشتُق تخطيط البتات الموضح هنا من المواصفة، ثم ثُبّت بثلاث طرق: تماثل الفك على ناتج المنشئ، واختبارات بايتات مصنوعة يدويًا تغذي حمولة $1066 اصطناعية مباشرة إلى المفكك، والتأكيد على RGB المسطح الدقيق. وهذا دليل أضعف من ملف Excel ملتقط، ويستحق القول كذلك بدل الإيحاء بعكسه. ثانيًا، يستخدم التسطيح لتعبئة لا تملك إلا السمة جدول سمة Office افتراضيًا مضمّنًا، لا جزء سمة مقروءًا من المصنف، لأن XLS الثنائي لا يملك جزء سمة بالمعنى الذي يملكه XLSX المعبأ — فإذا احتجت أن تقود سمة المصنف لونها المسطح، فمرر FillRgb بنفسك. ثالثًا، لا يقبل المفكك GelFrame إلا داخل كتلة سلسلة؛ فقد يظهر السجل نفسه في منطقة المخطط أو إطار محور، وقبوله هناك سينسب تعبئة خلفية إلى سلسلة بصمت، ولذلك تُتجاهل تلك الحالات. كما تُعامل قيمة fillColorExt التي لا تحمل العلم $08000000 كلون ممتد عادي، ولا تضبط HasFillTheme قط. وبالنسبة إلى المصنفات التي أُنشئ مخططها في عالم XLSX ولا تمر إلا عبر هذا المسار، فإن مسار الحفاظ في تعديل مخططات Excel من دون فقد ChartML هو الطريق الأكثر أمانًا، بينما تغطي قراءة ملفات OLE2 المركبة في Delphi من دون COM IStorage الحاوية التي تجلس فيها هذه السجلات

تأتي تعبئات المخططات ذات ألوان السمات ومرمّز GelFrame ومفككه ومنشئ تدفق مخطط BIFF8 الكامل ضمن مكوّن جداول HotXLS لـDelphi لـDelphi وC++Builder، الذي يقرأ ويكتب XLS وXLSX وODS من دون تثبيت Excel