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
52 changes: 38 additions & 14 deletions packages/layout-engine/measuring/dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,26 @@ function calculateTypographyMetrics(
};
}

/**
* Wraps `calculateTypographyMetrics` and applies inline-image height override.
*
* Typography metrics (ascent, descent) stay text-based so the baseline doesn't
* shift. When the line contains an inline image taller than the text line height,
* lineHeight is expanded to the image height — matching Word's behaviour where
* the text baseline stays fixed and the image occupies exactly its own height.
*/
function finalizeLineMetrics(
line: { maxFontSize: number; maxFontInfo?: FontInfo; maxImageHeight?: number },
spacing?: ParagraphSpacing,
): { ascent: number; descent: number; lineHeight: number } {
const metrics = calculateTypographyMetrics(line.maxFontSize, spacing, line.maxFontInfo);
const imageH = line.maxImageHeight ?? 0;
if (imageH > metrics.lineHeight) {
metrics.lineHeight = imageH;
}
return metrics;
}

/**
* Calculates typography metrics for empty paragraphs.
*
Expand Down Expand Up @@ -1048,6 +1068,8 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
maxFontSize: number;
/** Font info for the run with maxFontSize, used for accurate typography metrics */
maxFontInfo?: FontInfo;
/** Tallest inline image on this line (pixels) */
maxImageHeight?: number;
maxWidth: number;
segments: Line['segments'];
leaders?: Line['leaders'];
Expand Down Expand Up @@ -1275,7 +1297,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P

