Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions packages/layout-engine/contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@ export type WordLayoutMarker = {
italic?: boolean;
color?: string;
letterSpacing?: number;
vanish?: boolean;
};
};

Expand Down
120 changes: 62 additions & 58 deletions packages/layout-engine/painters/dom/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type WordLayoutMarker = {
italic?: boolean;
color?: string;
letterSpacing?: number;
vanish?: boolean;
};
};

Expand Down Expand Up @@ -2285,66 +2286,69 @@ export class DomPainter {
const marker = wordLayout.marker!;
lineEl.style.paddingLeft = `${paraIndentLeft + (paraIndent?.firstLine ?? 0) - (paraIndent?.hanging ?? 0)}px`; // HERE CONTROLS WHERE TAB STARTS - I think this will vary with justification

const markerContainer = this.doc!.createElement('span');
markerContainer.style.display = 'inline-block';
// Justification is implemented via `word-spacing` on the line element. The list marker (and its
// tab/space suffix) must not inherit this spacing or it will shift the text start and can
// cause overflow for justified list paragraphs.
markerContainer.style.wordSpacing = '0px';

const markerEl = this.doc!.createElement('span');
markerEl.classList.add('superdoc-paragraph-marker');
markerEl.textContent = marker.markerText ?? '';
markerEl.style.pointerEvents = 'none';

// Left-justified markers stay inline to share flow with the tab spacer.
// Other justifications use absolute positioning.
const markerJustification = marker.justification ?? 'left';

markerContainer.style.position = 'relative';
if (markerJustification === 'right') {
markerContainer.style.position = 'absolute';
markerContainer.style.left = `${markerStartPos}px`; // HERE CONTROLS MARKER POSITION - I think this will vary with justification
} else if (markerJustification === 'center') {
markerContainer.style.position = 'absolute';
markerContainer.style.left = `${markerStartPos - fragment.markerTextWidth! / 2}px`; // HERE CONTROLS MARKER POSITION - I think this will vary with justification
lineEl.style.paddingLeft = parseFloat(lineEl.style.paddingLeft) + fragment.markerTextWidth! / 2 + 'px';
}
// Skip marker rendering when hidden by vanish property (preserves list indentation)
if (!marker.run.vanish) {
const markerContainer = this.doc!.createElement('span');
markerContainer.style.display = 'inline-block';
// Justification is implemented via `word-spacing` on the line element. The list marker (and its
// tab/space suffix) must not inherit this spacing or it will shift the text start and can
// cause overflow for justified list paragraphs.
markerContainer.style.wordSpacing = '0px';

const markerEl = this.doc!.createElement('span');
markerEl.classList.add('superdoc-paragraph-marker');
markerEl.textContent = marker.markerText ?? '';
markerEl.style.pointerEvents = 'none';

// Left-justified markers stay inline to share flow with the tab spacer.
// Other justifications use absolute positioning.
const markerJustification = marker.justification ?? 'left';

markerContainer.style.position = 'relative';
if (markerJustification === 'right') {
markerContainer.style.position = 'absolute';
markerContainer.style.left = `${markerStartPos}px`; // HERE CONTROLS MARKER POSITION - I think this will vary with justification
} else if (markerJustification === 'center') {
markerContainer.style.position = 'absolute';
markerContainer.style.left = `${markerStartPos - fragment.markerTextWidth! / 2}px`; // HERE CONTROLS MARKER POSITION - I think this will vary with justification
lineEl.style.paddingLeft = parseFloat(lineEl.style.paddingLeft) + fragment.markerTextWidth! / 2 + 'px';
}

// Apply marker run styling with font fallback chain
markerEl.style.fontFamily = toCssFontFamily(marker.run.fontFamily) ?? marker.run.fontFamily;
markerEl.style.fontSize = `${marker.run.fontSize}px`;
markerEl.style.fontWeight = marker.run.bold ? 'bold' : '';
markerEl.style.fontStyle = marker.run.italic ? 'italic' : '';
if (marker.run.color) {
markerEl.style.color = marker.run.color;
}
if (marker.run.letterSpacing != null) {
markerEl.style.letterSpacing = `${marker.run.letterSpacing}px`;
}
markerContainer.appendChild(markerEl);

const suffix = marker.suffix ?? 'tab';
if (suffix === 'tab') {
const tabEl = this.doc!.createElement('span');
tabEl.className = 'superdoc-tab';
tabEl.innerHTML = ' ';
tabEl.style.display = 'inline-block';
tabEl.style.wordSpacing = '0px';
tabEl.style.width = `${listTabWidth}px`;

lineEl.prepend(tabEl);
} else if (suffix === 'space') {
// Insert a non-breaking space in the inline flow to separate marker and text.
// Wrap it so it can opt out of inherited `word-spacing` used for justification.
const spaceEl = this.doc!.createElement('span');
spaceEl.classList.add('superdoc-marker-suffix-space');
spaceEl.style.wordSpacing = '0px';
spaceEl.textContent = '\u00A0';

lineEl.prepend(spaceEl);
// Apply marker run styling with font fallback chain
markerEl.style.fontFamily = toCssFontFamily(marker.run.fontFamily) ?? marker.run.fontFamily;
markerEl.style.fontSize = `${marker.run.fontSize}px`;
markerEl.style.fontWeight = marker.run.bold ? 'bold' : '';
markerEl.style.fontStyle = marker.run.italic ? 'italic' : '';
if (marker.run.color) {
markerEl.style.color = marker.run.color;
}
if (marker.run.letterSpacing != null) {
markerEl.style.letterSpacing = `${marker.run.letterSpacing}px`;
}
markerContainer.appendChild(markerEl);

const suffix = marker.suffix ?? 'tab';
if (suffix === 'tab') {
const tabEl = this.doc!.createElement('span');
tabEl.className = 'superdoc-tab';
tabEl.innerHTML = ' ';
tabEl.style.display = 'inline-block';
tabEl.style.wordSpacing = '0px';
tabEl.style.width = `${listTabWidth}px`;

lineEl.prepend(tabEl);
} else if (suffix === 'space') {
// Insert a non-breaking space in the inline flow to separate marker and text.
// Wrap it so it can opt out of inherited `word-spacing` used for justification.
const spaceEl = this.doc!.createElement('span');
spaceEl.classList.add('superdoc-marker-suffix-space');
spaceEl.style.wordSpacing = '0px';
spaceEl.textContent = '\u00A0';

lineEl.prepend(spaceEl);
}
lineEl.prepend(markerContainer);
}
lineEl.prepend(markerContainer);
}
fragmentEl.appendChild(lineEl);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ type WordLayoutMarker = {
color?: string;
/** Letter spacing in pixels */
letterSpacing?: number;
/** Hidden text flag */
vanish?: boolean;
};
};

