Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4211e53
add darkMode to spreadsheeteditor (web-apps/issues/117) need related …
j-base64 Jul 8, 2026
ef62e3b
Remove unused CellTextColor skin token (superseded by per-color dark-…
j-base64 Jul 9, 2026
96b9b71
Respect explicit colors in dark-mode correction (text, fills, borders)
j-base64 Jul 15, 2026
5bd2b4e
support print (disable darkmode for print)
j-base64 Jul 17, 2026
1f448dc
- optim var names and comments for readability
j-base64 Jul 22, 2026
bd39527
💪 performance - adding rgb cache and a colorpicker object
j-base64 Jul 23, 2026
577e75f
- fix darkmode right border of cells overlapped by long text
j-base64 Jul 23, 2026
d18e1d4
comments and structure improvment
j-base64 Jul 23, 2026
b2c92ae
fix darkmode merged cells background (double correction + print leak)
j-base64 Jul 23, 2026
660393b
Remove the committed debug instrumentation
j-base64 Jul 23, 2026
f584d74
improve comment and doc
j-base64 Jul 24, 2026
1fed822
Improve column/row resize border draw management and connect it to da…
j-base64 Jul 24, 2026
d5a2e26
fix darkmode search-highlight text unreadable (light-on-yellow)
j-base64 Jul 24, 2026
6caa708
fix darkmode automatic text forced to black on any explicit fill
j-base64 Jul 28, 2026
26999c4
Rename dark-mode text-color correction flag to match what it actually…
j-base64 Jul 29, 2026
12c3905
Fix editor black text on gradient/pattern-filled cells in dark mode
j-base64 Jul 29, 2026
0f55894
End session custom transversal re-review :
j-base64 Jul 30, 2026
591ca31
End session custom transversal re-review :
j-base64 Jul 30, 2026
42705a0
Fix conditional-formatting Data Bar colors getting dark-mode corrected
j-base64 Jul 31, 2026
3f1dd8c
Resolve cell-editor background fallback once instead of twice
j-base64 Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions cell/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3411,6 +3411,12 @@ var editor;
this.wb = new AscCommonExcel.WorkbookView(this.wbModel, this.controller, this.handlers, this.HtmlElement,
this.topLineEditorElement, this, this.collaborativeEditing, this.fontRenderingMode);

// Needed here for its scrollbar color sync, stringRender reset, and initial draw.
// Its wb.updateSkin() call recomputes the worksheet style the constructor above
// already set via updateDarkMode, a harmless one-time duplication rather than
// something this call is relied on to fix.
this.updateSkin();

this.registerCustomFunctionsLibrary(undefined, true);

if (this.isCopyOutEnabled && this.topLineEditorElement) {
Expand Down Expand Up @@ -8628,6 +8634,9 @@ var editor;

if (this.wb) {
this.wb.updateSkin();
if (this.wb.stringRender) {
this.wb.stringRender._reset();
}
var ws = this.wb.getWorksheet();
if (ws) {
this.controller.updateScrollSettings();
Expand All @@ -8636,6 +8645,20 @@ var editor;
}
};

spreadsheet_api.prototype.updateDarkMode = function () {
if (this.wb) {
this.wb.updateDarkMode(this.isDarkMode);
var ws = this.wb.getWorksheet();
if (ws) {
ws.draw();
}
// TODO: a cell actively being edited doesn't refresh to the new theme until editing
// ends (pre-existing limitation, not specific to dark mode). Fixing it live needs
// WorksheetView to re-derive the edited cell's own fill; we're accepting this small,
// self-correcting gap for now rather than adding that for a narrow, transient case.
}
};

spreadsheet_api.prototype.turnOffSpecialModes = function() {
let bResult = false;
if (this.isStartAddShape) {
Expand Down
45 changes: 45 additions & 0 deletions cell/graphics/DrawingContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,54 @@

// AscCommon.CColor
this.fillColor = new AscCommon.CColor(255, 255, 255);

//////
// DarkMode support (DM)
this.isDarkMode = false;

// DM / performance - cache for darkModeCorrectColor2 results, avoids recalculating the
// same color thousands of times per redraw. Never exposed directly, only read back via
// _darkModeColorShuttle.
this._darkModeRgbCache = {};

// DM / performance - one shared CColor reused for every getDarkModeCorrectedColor call,
// instead of allocating a new one each time. Safe because every caller reads it
// synchronously (setStrokeStyle/setFillStyle unpack it immediately) and never retains it.
this._darkModeColorShuttle = new AscCommon.CColor(0, 0, 0, 1);

return this;
}

/**
* Returns the corrected color for the given automatic color.
* Callers should decide whether a color is eligible (explicit vs. automatic) before calling this.
* isDarkMode is not re-checked here: every current caller already gates the call itself on it.
* @param {Number} r 0-255
* @param {Number} g 0-255
* @param {Number} b 0-255
* @param {Number} [a] 0-1
* @return {AscCommon.CColor} the shared shuttle instance - read it immediately, it is
* overwritten by the next call, never store or mutate the reference
*/
DrawingContext.prototype.getDarkModeCorrectedColor = function (r, g, b, a) {

var shuttle = this._darkModeColorShuttle;
var key = r + ',' + g + ',' + b;
var corrected = this._darkModeRgbCache[key];

if (!corrected) {
corrected = AscCommon.darkModeCorrectColor2(r, g, b);
this._darkModeRgbCache[key] = corrected;
}

shuttle.put_r(corrected.R);
shuttle.put_g(corrected.G);
shuttle.put_b(corrected.B);
shuttle.a = a;

return shuttle;
};

DrawingContext.prototype._ppiInit = function () {
this.scaleFactor = 1;

Expand Down
28 changes: 27 additions & 1 deletion cell/model/WorkbookElems.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ var g_oRgbColorProperties = {
function RgbColor(rgb)
{
this.rgb = rgb;
// true only for g_oDefaultFormat.ColorAuto, the "no color set" default
this.isAutoColor = false;

this._hash;
}
Expand All @@ -153,7 +155,9 @@ RgbColor.prototype =
},
clone : function()
{
return new RgbColor(this.rgb);
var oColor = new RgbColor(this.rgb);
oColor.isAutoColor = this.isAutoColor; // stored on the color itself, survives cloning: true = still default, false = explicit (e.g. font color set by user/template)
return oColor;
},
getType : function()
{
Expand Down Expand Up @@ -473,6 +477,28 @@ g_oColorManager = new ColorManager();

xfs: new CellXfs()
};
// Marks this instance as the "no explicit color set" default, as opposed to a user
// explicitly choosing literal black (a different RgbColor(0) instance). Identity alone
// doesn't survive .clone(), so callers like dark-mode text-color correction check this
// flag instead.
g_oDefaultFormat.ColorAuto.isAutoColor = true;

// Is this color still Automatic, as opposed to something a user or template picked?
// Works for any color (font, border, ...): color.isAutoColor covers ColorAuto and its
// clones, and the identity check covers the one extra case that applies to font color
// once a workbook loads: g_oDefaultFormat.Font.c is then populated with the theme's
// default-text ThemeColor (see getBinaryOtherTableGVar in Serialize.js), and
// ThemeColor.clone() returns `this`, so that identity survives charProperties cloning.
function isColorAutomatic(color) {
if (!color) {
return true;
}
if (color.isAutoColor) {
return true;
}
return !!(g_oDefaultFormat.Font) && color === g_oDefaultFormat.Font.c;
}
window['AscCommonExcel'].isColorAutomatic = isColorAutomatic;

/** @constructor */
function Fragment(val) {
Expand Down
7 changes: 6 additions & 1 deletion cell/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2538,13 +2538,18 @@
}
}

function drawFillCell(ctx, graphics, fill, rect) {
function drawFillCell(ctx, graphics, fill, rect, bKeepsFillColorAsIs) {
if (!fill.hasFill()) {
return;
}

var solid = fill.getSolidFill();
if (solid) {
if (ctx.isDarkMode) {
if (!bKeepsFillColorAsIs) {
solid = ctx.getDarkModeCorrectedColor(solid.getR(), solid.getG(), solid.getB(), solid.getA());
}
}
ctx.setFillStyle(solid).fillRect(rect._x, rect._y, rect._width, rect._height);
return;
}
Expand Down
4 changes: 3 additions & 1 deletion cell/view/CellEditorView.js
Original file line number Diff line number Diff line change
Expand Up @@ -1633,7 +1633,9 @@ function (window, undefined) {
}

if (opt.fragments && opt.fragments.length > 0) {
t.textRender.render(undefined, t._getContentLeft(), dy || 0, t._getContentWidth(), opt.font.getColor());
// keepsAutomaticTextColorAsIs reaches beginFragment's and handleBidiFlow's own
// lighting mode correction checks
t.textRender.render(undefined, t._getContentLeft(), dy || 0, t._getContentWidth(), opt.font.getColor(), opt.keepsAutomaticTextColorAsIs);
}
};

Expand Down
59 changes: 53 additions & 6 deletions cell/view/StringRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -594,10 +594,13 @@
* @param {Number} y Top of the text rect
* @param {Number} maxWidth Text width restriction
* @param {String} textColor Default text color for formatless string
* @param {boolean} [bKeepsAutomaticTextColorAsIs] true when the effective background under
* this text (its own fill, the fixed search-highlight color, or nothing at all) is light
* enough that default/automatic text doesn't need dark-mode color correction
* @return {StringRender} Returns 'this' to allow chaining
*/
StringRender.prototype.render = function (drawingCtx, x, y, maxWidth, textColor) {
this._doRender(drawingCtx, x, y, maxWidth, textColor);
StringRender.prototype.render = function (drawingCtx, x, y, maxWidth, textColor, bKeepsAutomaticTextColorAsIs) {
this._doRender(drawingCtx, x, y, maxWidth, textColor, bKeepsAutomaticTextColorAsIs);
return this;
};

Expand Down Expand Up @@ -1170,12 +1173,12 @@
* @param {String} textColor
*/

StringRender.prototype._doRender = function (drawingCtx, x, y, maxWidth, textColor) {
StringRender.prototype._doRender = function (drawingCtx, x, y, maxWidth, textColor, bKeepsAutomaticTextColorAsIs) {
let self = this;
let ctx = drawingCtx || this.drawingCtx;
let zoom = ctx.getZoom();
let ppiy = ctx.getPPIY();
this.drawState.reset(drawingCtx, textColor, this.flags, this.angle);
this.drawState.reset(drawingCtx, textColor, this.flags, this.angle, bKeepsAutomaticTextColorAsIs);
let drawState = this.drawState;
let align = this.getEffectiveAlign();
let i, j, p, p_, strBeg;
Expand Down Expand Up @@ -1370,6 +1373,7 @@
this.currentFont = null;
this.currentColor = null;
this.textColor = null;
this.keepsAutomaticTextColorAsIs = false;
this.angle = 0;
this.currentLine = null;
this.startIdx = 0;
Expand Down Expand Up @@ -1407,7 +1411,17 @@

let fsz = prop.font.getSize();
let lw = asc_round(fsz * ppiy / 72 / 18) || 1;
ctx.setStrokeStyle(prop.c || textColor)

let decorationColor = prop.c || textColor;
if (ctx.isDarkMode) {
let isDecorationRecolorable = !this.keepsAutomaticTextColorAsIs && AscCommonExcel.isColorAutomatic(decorationColor);
if (isDecorationRecolorable) {
//only modify default colored cell (the ones not explicitly colored by the user or a table template)
decorationColor = ctx.getDarkModeCorrectedColor(decorationColor.getR(), decorationColor.getG(),
decorationColor.getB(), decorationColor.getA());
}
}
ctx.setStrokeStyle(decorationColor)
.setLineWidth(lw)
.beginPath();
let dy = (lw / 2);
Expand Down Expand Up @@ -1455,6 +1469,24 @@
let _g = textColor.getG();
let _b = textColor.getB();
let _a = textColor.getA();

if (this.drawingCtx.isDarkMode) {
// isColorAutomatic identifies the "no color set" default (see WorkbookElems.js);
// only that should be dark-mode-inverted, never a color some cell/run actually
// picked. keepsAutomaticTextColorAsIs exempts default text too, when the
// background it sits on (the cell's own fill, or nothing at all) is already
// light enough: that background was authored with some text color pairing in
// mind, and inverting default text on top of it can turn readable-on-light into
// unreadable-on-light (e.g. white text on a light table-style band).
let isTextRecolorable = !this.keepsAutomaticTextColorAsIs && AscCommonExcel.isColorAutomatic(textColor);
if (isTextRecolorable) {
//only modify default colored cell (the ones not explicitly colored by the user or a table template)
textColor = this.drawingCtx.getDarkModeCorrectedColor(_r, _g, _b, _a);
_r = textColor.getR();
_g = textColor.getG();
_b = textColor.getB();
}
}
let setColor = true;
if (this.drawingCtx.fillColor && this.drawingCtx.fillColor.isEqual(_r, _g, _b, _a)) {
setColor = false;
Expand Down Expand Up @@ -1518,6 +1550,20 @@
let _g = textColor.getG();
let _b = textColor.getB();
let _a = textColor.getA();

if (this.drawingCtx.isDarkMode) {
// see beginFragment above: use AscCommonExcel.isColorAutomatic plus
// keepsAutomaticTextColorAsIs, and only resolve/reallocate when dark mode is on
// and the color isn't explicit
let isTextRecolorable = !this.keepsAutomaticTextColorAsIs && AscCommonExcel.isColorAutomatic(textColor);
if (isTextRecolorable) {
//only modify default colored cell (the ones not explicitly colored by the user or a table template)
textColor = this.drawingCtx.getDarkModeCorrectedColor(_r, _g, _b, _a);
_r = textColor.getR();
_g = textColor.getG();
_b = textColor.getB();
}
}
let setColor = true;
if (this.drawingCtx.fillColor && this.drawingCtx.fillColor.isEqual(_r, _g, _b, _a)) {
setColor = false;
Expand Down Expand Up @@ -1584,8 +1630,9 @@



TableCellDrawState.prototype.reset = function(drawingCtx, textColor, flags, angle) {
TableCellDrawState.prototype.reset = function(drawingCtx, textColor, flags, angle, bKeepsAutomaticTextColorAsIs) {
this.drawingCtx = drawingCtx || this.stringRender.drawingCtx;
this.keepsAutomaticTextColorAsIs = !!bKeepsAutomaticTextColorAsIs;
this.x = 0;
this.y = 0;
this.baseY = 0;
Expand Down
Loading
Loading