fixes in emoji rendering

This commit is contained in:
Anton Lavrevov 2024-12-23 12:29:30 -05:00
parent f2ed14fa08
commit adba0073e3
2 changed files with 61 additions and 18 deletions

View File

@ -18,10 +18,15 @@ export function stringToArray(string: string): string[] {
return [...string].reduce((acc, char, index, array) => { return [...string].reduce((acc, char, index, array) => {
// Handle emoji with skin tone modifiers and ZWJ sequences // Handle emoji with skin tone modifiers and ZWJ sequences
if (/\p{Emoji}/u.test(char)) { if (/\p{Emoji}/u.test(char)) {
if (acc.length > 0 && /\p{Emoji}/u.test(acc[acc.length - 1])) { // Check if next character is a modifier or ZWJ sequence
// Combine with previous emoji if it's part of a sequence const nextChar = array[index + 1];
acc[acc.length - 1] += char; if (nextChar && /\p{Emoji_Modifier}|\u200D/u.test(nextChar)) {
// If we have a modifier, combine with current emoji
acc.push(char + nextChar);
// Skip the next character since we've used it
array[index + 1] = '';
} else { } else {
// No modifier - treat as separate emoji
acc.push(char); acc.push(char);
} }
} }
@ -36,7 +41,8 @@ export function stringToArray(string: string): string[] {
acc[acc.length - 1] += char; acc[acc.length - 1] += char;
} }
// Handle other characters // Handle other characters
else { else if (char) {
// Only push if not an empty string (skipped modifier)
acc.push(char); acc.push(char);
} }
return acc; return acc;
@ -520,13 +526,16 @@ export class Text extends Shape<TextConfig> {
* that would fit in the specified width * that would fit in the specified width
*/ */
let low = 0, let low = 0,
high = line.length, high = stringToArray(line).length, // Convert to array for proper emoji handling
match = '', match = '',
matchWidth = 0; matchWidth = 0;
while (low < high) { while (low < high) {
const mid = (low + high) >>> 1, const mid = (low + high) >>> 1,
substr = line.slice(0, mid + 1), // Convert array indices to string
lineArray = stringToArray(line),
substr = lineArray.slice(0, mid + 1).join(''),
substrWidth = this._getTextWidth(substr) + additionalWidth; substrWidth = this._getTextWidth(substr) + additionalWidth;
if (substrWidth <= maxWidth) { if (substrWidth <= maxWidth) {
low = mid + 1; low = mid + 1;
match = substr; match = substr;
@ -544,20 +553,24 @@ export class Text extends Shape<TextConfig> {
// a fitting substring was found // a fitting substring was found
if (wrapAtWord) { if (wrapAtWord) {
// try to find a space or dash where wrapping could be done // try to find a space or dash where wrapping could be done
var wrapIndex; const lineArray = stringToArray(line);
const nextChar = line[match.length]; const matchArray = stringToArray(match);
const nextChar = lineArray[matchArray.length];
const nextIsSpaceOrDash = nextChar === SPACE || nextChar === DASH; const nextIsSpaceOrDash = nextChar === SPACE || nextChar === DASH;
let wrapIndex;
if (nextIsSpaceOrDash && matchWidth <= maxWidth) { if (nextIsSpaceOrDash && matchWidth <= maxWidth) {
wrapIndex = match.length; wrapIndex = matchArray.length;
} else { } else {
wrapIndex = // Find last space or dash in the array
Math.max(match.lastIndexOf(SPACE), match.lastIndexOf(DASH)) + const lastSpaceIndex = matchArray.lastIndexOf(SPACE);
1; const lastDashIndex = matchArray.lastIndexOf(DASH);
wrapIndex = Math.max(lastSpaceIndex, lastDashIndex) + 1;
} }
if (wrapIndex > 0) { if (wrapIndex > 0) {
// re-cut the substring found at the space/dash position
low = wrapIndex; low = wrapIndex;
match = match.slice(0, low); match = lineArray.slice(0, low).join('');
matchWidth = this._getTextWidth(match); matchWidth = this._getTextWidth(match);
} }
} }
@ -578,13 +591,14 @@ export class Text extends Shape<TextConfig> {
*/ */
break; break;
} }
line = line.slice(low);
line = line.trimLeft(); // Convert remaining text using array operations
const lineArray = stringToArray(line);
line = lineArray.slice(low).join('').trimLeft();
if (line.length > 0) { if (line.length > 0) {
// Check if the remaining text would fit on one line
lineWidth = this._getTextWidth(line); lineWidth = this._getTextWidth(line);
if (lineWidth <= maxWidth) { if (lineWidth <= maxWidth) {
// if it does, add the line and break out of the loop
this._addTextLine(line); this._addTextLine(line);
currentHeightPx += lineHeightPx; currentHeightPx += lineHeightPx;
textWidth = Math.max(textWidth, lineWidth); textWidth = Math.max(textWidth, lineWidth);

View File

@ -161,6 +161,35 @@ describe('Text', function () {
context.fillStyle = 'darkgrey'; context.fillStyle = 'darkgrey';
context.fillText('😬👧🏿', 10, 10 + 25); context.fillText('😬👧🏿', 10, 10 + 25);
compareLayerAndCanvas(layer, canvas, 254, 100);
});
it('check emoji rendering', function () {
var stage = addStage();
var layer = new Konva.Layer();
var text = new Konva.Text({
text: '😁😁😁',
x: 10,
y: 10,
fontSize: 20,
draggable: true,
width: 65,
fill: 'black',
scaleY: 0.9999999999999973,
});
layer.add(text);
stage.add(layer);
var canvas = createCanvas();
var context = canvas.getContext('2d');
context.textBaseline = 'middle';
context.font = 'normal normal 20px Arial';
context.fillStyle = 'black';
context.fillText('😁😁', 10, 10 + 10);
context.fillText('😁', 10, 10 + 30);
compareLayerAndCanvas(layer, canvas, 254); compareLayerAndCanvas(layer, canvas, 254);
}); });