Expand Down Expand Up @@ -968,7 +970,8 @@ export const renderTableCell = (deps: TableCellRenderDependencies): TableCellRen
* - The marker has a non-zero width
*/
const shouldRenderMarker =
markerLayout && markerMeasure && lineIdx === 0 && localStartLine === 0 && markerMeasure.markerWidth > 0;
markerLayout && markerMeasure && lineIdx === 0 && localStartLine === 0 && markerMeasure.markerWidth > 0
&& !markerLayout.run?.vanish;

if (shouldRenderMarker) {
/**
Expand Down
10 changes: 10 additions & 0 deletions packages/layout-engine/pm-adapter/src/attributes/paragraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,14 @@ describe('computeRunAttrs', () => {
expect(result.fontSize).toBeGreaterThan(0);
expect(result.color).toBe('#FF0000');
});

it('includes the vanish property', () => {
const runProps = {
vanish: true,
};

const result = computeRunAttrs(runProps as never);

expect(result.vanish).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -338,5 +338,6 @@ export const computeRunAttrs = (
allCaps: runProps?.textTransform === 'uppercase',
letterSpacing: runProps.letterSpacing ? twipsToPx(runProps.letterSpacing) : undefined,
lang: runProps.lang?.val || undefined,
vanish: runProps.vanish,
};
};
1 change: 1 addition & 0 deletions packages/word-layout/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type ResolvedRunProperties = {
letterSpacing?: number;
scale?: number;
lang?: string;
vanish?: boolean;
};

export type NumberingProperties = {
Expand Down
Loading