fix: test for ios range line break error (#2635)

This commit is contained in:
Niklas von Hertzen 2021-08-09 17:14:40 +08:00 committed by GitHub
parent 7a06d0c2c2
commit f43f942fcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 4 deletions

View File

@ -1,3 +1,5 @@
import {fromCodePoint, toCodePoints} from 'css-line-break';
const testRangeBounds = (document: Document) => { const testRangeBounds = (document: Document) => {
const TEST_HEIGHT = 123; const TEST_HEIGHT = 123;
@ -22,6 +24,45 @@ const testRangeBounds = (document: Document) => {
return false; return false;
}; };
const testIOSLineBreak = (document: Document) => {
const testElement = document.createElement('boundtest');
testElement.style.width = '50px';
testElement.style.display = 'block';
testElement.style.fontSize = '12px';
testElement.style.letterSpacing = '0px';
testElement.style.wordSpacing = '0px';
document.body.appendChild(testElement);
const range = document.createRange();
testElement.innerHTML = typeof ''.repeat === 'function' ? '👨'.repeat(10) : '';
const node = testElement.firstChild as Text;
const textList = toCodePoints(node.data).map((i) => fromCodePoint(i));
let offset = 0;
let prev: DOMRect = {} as DOMRect;
// ios 13 does not handle range getBoundingClientRect line changes correctly #2177
const supports = textList.every((text, i) => {
range.setStart(node, offset);
range.setEnd(node, offset + text.length);
const rect = range.getBoundingClientRect();
offset += text.length;
const boundAhead = rect.x > prev.x || rect.y > prev.y;
prev = rect;
if (i === 0) {
return true;
}
return boundAhead;
});
document.body.removeChild(testElement);
return supports;
};
const testCORS = (): boolean => typeof new Image().crossOrigin !== 'undefined'; const testCORS = (): boolean => typeof new Image().crossOrigin !== 'undefined';
const testResponseType = (): boolean => typeof new XMLHttpRequest().responseType === 'string'; const testResponseType = (): boolean => typeof new XMLHttpRequest().responseType === 'string';
@ -132,6 +173,12 @@ export const FEATURES = {
Object.defineProperty(FEATURES, 'SUPPORT_RANGE_BOUNDS', {value}); Object.defineProperty(FEATURES, 'SUPPORT_RANGE_BOUNDS', {value});
return value; return value;
}, },
get SUPPORT_WORD_BREAKING(): boolean {
'use strict';
const value = FEATURES.SUPPORT_RANGE_BOUNDS && testIOSLineBreak(document);
Object.defineProperty(FEATURES, 'SUPPORT_WORD_BREAKING', {value});
return value;
},
get SUPPORT_SVG_DRAWING(): boolean { get SUPPORT_SVG_DRAWING(): boolean {
'use strict'; 'use strict';
const value = testSVG(document); const value = testSVG(document);

View File

@ -15,6 +15,20 @@ export class Bounds {
clientRect.height clientRect.height
); );
} }
static fromDOMRectList(context: Context, domRectList: DOMRectList): Bounds {
const domRect = domRectList[0];
return domRect
? new Bounds(
domRect.x + context.windowBounds.left,
domRect.y + context.windowBounds.top,
domRect.width,
domRect.height
)
: Bounds.EMPTY;
}
static EMPTY = new Bounds(0, 0, 0, 0);
} }
export const parseBounds = (context: Context, node: Element): Bounds => { export const parseBounds = (context: Context, node: Element): Bounds => {

View File

@ -27,7 +27,16 @@ export const parseTextBounds = (
textList.forEach((text) => { textList.forEach((text) => {
if (styles.textDecorationLine.length || text.trim().length > 0) { if (styles.textDecorationLine.length || text.trim().length > 0) {
if (FEATURES.SUPPORT_RANGE_BOUNDS) { if (FEATURES.SUPPORT_RANGE_BOUNDS) {
if (!FEATURES.SUPPORT_WORD_BREAKING) {
textBounds.push(
new TextBounds(
text,
Bounds.fromDOMRectList(context, createRange(node, offset, text.length).getClientRects())
)
);
} else {
textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length))); textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length)));
}
} else { } else {
const replacementNode = node.splitText(text.length); const replacementNode = node.splitText(text.length);
textBounds.push(new TextBounds(text, getWrapperBounds(context, node))); textBounds.push(new TextBounds(text, getWrapperBounds(context, node)));
@ -58,10 +67,10 @@ const getWrapperBounds = (context: Context, node: Text): Bounds => {
} }
} }
return new Bounds(0, 0, 0, 0); return Bounds.EMPTY;
}; };
const getRangeBounds = (context: Context, node: Text, offset: number, length: number): Bounds => { const createRange = (node: Text, offset: number, length: number): Range => {
const ownerDocument = node.ownerDocument; const ownerDocument = node.ownerDocument;
if (!ownerDocument) { if (!ownerDocument) {
throw new Error('Node has no owner document'); throw new Error('Node has no owner document');
@ -69,7 +78,11 @@ const getRangeBounds = (context: Context, node: Text, offset: number, length: nu
const range = ownerDocument.createRange(); const range = ownerDocument.createRange();
range.setStart(node, offset); range.setStart(node, offset);
range.setEnd(node, offset + length); range.setEnd(node, offset + length);
return Bounds.fromClientRect(context, range.getBoundingClientRect()); return range;
};
const getRangeBounds = (context: Context, node: Text, offset: number, length: number): Bounds => {
return Bounds.fromClientRect(context, createRange(node, offset, length).getBoundingClientRect());
}; };
const breakText = (value: string, styles: CSSParsedDeclaration): string[] => { const breakText = (value: string, styles: CSSParsedDeclaration): string[] => {