diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4de6ea696..e9d73e4fe10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- fix(textStyles): Split text into graphemes correctly [#9646](https://github.com/fabricjs/fabric.js/pull/9646) - fix(ActiveSelection): static default inheritance [#9635](https://github.com/fabricjs/fabric.js/pull/9635) - fix(StaticCanvas): StaticCanvas setDimensions typings [#9618](https://github.com/fabricjs/fabric.js/pull/9618) - refactor(): Align gradient with class registry usage, part of #9144 [#9627](https://github.com/fabricjs/fabric.js/pull/9627) diff --git a/src/shapes/Text/StyledText.spec.ts b/src/shapes/Text/StyledText.spec.ts index c81f2043d20..22b73cd2a95 100644 --- a/src/shapes/Text/StyledText.spec.ts +++ b/src/shapes/Text/StyledText.spec.ts @@ -1,4 +1,5 @@ import { FabricText } from './Text'; +import { graphemeSplit } from '../../util/lang_string'; describe('setSelectionStyles', () => { test('will set properties at the correct position', () => { @@ -35,3 +36,24 @@ describe('setSelectionStyles', () => { }); }); }); + +describe('toObject', () => { + it('Will serialize text with graphemes in mind', () => { + const text = new FabricText('🤩🤩\nHello', { + styles: { + 1: { + 0: { + fontSize: 40, + }, + }, + }, + }); + const serializedStyles = text.toObject().styles; + expect(serializedStyles).toEqual([ + { start: 2, end: 3, style: { fontSize: 40 } }, + ]); + expect(serializedStyles[0].start).toEqual( + graphemeSplit(text.textLines[0]).length + ); + }); +}); diff --git a/src/util/misc/textStyles.ts b/src/util/misc/textStyles.ts index e7cfeef5289..65a54ad1e2d 100644 --- a/src/util/misc/textStyles.ts +++ b/src/util/misc/textStyles.ts @@ -4,6 +4,7 @@ import type { TextStyleDeclaration, } from '../../shapes/Text/StyledText'; import { cloneDeep } from '../internals/cloneDeep'; +import { graphemeSplit } from '../lang_string'; export type TextStyleArray = { start: number; @@ -57,14 +58,15 @@ export const stylesToArray = ( //loop through each textLine for (let i = 0; i < textLines.length; i++) { + const chars = graphemeSplit(textLines[i]); if (!styles[i]) { //no styles exist for this line, so add the line's length to the charIndex total and reset prevStyle - charIndex += textLines[i].length; + charIndex += chars.length; prevStyle = {}; continue; } //loop through each character of the current line - for (let c = 0; c < textLines[i].length; c++) { + for (let c = 0; c < chars.length; c++) { charIndex++; const thisStyle = styles[i][c]; //check if style exists for this character @@ -108,8 +110,10 @@ export const stylesFromArray = ( styleIndex = 0; //loop through each textLine for (let i = 0; i < textLines.length; i++) { + const chars = graphemeSplit(textLines[i]); + //loop through each character of the current line - for (let c = 0; c < textLines[i].length; c++) { + for (let c = 0; c < chars.length; c++) { charIndex++; //check if there's a style collection that includes the current character if (