if ((run as Run).kind === 'break') {
if (currentLine) {
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = { ...lineBase, ...metrics };
addBarTabsToLine(completedLine);
Expand Down Expand Up @@ -1307,7 +1329,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
// For leading line breaks (before any text), use fallback font info for accurate height calculation
const lineBreakFontInfo = hasSeenTextRun ? undefined : fallbackFontInfo;
if (currentLine) {
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const completedLine: Line = {
...currentLine,
...metrics,
Expand Down Expand Up @@ -1489,7 +1511,8 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
toRun: runIndex,
toChar: 1, // Images are treated as single atomic units
width: imageWidth,
maxFontSize: imageHeight, // Use image height for line height calculation
maxFontSize: 0,
maxImageHeight: imageHeight,
maxWidth: getEffectiveWidth(lines.length === 0 ? initialAvailableWidth : bodyContentWidth),
spaceCount: 0,
segments: [
Expand Down Expand Up @@ -1518,7 +1541,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
if (!skipFitCheck && currentLine.width + imageWidth > currentLine.maxWidth && currentLine.width > 0) {
// Image doesn't fit - finish current line and start new line with image
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = {
...lineBase,
Expand All @@ -1538,7 +1561,8 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
toRun: runIndex,
toChar: 1,
width: imageWidth,
maxFontSize: imageHeight,
maxFontSize: 0,
maxImageHeight: imageHeight,
maxWidth: getEffectiveWidth(bodyContentWidth),
spaceCount: 0,
segments: [
Expand All @@ -1555,7 +1579,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
currentLine.toRun = runIndex;
currentLine.toChar = 1;
currentLine.width = roundValue(currentLine.width + imageWidth);
currentLine.maxFontSize = Math.max(currentLine.maxFontSize, imageHeight);
currentLine.maxImageHeight = Math.max(currentLine.maxImageHeight ?? 0, imageHeight);
if (!currentLine.segments) currentLine.segments = [];
currentLine.segments.push({
runIndex,
Expand Down Expand Up @@ -1668,7 +1692,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
if (currentLine.width + annotationWidth > currentLine.maxWidth && currentLine.width > 0) {
// Doesn't fit - finish current line and start new one
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = {
...lineBase,
Expand Down Expand Up @@ -1772,7 +1796,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
currentLine.width > 0
) {
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = {
...lineBase,
Expand Down Expand Up @@ -1886,7 +1910,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
) {
// Space doesn't fit - finish current line and start new one with the space
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = {
...lineBase,
Expand Down Expand Up @@ -1969,7 +1993,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
// long word can use the pending tab alignment.
if (currentLine && currentLine.width > 0 && currentLine.segments && currentLine.segments.length > 0) {
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = {
...lineBase,
Expand Down Expand Up @@ -2036,7 +2060,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
} else {
// More chunks to come - finish this line and push it
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = {
...lineBase,
Expand Down Expand Up @@ -2182,7 +2206,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P

if (shouldBreak) {
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = {
...lineBase,
Expand Down Expand Up @@ -2247,7 +2271,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
appendSegment(currentLine.segments, runIndex, wordStartChar, wordEndNoSpace, wordOnlyWidth, explicitXHere);
// finish current line and start a new one on next iteration
trimTrailingWrapSpaces(currentLine);
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const completedLine: Line = { ...lineBase, ...metrics };
addBarTabsToLine(completedLine);
Expand Down Expand Up @@ -2386,7 +2410,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
}

if (currentLine) {
const metrics = calculateTypographyMetrics(currentLine.maxFontSize, spacing, currentLine.maxFontInfo);
const metrics = finalizeLineMetrics(currentLine, spacing);
const lineBase = currentLine;
const finalLine: Line = {
...lineBase,
Expand Down
30 changes: 4 additions & 26 deletions packages/super-editor/src/components/toolbar/super-toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import { getActiveFormatting } from '@core/helpers/getActiveFormatting.js';
import { findParentNode } from '@helpers/index.js';
import { vClickOutside } from '@superdoc/common';
import Toolbar from './Toolbar.vue';
import {
checkAndProcessImage,
replaceSelectionWithImagePlaceholder,
uploadAndInsertImage,
getFileOpener,
} from '../../extensions/image/imageHelpers/index.js';
import { getFileOpener, processAndInsertImageFile } from '../../extensions/image/imageHelpers/index.js';
import { toolbarIcons } from './toolbarIcons.js';
import { toolbarTexts } from './toolbarTexts.js';
import { getQuickFormatList } from '@extensions/linked-styles/index.js';
Expand Down Expand Up @@ -459,29 +454,12 @@ export class SuperToolbar extends EventEmitter {
return;
}

const { size, file } = await checkAndProcessImage({
await processAndInsertImageFile({
file: result.file,
getMaxContentSize: () => this.activeEditor.getMaxContentSize(),
});

if (!file) {
return;
}

const id = {};

replaceSelectionWithImagePlaceholder({
view: this.activeEditor.view,
editorOptions: this.activeEditor.options,
id,
});

await uploadAndInsertImage({
editor: this.activeEditor,
view: this.activeEditor.view,
file,
size,
id,
editorOptions: this.activeEditor.options,
getMaxContentSize: () => this.activeEditor.getMaxContentSize(),
});
} catch (error) {
const err = new Error('[super-toolbar 🎨] Image upload failed');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
shouldUseCellSelection as shouldUseCellSelectionFromHelper,
} from './tables/TableSelectionUtilities.js';
import { DragDropManager } from './input/DragDropManager.js';
import { processAndInsertImageFile } from '@extensions/image/imageHelpers/processAndInsertImageFile.js';
import { HeaderFooterSessionManager } from './header-footer/HeaderFooterSessionManager.js';
import { decodeRPrFromMarks } from '../super-converter/styles.js';
import { halfPointToPoints } from '../super-converter/helpers.js';
Expand Down Expand Up @@ -2764,7 +2765,7 @@ export class PresentationEditor extends EventEmitter {
}

/**
* Sets up drag and drop handlers for field annotations.
* Sets up drag and drop handlers for field annotations and image files.
*/
#setupDragHandlers() {
// Clean up any existing manager
Expand All @@ -2777,6 +2778,7 @@ export class PresentationEditor extends EventEmitter {
scheduleSelectionUpdate: () => this.#scheduleSelectionUpdate(),
getViewportHost: () => this.#viewportHost,
getPainterHost: () => this.#painterHost,
insertImageFile: (params) => processAndInsertImageFile(params),
});
this.#dragDropManager.bind();
}
Expand Down
Loading
Loading