From ac62428556a7e3770132b51bd2be9a03490efde7 Mon Sep 17 00:00:00 2001 From: Ran Luo Date: Sat, 1 Jun 2024 17:29:04 +0800 Subject: [PATCH] feat: support for tilting the cursor when italicized (#1932) * feat: support for tilting the cursor when italicized * feat: add system highlight color as seleciton color * fix: stroke width * fix: add default value of opacity --- packages/core/src/shared/color/color-kit.ts | 1 + packages/engine-render/src/basics/range.ts | 2 +- packages/engine-render/src/basics/tools.ts | 19 ++++++++- .../docs/text-selection/text-range.ts | 40 +++++++++++++++---- .../text-selection-render-manager.ts | 26 ++++++++++-- 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/packages/core/src/shared/color/color-kit.ts b/packages/core/src/shared/color/color-kit.ts index d6764462453..50e22770932 100644 --- a/packages/core/src/shared/color/color-kit.ts +++ b/packages/core/src/shared/color/color-kit.ts @@ -410,6 +410,7 @@ const rgbNormalize = (val: number) => { return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4; }; +// eslint-disable-next-line max-lines-per-function const toColor: (color: string | Color) => Color | undefined = (color) => { if (isObject(color)) { if ('r' in color) { diff --git a/packages/engine-render/src/basics/range.ts b/packages/engine-render/src/basics/range.ts index 8db26f8d8f7..e25e61aef56 100644 --- a/packages/engine-render/src/basics/range.ts +++ b/packages/engine-render/src/basics/range.ts @@ -24,7 +24,7 @@ export interface ITextSelectionStyle { } export const NORMAL_TEXT_SELECTION_PLUGIN_STYLE: ITextSelectionStyle = { - strokeWidth: 1, + strokeWidth: 1.5, stroke: 'rgba(0, 0, 0, 0)', strokeActive: 'rgba(0, 0, 0, 1)', fill: 'rgba(0, 0, 0, 0.2)', diff --git a/packages/engine-render/src/basics/tools.ts b/packages/engine-render/src/basics/tools.ts index 8188bd139fc..9cb3888612e 100644 --- a/packages/engine-render/src/basics/tools.ts +++ b/packages/engine-render/src/basics/tools.ts @@ -24,7 +24,7 @@ import type { LocaleService, Nullable, } from '@univerjs/core'; -import { BaselineOffset, DEFAULT_STYLES, FontStyleType, Rectangle, Tools } from '@univerjs/core'; +import { BaselineOffset, ColorKit, DEFAULT_STYLES, FontStyleType, Rectangle, Tools } from '@univerjs/core'; import * as cjk from 'cjk-regex'; import { FontCache } from '../components/docs/layout/shaping-engine/font-cache'; @@ -833,3 +833,20 @@ export function clampRanges(range: IRange) { endColumn: Math.max(0, range.endColumn), }; } + +// Get system highlight color in rgb format. +export function getSystemHighlightColor() { + const hiddenEle = document.createElement('div'); + hiddenEle.style.width = '0'; + hiddenEle.style.height = '0'; + hiddenEle.style.backgroundColor = 'highlight'; + document.body.append(hiddenEle); + + const highlightColor = getComputedStyle(hiddenEle).backgroundColor; + + hiddenEle.remove(); + + const colorParser = new ColorKit(highlightColor); + + return colorParser.toRgb(); +} diff --git a/packages/engine-render/src/components/docs/text-selection/text-range.ts b/packages/engine-render/src/components/docs/text-selection/text-range.ts index 4c5ec12f533..7cdbaaeb2bc 100644 --- a/packages/engine-render/src/components/docs/text-selection/text-range.ts +++ b/packages/engine-render/src/components/docs/text-selection/text-range.ts @@ -15,7 +15,8 @@ */ import type { ITextRange, Nullable } from '@univerjs/core'; -import { COLORS, Tools } from '@univerjs/core'; +import { BooleanNumber, COLORS, Tools } from '@univerjs/core'; + import type { INodePosition } from '../../../basics/interfaces'; import type { ISuccinctTextRangeParam, ITextSelectionStyle } from '../../../basics/range'; import { NORMAL_TEXT_SELECTION_PLUGIN_STYLE, RANGE_DIRECTION } from '../../../basics/range'; @@ -27,6 +28,7 @@ import { RegularPolygon } from '../../../shape/regular-polygon'; import type { ThinScene } from '../../../thin-scene'; import type { DocumentSkeleton } from '../layout/doc-skeleton'; import type { Documents } from '../document'; +import type { IDocumentSkeletonGlyph } from '../../../basics'; import { compareNodePosition, compareNodePositionLogic, @@ -318,7 +320,11 @@ export class TextRange { const { contentBoxPointGroup, cursorList } = convertor.getRangePointData(anchor, anchor); this._setCursorList(cursorList); - contentBoxPointGroup.length > 0 && this._createOrUpdateAnchor(contentBoxPointGroup, docsLeft, docsTop); + + if (contentBoxPointGroup.length > 0) { + const glyphAtCursor = _docSkeleton.findGlyphByPosition(anchor); + this._createOrUpdateAnchor(contentBoxPointGroup, docsLeft, docsTop, glyphAtCursor); + } return; } @@ -388,26 +394,44 @@ export class TextRange { this._scene.addObject(polygon, TEXT_RANGE_LAYER_INDEX); } - private _createOrUpdateAnchor(pointsGroup: IPoint[][], docsLeft: number, docsTop: number) { + private _createOrUpdateAnchor(pointsGroup: IPoint[][], docsLeft: number, docsTop: number, glyph: Nullable) { const bounding = getAnchorBounding(pointsGroup); - const { left, top, height } = bounding; + const { left: boundingLeft, top: boundingTop, height } = bounding; + const ITALIC_DEGREE = 12; + let left = boundingLeft + docsLeft; + const top = boundingTop + docsTop; + const isItalic = glyph?.ts?.it === BooleanNumber.TRUE; + + if (isItalic) { + left += height * Math.tan((ITALIC_DEGREE * Math.PI) / 180) / 2; + } if (this._anchorShape) { - this._anchorShape.transformByState({ left: left + docsLeft, top: top + docsTop, height }); + this._anchorShape.transformByState({ left, top, height }); this._anchorShape.show(); + if (isItalic) { + this._anchorShape.skew(-ITALIC_DEGREE, 0); + } else { + this._anchorShape.skew(0, 0); + } + return; } const anchor = new Rect(TEXT_ANCHOR_KEY_PREFIX + Tools.generateRandomId(ID_LENGTH), { - left: left + docsLeft, - top: top + docsTop, + left, + top, height, - strokeWidth: this.style?.strokeWidth || 1, + strokeWidth: this.style?.strokeWidth || 1.5, stroke: this.style?.strokeActive || getColor(COLORS.black, 1), evented: false, }); + if (isItalic) { + anchor.skew(-ITALIC_DEGREE, 0); + } + this._anchorShape = anchor; this._scene.addObject(anchor, TEXT_RANGE_LAYER_INDEX); diff --git a/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts b/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts index 49ba336beb8..a7e48981fc2 100644 --- a/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts +++ b/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts @@ -41,6 +41,7 @@ import { ScrollTimer } from '../../../scroll-timer'; import type { IScrollObserverParam, Viewport } from '../../../viewport'; import type { DocumentSkeleton } from '../layout/doc-skeleton'; import type { Documents } from '../document'; +import { getSystemHighlightColor } from '../../../basics/tools'; import { cursorConvertToTextRange, TextRange } from './text-range'; export function getCanvasOffsetByEngine(engine: Nullable) { @@ -256,6 +257,8 @@ export class TextSelectionRenderManager extends RxDisposable implements ITextSel super(); this._initDOM(); + + this._setSystemHighlightColorToStyle(); } __getEditorContainer(): HTMLElement { @@ -290,7 +293,10 @@ export class TextSelectionRenderManager extends RxDisposable implements ITextSel const { _scene: scene, _docSkeleton: docSkeleton } = this; for (const range of ranges) { - const textSelection = cursorConvertToTextRange(scene!, range, docSkeleton!, this._document!); + const textSelection = cursorConvertToTextRange(scene!, { + style: this._selectionStyle, + ...range, + }, docSkeleton!, this._document!); this._add(textSelection); } @@ -491,7 +497,7 @@ export class TextSelectionRenderManager extends RxDisposable implements ITextSel if (evt.shiftKey && this._getActiveRangeInstance()) { this._updateActiveRangeFocusPosition(position); } else if (evt.ctrlKey || this._isEmpty()) { - const newTextSelection = new TextRange(scene, this._document!, this._docSkeleton!, position); + const newTextSelection = new TextRange(scene, this._document!, this._docSkeleton!, position, undefined, this._selectionStyle); this._addTextRange(newTextSelection); } else { @@ -577,6 +583,20 @@ export class TextSelectionRenderManager extends RxDisposable implements ITextSel this.deactivate(); } + private _setSystemHighlightColorToStyle() { + const { r, g, b, a } = getSystemHighlightColor(); + + // Only set selection use highlight color. + const style: ITextSelectionStyle = { + strokeWidth: 1.5, + stroke: 'rgba(0, 0, 0, 0)', + strokeActive: 'rgba(0, 0, 0, 1)', + fill: `rgba(${r}, ${g}, ${b}, ${a ?? 0.2})`, + }; + + this.setStyle(style); + } + private _getAllTextRanges() { return this._rangeList; } @@ -753,7 +773,7 @@ export class TextSelectionRenderManager extends RxDisposable implements ITextSel let lastRange = this._rangeList.pop(); if (!lastRange) { - lastRange = new TextRange(this._scene, this._document!, this._docSkeleton!, position); + lastRange = new TextRange(this._scene, this._document!, this._docSkeleton!, position, undefined, this._selectionStyle); } this._removeAllTextRanges();