Compare commits
1 Commits
master
...
dwelle/dar
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0458834681 |
@ -1,5 +1,90 @@
|
||||
import oc from "open-color";
|
||||
import type { Merge } from "./utility-types";
|
||||
import { clamp } from "../math/utils";
|
||||
import tinycolor from "tinycolor2";
|
||||
import { degreesToRadians } from "../math/angle";
|
||||
import type { Degrees } from "../math/types";
|
||||
|
||||
function cssHueRotate(
|
||||
red: number,
|
||||
green: number,
|
||||
blue: number,
|
||||
degrees: Degrees,
|
||||
): { r: number; g: number; b: number } {
|
||||
// normalize
|
||||
const r = red / 255;
|
||||
const g = green / 255;
|
||||
const b = blue / 255;
|
||||
|
||||
// Convert degrees to radians
|
||||
const a = degreesToRadians(degrees);
|
||||
|
||||
const c = Math.cos(a);
|
||||
const s = Math.sin(a);
|
||||
|
||||
// rotation matrix
|
||||
const matrix = [
|
||||
0.213 + c * 0.787 - s * 0.213,
|
||||
0.715 - c * 0.715 - s * 0.715,
|
||||
0.072 - c * 0.072 + s * 0.928,
|
||||
0.213 - c * 0.213 + s * 0.143,
|
||||
0.715 + c * 0.285 + s * 0.14,
|
||||
0.072 - c * 0.072 - s * 0.283,
|
||||
0.213 - c * 0.213 - s * 0.787,
|
||||
0.715 - c * 0.715 + s * 0.715,
|
||||
0.072 + c * 0.928 + s * 0.072,
|
||||
];
|
||||
|
||||
// transform
|
||||
const newR = r * matrix[0] + g * matrix[1] + b * matrix[2];
|
||||
const newG = r * matrix[3] + g * matrix[4] + b * matrix[5];
|
||||
const newB = r * matrix[6] + g * matrix[7] + b * matrix[8];
|
||||
|
||||
// clamp the values to [0, 1] range and convert back to [0, 255]
|
||||
return {
|
||||
r: Math.round(Math.max(0, Math.min(1, newR)) * 255),
|
||||
g: Math.round(Math.max(0, Math.min(1, newG)) * 255),
|
||||
b: Math.round(Math.max(0, Math.min(1, newB)) * 255),
|
||||
};
|
||||
}
|
||||
|
||||
const cssInvert = (
|
||||
r: number,
|
||||
g: number,
|
||||
b: number,
|
||||
percent: number,
|
||||
): { r: number; g: number; b: number } => {
|
||||
const p = clamp(percent, 0, 100) / 100;
|
||||
|
||||
// Function to invert a single color component
|
||||
const invertComponent = (color: number): number => {
|
||||
// Apply the invert formula
|
||||
const inverted = color * (1 - p) + (255 - color) * p;
|
||||
// Round to the nearest integer and clamp to [0, 255]
|
||||
return Math.round(clamp(inverted, 0, 255));
|
||||
};
|
||||
|
||||
// Calculate the inverted RGB components
|
||||
const invertedR = invertComponent(r);
|
||||
const invertedG = invertComponent(g);
|
||||
const invertedB = invertComponent(b);
|
||||
|
||||
return { r: invertedR, g: invertedG, b: invertedB };
|
||||
};
|
||||
|
||||
export const applyDarkModeFilter = (color: string) => {
|
||||
let tc = tinycolor(color);
|
||||
|
||||
const _alpha = tc._a;
|
||||
|
||||
// order of operations matters
|
||||
// (corresponds to "filter: invert(invertPercent) hue-rotate(hueDegrees)" in css)
|
||||
tc = tinycolor(cssInvert(tc._r, tc._g, tc._b, 93));
|
||||
tc = tinycolor(cssHueRotate(tc._r, tc._g, tc._b, 180 as Degrees));
|
||||
tc.setAlpha(_alpha);
|
||||
|
||||
return tc.toHex8String();
|
||||
};
|
||||
|
||||
// FIXME can't put to utils.ts rn because of circular dependency
|
||||
const pick = <R extends Record<string, any>, K extends readonly (keyof R)[]>(
|
||||
|
@ -1700,6 +1700,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elementsPendingErasure: this.elementsPendingErasure,
|
||||
pendingFlowchartNodes:
|
||||
this.flowChartCreator.pendingNodes,
|
||||
theme: this.state.theme,
|
||||
}}
|
||||
/>
|
||||
{this.state.newElement && (
|
||||
@ -1720,6 +1721,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elementsPendingErasure:
|
||||
this.elementsPendingErasure,
|
||||
pendingFlowchartNodes: null,
|
||||
theme: this.state.theme,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -2695,6 +2697,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
activeTool: updateActiveTool(this.state, { type: "selection" }),
|
||||
});
|
||||
}
|
||||
if (prevState.theme !== this.state.theme) {
|
||||
this.scene
|
||||
.getElementsIncludingDeleted()
|
||||
.forEach((element) => ShapeCache.delete(element));
|
||||
}
|
||||
if (
|
||||
this.state.activeTool.type === "eraser" &&
|
||||
prevState.theme !== this.state.theme
|
||||
|
@ -124,7 +124,7 @@ body.excalidraw-cursor-resize * {
|
||||
// recommends surface color of #121212, 93% yields #111111 for #FFF
|
||||
|
||||
canvas {
|
||||
filter: var(--theme-filter);
|
||||
// filter: var(--theme-filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +189,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
$theme-filter: "invert(93%) hue-rotate(180deg)";
|
||||
$theme-filter: "invert(93%) hue-rotate(180deg)"; // prod
|
||||
// $theme-filter: "invert(93%)"; // prod
|
||||
// $theme-filter: "hue-rotate(180deg)"; // prod
|
||||
// $theme-filter: "hue-rotate(180deg) invert(93%)";
|
||||
|
||||
$right-sidebar-width: "302px";
|
||||
|
||||
:export {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
isBoundToContainer,
|
||||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
import { CLASSES, isSafari, POINTER_BUTTON } from "../constants";
|
||||
import { CLASSES, isSafari, POINTER_BUTTON, THEME } from "../constants";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
@ -50,6 +50,7 @@ import {
|
||||
originalContainerCache,
|
||||
updateOriginalContainerCache,
|
||||
} from "./containerCache";
|
||||
import { applyDarkModeFilter } from "../colors";
|
||||
|
||||
const getTransform = (
|
||||
width: number,
|
||||
@ -273,10 +274,15 @@ export const textWysiwyg = ({
|
||||
textAlign,
|
||||
verticalAlign,
|
||||
color: updatedTextElement.strokeColor,
|
||||
// color:
|
||||
// appState.theme === THEME.DARK
|
||||
// ? applyDarkModeFilter(updatedTextElement.strokeColor)
|
||||
// : updatedTextElement.strokeColor,
|
||||
opacity: updatedTextElement.opacity / 100,
|
||||
filter: "var(--theme-filter)",
|
||||
maxHeight: `${editorMaxHeight}px`,
|
||||
});
|
||||
// console.log("...", updatedTextElement.strokeColor);
|
||||
editable.scrollTop = 0;
|
||||
// For some reason updating font attribute doesn't set font family
|
||||
// hence updating font family explicitly for test environment
|
||||
|
10
packages/excalidraw/global.d.ts
vendored
10
packages/excalidraw/global.d.ts
vendored
@ -104,3 +104,13 @@ declare namespace jest {
|
||||
toBeNonNaNNumber(): void;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace tinycolor {
|
||||
interface Instance {
|
||||
_r: number;
|
||||
_g: number;
|
||||
_b: number;
|
||||
_a: number;
|
||||
_ok: boolean;
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +63,7 @@
|
||||
"@radix-ui/react-popover": "1.0.3",
|
||||
"@radix-ui/react-tabs": "1.0.2",
|
||||
"@tldraw/vec": "1.7.1",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"browser-fs-access": "0.29.1",
|
||||
"canvas-roundrect-polyfill": "0.0.1",
|
||||
"clsx": "1.1.1",
|
||||
@ -84,6 +85,7 @@
|
||||
"pwacompat": "2.0.17",
|
||||
"roughjs": "4.6.4",
|
||||
"sass": "1.51.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tunnel-rat": "0.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -3,6 +3,7 @@ import type { StaticCanvasAppState, AppState } from "../types";
|
||||
import type { StaticCanvasRenderConfig } from "../scene/types";
|
||||
|
||||
import { THEME, THEME_FILTER } from "../constants";
|
||||
import { applyDarkModeFilter } from "../colors";
|
||||
|
||||
export const fillCircle = (
|
||||
context: CanvasRenderingContext2D,
|
||||
@ -50,7 +51,7 @@ export const bootstrapCanvas = ({
|
||||
context.scale(scale, scale);
|
||||
|
||||
if (isExporting && theme === THEME.DARK) {
|
||||
context.filter = THEME_FILTER;
|
||||
// context.filter = THEME_FILTER;
|
||||
}
|
||||
|
||||
// Paint background
|
||||
@ -64,7 +65,10 @@ export const bootstrapCanvas = ({
|
||||
context.clearRect(0, 0, normalizedWidth, normalizedHeight);
|
||||
}
|
||||
context.save();
|
||||
context.fillStyle = viewBackgroundColor;
|
||||
context.fillStyle =
|
||||
theme === THEME.DARK
|
||||
? applyDarkModeFilter(viewBackgroundColor)
|
||||
: viewBackgroundColor;
|
||||
context.fillRect(0, 0, normalizedWidth, normalizedHeight);
|
||||
context.restore();
|
||||
} else {
|
||||
|
@ -61,6 +61,7 @@ import { ShapeCache } from "../scene/ShapeCache";
|
||||
import { getVerticalOffset } from "../fonts";
|
||||
import { isRightAngleRads } from "../../math";
|
||||
import { getCornerRadius } from "../shapes";
|
||||
import { applyDarkModeFilter } from "../colors";
|
||||
|
||||
// using a stronger invert (100% vs our regular 93%) and saturate
|
||||
// as a temp hack to make images in dark theme look closer to original
|
||||
@ -247,9 +248,9 @@ const generateElementCanvas = (
|
||||
const rc = rough.canvas(canvas);
|
||||
|
||||
// in dark theme, revert the image color filter
|
||||
if (shouldResetImageFilter(element, renderConfig, appState)) {
|
||||
context.filter = IMAGE_INVERT_FILTER;
|
||||
}
|
||||
// if (shouldResetImageFilter(element, renderConfig, appState)) {
|
||||
// context.filter = IMAGE_INVERT_FILTER;
|
||||
// }
|
||||
|
||||
drawElementOnCanvas(element, rc, context, renderConfig, appState);
|
||||
|
||||
@ -403,7 +404,10 @@ const drawElementOnCanvas = (
|
||||
case "freedraw": {
|
||||
// Draw directly to canvas
|
||||
context.save();
|
||||
context.fillStyle = element.strokeColor;
|
||||
context.fillStyle =
|
||||
appState.theme === THEME.DARK
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor;
|
||||
|
||||
const path = getFreeDrawPath2D(element) as Path2D;
|
||||
const fillShape = ShapeCache.get(element);
|
||||
@ -412,7 +416,10 @@ const drawElementOnCanvas = (
|
||||
rc.draw(fillShape);
|
||||
}
|
||||
|
||||
context.fillStyle = element.strokeColor;
|
||||
context.fillStyle =
|
||||
appState.theme === THEME.DARK
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor;
|
||||
context.fill(path);
|
||||
|
||||
context.restore();
|
||||
@ -458,7 +465,10 @@ const drawElementOnCanvas = (
|
||||
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
|
||||
context.save();
|
||||
context.font = getFontString(element);
|
||||
context.fillStyle = element.strokeColor;
|
||||
context.fillStyle =
|
||||
appState.theme === THEME.DARK
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor;
|
||||
context.textAlign = element.textAlign as CanvasTextAlign;
|
||||
|
||||
// Canvas does not support multiline text by default
|
||||
@ -699,7 +709,10 @@ export const renderElement = (
|
||||
context.fillStyle = "rgba(0, 0, 200, 0.04)";
|
||||
|
||||
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
|
||||
context.strokeStyle = FRAME_STYLE.strokeColor;
|
||||
context.strokeStyle =
|
||||
appState.theme === THEME.DARK
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: FRAME_STYLE.strokeColor;
|
||||
|
||||
// TODO change later to only affect AI frames
|
||||
if (isMagicFrameElement(element)) {
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
MAX_DECIMALS_FOR_SVG_EXPORT,
|
||||
MIME_TYPES,
|
||||
SVG_NS,
|
||||
THEME,
|
||||
} from "../constants";
|
||||
import { normalizeLink, toValidURL } from "../data/url";
|
||||
import { getElementAbsoluteCoords } from "../element";
|
||||
@ -37,6 +38,7 @@ import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
|
||||
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
||||
import { getVerticalOffset } from "../fonts";
|
||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||
import { applyDarkModeFilter } from "../colors";
|
||||
|
||||
const roughSVGDrawWithPrecision = (
|
||||
rsvg: RoughSVG,
|
||||
@ -139,7 +141,7 @@ const renderElementToSvg = (
|
||||
case "rectangle":
|
||||
case "diamond":
|
||||
case "ellipse": {
|
||||
const shape = ShapeCache.generateElementShape(element, null);
|
||||
const shape = ShapeCache.generateElementShape(element, renderConfig);
|
||||
const node = roughSVGDrawWithPrecision(
|
||||
rsvg,
|
||||
shape,
|
||||
@ -389,7 +391,12 @@ const renderElementToSvg = (
|
||||
);
|
||||
node.setAttribute("stroke", "none");
|
||||
const path = svgRoot.ownerDocument!.createElementNS(SVG_NS, "path");
|
||||
path.setAttribute("fill", element.strokeColor);
|
||||
path.setAttribute(
|
||||
"fill",
|
||||
renderConfig.theme === THEME.DARK
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor,
|
||||
);
|
||||
path.setAttribute("d", getFreeDrawSvgPath(element));
|
||||
node.appendChild(path);
|
||||
|
||||
@ -526,7 +533,12 @@ const renderElementToSvg = (
|
||||
rect.setAttribute("ry", FRAME_STYLE.radius.toString());
|
||||
|
||||
rect.setAttribute("fill", "none");
|
||||
rect.setAttribute("stroke", FRAME_STYLE.strokeColor);
|
||||
rect.setAttribute(
|
||||
"stroke",
|
||||
renderConfig.theme === THEME.DARK
|
||||
? applyDarkModeFilter(FRAME_STYLE.strokeColor)
|
||||
: FRAME_STYLE.strokeColor,
|
||||
);
|
||||
rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString());
|
||||
|
||||
addToRoot(rect, element);
|
||||
@ -577,7 +589,12 @@ const renderElementToSvg = (
|
||||
text.setAttribute("y", `${i * lineHeightPx + verticalOffset}`);
|
||||
text.setAttribute("font-family", getFontFamilyString(element));
|
||||
text.setAttribute("font-size", `${element.fontSize}px`);
|
||||
text.setAttribute("fill", element.strokeColor);
|
||||
text.setAttribute(
|
||||
"fill",
|
||||
renderConfig.theme === THEME.DARK
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor,
|
||||
);
|
||||
text.setAttribute("text-anchor", textAnchor);
|
||||
text.setAttribute("style", "white-space: pre;");
|
||||
text.setAttribute("direction", direction);
|
||||
|
@ -13,7 +13,7 @@ import type {
|
||||
import { generateFreeDrawShape } from "../renderer/renderElement";
|
||||
import { isTransparent, assertNever } from "../utils";
|
||||
import { simplify } from "points-on-curve";
|
||||
import { ROUGHNESS } from "../constants";
|
||||
import { ROUGHNESS, THEME } from "../constants";
|
||||
import {
|
||||
isElbowArrow,
|
||||
isEmbeddableElement,
|
||||
@ -22,7 +22,7 @@ import {
|
||||
isLinearElement,
|
||||
} from "../element/typeChecks";
|
||||
import { canChangeRoundness } from "./comparisons";
|
||||
import type { EmbedsValidationStatus } from "../types";
|
||||
import type { AppState, EmbedsValidationStatus } from "../types";
|
||||
import {
|
||||
point,
|
||||
pointDistance,
|
||||
@ -30,6 +30,7 @@ import {
|
||||
type LocalPoint,
|
||||
} from "../../math";
|
||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||
import { applyDarkModeFilter } from "../colors";
|
||||
|
||||
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
|
||||
|
||||
@ -61,6 +62,7 @@ function adjustRoughness(element: ExcalidrawElement): number {
|
||||
export const generateRoughOptions = (
|
||||
element: ExcalidrawElement,
|
||||
continuousPath = false,
|
||||
isDarkMode: boolean = false,
|
||||
): Options => {
|
||||
const options: Options = {
|
||||
seed: element.seed,
|
||||
@ -85,7 +87,9 @@ export const generateRoughOptions = (
|
||||
fillWeight: element.strokeWidth / 2,
|
||||
hachureGap: element.strokeWidth * 4,
|
||||
roughness: adjustRoughness(element),
|
||||
stroke: element.strokeColor,
|
||||
stroke: isDarkMode
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor,
|
||||
preserveVertices:
|
||||
continuousPath || element.roughness < ROUGHNESS.cartoonist,
|
||||
};
|
||||
@ -99,6 +103,8 @@ export const generateRoughOptions = (
|
||||
options.fillStyle = element.fillStyle;
|
||||
options.fill = isTransparent(element.backgroundColor)
|
||||
? undefined
|
||||
: isDarkMode
|
||||
? applyDarkModeFilter(element.backgroundColor)
|
||||
: element.backgroundColor;
|
||||
if (element.type === "ellipse") {
|
||||
options.curveFitting = 1;
|
||||
@ -112,6 +118,8 @@ export const generateRoughOptions = (
|
||||
options.fill =
|
||||
element.backgroundColor === "transparent"
|
||||
? undefined
|
||||
: isDarkMode
|
||||
? applyDarkModeFilter(element.backgroundColor)
|
||||
: element.backgroundColor;
|
||||
}
|
||||
return options;
|
||||
@ -165,6 +173,7 @@ const getArrowheadShapes = (
|
||||
generator: RoughGenerator,
|
||||
options: Options,
|
||||
canvasBackgroundColor: string,
|
||||
isDarkMode: boolean,
|
||||
) => {
|
||||
const arrowheadPoints = getArrowheadPoints(
|
||||
element,
|
||||
@ -192,10 +201,14 @@ const getArrowheadShapes = (
|
||||
fill:
|
||||
arrowhead === "circle_outline"
|
||||
? canvasBackgroundColor
|
||||
: isDarkMode
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor,
|
||||
|
||||
fillStyle: "solid",
|
||||
stroke: element.strokeColor,
|
||||
stroke: isDarkMode
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor,
|
||||
roughness: Math.min(0.5, options.roughness || 0),
|
||||
}),
|
||||
];
|
||||
@ -220,6 +233,8 @@ const getArrowheadShapes = (
|
||||
fill:
|
||||
arrowhead === "triangle_outline"
|
||||
? canvasBackgroundColor
|
||||
: isDarkMode
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor,
|
||||
fillStyle: "solid",
|
||||
roughness: Math.min(1, options.roughness || 0),
|
||||
@ -248,6 +263,8 @@ const getArrowheadShapes = (
|
||||
fill:
|
||||
arrowhead === "diamond_outline"
|
||||
? canvasBackgroundColor
|
||||
: isDarkMode
|
||||
? applyDarkModeFilter(element.strokeColor)
|
||||
: element.strokeColor,
|
||||
fillStyle: "solid",
|
||||
roughness: Math.min(1, options.roughness || 0),
|
||||
@ -291,12 +308,15 @@ export const _generateElementShape = (
|
||||
isExporting,
|
||||
canvasBackgroundColor,
|
||||
embedsValidationStatus,
|
||||
theme,
|
||||
}: {
|
||||
isExporting: boolean;
|
||||
canvasBackgroundColor: string;
|
||||
embedsValidationStatus: EmbedsValidationStatus | null;
|
||||
theme: AppState["theme"];
|
||||
},
|
||||
): Drawable | Drawable[] | null => {
|
||||
const isDarkMode = theme === THEME.DARK;
|
||||
switch (element.type) {
|
||||
case "rectangle":
|
||||
case "iframe":
|
||||
@ -322,6 +342,7 @@ export const _generateElementShape = (
|
||||
embedsValidationStatus,
|
||||
),
|
||||
true,
|
||||
isDarkMode,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@ -337,6 +358,7 @@ export const _generateElementShape = (
|
||||
embedsValidationStatus,
|
||||
),
|
||||
false,
|
||||
isDarkMode,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -374,7 +396,7 @@ export const _generateElementShape = (
|
||||
C ${topX} ${topY}, ${topX} ${topY}, ${topX + verticalRadius} ${
|
||||
topY + horizontalRadius
|
||||
}`,
|
||||
generateRoughOptions(element, true),
|
||||
generateRoughOptions(element, true, isDarkMode),
|
||||
);
|
||||
} else {
|
||||
shape = generator.polygon(
|
||||
@ -384,7 +406,7 @@ export const _generateElementShape = (
|
||||
[bottomX, bottomY],
|
||||
[leftX, leftY],
|
||||
],
|
||||
generateRoughOptions(element),
|
||||
generateRoughOptions(element, undefined, isDarkMode),
|
||||
);
|
||||
}
|
||||
return shape;
|
||||
@ -395,14 +417,14 @@ export const _generateElementShape = (
|
||||
element.height / 2,
|
||||
element.width,
|
||||
element.height,
|
||||
generateRoughOptions(element),
|
||||
generateRoughOptions(element, undefined, isDarkMode),
|
||||
);
|
||||
return shape;
|
||||
}
|
||||
case "line":
|
||||
case "arrow": {
|
||||
let shape: ElementShapes[typeof element.type];
|
||||
const options = generateRoughOptions(element);
|
||||
const options = generateRoughOptions(element, undefined, isDarkMode);
|
||||
|
||||
// points array can be empty in the beginning, so it is important to add
|
||||
// initial position to it
|
||||
@ -414,7 +436,7 @@ export const _generateElementShape = (
|
||||
shape = [
|
||||
generator.path(
|
||||
generateElbowArrowShape(points, 16),
|
||||
generateRoughOptions(element, true),
|
||||
generateRoughOptions(element, true, isDarkMode),
|
||||
),
|
||||
];
|
||||
} else if (!element.roundness) {
|
||||
@ -446,6 +468,7 @@ export const _generateElementShape = (
|
||||
generator,
|
||||
options,
|
||||
canvasBackgroundColor,
|
||||
isDarkMode,
|
||||
);
|
||||
shape.push(...shapes);
|
||||
}
|
||||
@ -463,6 +486,7 @@ export const _generateElementShape = (
|
||||
generator,
|
||||
options,
|
||||
canvasBackgroundColor,
|
||||
isDarkMode,
|
||||
);
|
||||
shape.push(...shapes);
|
||||
}
|
||||
@ -477,7 +501,7 @@ export const _generateElementShape = (
|
||||
// generate rough polygon to fill freedraw shape
|
||||
const simplifiedPoints = simplify(element.points, 0.75);
|
||||
shape = generator.curve(simplifiedPoints as [number, number][], {
|
||||
...generateRoughOptions(element),
|
||||
...generateRoughOptions(element, undefined, isDarkMode),
|
||||
stroke: "none",
|
||||
});
|
||||
} else {
|
||||
|
@ -9,6 +9,7 @@ import { _generateElementShape } from "./Shape";
|
||||
import type { ElementShape, ElementShapes } from "./types";
|
||||
import { COLOR_PALETTE } from "../colors";
|
||||
import type { AppState, EmbedsValidationStatus } from "../types";
|
||||
import { THEME } from "..";
|
||||
|
||||
export class ShapeCache {
|
||||
private static rg = new RoughGenerator();
|
||||
@ -52,6 +53,7 @@ export class ShapeCache {
|
||||
isExporting: boolean;
|
||||
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
||||
embedsValidationStatus: EmbedsValidationStatus;
|
||||
theme: AppState["theme"];
|
||||
} | null,
|
||||
) => {
|
||||
// when exporting, always regenerated to guarantee the latest shape
|
||||
@ -74,6 +76,7 @@ export class ShapeCache {
|
||||
isExporting: false,
|
||||
canvasBackgroundColor: COLOR_PALETTE.white,
|
||||
embedsValidationStatus: null,
|
||||
theme: THEME.LIGHT,
|
||||
},
|
||||
) as T["type"] extends keyof ElementShapes
|
||||
? ElementShapes[T["type"]]
|
||||
|
@ -40,6 +40,7 @@ import { syncInvalidIndices } from "../fractionalIndex";
|
||||
import { renderStaticScene } from "../renderer/staticScene";
|
||||
import { Fonts } from "../fonts";
|
||||
import type { Font } from "../fonts/ExcalidrawFont";
|
||||
import { applyDarkModeFilter } from "../colors";
|
||||
|
||||
const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
||||
|
||||
@ -214,6 +215,8 @@ export const exportToCanvas = async (
|
||||
files,
|
||||
});
|
||||
|
||||
const theme = appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT;
|
||||
|
||||
renderStaticScene({
|
||||
canvas,
|
||||
rc: rough.canvas(canvas),
|
||||
@ -233,7 +236,7 @@ export const exportToCanvas = async (
|
||||
scrollY: -minY + exportPadding,
|
||||
zoom: defaultAppState.zoom,
|
||||
shouldCacheIgnoreZoom: false,
|
||||
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
|
||||
theme,
|
||||
},
|
||||
renderConfig: {
|
||||
canvasBackgroundColor: viewBackgroundColor,
|
||||
@ -244,6 +247,7 @@ export const exportToCanvas = async (
|
||||
embedsValidationStatus: new Map(),
|
||||
elementsPendingErasure: new Set(),
|
||||
pendingFlowchartNodes: null,
|
||||
theme,
|
||||
},
|
||||
});
|
||||
|
||||
@ -330,7 +334,7 @@ export const exportToSvg = async (
|
||||
svgRoot.setAttribute("width", `${width * exportScale}`);
|
||||
svgRoot.setAttribute("height", `${height * exportScale}`);
|
||||
if (exportWithDarkMode) {
|
||||
svgRoot.setAttribute("filter", THEME_FILTER);
|
||||
// svgRoot.setAttribute("filter", THEME_FILTER);
|
||||
}
|
||||
|
||||
const offsetX = -minX + exportPadding;
|
||||
@ -376,7 +380,12 @@ export const exportToSvg = async (
|
||||
rect.setAttribute("y", "0");
|
||||
rect.setAttribute("width", `${width}`);
|
||||
rect.setAttribute("height", `${height}`);
|
||||
rect.setAttribute("fill", viewBackgroundColor);
|
||||
rect.setAttribute(
|
||||
"fill",
|
||||
appState.exportWithDarkMode
|
||||
? applyDarkModeFilter(viewBackgroundColor)
|
||||
: viewBackgroundColor,
|
||||
);
|
||||
svgRoot.appendChild(rect);
|
||||
}
|
||||
|
||||
@ -384,6 +393,8 @@ export const exportToSvg = async (
|
||||
|
||||
const renderEmbeddables = opts?.renderEmbeddables ?? false;
|
||||
|
||||
const theme = appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT;
|
||||
|
||||
renderSceneToSvg(
|
||||
elementsForRender,
|
||||
toBrandedType<RenderableElementsMap>(arrayToMap(elementsForRender)),
|
||||
@ -405,6 +416,7 @@ export const exportToSvg = async (
|
||||
.map((element) => [element.id, true]),
|
||||
)
|
||||
: new Map(),
|
||||
theme,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -35,6 +35,7 @@ export type StaticCanvasRenderConfig = {
|
||||
embedsValidationStatus: EmbedsValidationStatus;
|
||||
elementsPendingErasure: ElementsPendingErasure;
|
||||
pendingFlowchartNodes: PendingExcalidrawElements | null;
|
||||
theme: AppState["theme"];
|
||||
};
|
||||
|
||||
export type SVGRenderConfig = {
|
||||
@ -46,6 +47,7 @@ export type SVGRenderConfig = {
|
||||
frameRendering: AppState["frameRendering"];
|
||||
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
||||
embedsValidationStatus: EmbedsValidationStatus;
|
||||
theme: AppState["theme"];
|
||||
};
|
||||
|
||||
export type InteractiveCanvasRenderConfig = {
|
||||
|
10
yarn.lock
10
yarn.lock
@ -3299,6 +3299,11 @@
|
||||
dependencies:
|
||||
"@types/jest" "*"
|
||||
|
||||
"@types/tinycolor2@1.4.6":
|
||||
version "1.4.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.6.tgz#670cbc0caf4e58dd61d1e3a6f26386e473087f06"
|
||||
integrity sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==
|
||||
|
||||
"@types/trusted-types@^2.0.2":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
|
||||
@ -9976,6 +9981,11 @@ tinybench@^2.8.0:
|
||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||
|
||||
tinycolor2@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
|
||||
integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==
|
||||
|
||||
tinypool@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe"
|
||||
|
Loading…
x
Reference in New Issue
Block a user