diff --git a/packages/docs-ui/src/services/selection/__tests__/convert-text-range.spec.ts b/packages/docs-ui/src/services/selection/__tests__/convert-text-range.spec.ts index aeb314242d..05058bf7b0 100644 --- a/packages/docs-ui/src/services/selection/__tests__/convert-text-range.spec.ts +++ b/packages/docs-ui/src/services/selection/__tests__/convert-text-range.spec.ts @@ -14,9 +14,14 @@ * limitations under the License. */ -import type { INodePosition } from '@univerjs/engine-render'; import { DataStreamTreeTokenType, PositionedObjectLayoutType } from '@univerjs/core'; -import { DocumentSkeletonPageType, setDocsTableRenderViewportProvider } from '@univerjs/engine-render'; +import { + DocumentSkeleton, + DocumentSkeletonPageType, + PageLayoutType, + setDocsTableRenderViewportProvider, + Transform, +} from '@univerjs/engine-render'; import { afterEach, describe, expect, it } from 'vitest'; import { compareNodePosition, @@ -203,7 +208,7 @@ describe('selection convert text range helpers', () => { }), }), }; - const position: INodePosition = { + const position = { column: 0, divide: 0, glyph: 0, @@ -307,7 +312,7 @@ describe('selection convert text range helpers', () => { }), }), }; - const position: INodePosition = { + const position = { column: 0, divide: 0, glyph: 0, @@ -401,7 +406,7 @@ describe('selection convert text range helpers', () => { }), }), }; - const position: INodePosition = { + const position = { column: 0, divide: 0, glyph: 0, @@ -516,7 +521,7 @@ describe('selection convert text range helpers', () => { }), }), }; - const position: INodePosition = { + const position = { column: 0, divide: 0, glyph: 0, @@ -542,6 +547,92 @@ describe('selection convert text range helpers', () => { expect(getAnchorBounding(result.contentBoxPointGroup).left).toBe(145); }); + it('projects selections through sibling traditional section columns without accumulating offsets', () => { + const createLine = () => ({ + asc: 10, + divides: [{ + glyphGroup: [{ + bBox: { ba: 8, bd: 2 }, + count: 1, + glyphType: 'LETTER', + left: 0, + width: 10, + }], + left: 0, + paddingLeft: 0, + }], + lineHeight: 20, + marginBottom: 0, + marginTop: 0, + paddingTop: 0, + top: 0, + }); + const page = { + marginLeft: 0, + marginTop: 0, + pageHeight: 500, + pageWidth: 500, + sections: [{ + columns: [{ left: 0, lines: [createLine()] }], + top: 10, + }, { + columns: [ + { left: 0, lines: [createLine()] }, + { left: 30, lines: [createLine()] }, + { left: 60, lines: [createLine()] }, + ], + top: 100, + }], + }; + const skeleton: DocumentSkeleton = Object.assign(Object.create(DocumentSkeleton.prototype), { + getSkeletonData: () => ({ + pages: [page], + skeFooters: new Map(), + skeHeaders: new Map(), + }), + }); + const start = { + column: 0, + divide: 0, + glyph: 0, + isBack: true, + line: 0, + page: 0, + pageType: DocumentSkeletonPageType.BODY, + path: ['pages', 0], + section: 0, + segmentPage: -1, + }; + const end = { + ...start, + column: 2, + isBack: false, + section: 1, + }; + const convertor = new NodePositionConvertToCursor({ + docsLeft: 0, + docsTop: 0, + documentTransform: new Transform(), + pageLayoutType: PageLayoutType.VERTICAL, + pageMarginLeft: 0, + pageMarginTop: 0, + }, skeleton); + + const getSelectionBounds = (selectionStart: typeof start, selectionEnd: typeof start) => { + const result = convertor.getRangePointData(selectionStart, selectionEnd); + return getLineBounding(result.contentBoxPointGroup).map(({ left, top }) => ({ left, top })); + }; + const expectedBounds = [ + { left: 0, top: 12 }, + { left: 0, top: 102 }, + { left: 30, top: 102 }, + { left: 60, top: 102 }, + ]; + + expect(getSelectionBounds(start, end)).toEqual(expectedBounds); + expect(getSelectionBounds(end, start)).toEqual(expectedBounds); + }); + it('excludes layout-only paragraph spacing from text selection height', () => { const glyph = { bBox: { ba: 10, bd: 3 }, @@ -585,7 +676,7 @@ describe('selection convert text range helpers', () => { skeHeaders: new Map(), }), }; - const position: INodePosition = { + const position = { column: 0, divide: 0, glyph: 0, @@ -758,7 +849,7 @@ function createEmbedCustomBlockCursorHarness(drawing?: Record) }), }), }; - const position: INodePosition = { + const position = { column: 0, divide: 0, glyph: 0, diff --git a/packages/docs-ui/src/services/selection/convert-text-range.ts b/packages/docs-ui/src/services/selection/convert-text-range.ts index 6be0cfc881..0ab80c24fb 100644 --- a/packages/docs-ui/src/services/selection/convert-text-range.ts +++ b/packages/docs-ui/src/services/selection/convert-text-range.ts @@ -711,6 +711,7 @@ export class NodePositionConvertToCursor { s ); + this._liquid.translateSave(); this._liquid.translateSection(section); for (let c = start_c; c <= end_c; c++) { @@ -724,6 +725,7 @@ export class NodePositionConvertToCursor { c ); + this._liquid.translateSave(); this._liquid.translateColumn(column); for (let l = start_l; l <= end_l; l++) { @@ -773,7 +775,11 @@ export class NodePositionConvertToCursor { this._liquid.translateRestore(); } + + this._liquid.translateRestore(); } + + this._liquid.translateRestore(); } this._liquid.translateRestore(); this._horizontalClip = previousHorizontalClip;