يحوّل HotXLS ألوان RGB وألوان السمات مهما كانت إلى لوحة ألوان BIFF8 ذات الخانات الـ 56 على طبقتين: NearestIndexedColor يجد أقرب مدخل موجود في اللوحة إدراكيًا ضمن فضاء OKLab، وBuildBiffPalettePlan مع ApplyBiffPalettePlan يعيدان كتابة الخانات الحرة كي ينجو مصنف بالألوان الحقيقية من عملية حفظ إلى XLS الكلاسيكي. الدافع هو تذكرة الدعم ذاتها دائمًا: أحدهم يبني تقريرًا في XLSX برؤوس بلون كحلي مؤسسي ولمسة فيروزية هادئة، ثم يحفظه بصيغة .xls لمستهلك قديم، فتعود الرؤوس سوداء صرفة بينما يتحول الفيروزي إلى تركوازي صارخ. لم ينهار شيء ولم يُطلق أي تحذير. النموذج اللوني للصيغة القديمة لا يتسع ببساطة لما وصفته الجديدة، وكل ما على المكتبة أن تختار شيئًا
لماذا لا يستطيع ملف XLS أن يحمل أكثر من 56 لونًا؟
لأن تنسيق خلية BIFF8 لا يخزن قيمة RGB أبدًا: الخطوط والتعبئات والحدود تحمل مؤشر لون، وسجل Palette العالمي للمصنف ($0092، [MS-XLS] §2.4.188) يزود 56 مدخل RGB معتمًا بالضبط للمؤشرات من 8 إلى 63. المؤشرات من 0 إلى 7 نسخ مثبتة من الألوان الأساسية الثمانية، والقيم فوق 63 ليست ألوانًا أصلًا بل رموز مثل لون النظام الأمامي ولون النظام الخلفي ونص الرسم البياني. يعرض HotXLS اللوحة عبر ColorIndex عام من 1 إلى 56، وهو المؤشر الفعلي ناقص 7، ويبقي ResolveIndexedColor أنظمة الترقيم الثلاثة منفصلة عبر TXLSIndexedColorSpace: xicsPublicColorIndex لقيم الـ API من 1 إلى 56، وxicsBiffIcv للمؤشرات الخام على القرص، وهي تتحقق مقابل المجموعة الجزئية IcvFont أو IcvXF أو IcvChart حسب الدور الذي تمرره، وxicsOoxmlIndexed حيث تعني 64 و65 لوني النظام الأمامي والخلفي
var
Res: TXLSIndexedColorResolution;
begin
// $40 رمز icv في BIFF، وليس خانة في اللوحة
Workbook.ResolveIndexedColor($40, xicsBiffIcv, Res);
case Res.Kind of
xickPalette: UseArgb(Res.ARGB); // خانة في اللوحة، إن حُسمت
xickAutomatic,
xickSystem: UseSystemColor(Res.SystemColorRole);
xickInvalid: RejectToken(Res.RawIndex);
end;
end;
لاحظ أن المثال يتفرع على Res.Kind ويتجاهل القيمة المنطقية المعادة. إن ResolveIndexedColor يعيد True فقط حين يحصل على ARGB ملموس، والتحميل الزائد القصير لا يقرأ سطح مكتب Windows قط، فرمز تلقائي أو نظامي يعيد False بشكل مشروع وهو ما يزال مصنفًا xickSystem. اصطدم HotXLS بذلك في مُسلسل مصنفاته هو: كود يعامل False بوصفها «لا لون» يتخلص بصمت من معنى Automatic وSystem للرمز. وإن احتجت قيم RGB حقيقية لتلك الرموز، فاستدعِ التحميل الزائد الطويل وزوّد رد نداء TXLSTryResolveSystemColor الذي يطبق سياستك أنت في الواجهة أو التصدير أو التشغيل بلا واجهة
لماذا يطابق HotXLS الألوان في OKLab بدل RGB؟
لأن قيم قنوات sRGB مشفرة غاما، فالمسافة الإقليدية في RGB لا تتبع ما يراه الإنسان، والخطأ يبلغ أسوأ حالاته في الدرجات الداكنة المشبعة تحديدًا التي تعشقها اللوحات المؤسسية. خذ الأزرق الداكن $000033. في RGB تبعد مسافة 51 عن الأسود و77 عن مدخل الكحلي الافتراضي $000080، فأي مطابِق يعتمد RGB يرسم رأسك أسود بكل ثقة. أما في OKLab فالمسافات المربعة نحو 0.0312 إلى الأسود و0.0235 إلى الكحلي، فيختار HotXLS الكحلي، ColorIndex 11 في الخانة الفعلية 18؛ وهذه الحالة بالذات مثبتة في جناح الاختبارات لكلا المحركين Classic وXLSX. التحويل داخل ArgbToOklab يجعل كل قناة sRGB خطية، ويطبق مصفوفة LMS الخاصة بـ OKLab، ويأخذ الجذور التكعيبية ويسقط على L وa وb، وعندها تصبح المسافة الإقليدية المربعة المجرّدة وكيلًا معقولًا للفرق المدرك. إن OKLab ليس CIEDE2000 ولا يدّعي كونه كذلك، لكنه لا يحمل أي تصحيحات تدرج مجزأة، وتكلفته حفنة ضربات لكل لون، وهو مستقر بما يكفي لقيادة حلقة تجميع، وهناك حيث يكسب مقعده فعلًا
ماذا يضمن NearestIndexedColor؟
يضمن NearestIndexedColor إجابة حتمية للقراءة فقط: تحويل واحد للمدخل، ومسح ثابت واحد فوق 56 مدخلًا مخزنًا مؤقتًا، وأدنى مؤشر عام متى تساوى مدخلان في القرب. يخزن كل مصنف مؤقتًا الـ ARGB المطبع وإحداثيات OKLab لكل الخانات الفعلية الـ 56 مع عدّاد توليد للوحة. إعادة ضبط اللوحة تعيد بناء المخزن، وتغيير خانة واحدة يحدّث تلك الخانة فقط، واستعلام ضد توليد قديم يعيد False بدل التخمين. يستخدم المسح مقارنة أصغر تمامًا بدءًا من الخانة 8، ولهذا تجيب لوحة تحوي اللون نفسه مرتين دائمًا بالمؤشر الأدنى؛ وهذا يهم حين تقارن ملفين مولدين وتتوقع مخرجات متطابقة بايتيًا. ألفا المدخل تتبع عقدًا ضيقًا: بايت ألفا معدوم يعامل كمعتم، والقيمة شبه الشفافة ترفض بـ ColorIndex مساوٍ 0 وPaletteSlot مساوٍ -1، لأن مداخيل اللوحة لا تحمل ألفا. كاتب التعبئات والحدود في المحرك Classic يحوّل RGB وألوان السمات إلى مؤشر بالروتين نفسه لمطابقة OKLab وقت الحفظ، فتتفق الـ API والملف المخزن على الخانة التي يهبط إليها لون
var
Match: TXLSNearestIndexedColorMatch;
begin
if Workbook.NearestIndexedColor($FF000033, Match) then
begin
// Match.ColorIndex تساوي 11 وMatch.PaletteSlot تساوي 18 وMatch.ARGB تساوي $FF000080
if not Match.ExactMatch then
LogApproximation(Match.InputARGB, Match.ARGB, Match.DistanceSquared);
end;
end;
كيف يُدخل BuildBiffPalettePlan الألوان الحقيقية في 56 خانة؟
يحسب BuildBiffPalettePlan مقترحًا كاملًا لكل الخانات الـ 56 دون أن يلمس المصنف، فيمكنك فحصه أو تسجيله أو التخلص منه. يستدعي المخطط أولًا ScanIndexedColorUsage: أي خانة يشير إليها خط أو تعبئة أو حدود أو تنسيق شرطي أو شكل أو تعليق أو خط شبكة ورقة عمل بمؤشر تُقفل، لأن تغيير مدخل في اللوحة يعيد تلوين كل مستهلك لذلك المؤشر دفعة واحدة. الأهداف هي ألوان RGB المباشرة وألوان السمات المحسومة من الخطوط والتعبئات والحدود والأنماط التفاضلية وأشرطة البيانات ومقاييس الألوان. وكل هدف يوزن بالأكبر بين عدد مرات إشارته المرئية وعدد تعريفاته، والتنسيق الشرطي يحسب الخلايا التي تغطيها نطاقاته، فاللون المرسوم على عمود كامل يفوق المستخدم في تعليق واحد. ثم يجري الإسناد بترتيب ثابت:
- الخانات المقفلة تحتفظ بلونها المصدر بلا شروط
- الهدف الموجود سلفًا في اللوحة يبقى عند أدنى خانة مطابقة له وتصير تلك الخانة مثبتة
- إن اتسعت الخانات الحرة للأهداف الفريدة المتبقية، نالت كل واحدة خانة مطابقة تمامًا، تسند بترتيب ARGB تصاعديًا
- وإلا يضبط
Quantized، وتُبذر كل خانة حرة بالهدف الذي تساوي مسافته إلى أقرب مركز موجود مضروبةً في وزنه أكبر ما يمكن، وتمر حتى 16 جولة من k-means موزونة بالتكرار في OKLab تحرك المراكز الحرة فقط حتى تتوقف الإسنادات عن التغير
كن صريحًا مع نفسك بشأن ما يقدمه مسار الفيضان. التجميع تحسين محلي محدود لا أمثلية عالمية، وتنتهي الخانة الحرة حاملة مركزًا ناقلًا تحول مرة أخرى إلى sRGB مع القطع إلى المدى، وقد يكون ذلك لونًا لم تستخدمه أي خلية حرفيًا. ما تحصل عليه فعلًا هو التكرارية: المصنف نفسه يعطي الخطة نفسها دائمًا، وتبلغ الخطة عن ضررها بنفسها عبر WeightedError وMaxDistanceSquared وExactTargetWeight وTotalTargetWeight، فيستطيع عمل دفعي أن يرفض الحفظ حين يصير التقريب أغظ من دليل هوية العلامة
var
Plan: TXLSBiffPalettePlan;
I: Integer;
begin
Plan := Workbook.BuildBiffPalettePlan; // للقراءة فقط
if Plan.Quantized and (Plan.MaxDistanceSquared > MaxAcceptedError) then
raise Exception.Create('Too many distinct colors for a BIFF8 palette');
for I := 0 to High(Plan.Slots) do
if Plan.Slots[I].Changed then
LogSlot(Plan.Slots[I].ColorIndex, Plan.Slots[I].SourceARGB,
Plan.Slots[I].TargetARGB);
if not Workbook.ApplyBiffPalettePlan(Plan) then
raise Exception.Create('The palette changed after planning');
end;
كيف يرفض ApplyBiffPalettePlan خطة قديمة؟
يتحقق ApplyBiffPalettePlan من الخطة بأكملها قبل أن يكتب خانة واحدة، ويعيد False واللوحة بلا مساس إن خالف شيء ما المصنف الحالي. تحمل الخطة SourcePaletteGeneration وSourcePaletteHash، وهي بصمة FNV-1a بعرض 64 بت فوق الألوان المصدرية الـ 56؛ ويعيد التحقق فحص كل مؤشر عام وفعلي، وكل لون مصدري، وأنه لا توجد خانة مقفلة موسومة كمتغيرة، وعددَي المقفل والمتغير، وأن كل هدف معتم. أي تغيير فعلي في اللوحة بينهما، بما فيه تطبيق سابق ناجح للخطة ذاتها، يجعل الخطة قديمة، فالخطط عمليًا ذات استخدام واحد. الخطة الصحيحة بلا خانات متغيرة تنجح دون تقديم التوليد، والتغيير الحقيقي يدفع التوليد مرة واحدة ويعيد بناء مطابِق OKLab مرة واحدة، في المحرك Classic بإعادة كتابة مصفوفة اللوحة المثبتة وفي محرك XLSX باستبدال قائمة تجاوز ألوان مفهرسة معدّة سلفًا
تشغيل الميزة في حفظ BIFF8 وتحويل XLSX إلى XLS
القيمة الافتراضية للخاصية BiffPaletteSavePolicy هي xbpsPreserve، فترقية HotXLS لا تعيد كتابة لوحة أحد خلف ظهره قط. وضبطها على xbpsOptimizeTrueColors يجعل مصنف Classic يبني خطة جديدة ويطبقها داخل SaveAs، لكن فقط حين يكون تنسيق الهدف xlExcel97؛ وكتابات BIFF5 وCSV وHTML وPDF وXLSX وغيرها تتجاهل الضبط. بعد حفظ ناجح تبقى اللوحة المحسنة في نموذج المصنف، فترى الاستعلامات والحفظ اللاحقة mapping نفسه. وإن فشل الحفظ أو ألغي، استُرجعت الألوان الـ 56 الأصلية والتوليد الأصلي. أما مصادر XLSX فيبني SaveXLSXWorkbookAsXLS في lxXlsxExport خطة واحدة من المصنف المحمل ويكتبها في لوحة الوجهة قبل تحويل أي نمط، وهي الجسر الحتمي الذي تمارسته تجربة منضدة تدقيق المصنفات وتحويلها. وتعبر ألوان السمات المخطط ذاته بعد حسم درجتها إلى RGB؛ وإن كنت تفضل إبقاء السمات حية في تعبئات الرسوم البيانية، فمقال تعبئات رسوم GelFrame بألوان السمة يغطي كيف يخزن XLS الثنائي مؤشر مخطط بدل لون مسطّح
// مصنف Classic: تفعيل اختياري، BIFF8 فقط
Workbook.BiffPaletteSavePolicy := xbpsOptimizeTrueColors;
if Workbook.SaveAs('report.xls', xlExcel97) <> 1 then
HandleSaveFailure; // اللوحة استُرجعت سلفًا
// من نموذج XLSX إلى BIFF8 بخطة لوحة حتمية واحدة
XWorkbook := TXLSXWorkbook.Create;
try
if XWorkbook.Open('report.xlsx') = 1 then
SaveXLSXWorkbookAsXLS(XWorkbook, 'report.xls');
finally
XWorkbook.Free;
end;
تعمل واجهات لوحة الألوان في HotXLS بالطريقة نفسها على IXLSWorkbook وTXLSXWorkbook، من Delphi وC++Builder على السواء. نزّل النسخة التجريبية ووجهها إلى أكثر جداولك ألوانًا من صفحة مكوّن HotXLS Excel لدلفي