ducjs
Version:
The duc 2D CAD file format is a cornerstone of our advanced design system, conceived to cater to professionals seeking precision and efficiency in their design work.
911 lines • 71 kB
JavaScript
import { isValidBezierMirroringValue, isValidBlendingValue, isValidBoolean, isValidBoolean as isValidBooleanValue, // Renaming to avoid conflict
isValidColor, isValidDucHead, isValidEnumValue, isValidFunction, isValidImageScaleValue, isValidImageStatusValue, isValidPercentageValue, isValidPolygonSides, isValidRadianValue, isValidString, isValidTextAlignValue, isValidVerticalAlignValue, restoreDucStackProperties, restorePrecisionValue, validateBackground, validateStroke } from "./restoreDataState";
import { getPrecisionValueFromRaw, getPrecisionValueFromScoped, getScaledZoomValueForScope, getScopedBezierPointFromDucPoint, getScopedZoomValue, NEUTRAL_SCOPE, ScaleFactors, } from "../technical/scopes";
import { isElbowArrow, isLinearElement, isTextElement, } from "../types";
import { arrayToMap, bumpVersion, DEFAULT_ELEMENT_PROPS, DEFAULT_ELLIPSE_ELEMENT, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_FREEDRAW_ELEMENT, detectLineHeight, FONT_FAMILY, getContainerElement, getDefaultLocalState, getDefaultTableData, getNormalizedDimensions, getNormalizedPoints, getNormalizedZoom, getSizeFromPoints, getUpdatedTimestamp, isFiniteNumber, isInvisiblySmallElement, LINE_CONFIRM_THRESHOLD, mergeOverlappingPoints, migratePoints, normalizeFixedPoint, normalizeLink, randomId, refreshTextDimensions, validateClosedPath, } from "../utils";
import { AXIS, BLOCK_ATTACHMENT, COLUMN_TYPE, DATUM_BRACKET_STYLE, DIMENSION_FIT_RULE, DIMENSION_TEXT_PLACEMENT, DIMENSION_TYPE, GDT_SYMBOL, LEADER_CONTENT_TYPE, LINE_SPACING_TYPE, MARK_ELLIPSE_CENTER, MATERIAL_CONDITION, PARAMETRIC_SOURCE_TYPE, STACKED_TEXT_ALIGN, TABLE_CELL_ALIGNMENT, TABLE_FLOW_DIRECTION, TEXT_FLOW_DIRECTION, TOLERANCE_DISPLAY, TOLERANCE_ZONE_TYPE, VERTICAL_ALIGN, VIEWPORT_SHADE_PLOT, } from "../flatbuffers/duc";
import tinycolor from "tinycolor2";
const restoreElementWithProperties = (element, extra, localState, globalState) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const _element = Object.assign(Object.assign({}, element), extra);
const elementScope = isValidElementScopeValue(_element.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
const currentScope = isValidElementScopeValue(localState === null || localState === void 0 ? void 0 : localState.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
// Handle legacy stroke properties first
let stroke = [DEFAULT_ELEMENT_PROPS.stroke];
if (_element.strokeColor) {
// Legacy strokeColor property
stroke = [
Object.assign(Object.assign({}, DEFAULT_ELEMENT_PROPS.stroke), { content: validateDeprecatedElementContent(_element.strokeColor, DEFAULT_ELEMENT_PROPS.stroke.content), width: restorePrecisionValue(_element.strokeWidth ||
DEFAULT_ELEMENT_PROPS.stroke.width.value, elementScope, currentScope, DEFAULT_ELEMENT_PROPS.stroke.width.value) }),
];
}
else if (_element.stroke) {
// New stroke array property
stroke = _element.stroke.map((s) => validateStroke(s, elementScope, currentScope));
}
// Handle legacy background properties
let background = [DEFAULT_ELEMENT_PROPS.background];
if (_element.backgroundColor) {
// Legacy backgroundColor property
background = [
Object.assign(Object.assign({}, DEFAULT_ELEMENT_PROPS.background), { content: validateDeprecatedElementContent(_element.backgroundColor, DEFAULT_ELEMENT_PROPS.background.content) }),
];
}
else if (_element.background) {
// New background array property
background = _element.background.map((bg) => validateBackground(bg));
}
const base = {
id: _element.id || randomId(),
version: _element.version || 1,
versionNonce: (_a = _element.versionNonce) !== null && _a !== void 0 ? _a : 0,
index: (_b = _element.index) !== null && _b !== void 0 ? _b : null,
isDeleted: isValidBooleanValue(_element.isDeleted, false),
blending: isValidBlendingValue(_element.blending),
stroke,
background,
opacity: isValidPercentageValue(_element.opacity, DEFAULT_ELEMENT_PROPS.opacity),
angle: isValidRadianValue(_element.angle, DEFAULT_ELEMENT_PROPS.angle),
x: restorePrecisionValue(_element.x, elementScope, currentScope, 0),
y: restorePrecisionValue(_element.y, elementScope, currentScope, 0),
scope: elementScope,
label: (_c = _element.label) !== null && _c !== void 0 ? _c : "Lost Element Label",
isVisible: isValidBoolean(_element.isVisible, DEFAULT_ELEMENT_PROPS.isVisible),
isPlot: isValidBoolean(_element.isPlot, DEFAULT_ELEMENT_PROPS.isPlot),
isAnnotative: isValidBoolean(_element.isAnnotative, DEFAULT_ELEMENT_PROPS.isAnnotative),
layerId: (_d = _element.layerId) !== null && _d !== void 0 ? _d : null,
regionIds: Array.isArray(_element.regionIds) ? _element.regionIds : [],
width: restorePrecisionValue(_element.width, elementScope, currentScope, 0),
height: restorePrecisionValue(_element.height, elementScope, currentScope, 0),
seed: (_e = _element.seed) !== null && _e !== void 0 ? _e : 1,
groupIds: (_f = _element.groupIds) !== null && _f !== void 0 ? _f : [],
frameId: (_g = _element.frameId) !== null && _g !== void 0 ? _g : null,
roundness: restorePrecisionValue(_element.roundness, elementScope, currentScope, DEFAULT_ELEMENT_PROPS.roundness.value),
boundElements: (_h = _element.boundElements) !== null && _h !== void 0 ? _h : undefined,
updated: (_j = _element.updated) !== null && _j !== void 0 ? _j : getUpdatedTimestamp(),
link: _element.link ? normalizeLink(_element.link) : undefined,
locked: isValidBooleanValue(_element.locked, false),
description: (_k = _element.description) !== null && _k !== void 0 ? _k : undefined,
};
if ("customData" in element || "customData" in extra) {
base.customData = _element.customData;
}
const normalizedRaw = getNormalizedDimensions(base);
const normalized = {
x: restorePrecisionValue(normalizedRaw.x, elementScope, currentScope, 0, true),
y: restorePrecisionValue(normalizedRaw.y, elementScope, currentScope, 0, true),
width: restorePrecisionValue(normalizedRaw.width, elementScope, currentScope, 0, true),
height: restorePrecisionValue(normalizedRaw.height, elementScope, currentScope, 0, true),
};
return Object.assign(Object.assign(Object.assign(Object.assign({}, _element), base), normalized), { type: _element.type });
};
const restoreElement = (element, currentScope, restoredBlocks, localState, globalState) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
// Migration: convert deprecated 'diamond' to 'polygon' with 4 sides
if (element.type === "diamond") {
const migrated = Object.assign(Object.assign({}, element), { type: "polygon", sides: 4 });
return restoreElement(migrated, currentScope, restoredBlocks, localState);
}
switch (element.type) {
case "text": {
let fontSize = element.fontSize;
let fontFamily = element.fontFamily;
if ("font" in element) {
try {
const fontParts = String(element.font).split(" ");
if (fontParts.length === 2) {
const [fontSizeStr, fontFamilyName] = fontParts;
const parsedSize = parseFloat(fontSizeStr);
if (!isNaN(parsedSize) && Number.isFinite(parsedSize)) {
fontSize = parsedSize;
fontFamily = getFontFamilyByName(fontFamilyName);
}
}
}
catch (error) {
console.error("Failed to parse legacy font value:", error);
fontSize = DEFAULT_FONT_SIZE;
fontFamily = DEFAULT_FONT_FAMILY;
}
}
const text = (typeof element.text === "string" && element.text) || "";
const lineHeight = (element.lineHeight && Number.isFinite(element.lineHeight)
? element.lineHeight
: detectLineHeight(element));
const restoredLineSpacing = restoreLineSpacing(element.lineSpacing, lineHeight, currentScope);
const textElementScope = isValidElementScopeValue(element.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
const finalFontSize = restorePrecisionValue(fontSize, textElementScope, currentScope, DEFAULT_FONT_SIZE);
const restoredTextElement = restoreElementWithProperties(element, {
// DucTextElement specific
text,
dynamic: restoreTextDynamicParts(element.dynamic),
autoResize: isValidBooleanValue(element.autoResize, true),
containerId: (_a = element.containerId) !== null && _a !== void 0 ? _a : null,
originalText: element.originalText || text,
// DucTextStyle specific
isLtr: isValidBooleanValue(element.isLtr, true),
fontFamily,
bigFontFamily: isValidString(element.bigFontFamily, "sans-serif"),
textAlign: isValidTextAlignValue(element.textAlign),
verticalAlign: isValidVerticalAlignValue(element.verticalAlign),
lineHeight: lineHeight,
lineSpacing: restoredLineSpacing,
obliqueAngle: isValidRadianValue(element.obliqueAngle, 0),
fontSize: finalFontSize,
paperTextHeight: element.paperTextHeight
? restorePrecisionValue(element.paperTextHeight, textElementScope, currentScope)
: undefined,
widthFactor: typeof element.widthFactor === "number"
? element.widthFactor
: 1,
isUpsideDown: isValidBooleanValue(element.isUpsideDown, false),
isBackwards: isValidBooleanValue(element.isBackwards, false),
}, localState);
// if empty text, mark as deleted. We keep in array
// for data integrity purposes (collab etc.)
if (!text && !isValidBooleanValue(element.isDeleted, false)) {
element = Object.assign(Object.assign({}, element), { originalText: text, isDeleted: true });
element = bumpVersion(element);
}
return restoredTextElement;
}
case "freedraw": {
const elementScope = isValidElementScopeValue(element.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
const points = restoreElementPoints({
points: element.points,
x: element.x,
y: element.y,
width: element.width,
height: element.height,
elementScope,
currentScope,
});
return restoreElementWithProperties(element, {
points,
size: restorePrecisionValue(element.size, elementScope, currentScope, DEFAULT_FREEDRAW_ELEMENT.size),
lastCommittedPoint: element.lastCommittedPoint
? restorePoint(element.lastCommittedPoint, elementScope, currentScope)
: null,
simulatePressure: isValidBoolean(element.simulatePressure, false),
pressures: Array.isArray(element.pressures) ? element.pressures : [],
thinning: isValidPercentageValue(element.thinning, DEFAULT_FREEDRAW_ELEMENT.thinning, true),
smoothing: isValidPercentageValue(element.smoothing, DEFAULT_FREEDRAW_ELEMENT.smoothing),
streamline: isValidPercentageValue(element.streamline, DEFAULT_FREEDRAW_ELEMENT.streamline),
easing: isValidFreeDrawEasingValue(element.easing),
svgPath: isValidString(element.svgPath) || null,
start: restoreFreeDrawEnds(element.start),
end: restoreFreeDrawEnds(element.end),
}, localState);
}
case "image":
return restoreElementWithProperties(element, {
status: isValidImageStatusValue(element.status),
fileId: element.fileId,
scaleFlip: isValidImageScaleValue(element.scaleFlip),
crop: element.crop || null,
filter: element.filter || null,
}, localState);
case "line": {
// Don't normalize points if there are bindings
const hasBindings = !!(element.startBinding || element.endBinding);
const elementScope = isValidElementScopeValue(element.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
const { points, lines } = restoreLinearElementPointsAndLines({
points: element.points,
lines: element.lines,
x: element.x,
y: element.y,
width: element.width,
height: element.height,
elementScope,
currentScope,
skipNormalization: hasBindings,
});
const isEditing = false; // TODO: Handle editingLinearElement properly
let finalPoints = points;
// Ensure we have at least 2 points for visibility check
if (finalPoints.length < 2 && !isEditing) {
const elementWidth = restorePrecisionValue(element.width, elementScope, currentScope, 0);
const elementHeight = restorePrecisionValue(element.height, elementScope, currentScope, 0);
finalPoints = [
{
x: getPrecisionValueFromRaw(0, elementScope, currentScope),
y: getPrecisionValueFromRaw(0, elementScope, currentScope),
mirroring: undefined,
},
{
x: elementWidth,
y: elementHeight,
mirroring: undefined,
},
];
}
// Handle bindings, with special case for head-only bindings
const startBinding = element.startBinding;
const endBinding = element.endBinding;
// Process bindings
const processedStartBinding = startBinding &&
startBinding.head !== undefined &&
(!startBinding.elementId || startBinding.elementId === "")
? createHeadOnlyBinding(startBinding.head, restoredBlocks, currentScope)
: startBinding
? repairBinding(element, startBinding, currentScope, restoredBlocks)
: null;
const processedEndBinding = endBinding &&
endBinding.head !== undefined &&
(!endBinding.elementId || endBinding.elementId === "")
? createHeadOnlyBinding(endBinding.head, restoredBlocks, currentScope)
: endBinding
? repairBinding(element, endBinding, currentScope, restoredBlocks)
: null;
// Create the base restored element
const sizeFromPoints = !hasBindings &&
getSizeFromPoints(finalPoints.map(getScopedBezierPointFromDucPoint));
let restoredElement = restoreElementWithProperties(element, Object.assign({ points: finalPoints, lines, lastCommittedPoint: element.lastCommittedPoint
? restorePoint(element.lastCommittedPoint, element.scope, currentScope)
: null, startBinding: processedStartBinding, wipeoutBelow: isValidBooleanValue(element.wipeoutBelow, false), endBinding: processedEndBinding, x: element.x, y: element.y }, (!sizeFromPoints
? {}
: {
width: getPrecisionValueFromScoped(sizeFromPoints.width, element.scope, currentScope),
height: getPrecisionValueFromScoped(sizeFromPoints.height, element.scope, currentScope),
})), localState);
const { points: updatedPoints, lines: updatedLines } = mergeOverlappingPoints(finalPoints, lines, LINE_CONFIRM_THRESHOLD);
restoredElement = Object.assign(Object.assign({}, restoredElement), { points: updatedPoints, lines: updatedLines });
// Validate path overrides after we have the final points and lines
const validatedPathOverrides = validatePathOverrides(element.pathOverrides, restoredElement, elementScope, currentScope);
return Object.assign(Object.assign({}, restoredElement), { pathOverrides: validatedPathOverrides });
}
case "arrow": {
// since arrow is deprecated, we convert it to line
return restoreElement(Object.assign(Object.assign({}, element), { type: "line", wipeoutBelow: false }), currentScope, restoredBlocks, localState);
}
case "ellipse": {
const ratio = isValidPercentageValue(element.ratio, DEFAULT_ELLIPSE_ELEMENT.ratio);
const startAngle = isValidCutAngleValue(element.startAngle, DEFAULT_ELLIPSE_ELEMENT.startAngle);
const endAngle = isValidCutAngleValue(element.endAngle, DEFAULT_ELLIPSE_ELEMENT.endAngle);
const showAuxCrosshair = (_b = element.showAuxCrosshair) !== null && _b !== void 0 ? _b : DEFAULT_ELLIPSE_ELEMENT.showAuxCrosshair;
return restoreElementWithProperties(element, {
ratio,
startAngle,
endAngle,
showAuxCrosshair,
}, localState);
}
case "rectangle":
case "embeddable":
return restoreElementWithProperties(element, {}, localState);
case "polygon": {
const sides = isValidPolygonSides(element.sides);
return restoreElementWithProperties(Object.assign(Object.assign({}, element), { sides }), {}, localState);
}
// Stack-like elements
case "frame": {
const frameElement = element;
return restoreElementWithProperties(frameElement, Object.assign({}, restoreStackElementProperties(frameElement, currentScope)), localState);
}
case "plot": {
const plotElement = element;
const elementScope = isValidElementScopeValue(plotElement.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
// Restore base stack element properties (label, visibility, etc.)
const stackProperties = restoreStackElementProperties(plotElement, currentScope);
// Restore layout-specific properties
const layout = {
margins: {
top: restorePrecisionValue((_d = (_c = plotElement.layout) === null || _c === void 0 ? void 0 : _c.margins) === null || _d === void 0 ? void 0 : _d.top, elementScope, currentScope, 0),
right: restorePrecisionValue((_f = (_e = plotElement.layout) === null || _e === void 0 ? void 0 : _e.margins) === null || _f === void 0 ? void 0 : _f.right, elementScope, currentScope, 0),
bottom: restorePrecisionValue((_h = (_g = plotElement.layout) === null || _g === void 0 ? void 0 : _g.margins) === null || _h === void 0 ? void 0 : _h.bottom, elementScope, currentScope, 0),
left: restorePrecisionValue((_k = (_j = plotElement.layout) === null || _j === void 0 ? void 0 : _j.margins) === null || _k === void 0 ? void 0 : _k.left, elementScope, currentScope, 0),
},
};
return restoreElementWithProperties(plotElement, Object.assign(Object.assign({}, stackProperties), { layout }), localState, globalState);
}
case "viewport": {
const viewportElement = element;
const elementScope = isValidElementScopeValue(viewportElement.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
// A viewport has both linear and stack properties
const { points, lines } = restoreLinearElementPointsAndLines({
points: viewportElement.points,
lines: viewportElement.lines,
x: viewportElement.x,
y: viewportElement.y,
width: viewportElement.width,
height: viewportElement.height,
elementScope,
currentScope,
skipNormalization: false, // Viewports are typically non-binding polygons
});
const stackProperties = restoreStackElementProperties(viewportElement, currentScope);
const defaults = getDefaultLocalState();
const zoomValue = getNormalizedZoom(isFiniteNumber((_l = localState === null || localState === void 0 ? void 0 : localState.zoom) === null || _l === void 0 ? void 0 : _l.value)
? localState.zoom.value
: defaults.zoom.value);
const scopedZoom = getScopedZoomValue(zoomValue, currentScope);
const view = {
scrollX: restorePrecisionValue((_m = viewportElement.view) === null || _m === void 0 ? void 0 : _m.scrollX, NEUTRAL_SCOPE, currentScope, 0),
scrollY: restorePrecisionValue((_o = viewportElement.view) === null || _o === void 0 ? void 0 : _o.scrollY, NEUTRAL_SCOPE, currentScope, 0),
zoom: {
value: zoomValue,
scoped: scopedZoom,
scaled: getScaledZoomValueForScope(scopedZoom, currentScope),
},
twistAngle: isValidRadianValue((_p = viewportElement.view) === null || _p === void 0 ? void 0 : _p.twistAngle, 0),
centerPoint: restorePoint((_q = viewportElement.view) === null || _q === void 0 ? void 0 : _q.centerPoint, NEUTRAL_SCOPE, currentScope), // Assuming centerPoint is always present
scope: isValidElementScopeValue((_r = viewportElement.view) === null || _r === void 0 ? void 0 : _r.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope),
};
return restoreElementWithProperties(viewportElement, Object.assign(Object.assign({}, stackProperties), { points,
lines,
view, scale: (typeof viewportElement.scale === "number"
? viewportElement.scale
: 1), shadePlot: isValidEnumValue(viewportElement.shadePlot, VIEWPORT_SHADE_PLOT, VIEWPORT_SHADE_PLOT.AS_DISPLAYED), frozenGroupIds: Array.isArray(viewportElement.frozenGroupIds)
? viewportElement.frozenGroupIds
: [],
// viewport-specific style
scaleIndicatorVisible: isValidBoolean(viewportElement.scaleIndicatorVisible, true) }), localState, globalState);
}
// Other elements
case "pdf":
return restoreElementWithProperties(element, { fileId: isValidString(element.fileId) || null }, localState);
case "mermaid":
return restoreElementWithProperties(element, {
source: isValidString(element.source),
theme: isValidString(element.theme) || undefined,
svgPath: isValidString(element.svgPath) || null,
}, localState);
// Complex elements requiring specific restore logic
case "table": {
const tableElement = element;
const defaultData = getDefaultTableData(currentScope);
return restoreElementWithProperties(tableElement, {
columnOrder: Array.isArray(tableElement.columnOrder)
? tableElement.columnOrder
: defaultData.columnOrder,
rowOrder: Array.isArray(tableElement.rowOrder)
? tableElement.rowOrder
: defaultData.rowOrder,
columns: restoreTableColumns(tableElement.columns, currentScope, defaultData.columns),
rows: restoreTableRows(tableElement.rows, currentScope, defaultData.rows),
cells: restoreTableCells(tableElement.cells, defaultData.cells),
headerRowCount: typeof tableElement.headerRowCount === "number"
? tableElement.headerRowCount
: defaultData.headerRowCount,
autoSize: tableElement.autoSize || defaultData.autoSize,
// DucTableStyle properties
flowDirection: (Object.values(TABLE_FLOW_DIRECTION)).includes(tableElement.flowDirection)
? tableElement.flowDirection
: defaultData.flowDirection,
headerRowStyle: restoreTableCellStyle(tableElement.headerRowStyle, currentScope, defaultData.headerRowStyle),
dataRowStyle: restoreTableCellStyle(tableElement.dataRowStyle, currentScope, defaultData.dataRowStyle),
dataColumnStyle: restoreTableCellStyle(tableElement.dataColumnStyle, currentScope, defaultData.dataColumnStyle),
}, localState);
}
case "doc": {
const docElement = element;
return restoreElementWithProperties(element, Object.assign(Object.assign({}, restoreDocStyleProperties(docElement, currentScope)), { text: isValidString(docElement.text), dynamic: restoreTextDynamicParts(docElement.dynamic), flowDirection: (Object.values(TEXT_FLOW_DIRECTION)).includes(docElement.flowDirection)
? docElement.flowDirection
: TEXT_FLOW_DIRECTION.TOP_TO_BOTTOM, columns: restoreTextColumns(docElement.columns, currentScope), autoResize: isValidBooleanValue(docElement.autoResize, true) }), localState);
}
case "xray": {
const xrayElement = element;
const elementScope = isValidElementScopeValue(xrayElement.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
return restoreElementWithProperties(xrayElement, {
origin: restorePoint(xrayElement.origin, elementScope, currentScope),
direction: restorePoint(xrayElement.direction, elementScope, currentScope),
startFromOrigin: isValidBoolean(xrayElement.startFromOrigin, false),
color: isValidColor(xrayElement.color, "#FF00FF"),
}, localState, globalState);
}
case "parametric": {
const parametricElement = element;
let source;
if (((_s = parametricElement.source) === null || _s === void 0 ? void 0 : _s.type) === PARAMETRIC_SOURCE_TYPE.FILE) {
source = {
type: PARAMETRIC_SOURCE_TYPE.FILE,
fileId: isValidString((_t = parametricElement.source) === null || _t === void 0 ? void 0 : _t.fileId),
};
}
else {
source = {
type: PARAMETRIC_SOURCE_TYPE.CODE,
code: isValidString((_u = parametricElement.source) === null || _u === void 0 ? void 0 : _u.code),
};
}
return restoreElementWithProperties(parametricElement, { source }, localState, globalState);
}
case "featurecontrolframe": {
const fcfElement = element;
const elementScope = isValidElementScopeValue(fcfElement.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
return restoreElementWithProperties(fcfElement, {
// Restore style properties
textStyle: restoreTextStyle(fcfElement.textStyle, currentScope),
layout: {
padding: restorePrecisionValue((_v = fcfElement.layout) === null || _v === void 0 ? void 0 : _v.padding, elementScope, currentScope, 2),
segmentSpacing: restorePrecisionValue((_w = fcfElement.layout) === null || _w === void 0 ? void 0 : _w.segmentSpacing, elementScope, currentScope, 2),
rowSpacing: restorePrecisionValue((_x = fcfElement.layout) === null || _x === void 0 ? void 0 : _x.rowSpacing, elementScope, currentScope, 2),
},
symbols: {
scale: (_z = (_y = fcfElement.symbols) === null || _y === void 0 ? void 0 : _y.scale) !== null && _z !== void 0 ? _z : 1,
},
datumStyle: {
bracketStyle: isValidEnumValue((_0 = fcfElement.datumStyle) === null || _0 === void 0 ? void 0 : _0.bracketStyle, DATUM_BRACKET_STYLE, DATUM_BRACKET_STYLE.SQUARE),
},
// Restore data properties
rows: restoreFcfRows(fcfElement.rows),
frameModifiers: restoreFcfFrameModifiers(fcfElement.frameModifiers, elementScope, currentScope),
leaderElementId: isValidString(fcfElement.leaderElementId) || null,
datumDefinition: restoreFcfDatumDefinition(fcfElement.datumDefinition, elementScope, currentScope, restoredBlocks),
}, localState, globalState);
}
case "leader": {
const leaderElement = element;
const elementScope = isValidElementScopeValue(leaderElement.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
// A leader is a linear element
const { points, lines } = restoreLinearElementPointsAndLines({
points: leaderElement.points,
lines: leaderElement.lines,
x: leaderElement.x,
y: leaderElement.y,
width: leaderElement.width,
height: leaderElement.height,
elementScope,
currentScope,
skipNormalization: !!(leaderElement.startBinding || leaderElement.endBinding),
});
return restoreElementWithProperties(leaderElement, {
points,
lines,
// DucLeaderStyle properties
headsOverride: restoreHeadsOverride(leaderElement.headsOverride, restoredBlocks, elementScope, currentScope),
dogleg: restorePrecisionValue(leaderElement.dogleg, elementScope, currentScope, 10),
textStyle: restoreTextStyle(leaderElement.textStyle, currentScope),
textAttachment: isValidEnumValue(leaderElement.textAttachment, VERTICAL_ALIGN, VERTICAL_ALIGN.TOP),
blockAttachment: isValidEnumValue(leaderElement.blockAttachment, BLOCK_ATTACHMENT, BLOCK_ATTACHMENT.CENTER_EXTENTS),
// Leader data
leaderContent: restoreLeaderContent(leaderElement.leaderContent),
contentAnchor: leaderElement.contentAnchor || { x: 0, y: 0 },
}, localState, globalState);
}
case "dimension": {
const dimElement = element;
const elementScope = isValidElementScopeValue(dimElement.scope, globalState === null || globalState === void 0 ? void 0 : globalState.mainScope);
return restoreElementWithProperties(dimElement, Object.assign(Object.assign({}, restoreDimensionStyle(dimElement, currentScope)), {
// Dimension data
dimensionType: isValidEnumValue(dimElement.dimensionType, DIMENSION_TYPE, DIMENSION_TYPE.LINEAR), definitionPoints: restoreDimensionDefinitionPoints(dimElement.definitionPoints), obliqueAngle: isValidRadianValue(dimElement.obliqueAngle, 0), ordinateAxis: isValidEnumValue(dimElement.ordinateAxis, AXIS, null), bindings: restoreDimensionBindings(dimElement.bindings, elementScope, currentScope, restoredBlocks), textOverride: isValidString(dimElement.textOverride) || null, textPosition: dimElement.textPosition || null, toleranceOverride: restoreDimensionToleranceStyle(dimElement.toleranceOverride, currentScope), baselineData: dimElement.baselineData
? {
baseDimensionId: isValidString(dimElement.baselineData.baseDimensionId),
}
: undefined, continueData: dimElement.continueData
? {
continueFromDimensionId: isValidString(dimElement.continueData.continueFromDimensionId),
}
: undefined }), localState, globalState);
}
}
return null;
};
/**
* Restores a point that might have legacy format (x,y as numbers)
* @param point - The point to restore
* @param elementScope - The scope to use for the precision values
* @returns A properly formatted DucPoint
*/
export const restorePoint = (point, elementScope, currentScope) => {
if (!point) {
return null;
}
// Handle x and y conversion
const xValue = restorePrecisionValue(point.x, elementScope, currentScope);
const yValue = restorePrecisionValue(point.y, elementScope, currentScope);
if (!xValue || !yValue) {
return null;
}
// Only keep the new DucPoint structure, ignore legacy properties
return {
x: xValue,
y: yValue,
mirroring: isValidBezierMirroringValue(point.mirroring),
};
};
const restoreElementPoints = ({ points: oldPoints, x, y, width, height, elementScope, currentScope, }) => {
// Convert input parameters to plain numbers if they're precision values
const validatedWidth = restorePrecisionValue(width, elementScope, currentScope, 0);
const validatedHeight = restorePrecisionValue(height, elementScope, currentScope, 0);
// Process the points array with proper conversion
let points = [];
if (typeof oldPoints === "object" && Array.isArray(oldPoints)) {
const migratedPoints = migratePoints(oldPoints);
points = migratedPoints
.map((point) => {
const restoredPoint = restorePoint(point, elementScope, currentScope);
if (!restoredPoint) {
return null;
}
return restoredPoint;
})
.filter(Boolean);
// Ensure we have at least 2 points for linear elements
if (points.length === 1) {
points.push({
x: validatedWidth,
y: validatedHeight,
mirroring: undefined,
});
}
}
else {
// Default two points
points = [
{
x: getPrecisionValueFromRaw(0, elementScope, currentScope),
y: getPrecisionValueFromRaw(0, elementScope, currentScope),
mirroring: undefined,
},
{
x: validatedWidth,
y: validatedHeight,
mirroring: undefined,
},
];
}
if (points.length === 2 &&
points[1] && // Should always be true if points.length === 2
points[1].x.value === 0 &&
points[1].y.value === 0 &&
(validatedWidth.value !== 0 || validatedHeight.value !== 0)) {
points[1] = Object.assign(Object.assign({}, points[1]), { x: validatedWidth, y: validatedHeight });
}
return points;
};
const restoreLinearElementPointsAndLines = ({ points: oldPoints, lines: oldLines, x, y, width, height, elementScope, currentScope, skipNormalization, }) => {
const validatedX = restorePrecisionValue(x, elementScope, currentScope, 0);
const validatedY = restorePrecisionValue(y, elementScope, currentScope, 0);
const validatedWidth = restorePrecisionValue(width, elementScope, currentScope, 0);
const validatedHeight = restorePrecisionValue(height, elementScope, currentScope, 0);
let points = restoreElementPoints({
points: oldPoints,
x,
y,
width,
height,
elementScope,
currentScope,
});
let lines = oldLines;
if (lines === undefined || lines === null) {
lines = [];
for (let i = 0; i < points.length - 1; i++) {
lines.push([
{ index: i, handle: null },
{ index: i + 1, handle: null },
]);
}
}
else {
lines = lines.map((line) => {
return [
{
index: line[0].index,
handle: line[0].handle
? restorePoint(line[0].handle, elementScope, currentScope)
: null,
},
{
index: line[1].index,
handle: line[1].handle
? restorePoint(line[1].handle, elementScope, currentScope)
: null,
},
];
});
}
if (!skipNormalization &&
points.length >= 2 &&
(points[0].x.value !== 0 || points[0].y.value !== 0)) {
const normalizedResult = getNormalizedPoints({
points,
lines,
x: validatedX,
y: validatedY,
scope: elementScope,
}, currentScope);
const firstPoint = points[0];
const lastPoint = points[points.length - 1];
points = normalizedResult.points;
lines = normalizedResult.lines;
if (oldPoints && oldPoints.length === points.length) {
if (firstPoint.x.value !== 0 || firstPoint.y.value !== 0) {
points[0] = Object.assign({}, points[0]);
}
if (lastPoint.x.value !== validatedWidth.value ||
lastPoint.y.value !== validatedHeight.value) {
points[points.length - 1] = Object.assign({}, points[points.length - 1]);
}
}
}
return { points, lines };
};
/**
* Repairs container element's boundElements array by removing duplicates and
* fixing containerId of bound elements if not present. Also removes any
* bound elements that do not exist in the elements array.
*
* NOTE mutates elements.
*/
const repairContainerElement = (container, elementsMap) => {
if (container.boundElements) {
const boundElements = container.boundElements.slice();
const boundIds = new Set();
container.boundElements = boundElements.reduce((acc, binding) => {
const boundElement = elementsMap.get(binding.id);
if (boundElement && !boundIds.has(binding.id)) {
boundIds.add(binding.id);
if (boundElement.isDeleted) {
return acc;
}
acc.push(binding);
if (isTextElement(boundElement) && !boundElement.containerId) {
boundElement.containerId =
container.id;
}
}
return acc;
}, []);
}
};
/**
* Repairs target bound element's container's boundElements array,
* or removes contaienrId if container does not exist.
*
* NOTE mutates elements.
*/
const repairBoundElement = (boundElement, elementsMap) => {
const container = boundElement.containerId
? elementsMap.get(boundElement.containerId)
: null;
if (!container) {
boundElement.containerId = null;
return;
}
if (boundElement.isDeleted) {
return;
}
if (container.boundElements &&
!container.boundElements.find((binding) => binding.id === boundElement.id)) {
const boundElements = (container.boundElements || (container.boundElements = [])).slice();
boundElements.push({ type: "text", id: boundElement.id });
container.boundElements = boundElements;
}
};
/**
* Remove an element's frameId if its containing frame is non-existent
*
* NOTE mutates elements.
*/
const repairFrameMembership = (element, elementsMap) => {
if (element.frameId) {
const containingFrame = elementsMap.get(element.frameId);
if (!containingFrame) {
element.frameId = null;
}
}
};
/**
* Assigns z-index values to elements based on their position in the array.
* This recreates the original visual stacking that was implied by array order
* before explicit z-index support was added.
*/
export const migrateArrayOrderToZIndex = (elements) => {
// Check if we need to do a migration (if any element lacks z-index)
const needsMigration = elements.some((el) => el.zIndex === undefined || el.zIndex === null);
if (!needsMigration) {
return elements;
}
// Create a new array with explicit z-index values
return [...elements].map((element, index) => {
// Only update elements that don't already have a valid z-index
if (element.zIndex === undefined || element.zIndex === null) {
return Object.assign(Object.assign({}, element), { zIndex: index });
}
return element;
});
};
export const restoreElements = (elements, currentScope, restoredBlocks, opts) => {
const existingIds = new Set();
// First restore all elements with their original properties
const restored = (elements || []).reduce((elements, element) => {
if (element.type !== "selection") {
let migratedElement = restoreElement(element, currentScope, restoredBlocks, opts === null || opts === void 0 ? void 0 : opts.localState);
// Allow forced pass-through for specific element ids
const isPassThrough = Array.isArray(opts === null || opts === void 0 ? void 0 : opts.passThroughElementIds) &&
migratedElement &&
migratedElement.id &&
opts.passThroughElementIds.includes(migratedElement.id);
if (migratedElement &&
(isPassThrough || !isInvisiblySmallElement(migratedElement))) {
if (existingIds.has(migratedElement.id)) {
migratedElement = Object.assign(Object.assign({}, migratedElement), { id: randomId() });
}
existingIds.add(migratedElement.id);
elements.push(migratedElement);
}
}
return elements;
}, []);
let restoredElements = (opts === null || opts === void 0 ? void 0 : opts.syncInvalidIndices)
? opts.syncInvalidIndices(restored, currentScope)
: restored;
// Migrate array ordering to explicit z-index values if needed
restoredElements = migrateArrayOrderToZIndex(restoredElements);
if (!(opts === null || opts === void 0 ? void 0 : opts.repairBindings)) {
return restoredElements;
}
const restoredElementsMap = arrayToMap(restoredElements);
for (const element of restoredElements) {
if (element.frameId) {
repairFrameMembership(element, restoredElementsMap);
}
if (isTextElement(element) && element.containerId) {
repairBoundElement(element, restoredElementsMap);
}
else if (element.boundElements) {
repairContainerElement(element, restoredElementsMap);
}
if (opts.refreshDimensions && isTextElement(element)) {
Object.assign(element, refreshTextDimensions(element, getContainerElement(element, restoredElementsMap), restoredElementsMap, currentScope));
}
if (isLinearElement(element)) {
// Helper function to check if we should keep a binding
const shouldKeepBinding = (binding) => {
if (!binding)
return true; // No binding, nothing to remove
// Keep head-only bindings
if (binding.head !== undefined &&
(!binding.elementId || binding.elementId === "")) {
return true;
}
// If binding has an elementId, check if it points to a valid element
const elementExists = restoredElementsMap.has(binding.elementId);
// For linear-to-linear bindings, ensure the point attribute is valid
if (binding.point && elementExists) {
const targetElement = restoredElementsMap.get(binding.elementId);
if (isLinearElement(targetElement)) {
// Check if the point index is within bounds of the target element's points
if (binding.point.index >= 0 &&
binding.point.index < targetElement.points.length &&
Math.abs(binding.point.offset) <= 1) {
return true;
}
}
}
return elementExists;
};
// Process start binding
if (element.startBinding && !shouldKeepBinding(element.startBinding)) {
element.startBinding = null;
}
// Process end binding
if (element.endBinding && !shouldKeepBinding(element.endBinding)) {
element.endBinding = null;
}
// Update the bound elements reference for each valid binding
// This ensures both sides of the binding relationship are updated
const updateBoundElementsRef = (binding) => {
var _a;
if (!binding || !binding.elementId)
return;
const targetElement = restoredElementsMap.get(binding.elementId);
if (targetElement && !targetElement.isDeleted) {
// Add the linear element to the target's boundElements if not already there
if (!((_a = targetElement.boundElements) === null || _a === void 0 ? void 0 : _a.some((be) => be.id === element.id))) {
const boundElements = targetElement.boundElements || [];
targetElement.boundElements = [
...boundElements,
{
id: element.id,
type: element.type,
},
];
}
}
};
// Update bound elements references
updateBoundElementsRef(element.startBinding);
updateBoundElementsRef(element.endBinding);
}
}
return restoredElements;
};
export const isValidElementScopeValue = (value, mainScope = NEUTRAL_SCOPE) => {
// Only check if the provided value is valid
if (value !== undefined && Object.keys(ScaleFactors).includes(value)) {
return value;
}
return mainScope;
};
const restoreTextDynamicParts = (dynamic) => {
if (!Array.isArray(dynamic)) {
return [];
}
return dynamic
.map((part) => ({
tag: isValidString(part.tag),
source: part.source, // Assuming source is valid, could be further validated
formatting: part.formatting, // Assuming valid
cachedValue: isValidString(part.cachedValue),
}))
.filter((part) => part.tag);
};
const restoreLineSpacing = (lineSpacing, legacyLineHeight, currentScope) => {
if (lineSpacing && lineSpacing.type && lineSpacing.value) {
return {
type: isValidLineSpacingTypeValue(lineSpacing.type),
value: restorePrecisionValue(lineSpacing.value, currentScope, currentScope, 1.15, true),
};
}
// Fallback to legacy lineHeight
return {
type: LINE_SPACING_TYPE.MULTIPLE,
value: legacyLineHeight,
};
};
const restoreTableCellStyle = (style, currentScope, defaultStyle) => {
if (!style || typeof style !== "object") {
return defaultStyle;
}
return Object.assign(Object.assign(Object.assign({}, defaultStyle), style), { alignment: Object.values(TABLE_CELL_ALIGNMENT).includes(style.alignment)
? style.alignment
: defaultStyle.alignment });
};
const restoreTableColumns = (columns, currentScope, defaultColumns) => {
if (!columns || typeof columns !== "object") {
return defaultColumns;
}
const restored = {};
for (const id in columns) {
const col = columns[id];
if (col && typeof col === "object" && isValidString(col.id)) {
restored[id] = {
id: col.id,
width: restorePrecisionValue(col.width, currentScope, currentScope, 100),
styleOverrides: col.styleOverrides
? restoreTableCellStyle(col.styleOverrides, currentScope, {})
: undefined,
};
}
}
return restored;
};
const restoreTableRows = (rows, currentScope, defaultRows) => {
if (!rows || typeof rows !== "object") {
return defaultRows;
}
const restored = {};
for (const id in rows) {
const row = rows[id];
if (row && typeof row === "object" && isValidString(row.id)) {
restored[id] = {
id: row.id,
height: restorePrecisionValue(row.height, currentScope, currentScope, 40),
styleOverrides: row.styleOverrides
? restoreTableCellStyle(row.styleOverrides, currentScope, {})
: undefined,
};
}
}
return restored;
};
const restoreTableCells = (cells, defaultCells) => {
if (!cells || typeof cells !== "object") {
return defaultCells;
}
const restored = {};
for (const id in cells) {
const cell = cells[id];
if (cell &&
typeof cell === "object" &&
isValidString(cell.rowId) &&
isValidString(cell.columnId)) {
restored[id] = {
rowId: cell.rowId,
columnId: cell.columnId,
data: isValidString(cell.data),
locked: isValidBooleanValue(cell.locked, false),
span: cel