Signal drop!
Relay (operand.online) is unreachable.
Usually, a dropped signal means an upgrade is happening. Hold on!
Sorry, no connección.
Hang in there while we get back on track
gram: docs
> ./packages/core/src/prosemirror/conversion/toProseDoc/marks.ts
/**
* TextFormatting → PM marks projection (Document → ProseMirror direction).
*
* The inverse of `fromProseDoc/marks.ts`. Maps every OOXML-shaped
* `TextFormatting` field (bold/italic/underline/color/font/effect/etc.)
* onto the matching `schema.mark(...)` call. Also owns `resolveTextFormatting`
* which threads the character-style cascade so runs without an explicit
* `<w:rStyle>` still inherit from the default character style (§17.7.4.18).
*/
import { schema } from '../../schema';
import type { TextFormatting } from '../../../types/document';
import type { ShadingProperties } from '../../../types/colors';
import { ensureHexPrefix } from '../../../utils/colorResolver';
import { mergeTextFormatting } from '../../../utils/textFormattingMerge';
import type { StyleResolver } from '../../styles';
/**
* Map a run-level shading (`w:shd`) to a highlight color string, when it
* encodes a solid background fill. The editor emits `w:shd w:val="clear"` as
* the carrier for custom highlight colors that fall outside the OOXML
* named-highlight palette (§17.18.40), so a concrete `fill` rgb reads back as
* the highlight mark. Only a `clear` / no-pattern fill maps, where Word paints
* the background as `w:fill`. Other patterns are skipped: `solid` paints the
* pattern color (`w:color`) over the fill, and `pct*`/stripes blend the two —
* neither is a flat color the highlight can reproduce. Theme-only / `auto`
* fills carry no concrete rgb and are likewise left untouched. (#712)
*/
function runShadingToHighlight(shading: ShadingProperties | undefined): string | undefined {
if (!shading) return undefined;
if (shading.pattern && shading.pattern !== 'clear') return undefined;
const rgb = shading.fill?.rgb;
if (!rgb || shading.fill?.auto) return undefined;
return ensureHexPrefix(rgb);
}
/**
* Convert TextFormatting to ProseMirror marks
*/
export function textFormattingToMarks(
formatting: TextFormatting | undefined
): ReturnType<typeof schema.mark>[] {
if (!formatting) return [];
const marks: ReturnType<typeof schema.mark>[] = [];
// Bold
if (formatting.bold) {
marks.push(schema.mark('bold'));
}
// Italic
if (formatting.italic) {
marks.push(schema.mark('italic'));
}
// Underline
if (formatting.underline && formatting.underline.style !== 'none') {
marks.push(
schema.mark('underline', {
style: formatting.underline.style,
color: formatting.underline.color,
})
);
}
// Strikethrough
if (formatting.strike || formatting.doubleStrike) {
marks.push(
schema.mark('strike', {
double: formatting.doubleStrike || false,
})
);
}
// Text color
if (formatting.color && !formatting.color.auto) {
marks.push(
schema.mark('textColor', {
rgb: formatting.color.rgb,
themeColor: formatting.color.themeColor,
themeTint: formatting.color.themeTint,
themeShade: formatting.color.themeShade,
})
);
}
// Highlight (w:highlight) — or run-level shading fill (w:shd) used as a
// background. The editor serializes custom (non-OOXML-named) highlight colors
// as `<w:shd w:fill="...">` because w:highlight only permits a fixed palette
// (§17.18.40). On import that shading must round-trip back to the highlight
// mark, otherwise the background silently disappears. (#712)
const highlightColor =
formatting.highlight && formatting.highlight !== 'none'
? formatting.highlight
: runShadingToHighlight(formatting.shading);
if (highlightColor) {
marks.push(
schema.mark('highlight', {
color: highlightColor,
})
);
}
// Font size
if (formatting.fontSize) {
marks.push(
schema.mark('fontSize', {
size: formatting.fontSize,
})
);
}
// Font family
if (formatting.fontFamily) {
marks.push(
schema.mark('fontFamily', {
ascii: formatting.fontFamily.ascii,
hAnsi: formatting.fontFamily.hAnsi,
eastAsia: formatting.fontFamily.eastAsia,
cs: formatting.fontFamily.cs,
asciiTheme: formatting.fontFamily.asciiTheme,
hAnsiTheme: formatting.fontFamily.hAnsiTheme,
eastAsiaTheme: formatting.fontFamily.eastAsiaTheme,
csTheme: formatting.fontFamily.csTheme,
})
);
}
// Superscript/Subscript
if (formatting.vertAlign === 'superscript') {
marks.push(schema.mark('superscript'));
} else if (formatting.vertAlign === 'subscript') {
marks.push(schema.mark('subscript'));
}
// All caps (w:caps)
if (formatting.allCaps) {
marks.push(schema.mark('allCaps'));
}
// Small caps (w:smallCaps)
if (formatting.smallCaps) {
marks.push(schema.mark('smallCaps'));
}
// Character spacing (spacing, position, scale, kerning)
if (
formatting.spacing != null ||
formatting.position != null ||
formatting.scale != null ||
formatting.kerning != null
) {
marks.push(
schema.mark('characterSpacing', {
spacing: formatting.spacing ?? null,
position: formatting.position ?? null,
scale: formatting.scale ?? null,
kerning: formatting.kerning ?? null,
})
);
}
// Emboss (w:emboss)
if (formatting.emboss) {
marks.push(schema.mark('emboss'));
}
// Imprint/Engrave (w:imprint)
if (formatting.imprint) {
marks.push(schema.mark('imprint'));
}
// Text shadow (w:shadow)
if (formatting.shadow) {
marks.push(schema.mark('textShadow'));
}
// Emphasis mark (w:em)
if (formatting.emphasisMark && formatting.emphasisMark !== 'none') {
marks.push(schema.mark('emphasisMark', { type: formatting.emphasisMark }));
}
// Text outline (w:outline)
if (formatting.outline) {
marks.push(schema.mark('textOutline'));
}
// Hidden text (w:vanish)
if (formatting.hidden) {
marks.push(schema.mark('hidden'));
}
// Per-run RTL (w:rtl) — independent of paragraph direction
if (formatting.rtl) {
marks.push(schema.mark('rtl'));
}
// Text effect animations (w:effect)
if (formatting.effect && formatting.effect !== 'none') {
marks.push(schema.mark('textEffect', { effect: formatting.effect }));
}
// Character-style reference (w:rStyle). The style's formatting is already
// baked into the direct marks above via resolveTextFormatting; this only
// carries the named reference so `<w:rStyle>` survives the round-trip.
if (formatting.styleId) {
marks.push(schema.mark('runStyle', { styleId: formatting.styleId }));
}
return marks;
}
/**
* Resolve a run's TextFormatting against the character-style cascade.
*
* Even when the run has no explicit <w:rStyle>, OOXML §17.7.4.18 says it
* still inherits from the default character style. resolveRunStyle(undef)
* returns docDefaults.rPr merged with the default character style's rPr —
* without this path, runs without a styleId lose any property the default
* character style sets.
*/
export function resolveTextFormatting(
formatting: TextFormatting | undefined,
styleResolver: StyleResolver | null
): TextFormatting | undefined {
if (!formatting) return undefined;
if (!styleResolver) return formatting;
const styleFormatting = styleResolver.resolveRunStyle(formatting.styleId);
if (!styleFormatting) return formatting;
return mergeTextFormatting(styleFormatting, formatting);
}