2d.text.measure.selection-rects.tentative.html (85753B)
1 <!DOCTYPE html> 2 <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> 3 <meta charset="UTF-8"> 4 <title>OffscreenCanvas test: 2d.text.measure.selection-rects.tentative</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="/html/canvas/resources/canvas-tests.js"></script> 8 9 <h1>2d.text.measure.selection-rects.tentative</h1> 10 11 <script> 12 13 test(t => { 14 const canvas = new OffscreenCanvas(100, 50); 15 const ctx = canvas.getContext('2d'); 16 17 function placeAndSelectTextInDOM(text, from, to) { 18 const el = document.createElement("div"); 19 el.innerHTML = text; 20 el.style.font = '50px sans-serif'; 21 el.style.direction = 'ltr'; 22 el.style.textAlign = 'left'; 23 el.style.letterSpacing = '0px'; 24 document.body.appendChild(el); 25 26 let range = document.createRange(); 27 28 range.setStart(el.childNodes[0], 0); 29 range.setEnd(el.childNodes[0], text.length); 30 const parent = range.getClientRects()[0]; 31 let width = 0; 32 for (const rect of range.getClientRects()) { 33 width += rect.width; 34 } 35 36 range.setStart(el.childNodes[0], from); 37 range.setEnd(el.childNodes[0], to); 38 39 let sel = window.getSelection(); 40 sel.removeAllRanges(); 41 sel.addRange(range); 42 43 let sel_rects = sel.getRangeAt(0).getClientRects(); 44 45 document.body.removeChild(el); 46 47 // Offset to the alignment point determined by textAlign. 48 let text_align_dx; 49 switch (el.style.textAlign) { 50 case 'right': 51 text_align_dx = width; 52 break; 53 case 'center': 54 text_align_dx = width / 2; 55 break; 56 default: 57 text_align_dx = 0; 58 } 59 60 for(let i = 0 ; i < sel_rects.length ; ++i) { 61 sel_rects[i].x -= parent.x + text_align_dx; 62 sel_rects[i].y -= parent.y; 63 } 64 65 return sel_rects; 66 } 67 68 function checkRectListsMatch(list_a, list_b) { 69 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 70 for(let i = 0 ; i < list_a.length ; ++i) { 71 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 72 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 73 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 74 // Y-position not tested here as getting the baseline for text in the 75 // DOM is not straightforward. 76 } 77 } 78 79 ctx.font = '50px sans-serif'; 80 ctx.direction = 'ltr'; 81 ctx.textAlign = 'left'; 82 ctx.letterSpacing = '0px'; 83 84 const kTexts = [ 85 'UNAVAILABLE', 86 '🏁🎶🏁', 87 ')(あ)(', 88 '-abcd_', 89 'איפה הספרייה?', 90 'bidiמתמטיקה' 91 ] 92 93 for (text of kTexts) { 94 const tm = ctx.measureText(text); 95 // First character. 96 checkRectListsMatch( 97 tm.getSelectionRects(0, 1), 98 placeAndSelectTextInDOM(text, 0, 1) 99 ); 100 // Last character. 101 checkRectListsMatch( 102 tm.getSelectionRects(text.length - 1, text.length), 103 placeAndSelectTextInDOM(text, text.length - 1, text.length) 104 ); 105 // Whole string. 106 checkRectListsMatch( 107 tm.getSelectionRects(0, text.length), 108 placeAndSelectTextInDOM(text, 0, text.length) 109 ); 110 // Intermediate string. 111 checkRectListsMatch( 112 tm.getSelectionRects(1, text.length - 1), 113 placeAndSelectTextInDOM(text, 1, text.length - 1) 114 ); 115 // Invalid start > end string. Creates 0 width rectangle. 116 checkRectListsMatch( 117 tm.getSelectionRects(3, 2), 118 placeAndSelectTextInDOM(text, 3, 2) 119 ); 120 checkRectListsMatch( 121 tm.getSelectionRects(1, 0), 122 placeAndSelectTextInDOM(text, 1, 0) 123 ); 124 } 125 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override."); 126 127 test(t => { 128 const canvas = new OffscreenCanvas(100, 50); 129 const ctx = canvas.getContext('2d'); 130 131 function placeAndSelectTextInDOM(text, from, to) { 132 const el = document.createElement("div"); 133 el.innerHTML = text; 134 el.style.font = '50px sans-serif'; 135 el.style.direction = 'rtl'; 136 el.style.textAlign = 'left'; 137 el.style.letterSpacing = '0px'; 138 document.body.appendChild(el); 139 140 let range = document.createRange(); 141 142 range.setStart(el.childNodes[0], 0); 143 range.setEnd(el.childNodes[0], text.length); 144 const parent = range.getClientRects()[0]; 145 let width = 0; 146 for (const rect of range.getClientRects()) { 147 width += rect.width; 148 } 149 150 range.setStart(el.childNodes[0], from); 151 range.setEnd(el.childNodes[0], to); 152 153 let sel = window.getSelection(); 154 sel.removeAllRanges(); 155 sel.addRange(range); 156 157 let sel_rects = sel.getRangeAt(0).getClientRects(); 158 159 document.body.removeChild(el); 160 161 // Offset to the alignment point determined by textAlign. 162 let text_align_dx; 163 switch (el.style.textAlign) { 164 case 'right': 165 text_align_dx = width; 166 break; 167 case 'center': 168 text_align_dx = width / 2; 169 break; 170 default: 171 text_align_dx = 0; 172 } 173 174 for(let i = 0 ; i < sel_rects.length ; ++i) { 175 sel_rects[i].x -= parent.x + text_align_dx; 176 sel_rects[i].y -= parent.y; 177 } 178 179 return sel_rects; 180 } 181 182 function checkRectListsMatch(list_a, list_b) { 183 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 184 for(let i = 0 ; i < list_a.length ; ++i) { 185 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 186 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 187 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 188 // Y-position not tested here as getting the baseline for text in the 189 // DOM is not straightforward. 190 } 191 } 192 193 ctx.font = '50px sans-serif'; 194 ctx.direction = 'rtl'; 195 ctx.textAlign = 'left'; 196 ctx.letterSpacing = '0px'; 197 198 const kTexts = [ 199 'UNAVAILABLE', 200 '🏁🎶🏁', 201 ')(あ)(', 202 '-abcd_', 203 'איפה הספרייה?', 204 'bidiמתמטיקה' 205 ] 206 207 for (text of kTexts) { 208 const tm = ctx.measureText(text); 209 // First character. 210 checkRectListsMatch( 211 tm.getSelectionRects(0, 1), 212 placeAndSelectTextInDOM(text, 0, 1) 213 ); 214 // Last character. 215 checkRectListsMatch( 216 tm.getSelectionRects(text.length - 1, text.length), 217 placeAndSelectTextInDOM(text, text.length - 1, text.length) 218 ); 219 // Whole string. 220 checkRectListsMatch( 221 tm.getSelectionRects(0, text.length), 222 placeAndSelectTextInDOM(text, 0, text.length) 223 ); 224 // Intermediate string. 225 checkRectListsMatch( 226 tm.getSelectionRects(1, text.length - 1), 227 placeAndSelectTextInDOM(text, 1, text.length - 1) 228 ); 229 // Invalid start > end string. Creates 0 width rectangle. 230 checkRectListsMatch( 231 tm.getSelectionRects(3, 2), 232 placeAndSelectTextInDOM(text, 3, 2) 233 ); 234 checkRectListsMatch( 235 tm.getSelectionRects(1, 0), 236 placeAndSelectTextInDOM(text, 1, 0) 237 ); 238 } 239 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override."); 240 241 test(t => { 242 const canvas = new OffscreenCanvas(100, 50); 243 const ctx = canvas.getContext('2d'); 244 245 function placeAndSelectTextInDOM(text, from, to) { 246 const el = document.createElement("div"); 247 el.innerHTML = text; 248 el.style.font = '50px sans-serif'; 249 el.style.direction = 'ltr'; 250 el.style.textAlign = 'center'; 251 el.style.letterSpacing = '0px'; 252 document.body.appendChild(el); 253 254 let range = document.createRange(); 255 256 range.setStart(el.childNodes[0], 0); 257 range.setEnd(el.childNodes[0], text.length); 258 const parent = range.getClientRects()[0]; 259 let width = 0; 260 for (const rect of range.getClientRects()) { 261 width += rect.width; 262 } 263 264 range.setStart(el.childNodes[0], from); 265 range.setEnd(el.childNodes[0], to); 266 267 let sel = window.getSelection(); 268 sel.removeAllRanges(); 269 sel.addRange(range); 270 271 let sel_rects = sel.getRangeAt(0).getClientRects(); 272 273 document.body.removeChild(el); 274 275 // Offset to the alignment point determined by textAlign. 276 let text_align_dx; 277 switch (el.style.textAlign) { 278 case 'right': 279 text_align_dx = width; 280 break; 281 case 'center': 282 text_align_dx = width / 2; 283 break; 284 default: 285 text_align_dx = 0; 286 } 287 288 for(let i = 0 ; i < sel_rects.length ; ++i) { 289 sel_rects[i].x -= parent.x + text_align_dx; 290 sel_rects[i].y -= parent.y; 291 } 292 293 return sel_rects; 294 } 295 296 function checkRectListsMatch(list_a, list_b) { 297 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 298 for(let i = 0 ; i < list_a.length ; ++i) { 299 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 300 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 301 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 302 // Y-position not tested here as getting the baseline for text in the 303 // DOM is not straightforward. 304 } 305 } 306 307 ctx.font = '50px sans-serif'; 308 ctx.direction = 'ltr'; 309 ctx.textAlign = 'center'; 310 ctx.letterSpacing = '0px'; 311 312 const kTexts = [ 313 'UNAVAILABLE', 314 '🏁🎶🏁', 315 ')(あ)(', 316 '-abcd_', 317 'איפה הספרייה?', 318 'bidiמתמטיקה' 319 ] 320 321 for (text of kTexts) { 322 const tm = ctx.measureText(text); 323 // First character. 324 checkRectListsMatch( 325 tm.getSelectionRects(0, 1), 326 placeAndSelectTextInDOM(text, 0, 1) 327 ); 328 // Last character. 329 checkRectListsMatch( 330 tm.getSelectionRects(text.length - 1, text.length), 331 placeAndSelectTextInDOM(text, text.length - 1, text.length) 332 ); 333 // Whole string. 334 checkRectListsMatch( 335 tm.getSelectionRects(0, text.length), 336 placeAndSelectTextInDOM(text, 0, text.length) 337 ); 338 // Intermediate string. 339 checkRectListsMatch( 340 tm.getSelectionRects(1, text.length - 1), 341 placeAndSelectTextInDOM(text, 1, text.length - 1) 342 ); 343 // Invalid start > end string. Creates 0 width rectangle. 344 checkRectListsMatch( 345 tm.getSelectionRects(3, 2), 346 placeAndSelectTextInDOM(text, 3, 2) 347 ); 348 checkRectListsMatch( 349 tm.getSelectionRects(1, 0), 350 placeAndSelectTextInDOM(text, 1, 0) 351 ); 352 } 353 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override."); 354 355 test(t => { 356 const canvas = new OffscreenCanvas(100, 50); 357 const ctx = canvas.getContext('2d'); 358 359 function placeAndSelectTextInDOM(text, from, to) { 360 const el = document.createElement("div"); 361 el.innerHTML = text; 362 el.style.font = '50px sans-serif'; 363 el.style.direction = 'rtl'; 364 el.style.textAlign = 'center'; 365 el.style.letterSpacing = '0px'; 366 document.body.appendChild(el); 367 368 let range = document.createRange(); 369 370 range.setStart(el.childNodes[0], 0); 371 range.setEnd(el.childNodes[0], text.length); 372 const parent = range.getClientRects()[0]; 373 let width = 0; 374 for (const rect of range.getClientRects()) { 375 width += rect.width; 376 } 377 378 range.setStart(el.childNodes[0], from); 379 range.setEnd(el.childNodes[0], to); 380 381 let sel = window.getSelection(); 382 sel.removeAllRanges(); 383 sel.addRange(range); 384 385 let sel_rects = sel.getRangeAt(0).getClientRects(); 386 387 document.body.removeChild(el); 388 389 // Offset to the alignment point determined by textAlign. 390 let text_align_dx; 391 switch (el.style.textAlign) { 392 case 'right': 393 text_align_dx = width; 394 break; 395 case 'center': 396 text_align_dx = width / 2; 397 break; 398 default: 399 text_align_dx = 0; 400 } 401 402 for(let i = 0 ; i < sel_rects.length ; ++i) { 403 sel_rects[i].x -= parent.x + text_align_dx; 404 sel_rects[i].y -= parent.y; 405 } 406 407 return sel_rects; 408 } 409 410 function checkRectListsMatch(list_a, list_b) { 411 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 412 for(let i = 0 ; i < list_a.length ; ++i) { 413 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 414 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 415 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 416 // Y-position not tested here as getting the baseline for text in the 417 // DOM is not straightforward. 418 } 419 } 420 421 ctx.font = '50px sans-serif'; 422 ctx.direction = 'rtl'; 423 ctx.textAlign = 'center'; 424 ctx.letterSpacing = '0px'; 425 426 const kTexts = [ 427 'UNAVAILABLE', 428 '🏁🎶🏁', 429 ')(あ)(', 430 '-abcd_', 431 'איפה הספרייה?', 432 'bidiמתמטיקה' 433 ] 434 435 for (text of kTexts) { 436 const tm = ctx.measureText(text); 437 // First character. 438 checkRectListsMatch( 439 tm.getSelectionRects(0, 1), 440 placeAndSelectTextInDOM(text, 0, 1) 441 ); 442 // Last character. 443 checkRectListsMatch( 444 tm.getSelectionRects(text.length - 1, text.length), 445 placeAndSelectTextInDOM(text, text.length - 1, text.length) 446 ); 447 // Whole string. 448 checkRectListsMatch( 449 tm.getSelectionRects(0, text.length), 450 placeAndSelectTextInDOM(text, 0, text.length) 451 ); 452 // Intermediate string. 453 checkRectListsMatch( 454 tm.getSelectionRects(1, text.length - 1), 455 placeAndSelectTextInDOM(text, 1, text.length - 1) 456 ); 457 // Invalid start > end string. Creates 0 width rectangle. 458 checkRectListsMatch( 459 tm.getSelectionRects(3, 2), 460 placeAndSelectTextInDOM(text, 3, 2) 461 ); 462 checkRectListsMatch( 463 tm.getSelectionRects(1, 0), 464 placeAndSelectTextInDOM(text, 1, 0) 465 ); 466 } 467 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override."); 468 469 test(t => { 470 const canvas = new OffscreenCanvas(100, 50); 471 const ctx = canvas.getContext('2d'); 472 473 function placeAndSelectTextInDOM(text, from, to) { 474 const el = document.createElement("div"); 475 el.innerHTML = text; 476 el.style.font = '50px sans-serif'; 477 el.style.direction = 'ltr'; 478 el.style.textAlign = 'right'; 479 el.style.letterSpacing = '0px'; 480 document.body.appendChild(el); 481 482 let range = document.createRange(); 483 484 range.setStart(el.childNodes[0], 0); 485 range.setEnd(el.childNodes[0], text.length); 486 const parent = range.getClientRects()[0]; 487 let width = 0; 488 for (const rect of range.getClientRects()) { 489 width += rect.width; 490 } 491 492 range.setStart(el.childNodes[0], from); 493 range.setEnd(el.childNodes[0], to); 494 495 let sel = window.getSelection(); 496 sel.removeAllRanges(); 497 sel.addRange(range); 498 499 let sel_rects = sel.getRangeAt(0).getClientRects(); 500 501 document.body.removeChild(el); 502 503 // Offset to the alignment point determined by textAlign. 504 let text_align_dx; 505 switch (el.style.textAlign) { 506 case 'right': 507 text_align_dx = width; 508 break; 509 case 'center': 510 text_align_dx = width / 2; 511 break; 512 default: 513 text_align_dx = 0; 514 } 515 516 for(let i = 0 ; i < sel_rects.length ; ++i) { 517 sel_rects[i].x -= parent.x + text_align_dx; 518 sel_rects[i].y -= parent.y; 519 } 520 521 return sel_rects; 522 } 523 524 function checkRectListsMatch(list_a, list_b) { 525 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 526 for(let i = 0 ; i < list_a.length ; ++i) { 527 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 528 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 529 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 530 // Y-position not tested here as getting the baseline for text in the 531 // DOM is not straightforward. 532 } 533 } 534 535 ctx.font = '50px sans-serif'; 536 ctx.direction = 'ltr'; 537 ctx.textAlign = 'right'; 538 ctx.letterSpacing = '0px'; 539 540 const kTexts = [ 541 'UNAVAILABLE', 542 '🏁🎶🏁', 543 ')(あ)(', 544 '-abcd_', 545 'איפה הספרייה?', 546 'bidiמתמטיקה' 547 ] 548 549 for (text of kTexts) { 550 const tm = ctx.measureText(text); 551 // First character. 552 checkRectListsMatch( 553 tm.getSelectionRects(0, 1), 554 placeAndSelectTextInDOM(text, 0, 1) 555 ); 556 // Last character. 557 checkRectListsMatch( 558 tm.getSelectionRects(text.length - 1, text.length), 559 placeAndSelectTextInDOM(text, text.length - 1, text.length) 560 ); 561 // Whole string. 562 checkRectListsMatch( 563 tm.getSelectionRects(0, text.length), 564 placeAndSelectTextInDOM(text, 0, text.length) 565 ); 566 // Intermediate string. 567 checkRectListsMatch( 568 tm.getSelectionRects(1, text.length - 1), 569 placeAndSelectTextInDOM(text, 1, text.length - 1) 570 ); 571 // Invalid start > end string. Creates 0 width rectangle. 572 checkRectListsMatch( 573 tm.getSelectionRects(3, 2), 574 placeAndSelectTextInDOM(text, 3, 2) 575 ); 576 checkRectListsMatch( 577 tm.getSelectionRects(1, 0), 578 placeAndSelectTextInDOM(text, 1, 0) 579 ); 580 } 581 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override."); 582 583 test(t => { 584 const canvas = new OffscreenCanvas(100, 50); 585 const ctx = canvas.getContext('2d'); 586 587 function placeAndSelectTextInDOM(text, from, to) { 588 const el = document.createElement("div"); 589 el.innerHTML = text; 590 el.style.font = '50px sans-serif'; 591 el.style.direction = 'rtl'; 592 el.style.textAlign = 'right'; 593 el.style.letterSpacing = '0px'; 594 document.body.appendChild(el); 595 596 let range = document.createRange(); 597 598 range.setStart(el.childNodes[0], 0); 599 range.setEnd(el.childNodes[0], text.length); 600 const parent = range.getClientRects()[0]; 601 let width = 0; 602 for (const rect of range.getClientRects()) { 603 width += rect.width; 604 } 605 606 range.setStart(el.childNodes[0], from); 607 range.setEnd(el.childNodes[0], to); 608 609 let sel = window.getSelection(); 610 sel.removeAllRanges(); 611 sel.addRange(range); 612 613 let sel_rects = sel.getRangeAt(0).getClientRects(); 614 615 document.body.removeChild(el); 616 617 // Offset to the alignment point determined by textAlign. 618 let text_align_dx; 619 switch (el.style.textAlign) { 620 case 'right': 621 text_align_dx = width; 622 break; 623 case 'center': 624 text_align_dx = width / 2; 625 break; 626 default: 627 text_align_dx = 0; 628 } 629 630 for(let i = 0 ; i < sel_rects.length ; ++i) { 631 sel_rects[i].x -= parent.x + text_align_dx; 632 sel_rects[i].y -= parent.y; 633 } 634 635 return sel_rects; 636 } 637 638 function checkRectListsMatch(list_a, list_b) { 639 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 640 for(let i = 0 ; i < list_a.length ; ++i) { 641 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 642 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 643 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 644 // Y-position not tested here as getting the baseline for text in the 645 // DOM is not straightforward. 646 } 647 } 648 649 ctx.font = '50px sans-serif'; 650 ctx.direction = 'rtl'; 651 ctx.textAlign = 'right'; 652 ctx.letterSpacing = '0px'; 653 654 const kTexts = [ 655 'UNAVAILABLE', 656 '🏁🎶🏁', 657 ')(あ)(', 658 '-abcd_', 659 'איפה הספרייה?', 660 'bidiמתמטיקה' 661 ] 662 663 for (text of kTexts) { 664 const tm = ctx.measureText(text); 665 // First character. 666 checkRectListsMatch( 667 tm.getSelectionRects(0, 1), 668 placeAndSelectTextInDOM(text, 0, 1) 669 ); 670 // Last character. 671 checkRectListsMatch( 672 tm.getSelectionRects(text.length - 1, text.length), 673 placeAndSelectTextInDOM(text, text.length - 1, text.length) 674 ); 675 // Whole string. 676 checkRectListsMatch( 677 tm.getSelectionRects(0, text.length), 678 placeAndSelectTextInDOM(text, 0, text.length) 679 ); 680 // Intermediate string. 681 checkRectListsMatch( 682 tm.getSelectionRects(1, text.length - 1), 683 placeAndSelectTextInDOM(text, 1, text.length - 1) 684 ); 685 // Invalid start > end string. Creates 0 width rectangle. 686 checkRectListsMatch( 687 tm.getSelectionRects(3, 2), 688 placeAndSelectTextInDOM(text, 3, 2) 689 ); 690 checkRectListsMatch( 691 tm.getSelectionRects(1, 0), 692 placeAndSelectTextInDOM(text, 1, 0) 693 ); 694 } 695 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override."); 696 697 test(t => { 698 const canvas = new OffscreenCanvas(100, 50); 699 const ctx = canvas.getContext('2d'); 700 701 function placeAndSelectTextInDOM(text, from, to) { 702 const el = document.createElement("div"); 703 el.innerHTML = text; 704 el.style.font = '50px sans-serif'; 705 el.style.direction = 'ltr'; 706 el.style.textAlign = 'left'; 707 el.style.letterSpacing = '10px'; 708 document.body.appendChild(el); 709 710 let range = document.createRange(); 711 712 range.setStart(el.childNodes[0], 0); 713 range.setEnd(el.childNodes[0], text.length); 714 const parent = range.getClientRects()[0]; 715 let width = 0; 716 for (const rect of range.getClientRects()) { 717 width += rect.width; 718 } 719 720 range.setStart(el.childNodes[0], from); 721 range.setEnd(el.childNodes[0], to); 722 723 let sel = window.getSelection(); 724 sel.removeAllRanges(); 725 sel.addRange(range); 726 727 let sel_rects = sel.getRangeAt(0).getClientRects(); 728 729 document.body.removeChild(el); 730 731 // Offset to the alignment point determined by textAlign. 732 let text_align_dx; 733 switch (el.style.textAlign) { 734 case 'right': 735 text_align_dx = width; 736 break; 737 case 'center': 738 text_align_dx = width / 2; 739 break; 740 default: 741 text_align_dx = 0; 742 } 743 744 for(let i = 0 ; i < sel_rects.length ; ++i) { 745 sel_rects[i].x -= parent.x + text_align_dx; 746 sel_rects[i].y -= parent.y; 747 } 748 749 return sel_rects; 750 } 751 752 function checkRectListsMatch(list_a, list_b) { 753 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 754 for(let i = 0 ; i < list_a.length ; ++i) { 755 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 756 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 757 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 758 // Y-position not tested here as getting the baseline for text in the 759 // DOM is not straightforward. 760 } 761 } 762 763 ctx.font = '50px sans-serif'; 764 ctx.direction = 'ltr'; 765 ctx.textAlign = 'left'; 766 ctx.letterSpacing = '10px'; 767 768 const kTexts = [ 769 'UNAVAILABLE', 770 '🏁🎶🏁', 771 ')(あ)(', 772 '-abcd_', 773 'איפה הספרייה?', 774 'bidiמתמטיקה' 775 ] 776 777 for (text of kTexts) { 778 const tm = ctx.measureText(text); 779 // First character. 780 checkRectListsMatch( 781 tm.getSelectionRects(0, 1), 782 placeAndSelectTextInDOM(text, 0, 1) 783 ); 784 // Last character. 785 checkRectListsMatch( 786 tm.getSelectionRects(text.length - 1, text.length), 787 placeAndSelectTextInDOM(text, text.length - 1, text.length) 788 ); 789 // Whole string. 790 checkRectListsMatch( 791 tm.getSelectionRects(0, text.length), 792 placeAndSelectTextInDOM(text, 0, text.length) 793 ); 794 // Intermediate string. 795 checkRectListsMatch( 796 tm.getSelectionRects(1, text.length - 1), 797 placeAndSelectTextInDOM(text, 1, text.length - 1) 798 ); 799 // Invalid start > end string. Creates 0 width rectangle. 800 checkRectListsMatch( 801 tm.getSelectionRects(3, 2), 802 placeAndSelectTextInDOM(text, 3, 2) 803 ); 804 checkRectListsMatch( 805 tm.getSelectionRects(1, 0), 806 placeAndSelectTextInDOM(text, 1, 0) 807 ); 808 } 809 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override."); 810 811 test(t => { 812 const canvas = new OffscreenCanvas(100, 50); 813 const ctx = canvas.getContext('2d'); 814 815 function placeAndSelectTextInDOM(text, from, to) { 816 const el = document.createElement("div"); 817 el.innerHTML = text; 818 el.style.font = '50px sans-serif'; 819 el.style.direction = 'rtl'; 820 el.style.textAlign = 'left'; 821 el.style.letterSpacing = '10px'; 822 document.body.appendChild(el); 823 824 let range = document.createRange(); 825 826 range.setStart(el.childNodes[0], 0); 827 range.setEnd(el.childNodes[0], text.length); 828 const parent = range.getClientRects()[0]; 829 let width = 0; 830 for (const rect of range.getClientRects()) { 831 width += rect.width; 832 } 833 834 range.setStart(el.childNodes[0], from); 835 range.setEnd(el.childNodes[0], to); 836 837 let sel = window.getSelection(); 838 sel.removeAllRanges(); 839 sel.addRange(range); 840 841 let sel_rects = sel.getRangeAt(0).getClientRects(); 842 843 document.body.removeChild(el); 844 845 // Offset to the alignment point determined by textAlign. 846 let text_align_dx; 847 switch (el.style.textAlign) { 848 case 'right': 849 text_align_dx = width; 850 break; 851 case 'center': 852 text_align_dx = width / 2; 853 break; 854 default: 855 text_align_dx = 0; 856 } 857 858 for(let i = 0 ; i < sel_rects.length ; ++i) { 859 sel_rects[i].x -= parent.x + text_align_dx; 860 sel_rects[i].y -= parent.y; 861 } 862 863 return sel_rects; 864 } 865 866 function checkRectListsMatch(list_a, list_b) { 867 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 868 for(let i = 0 ; i < list_a.length ; ++i) { 869 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 870 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 871 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 872 // Y-position not tested here as getting the baseline for text in the 873 // DOM is not straightforward. 874 } 875 } 876 877 ctx.font = '50px sans-serif'; 878 ctx.direction = 'rtl'; 879 ctx.textAlign = 'left'; 880 ctx.letterSpacing = '10px'; 881 882 const kTexts = [ 883 'UNAVAILABLE', 884 '🏁🎶🏁', 885 ')(あ)(', 886 '-abcd_', 887 'איפה הספרייה?', 888 'bidiמתמטיקה' 889 ] 890 891 for (text of kTexts) { 892 const tm = ctx.measureText(text); 893 // First character. 894 checkRectListsMatch( 895 tm.getSelectionRects(0, 1), 896 placeAndSelectTextInDOM(text, 0, 1) 897 ); 898 // Last character. 899 checkRectListsMatch( 900 tm.getSelectionRects(text.length - 1, text.length), 901 placeAndSelectTextInDOM(text, text.length - 1, text.length) 902 ); 903 // Whole string. 904 checkRectListsMatch( 905 tm.getSelectionRects(0, text.length), 906 placeAndSelectTextInDOM(text, 0, text.length) 907 ); 908 // Intermediate string. 909 checkRectListsMatch( 910 tm.getSelectionRects(1, text.length - 1), 911 placeAndSelectTextInDOM(text, 1, text.length - 1) 912 ); 913 // Invalid start > end string. Creates 0 width rectangle. 914 checkRectListsMatch( 915 tm.getSelectionRects(3, 2), 916 placeAndSelectTextInDOM(text, 3, 2) 917 ); 918 checkRectListsMatch( 919 tm.getSelectionRects(1, 0), 920 placeAndSelectTextInDOM(text, 1, 0) 921 ); 922 } 923 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override."); 924 925 test(t => { 926 const canvas = new OffscreenCanvas(100, 50); 927 const ctx = canvas.getContext('2d'); 928 929 function placeAndSelectTextInDOM(text, from, to) { 930 const el = document.createElement("div"); 931 el.innerHTML = text; 932 el.style.font = '50px sans-serif'; 933 el.style.direction = 'ltr'; 934 el.style.textAlign = 'center'; 935 el.style.letterSpacing = '10px'; 936 document.body.appendChild(el); 937 938 let range = document.createRange(); 939 940 range.setStart(el.childNodes[0], 0); 941 range.setEnd(el.childNodes[0], text.length); 942 const parent = range.getClientRects()[0]; 943 let width = 0; 944 for (const rect of range.getClientRects()) { 945 width += rect.width; 946 } 947 948 range.setStart(el.childNodes[0], from); 949 range.setEnd(el.childNodes[0], to); 950 951 let sel = window.getSelection(); 952 sel.removeAllRanges(); 953 sel.addRange(range); 954 955 let sel_rects = sel.getRangeAt(0).getClientRects(); 956 957 document.body.removeChild(el); 958 959 // Offset to the alignment point determined by textAlign. 960 let text_align_dx; 961 switch (el.style.textAlign) { 962 case 'right': 963 text_align_dx = width; 964 break; 965 case 'center': 966 text_align_dx = width / 2; 967 break; 968 default: 969 text_align_dx = 0; 970 } 971 972 for(let i = 0 ; i < sel_rects.length ; ++i) { 973 sel_rects[i].x -= parent.x + text_align_dx; 974 sel_rects[i].y -= parent.y; 975 } 976 977 return sel_rects; 978 } 979 980 function checkRectListsMatch(list_a, list_b) { 981 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 982 for(let i = 0 ; i < list_a.length ; ++i) { 983 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 984 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 985 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 986 // Y-position not tested here as getting the baseline for text in the 987 // DOM is not straightforward. 988 } 989 } 990 991 ctx.font = '50px sans-serif'; 992 ctx.direction = 'ltr'; 993 ctx.textAlign = 'center'; 994 ctx.letterSpacing = '10px'; 995 996 const kTexts = [ 997 'UNAVAILABLE', 998 '🏁🎶🏁', 999 ')(あ)(', 1000 '-abcd_', 1001 'איפה הספרייה?', 1002 'bidiמתמטיקה' 1003 ] 1004 1005 for (text of kTexts) { 1006 const tm = ctx.measureText(text); 1007 // First character. 1008 checkRectListsMatch( 1009 tm.getSelectionRects(0, 1), 1010 placeAndSelectTextInDOM(text, 0, 1) 1011 ); 1012 // Last character. 1013 checkRectListsMatch( 1014 tm.getSelectionRects(text.length - 1, text.length), 1015 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1016 ); 1017 // Whole string. 1018 checkRectListsMatch( 1019 tm.getSelectionRects(0, text.length), 1020 placeAndSelectTextInDOM(text, 0, text.length) 1021 ); 1022 // Intermediate string. 1023 checkRectListsMatch( 1024 tm.getSelectionRects(1, text.length - 1), 1025 placeAndSelectTextInDOM(text, 1, text.length - 1) 1026 ); 1027 // Invalid start > end string. Creates 0 width rectangle. 1028 checkRectListsMatch( 1029 tm.getSelectionRects(3, 2), 1030 placeAndSelectTextInDOM(text, 3, 2) 1031 ); 1032 checkRectListsMatch( 1033 tm.getSelectionRects(1, 0), 1034 placeAndSelectTextInDOM(text, 1, 0) 1035 ); 1036 } 1037 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override."); 1038 1039 test(t => { 1040 const canvas = new OffscreenCanvas(100, 50); 1041 const ctx = canvas.getContext('2d'); 1042 1043 function placeAndSelectTextInDOM(text, from, to) { 1044 const el = document.createElement("div"); 1045 el.innerHTML = text; 1046 el.style.font = '50px sans-serif'; 1047 el.style.direction = 'rtl'; 1048 el.style.textAlign = 'center'; 1049 el.style.letterSpacing = '10px'; 1050 document.body.appendChild(el); 1051 1052 let range = document.createRange(); 1053 1054 range.setStart(el.childNodes[0], 0); 1055 range.setEnd(el.childNodes[0], text.length); 1056 const parent = range.getClientRects()[0]; 1057 let width = 0; 1058 for (const rect of range.getClientRects()) { 1059 width += rect.width; 1060 } 1061 1062 range.setStart(el.childNodes[0], from); 1063 range.setEnd(el.childNodes[0], to); 1064 1065 let sel = window.getSelection(); 1066 sel.removeAllRanges(); 1067 sel.addRange(range); 1068 1069 let sel_rects = sel.getRangeAt(0).getClientRects(); 1070 1071 document.body.removeChild(el); 1072 1073 // Offset to the alignment point determined by textAlign. 1074 let text_align_dx; 1075 switch (el.style.textAlign) { 1076 case 'right': 1077 text_align_dx = width; 1078 break; 1079 case 'center': 1080 text_align_dx = width / 2; 1081 break; 1082 default: 1083 text_align_dx = 0; 1084 } 1085 1086 for(let i = 0 ; i < sel_rects.length ; ++i) { 1087 sel_rects[i].x -= parent.x + text_align_dx; 1088 sel_rects[i].y -= parent.y; 1089 } 1090 1091 return sel_rects; 1092 } 1093 1094 function checkRectListsMatch(list_a, list_b) { 1095 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1096 for(let i = 0 ; i < list_a.length ; ++i) { 1097 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1098 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1099 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1100 // Y-position not tested here as getting the baseline for text in the 1101 // DOM is not straightforward. 1102 } 1103 } 1104 1105 ctx.font = '50px sans-serif'; 1106 ctx.direction = 'rtl'; 1107 ctx.textAlign = 'center'; 1108 ctx.letterSpacing = '10px'; 1109 1110 const kTexts = [ 1111 'UNAVAILABLE', 1112 '🏁🎶🏁', 1113 ')(あ)(', 1114 '-abcd_', 1115 'איפה הספרייה?', 1116 'bidiמתמטיקה' 1117 ] 1118 1119 for (text of kTexts) { 1120 const tm = ctx.measureText(text); 1121 // First character. 1122 checkRectListsMatch( 1123 tm.getSelectionRects(0, 1), 1124 placeAndSelectTextInDOM(text, 0, 1) 1125 ); 1126 // Last character. 1127 checkRectListsMatch( 1128 tm.getSelectionRects(text.length - 1, text.length), 1129 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1130 ); 1131 // Whole string. 1132 checkRectListsMatch( 1133 tm.getSelectionRects(0, text.length), 1134 placeAndSelectTextInDOM(text, 0, text.length) 1135 ); 1136 // Intermediate string. 1137 checkRectListsMatch( 1138 tm.getSelectionRects(1, text.length - 1), 1139 placeAndSelectTextInDOM(text, 1, text.length - 1) 1140 ); 1141 // Invalid start > end string. Creates 0 width rectangle. 1142 checkRectListsMatch( 1143 tm.getSelectionRects(3, 2), 1144 placeAndSelectTextInDOM(text, 3, 2) 1145 ); 1146 checkRectListsMatch( 1147 tm.getSelectionRects(1, 0), 1148 placeAndSelectTextInDOM(text, 1, 0) 1149 ); 1150 } 1151 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override."); 1152 1153 test(t => { 1154 const canvas = new OffscreenCanvas(100, 50); 1155 const ctx = canvas.getContext('2d'); 1156 1157 function placeAndSelectTextInDOM(text, from, to) { 1158 const el = document.createElement("div"); 1159 el.innerHTML = text; 1160 el.style.font = '50px sans-serif'; 1161 el.style.direction = 'ltr'; 1162 el.style.textAlign = 'right'; 1163 el.style.letterSpacing = '10px'; 1164 document.body.appendChild(el); 1165 1166 let range = document.createRange(); 1167 1168 range.setStart(el.childNodes[0], 0); 1169 range.setEnd(el.childNodes[0], text.length); 1170 const parent = range.getClientRects()[0]; 1171 let width = 0; 1172 for (const rect of range.getClientRects()) { 1173 width += rect.width; 1174 } 1175 1176 range.setStart(el.childNodes[0], from); 1177 range.setEnd(el.childNodes[0], to); 1178 1179 let sel = window.getSelection(); 1180 sel.removeAllRanges(); 1181 sel.addRange(range); 1182 1183 let sel_rects = sel.getRangeAt(0).getClientRects(); 1184 1185 document.body.removeChild(el); 1186 1187 // Offset to the alignment point determined by textAlign. 1188 let text_align_dx; 1189 switch (el.style.textAlign) { 1190 case 'right': 1191 text_align_dx = width; 1192 break; 1193 case 'center': 1194 text_align_dx = width / 2; 1195 break; 1196 default: 1197 text_align_dx = 0; 1198 } 1199 1200 for(let i = 0 ; i < sel_rects.length ; ++i) { 1201 sel_rects[i].x -= parent.x + text_align_dx; 1202 sel_rects[i].y -= parent.y; 1203 } 1204 1205 return sel_rects; 1206 } 1207 1208 function checkRectListsMatch(list_a, list_b) { 1209 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1210 for(let i = 0 ; i < list_a.length ; ++i) { 1211 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1212 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1213 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1214 // Y-position not tested here as getting the baseline for text in the 1215 // DOM is not straightforward. 1216 } 1217 } 1218 1219 ctx.font = '50px sans-serif'; 1220 ctx.direction = 'ltr'; 1221 ctx.textAlign = 'right'; 1222 ctx.letterSpacing = '10px'; 1223 1224 const kTexts = [ 1225 'UNAVAILABLE', 1226 '🏁🎶🏁', 1227 ')(あ)(', 1228 '-abcd_', 1229 'איפה הספרייה?', 1230 'bidiמתמטיקה' 1231 ] 1232 1233 for (text of kTexts) { 1234 const tm = ctx.measureText(text); 1235 // First character. 1236 checkRectListsMatch( 1237 tm.getSelectionRects(0, 1), 1238 placeAndSelectTextInDOM(text, 0, 1) 1239 ); 1240 // Last character. 1241 checkRectListsMatch( 1242 tm.getSelectionRects(text.length - 1, text.length), 1243 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1244 ); 1245 // Whole string. 1246 checkRectListsMatch( 1247 tm.getSelectionRects(0, text.length), 1248 placeAndSelectTextInDOM(text, 0, text.length) 1249 ); 1250 // Intermediate string. 1251 checkRectListsMatch( 1252 tm.getSelectionRects(1, text.length - 1), 1253 placeAndSelectTextInDOM(text, 1, text.length - 1) 1254 ); 1255 // Invalid start > end string. Creates 0 width rectangle. 1256 checkRectListsMatch( 1257 tm.getSelectionRects(3, 2), 1258 placeAndSelectTextInDOM(text, 3, 2) 1259 ); 1260 checkRectListsMatch( 1261 tm.getSelectionRects(1, 0), 1262 placeAndSelectTextInDOM(text, 1, 0) 1263 ); 1264 } 1265 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override."); 1266 1267 test(t => { 1268 const canvas = new OffscreenCanvas(100, 50); 1269 const ctx = canvas.getContext('2d'); 1270 1271 function placeAndSelectTextInDOM(text, from, to) { 1272 const el = document.createElement("div"); 1273 el.innerHTML = text; 1274 el.style.font = '50px sans-serif'; 1275 el.style.direction = 'rtl'; 1276 el.style.textAlign = 'right'; 1277 el.style.letterSpacing = '10px'; 1278 document.body.appendChild(el); 1279 1280 let range = document.createRange(); 1281 1282 range.setStart(el.childNodes[0], 0); 1283 range.setEnd(el.childNodes[0], text.length); 1284 const parent = range.getClientRects()[0]; 1285 let width = 0; 1286 for (const rect of range.getClientRects()) { 1287 width += rect.width; 1288 } 1289 1290 range.setStart(el.childNodes[0], from); 1291 range.setEnd(el.childNodes[0], to); 1292 1293 let sel = window.getSelection(); 1294 sel.removeAllRanges(); 1295 sel.addRange(range); 1296 1297 let sel_rects = sel.getRangeAt(0).getClientRects(); 1298 1299 document.body.removeChild(el); 1300 1301 // Offset to the alignment point determined by textAlign. 1302 let text_align_dx; 1303 switch (el.style.textAlign) { 1304 case 'right': 1305 text_align_dx = width; 1306 break; 1307 case 'center': 1308 text_align_dx = width / 2; 1309 break; 1310 default: 1311 text_align_dx = 0; 1312 } 1313 1314 for(let i = 0 ; i < sel_rects.length ; ++i) { 1315 sel_rects[i].x -= parent.x + text_align_dx; 1316 sel_rects[i].y -= parent.y; 1317 } 1318 1319 return sel_rects; 1320 } 1321 1322 function checkRectListsMatch(list_a, list_b) { 1323 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1324 for(let i = 0 ; i < list_a.length ; ++i) { 1325 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1326 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1327 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1328 // Y-position not tested here as getting the baseline for text in the 1329 // DOM is not straightforward. 1330 } 1331 } 1332 1333 ctx.font = '50px sans-serif'; 1334 ctx.direction = 'rtl'; 1335 ctx.textAlign = 'right'; 1336 ctx.letterSpacing = '10px'; 1337 1338 const kTexts = [ 1339 'UNAVAILABLE', 1340 '🏁🎶🏁', 1341 ')(あ)(', 1342 '-abcd_', 1343 'איפה הספרייה?', 1344 'bidiמתמטיקה' 1345 ] 1346 1347 for (text of kTexts) { 1348 const tm = ctx.measureText(text); 1349 // First character. 1350 checkRectListsMatch( 1351 tm.getSelectionRects(0, 1), 1352 placeAndSelectTextInDOM(text, 0, 1) 1353 ); 1354 // Last character. 1355 checkRectListsMatch( 1356 tm.getSelectionRects(text.length - 1, text.length), 1357 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1358 ); 1359 // Whole string. 1360 checkRectListsMatch( 1361 tm.getSelectionRects(0, text.length), 1362 placeAndSelectTextInDOM(text, 0, text.length) 1363 ); 1364 // Intermediate string. 1365 checkRectListsMatch( 1366 tm.getSelectionRects(1, text.length - 1), 1367 placeAndSelectTextInDOM(text, 1, text.length - 1) 1368 ); 1369 // Invalid start > end string. Creates 0 width rectangle. 1370 checkRectListsMatch( 1371 tm.getSelectionRects(3, 2), 1372 placeAndSelectTextInDOM(text, 3, 2) 1373 ); 1374 checkRectListsMatch( 1375 tm.getSelectionRects(1, 0), 1376 placeAndSelectTextInDOM(text, 1, 0) 1377 ); 1378 } 1379 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override."); 1380 1381 test(t => { 1382 const canvas = new OffscreenCanvas(100, 50); 1383 const ctx = canvas.getContext('2d'); 1384 1385 function placeAndSelectTextInDOM(text, from, to) { 1386 const el = document.createElement("div"); 1387 el.innerHTML = text; 1388 el.style.font = '50px sans-serif'; 1389 el.style.direction = 'ltr'; 1390 el.style.textAlign = 'left'; 1391 el.style.letterSpacing = '0px'; 1392 document.body.appendChild(el); 1393 1394 let range = document.createRange(); 1395 1396 range.setStart(el.childNodes[0], 0); 1397 range.setEnd(el.childNodes[0], text.length); 1398 const parent = range.getClientRects()[0]; 1399 let width = 0; 1400 for (const rect of range.getClientRects()) { 1401 width += rect.width; 1402 } 1403 1404 range.setStart(el.childNodes[0], from); 1405 range.setEnd(el.childNodes[0], to); 1406 1407 let sel = window.getSelection(); 1408 sel.removeAllRanges(); 1409 sel.addRange(range); 1410 1411 let sel_rects = sel.getRangeAt(0).getClientRects(); 1412 1413 document.body.removeChild(el); 1414 1415 // Offset to the alignment point determined by textAlign. 1416 let text_align_dx; 1417 switch (el.style.textAlign) { 1418 case 'right': 1419 text_align_dx = width; 1420 break; 1421 case 'center': 1422 text_align_dx = width / 2; 1423 break; 1424 default: 1425 text_align_dx = 0; 1426 } 1427 1428 for(let i = 0 ; i < sel_rects.length ; ++i) { 1429 sel_rects[i].x -= parent.x + text_align_dx; 1430 sel_rects[i].y -= parent.y; 1431 } 1432 1433 return sel_rects; 1434 } 1435 1436 function checkRectListsMatch(list_a, list_b) { 1437 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1438 for(let i = 0 ; i < list_a.length ; ++i) { 1439 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1440 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1441 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1442 // Y-position not tested here as getting the baseline for text in the 1443 // DOM is not straightforward. 1444 } 1445 } 1446 1447 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1448 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1449 const LTR_OVERRIDE = '\u202D'; 1450 const RTL_OVERRIDE = '\u202E'; 1451 const OVERRIDE_END = '\u202C'; 1452 if (direction_is_ltr) { 1453 return LTR_OVERRIDE + text + OVERRIDE_END; 1454 } 1455 return RTL_OVERRIDE + text + OVERRIDE_END; 1456 } 1457 1458 ctx.font = '50px sans-serif'; 1459 ctx.direction = 'ltr'; 1460 ctx.textAlign = 'left'; 1461 ctx.letterSpacing = '0px'; 1462 1463 const kTexts = [ 1464 'UNAVAILABLE', 1465 '🏁🎶🏁', 1466 ')(あ)(', 1467 '-abcd_', 1468 'איפה הספרייה?', 1469 'bidiמתמטיקה' 1470 ] 1471 1472 for (text of kTexts) { 1473 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1474 const tm = ctx.measureText(text); 1475 // First character. 1476 checkRectListsMatch( 1477 tm.getSelectionRects(0, 1), 1478 placeAndSelectTextInDOM(text, 0, 1) 1479 ); 1480 // Last character. 1481 checkRectListsMatch( 1482 tm.getSelectionRects(text.length - 1, text.length), 1483 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1484 ); 1485 // Whole string. 1486 checkRectListsMatch( 1487 tm.getSelectionRects(0, text.length), 1488 placeAndSelectTextInDOM(text, 0, text.length) 1489 ); 1490 // Intermediate string. 1491 checkRectListsMatch( 1492 tm.getSelectionRects(1, text.length - 1), 1493 placeAndSelectTextInDOM(text, 1, text.length - 1) 1494 ); 1495 // Invalid start > end string. Creates 0 width rectangle. 1496 checkRectListsMatch( 1497 tm.getSelectionRects(3, 2), 1498 placeAndSelectTextInDOM(text, 3, 2) 1499 ); 1500 checkRectListsMatch( 1501 tm.getSelectionRects(1, 0), 1502 placeAndSelectTextInDOM(text, 1, 0) 1503 ); 1504 } 1505 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override."); 1506 1507 test(t => { 1508 const canvas = new OffscreenCanvas(100, 50); 1509 const ctx = canvas.getContext('2d'); 1510 1511 function placeAndSelectTextInDOM(text, from, to) { 1512 const el = document.createElement("div"); 1513 el.innerHTML = text; 1514 el.style.font = '50px sans-serif'; 1515 el.style.direction = 'rtl'; 1516 el.style.textAlign = 'left'; 1517 el.style.letterSpacing = '0px'; 1518 document.body.appendChild(el); 1519 1520 let range = document.createRange(); 1521 1522 range.setStart(el.childNodes[0], 0); 1523 range.setEnd(el.childNodes[0], text.length); 1524 const parent = range.getClientRects()[0]; 1525 let width = 0; 1526 for (const rect of range.getClientRects()) { 1527 width += rect.width; 1528 } 1529 1530 range.setStart(el.childNodes[0], from); 1531 range.setEnd(el.childNodes[0], to); 1532 1533 let sel = window.getSelection(); 1534 sel.removeAllRanges(); 1535 sel.addRange(range); 1536 1537 let sel_rects = sel.getRangeAt(0).getClientRects(); 1538 1539 document.body.removeChild(el); 1540 1541 // Offset to the alignment point determined by textAlign. 1542 let text_align_dx; 1543 switch (el.style.textAlign) { 1544 case 'right': 1545 text_align_dx = width; 1546 break; 1547 case 'center': 1548 text_align_dx = width / 2; 1549 break; 1550 default: 1551 text_align_dx = 0; 1552 } 1553 1554 for(let i = 0 ; i < sel_rects.length ; ++i) { 1555 sel_rects[i].x -= parent.x + text_align_dx; 1556 sel_rects[i].y -= parent.y; 1557 } 1558 1559 return sel_rects; 1560 } 1561 1562 function checkRectListsMatch(list_a, list_b) { 1563 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1564 for(let i = 0 ; i < list_a.length ; ++i) { 1565 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1566 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1567 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1568 // Y-position not tested here as getting the baseline for text in the 1569 // DOM is not straightforward. 1570 } 1571 } 1572 1573 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1574 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1575 const LTR_OVERRIDE = '\u202D'; 1576 const RTL_OVERRIDE = '\u202E'; 1577 const OVERRIDE_END = '\u202C'; 1578 if (direction_is_ltr) { 1579 return LTR_OVERRIDE + text + OVERRIDE_END; 1580 } 1581 return RTL_OVERRIDE + text + OVERRIDE_END; 1582 } 1583 1584 ctx.font = '50px sans-serif'; 1585 ctx.direction = 'rtl'; 1586 ctx.textAlign = 'left'; 1587 ctx.letterSpacing = '0px'; 1588 1589 const kTexts = [ 1590 'UNAVAILABLE', 1591 '🏁🎶🏁', 1592 ')(あ)(', 1593 '-abcd_', 1594 'איפה הספרייה?', 1595 'bidiמתמטיקה' 1596 ] 1597 1598 for (text of kTexts) { 1599 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1600 const tm = ctx.measureText(text); 1601 // First character. 1602 checkRectListsMatch( 1603 tm.getSelectionRects(0, 1), 1604 placeAndSelectTextInDOM(text, 0, 1) 1605 ); 1606 // Last character. 1607 checkRectListsMatch( 1608 tm.getSelectionRects(text.length - 1, text.length), 1609 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1610 ); 1611 // Whole string. 1612 checkRectListsMatch( 1613 tm.getSelectionRects(0, text.length), 1614 placeAndSelectTextInDOM(text, 0, text.length) 1615 ); 1616 // Intermediate string. 1617 checkRectListsMatch( 1618 tm.getSelectionRects(1, text.length - 1), 1619 placeAndSelectTextInDOM(text, 1, text.length - 1) 1620 ); 1621 // Invalid start > end string. Creates 0 width rectangle. 1622 checkRectListsMatch( 1623 tm.getSelectionRects(3, 2), 1624 placeAndSelectTextInDOM(text, 3, 2) 1625 ); 1626 checkRectListsMatch( 1627 tm.getSelectionRects(1, 0), 1628 placeAndSelectTextInDOM(text, 1, 0) 1629 ); 1630 } 1631 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override."); 1632 1633 test(t => { 1634 const canvas = new OffscreenCanvas(100, 50); 1635 const ctx = canvas.getContext('2d'); 1636 1637 function placeAndSelectTextInDOM(text, from, to) { 1638 const el = document.createElement("div"); 1639 el.innerHTML = text; 1640 el.style.font = '50px sans-serif'; 1641 el.style.direction = 'ltr'; 1642 el.style.textAlign = 'center'; 1643 el.style.letterSpacing = '0px'; 1644 document.body.appendChild(el); 1645 1646 let range = document.createRange(); 1647 1648 range.setStart(el.childNodes[0], 0); 1649 range.setEnd(el.childNodes[0], text.length); 1650 const parent = range.getClientRects()[0]; 1651 let width = 0; 1652 for (const rect of range.getClientRects()) { 1653 width += rect.width; 1654 } 1655 1656 range.setStart(el.childNodes[0], from); 1657 range.setEnd(el.childNodes[0], to); 1658 1659 let sel = window.getSelection(); 1660 sel.removeAllRanges(); 1661 sel.addRange(range); 1662 1663 let sel_rects = sel.getRangeAt(0).getClientRects(); 1664 1665 document.body.removeChild(el); 1666 1667 // Offset to the alignment point determined by textAlign. 1668 let text_align_dx; 1669 switch (el.style.textAlign) { 1670 case 'right': 1671 text_align_dx = width; 1672 break; 1673 case 'center': 1674 text_align_dx = width / 2; 1675 break; 1676 default: 1677 text_align_dx = 0; 1678 } 1679 1680 for(let i = 0 ; i < sel_rects.length ; ++i) { 1681 sel_rects[i].x -= parent.x + text_align_dx; 1682 sel_rects[i].y -= parent.y; 1683 } 1684 1685 return sel_rects; 1686 } 1687 1688 function checkRectListsMatch(list_a, list_b) { 1689 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1690 for(let i = 0 ; i < list_a.length ; ++i) { 1691 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1692 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1693 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1694 // Y-position not tested here as getting the baseline for text in the 1695 // DOM is not straightforward. 1696 } 1697 } 1698 1699 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1700 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1701 const LTR_OVERRIDE = '\u202D'; 1702 const RTL_OVERRIDE = '\u202E'; 1703 const OVERRIDE_END = '\u202C'; 1704 if (direction_is_ltr) { 1705 return LTR_OVERRIDE + text + OVERRIDE_END; 1706 } 1707 return RTL_OVERRIDE + text + OVERRIDE_END; 1708 } 1709 1710 ctx.font = '50px sans-serif'; 1711 ctx.direction = 'ltr'; 1712 ctx.textAlign = 'center'; 1713 ctx.letterSpacing = '0px'; 1714 1715 const kTexts = [ 1716 'UNAVAILABLE', 1717 '🏁🎶🏁', 1718 ')(あ)(', 1719 '-abcd_', 1720 'איפה הספרייה?', 1721 'bidiמתמטיקה' 1722 ] 1723 1724 for (text of kTexts) { 1725 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1726 const tm = ctx.measureText(text); 1727 // First character. 1728 checkRectListsMatch( 1729 tm.getSelectionRects(0, 1), 1730 placeAndSelectTextInDOM(text, 0, 1) 1731 ); 1732 // Last character. 1733 checkRectListsMatch( 1734 tm.getSelectionRects(text.length - 1, text.length), 1735 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1736 ); 1737 // Whole string. 1738 checkRectListsMatch( 1739 tm.getSelectionRects(0, text.length), 1740 placeAndSelectTextInDOM(text, 0, text.length) 1741 ); 1742 // Intermediate string. 1743 checkRectListsMatch( 1744 tm.getSelectionRects(1, text.length - 1), 1745 placeAndSelectTextInDOM(text, 1, text.length - 1) 1746 ); 1747 // Invalid start > end string. Creates 0 width rectangle. 1748 checkRectListsMatch( 1749 tm.getSelectionRects(3, 2), 1750 placeAndSelectTextInDOM(text, 3, 2) 1751 ); 1752 checkRectListsMatch( 1753 tm.getSelectionRects(1, 0), 1754 placeAndSelectTextInDOM(text, 1, 0) 1755 ); 1756 } 1757 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override."); 1758 1759 test(t => { 1760 const canvas = new OffscreenCanvas(100, 50); 1761 const ctx = canvas.getContext('2d'); 1762 1763 function placeAndSelectTextInDOM(text, from, to) { 1764 const el = document.createElement("div"); 1765 el.innerHTML = text; 1766 el.style.font = '50px sans-serif'; 1767 el.style.direction = 'rtl'; 1768 el.style.textAlign = 'center'; 1769 el.style.letterSpacing = '0px'; 1770 document.body.appendChild(el); 1771 1772 let range = document.createRange(); 1773 1774 range.setStart(el.childNodes[0], 0); 1775 range.setEnd(el.childNodes[0], text.length); 1776 const parent = range.getClientRects()[0]; 1777 let width = 0; 1778 for (const rect of range.getClientRects()) { 1779 width += rect.width; 1780 } 1781 1782 range.setStart(el.childNodes[0], from); 1783 range.setEnd(el.childNodes[0], to); 1784 1785 let sel = window.getSelection(); 1786 sel.removeAllRanges(); 1787 sel.addRange(range); 1788 1789 let sel_rects = sel.getRangeAt(0).getClientRects(); 1790 1791 document.body.removeChild(el); 1792 1793 // Offset to the alignment point determined by textAlign. 1794 let text_align_dx; 1795 switch (el.style.textAlign) { 1796 case 'right': 1797 text_align_dx = width; 1798 break; 1799 case 'center': 1800 text_align_dx = width / 2; 1801 break; 1802 default: 1803 text_align_dx = 0; 1804 } 1805 1806 for(let i = 0 ; i < sel_rects.length ; ++i) { 1807 sel_rects[i].x -= parent.x + text_align_dx; 1808 sel_rects[i].y -= parent.y; 1809 } 1810 1811 return sel_rects; 1812 } 1813 1814 function checkRectListsMatch(list_a, list_b) { 1815 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1816 for(let i = 0 ; i < list_a.length ; ++i) { 1817 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1818 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1819 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1820 // Y-position not tested here as getting the baseline for text in the 1821 // DOM is not straightforward. 1822 } 1823 } 1824 1825 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1826 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1827 const LTR_OVERRIDE = '\u202D'; 1828 const RTL_OVERRIDE = '\u202E'; 1829 const OVERRIDE_END = '\u202C'; 1830 if (direction_is_ltr) { 1831 return LTR_OVERRIDE + text + OVERRIDE_END; 1832 } 1833 return RTL_OVERRIDE + text + OVERRIDE_END; 1834 } 1835 1836 ctx.font = '50px sans-serif'; 1837 ctx.direction = 'rtl'; 1838 ctx.textAlign = 'center'; 1839 ctx.letterSpacing = '0px'; 1840 1841 const kTexts = [ 1842 'UNAVAILABLE', 1843 '🏁🎶🏁', 1844 ')(あ)(', 1845 '-abcd_', 1846 'איפה הספרייה?', 1847 'bidiמתמטיקה' 1848 ] 1849 1850 for (text of kTexts) { 1851 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1852 const tm = ctx.measureText(text); 1853 // First character. 1854 checkRectListsMatch( 1855 tm.getSelectionRects(0, 1), 1856 placeAndSelectTextInDOM(text, 0, 1) 1857 ); 1858 // Last character. 1859 checkRectListsMatch( 1860 tm.getSelectionRects(text.length - 1, text.length), 1861 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1862 ); 1863 // Whole string. 1864 checkRectListsMatch( 1865 tm.getSelectionRects(0, text.length), 1866 placeAndSelectTextInDOM(text, 0, text.length) 1867 ); 1868 // Intermediate string. 1869 checkRectListsMatch( 1870 tm.getSelectionRects(1, text.length - 1), 1871 placeAndSelectTextInDOM(text, 1, text.length - 1) 1872 ); 1873 // Invalid start > end string. Creates 0 width rectangle. 1874 checkRectListsMatch( 1875 tm.getSelectionRects(3, 2), 1876 placeAndSelectTextInDOM(text, 3, 2) 1877 ); 1878 checkRectListsMatch( 1879 tm.getSelectionRects(1, 0), 1880 placeAndSelectTextInDOM(text, 1, 0) 1881 ); 1882 } 1883 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override."); 1884 1885 test(t => { 1886 const canvas = new OffscreenCanvas(100, 50); 1887 const ctx = canvas.getContext('2d'); 1888 1889 function placeAndSelectTextInDOM(text, from, to) { 1890 const el = document.createElement("div"); 1891 el.innerHTML = text; 1892 el.style.font = '50px sans-serif'; 1893 el.style.direction = 'ltr'; 1894 el.style.textAlign = 'right'; 1895 el.style.letterSpacing = '0px'; 1896 document.body.appendChild(el); 1897 1898 let range = document.createRange(); 1899 1900 range.setStart(el.childNodes[0], 0); 1901 range.setEnd(el.childNodes[0], text.length); 1902 const parent = range.getClientRects()[0]; 1903 let width = 0; 1904 for (const rect of range.getClientRects()) { 1905 width += rect.width; 1906 } 1907 1908 range.setStart(el.childNodes[0], from); 1909 range.setEnd(el.childNodes[0], to); 1910 1911 let sel = window.getSelection(); 1912 sel.removeAllRanges(); 1913 sel.addRange(range); 1914 1915 let sel_rects = sel.getRangeAt(0).getClientRects(); 1916 1917 document.body.removeChild(el); 1918 1919 // Offset to the alignment point determined by textAlign. 1920 let text_align_dx; 1921 switch (el.style.textAlign) { 1922 case 'right': 1923 text_align_dx = width; 1924 break; 1925 case 'center': 1926 text_align_dx = width / 2; 1927 break; 1928 default: 1929 text_align_dx = 0; 1930 } 1931 1932 for(let i = 0 ; i < sel_rects.length ; ++i) { 1933 sel_rects[i].x -= parent.x + text_align_dx; 1934 sel_rects[i].y -= parent.y; 1935 } 1936 1937 return sel_rects; 1938 } 1939 1940 function checkRectListsMatch(list_a, list_b) { 1941 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 1942 for(let i = 0 ; i < list_a.length ; ++i) { 1943 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 1944 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 1945 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 1946 // Y-position not tested here as getting the baseline for text in the 1947 // DOM is not straightforward. 1948 } 1949 } 1950 1951 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 1952 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 1953 const LTR_OVERRIDE = '\u202D'; 1954 const RTL_OVERRIDE = '\u202E'; 1955 const OVERRIDE_END = '\u202C'; 1956 if (direction_is_ltr) { 1957 return LTR_OVERRIDE + text + OVERRIDE_END; 1958 } 1959 return RTL_OVERRIDE + text + OVERRIDE_END; 1960 } 1961 1962 ctx.font = '50px sans-serif'; 1963 ctx.direction = 'ltr'; 1964 ctx.textAlign = 'right'; 1965 ctx.letterSpacing = '0px'; 1966 1967 const kTexts = [ 1968 'UNAVAILABLE', 1969 '🏁🎶🏁', 1970 ')(あ)(', 1971 '-abcd_', 1972 'איפה הספרייה?', 1973 'bidiמתמטיקה' 1974 ] 1975 1976 for (text of kTexts) { 1977 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 1978 const tm = ctx.measureText(text); 1979 // First character. 1980 checkRectListsMatch( 1981 tm.getSelectionRects(0, 1), 1982 placeAndSelectTextInDOM(text, 0, 1) 1983 ); 1984 // Last character. 1985 checkRectListsMatch( 1986 tm.getSelectionRects(text.length - 1, text.length), 1987 placeAndSelectTextInDOM(text, text.length - 1, text.length) 1988 ); 1989 // Whole string. 1990 checkRectListsMatch( 1991 tm.getSelectionRects(0, text.length), 1992 placeAndSelectTextInDOM(text, 0, text.length) 1993 ); 1994 // Intermediate string. 1995 checkRectListsMatch( 1996 tm.getSelectionRects(1, text.length - 1), 1997 placeAndSelectTextInDOM(text, 1, text.length - 1) 1998 ); 1999 // Invalid start > end string. Creates 0 width rectangle. 2000 checkRectListsMatch( 2001 tm.getSelectionRects(3, 2), 2002 placeAndSelectTextInDOM(text, 3, 2) 2003 ); 2004 checkRectListsMatch( 2005 tm.getSelectionRects(1, 0), 2006 placeAndSelectTextInDOM(text, 1, 0) 2007 ); 2008 } 2009 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override."); 2010 2011 test(t => { 2012 const canvas = new OffscreenCanvas(100, 50); 2013 const ctx = canvas.getContext('2d'); 2014 2015 function placeAndSelectTextInDOM(text, from, to) { 2016 const el = document.createElement("div"); 2017 el.innerHTML = text; 2018 el.style.font = '50px sans-serif'; 2019 el.style.direction = 'rtl'; 2020 el.style.textAlign = 'right'; 2021 el.style.letterSpacing = '0px'; 2022 document.body.appendChild(el); 2023 2024 let range = document.createRange(); 2025 2026 range.setStart(el.childNodes[0], 0); 2027 range.setEnd(el.childNodes[0], text.length); 2028 const parent = range.getClientRects()[0]; 2029 let width = 0; 2030 for (const rect of range.getClientRects()) { 2031 width += rect.width; 2032 } 2033 2034 range.setStart(el.childNodes[0], from); 2035 range.setEnd(el.childNodes[0], to); 2036 2037 let sel = window.getSelection(); 2038 sel.removeAllRanges(); 2039 sel.addRange(range); 2040 2041 let sel_rects = sel.getRangeAt(0).getClientRects(); 2042 2043 document.body.removeChild(el); 2044 2045 // Offset to the alignment point determined by textAlign. 2046 let text_align_dx; 2047 switch (el.style.textAlign) { 2048 case 'right': 2049 text_align_dx = width; 2050 break; 2051 case 'center': 2052 text_align_dx = width / 2; 2053 break; 2054 default: 2055 text_align_dx = 0; 2056 } 2057 2058 for(let i = 0 ; i < sel_rects.length ; ++i) { 2059 sel_rects[i].x -= parent.x + text_align_dx; 2060 sel_rects[i].y -= parent.y; 2061 } 2062 2063 return sel_rects; 2064 } 2065 2066 function checkRectListsMatch(list_a, list_b) { 2067 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 2068 for(let i = 0 ; i < list_a.length ; ++i) { 2069 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 2070 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 2071 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 2072 // Y-position not tested here as getting the baseline for text in the 2073 // DOM is not straightforward. 2074 } 2075 } 2076 2077 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 2078 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 2079 const LTR_OVERRIDE = '\u202D'; 2080 const RTL_OVERRIDE = '\u202E'; 2081 const OVERRIDE_END = '\u202C'; 2082 if (direction_is_ltr) { 2083 return LTR_OVERRIDE + text + OVERRIDE_END; 2084 } 2085 return RTL_OVERRIDE + text + OVERRIDE_END; 2086 } 2087 2088 ctx.font = '50px sans-serif'; 2089 ctx.direction = 'rtl'; 2090 ctx.textAlign = 'right'; 2091 ctx.letterSpacing = '0px'; 2092 2093 const kTexts = [ 2094 'UNAVAILABLE', 2095 '🏁🎶🏁', 2096 ')(あ)(', 2097 '-abcd_', 2098 'איפה הספרייה?', 2099 'bidiמתמטיקה' 2100 ] 2101 2102 for (text of kTexts) { 2103 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 2104 const tm = ctx.measureText(text); 2105 // First character. 2106 checkRectListsMatch( 2107 tm.getSelectionRects(0, 1), 2108 placeAndSelectTextInDOM(text, 0, 1) 2109 ); 2110 // Last character. 2111 checkRectListsMatch( 2112 tm.getSelectionRects(text.length - 1, text.length), 2113 placeAndSelectTextInDOM(text, text.length - 1, text.length) 2114 ); 2115 // Whole string. 2116 checkRectListsMatch( 2117 tm.getSelectionRects(0, text.length), 2118 placeAndSelectTextInDOM(text, 0, text.length) 2119 ); 2120 // Intermediate string. 2121 checkRectListsMatch( 2122 tm.getSelectionRects(1, text.length - 1), 2123 placeAndSelectTextInDOM(text, 1, text.length - 1) 2124 ); 2125 // Invalid start > end string. Creates 0 width rectangle. 2126 checkRectListsMatch( 2127 tm.getSelectionRects(3, 2), 2128 placeAndSelectTextInDOM(text, 3, 2) 2129 ); 2130 checkRectListsMatch( 2131 tm.getSelectionRects(1, 0), 2132 placeAndSelectTextInDOM(text, 1, 0) 2133 ); 2134 } 2135 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override."); 2136 2137 test(t => { 2138 const canvas = new OffscreenCanvas(100, 50); 2139 const ctx = canvas.getContext('2d'); 2140 2141 function placeAndSelectTextInDOM(text, from, to) { 2142 const el = document.createElement("div"); 2143 el.innerHTML = text; 2144 el.style.font = '50px sans-serif'; 2145 el.style.direction = 'ltr'; 2146 el.style.textAlign = 'left'; 2147 el.style.letterSpacing = '10px'; 2148 document.body.appendChild(el); 2149 2150 let range = document.createRange(); 2151 2152 range.setStart(el.childNodes[0], 0); 2153 range.setEnd(el.childNodes[0], text.length); 2154 const parent = range.getClientRects()[0]; 2155 let width = 0; 2156 for (const rect of range.getClientRects()) { 2157 width += rect.width; 2158 } 2159 2160 range.setStart(el.childNodes[0], from); 2161 range.setEnd(el.childNodes[0], to); 2162 2163 let sel = window.getSelection(); 2164 sel.removeAllRanges(); 2165 sel.addRange(range); 2166 2167 let sel_rects = sel.getRangeAt(0).getClientRects(); 2168 2169 document.body.removeChild(el); 2170 2171 // Offset to the alignment point determined by textAlign. 2172 let text_align_dx; 2173 switch (el.style.textAlign) { 2174 case 'right': 2175 text_align_dx = width; 2176 break; 2177 case 'center': 2178 text_align_dx = width / 2; 2179 break; 2180 default: 2181 text_align_dx = 0; 2182 } 2183 2184 for(let i = 0 ; i < sel_rects.length ; ++i) { 2185 sel_rects[i].x -= parent.x + text_align_dx; 2186 sel_rects[i].y -= parent.y; 2187 } 2188 2189 return sel_rects; 2190 } 2191 2192 function checkRectListsMatch(list_a, list_b) { 2193 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 2194 for(let i = 0 ; i < list_a.length ; ++i) { 2195 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 2196 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 2197 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 2198 // Y-position not tested here as getting the baseline for text in the 2199 // DOM is not straightforward. 2200 } 2201 } 2202 2203 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 2204 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 2205 const LTR_OVERRIDE = '\u202D'; 2206 const RTL_OVERRIDE = '\u202E'; 2207 const OVERRIDE_END = '\u202C'; 2208 if (direction_is_ltr) { 2209 return LTR_OVERRIDE + text + OVERRIDE_END; 2210 } 2211 return RTL_OVERRIDE + text + OVERRIDE_END; 2212 } 2213 2214 ctx.font = '50px sans-serif'; 2215 ctx.direction = 'ltr'; 2216 ctx.textAlign = 'left'; 2217 ctx.letterSpacing = '10px'; 2218 2219 const kTexts = [ 2220 'UNAVAILABLE', 2221 '🏁🎶🏁', 2222 ')(あ)(', 2223 '-abcd_', 2224 'איפה הספרייה?', 2225 'bidiמתמטיקה' 2226 ] 2227 2228 for (text of kTexts) { 2229 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 2230 const tm = ctx.measureText(text); 2231 // First character. 2232 checkRectListsMatch( 2233 tm.getSelectionRects(0, 1), 2234 placeAndSelectTextInDOM(text, 0, 1) 2235 ); 2236 // Last character. 2237 checkRectListsMatch( 2238 tm.getSelectionRects(text.length - 1, text.length), 2239 placeAndSelectTextInDOM(text, text.length - 1, text.length) 2240 ); 2241 // Whole string. 2242 checkRectListsMatch( 2243 tm.getSelectionRects(0, text.length), 2244 placeAndSelectTextInDOM(text, 0, text.length) 2245 ); 2246 // Intermediate string. 2247 checkRectListsMatch( 2248 tm.getSelectionRects(1, text.length - 1), 2249 placeAndSelectTextInDOM(text, 1, text.length - 1) 2250 ); 2251 // Invalid start > end string. Creates 0 width rectangle. 2252 checkRectListsMatch( 2253 tm.getSelectionRects(3, 2), 2254 placeAndSelectTextInDOM(text, 3, 2) 2255 ); 2256 checkRectListsMatch( 2257 tm.getSelectionRects(1, 0), 2258 placeAndSelectTextInDOM(text, 1, 0) 2259 ); 2260 } 2261 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override."); 2262 2263 test(t => { 2264 const canvas = new OffscreenCanvas(100, 50); 2265 const ctx = canvas.getContext('2d'); 2266 2267 function placeAndSelectTextInDOM(text, from, to) { 2268 const el = document.createElement("div"); 2269 el.innerHTML = text; 2270 el.style.font = '50px sans-serif'; 2271 el.style.direction = 'rtl'; 2272 el.style.textAlign = 'left'; 2273 el.style.letterSpacing = '10px'; 2274 document.body.appendChild(el); 2275 2276 let range = document.createRange(); 2277 2278 range.setStart(el.childNodes[0], 0); 2279 range.setEnd(el.childNodes[0], text.length); 2280 const parent = range.getClientRects()[0]; 2281 let width = 0; 2282 for (const rect of range.getClientRects()) { 2283 width += rect.width; 2284 } 2285 2286 range.setStart(el.childNodes[0], from); 2287 range.setEnd(el.childNodes[0], to); 2288 2289 let sel = window.getSelection(); 2290 sel.removeAllRanges(); 2291 sel.addRange(range); 2292 2293 let sel_rects = sel.getRangeAt(0).getClientRects(); 2294 2295 document.body.removeChild(el); 2296 2297 // Offset to the alignment point determined by textAlign. 2298 let text_align_dx; 2299 switch (el.style.textAlign) { 2300 case 'right': 2301 text_align_dx = width; 2302 break; 2303 case 'center': 2304 text_align_dx = width / 2; 2305 break; 2306 default: 2307 text_align_dx = 0; 2308 } 2309 2310 for(let i = 0 ; i < sel_rects.length ; ++i) { 2311 sel_rects[i].x -= parent.x + text_align_dx; 2312 sel_rects[i].y -= parent.y; 2313 } 2314 2315 return sel_rects; 2316 } 2317 2318 function checkRectListsMatch(list_a, list_b) { 2319 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 2320 for(let i = 0 ; i < list_a.length ; ++i) { 2321 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 2322 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 2323 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 2324 // Y-position not tested here as getting the baseline for text in the 2325 // DOM is not straightforward. 2326 } 2327 } 2328 2329 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 2330 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 2331 const LTR_OVERRIDE = '\u202D'; 2332 const RTL_OVERRIDE = '\u202E'; 2333 const OVERRIDE_END = '\u202C'; 2334 if (direction_is_ltr) { 2335 return LTR_OVERRIDE + text + OVERRIDE_END; 2336 } 2337 return RTL_OVERRIDE + text + OVERRIDE_END; 2338 } 2339 2340 ctx.font = '50px sans-serif'; 2341 ctx.direction = 'rtl'; 2342 ctx.textAlign = 'left'; 2343 ctx.letterSpacing = '10px'; 2344 2345 const kTexts = [ 2346 'UNAVAILABLE', 2347 '🏁🎶🏁', 2348 ')(あ)(', 2349 '-abcd_', 2350 'איפה הספרייה?', 2351 'bidiמתמטיקה' 2352 ] 2353 2354 for (text of kTexts) { 2355 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 2356 const tm = ctx.measureText(text); 2357 // First character. 2358 checkRectListsMatch( 2359 tm.getSelectionRects(0, 1), 2360 placeAndSelectTextInDOM(text, 0, 1) 2361 ); 2362 // Last character. 2363 checkRectListsMatch( 2364 tm.getSelectionRects(text.length - 1, text.length), 2365 placeAndSelectTextInDOM(text, text.length - 1, text.length) 2366 ); 2367 // Whole string. 2368 checkRectListsMatch( 2369 tm.getSelectionRects(0, text.length), 2370 placeAndSelectTextInDOM(text, 0, text.length) 2371 ); 2372 // Intermediate string. 2373 checkRectListsMatch( 2374 tm.getSelectionRects(1, text.length - 1), 2375 placeAndSelectTextInDOM(text, 1, text.length - 1) 2376 ); 2377 // Invalid start > end string. Creates 0 width rectangle. 2378 checkRectListsMatch( 2379 tm.getSelectionRects(3, 2), 2380 placeAndSelectTextInDOM(text, 3, 2) 2381 ); 2382 checkRectListsMatch( 2383 tm.getSelectionRects(1, 0), 2384 placeAndSelectTextInDOM(text, 1, 0) 2385 ); 2386 } 2387 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override."); 2388 2389 test(t => { 2390 const canvas = new OffscreenCanvas(100, 50); 2391 const ctx = canvas.getContext('2d'); 2392 2393 function placeAndSelectTextInDOM(text, from, to) { 2394 const el = document.createElement("div"); 2395 el.innerHTML = text; 2396 el.style.font = '50px sans-serif'; 2397 el.style.direction = 'ltr'; 2398 el.style.textAlign = 'center'; 2399 el.style.letterSpacing = '10px'; 2400 document.body.appendChild(el); 2401 2402 let range = document.createRange(); 2403 2404 range.setStart(el.childNodes[0], 0); 2405 range.setEnd(el.childNodes[0], text.length); 2406 const parent = range.getClientRects()[0]; 2407 let width = 0; 2408 for (const rect of range.getClientRects()) { 2409 width += rect.width; 2410 } 2411 2412 range.setStart(el.childNodes[0], from); 2413 range.setEnd(el.childNodes[0], to); 2414 2415 let sel = window.getSelection(); 2416 sel.removeAllRanges(); 2417 sel.addRange(range); 2418 2419 let sel_rects = sel.getRangeAt(0).getClientRects(); 2420 2421 document.body.removeChild(el); 2422 2423 // Offset to the alignment point determined by textAlign. 2424 let text_align_dx; 2425 switch (el.style.textAlign) { 2426 case 'right': 2427 text_align_dx = width; 2428 break; 2429 case 'center': 2430 text_align_dx = width / 2; 2431 break; 2432 default: 2433 text_align_dx = 0; 2434 } 2435 2436 for(let i = 0 ; i < sel_rects.length ; ++i) { 2437 sel_rects[i].x -= parent.x + text_align_dx; 2438 sel_rects[i].y -= parent.y; 2439 } 2440 2441 return sel_rects; 2442 } 2443 2444 function checkRectListsMatch(list_a, list_b) { 2445 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 2446 for(let i = 0 ; i < list_a.length ; ++i) { 2447 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 2448 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 2449 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 2450 // Y-position not tested here as getting the baseline for text in the 2451 // DOM is not straightforward. 2452 } 2453 } 2454 2455 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 2456 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 2457 const LTR_OVERRIDE = '\u202D'; 2458 const RTL_OVERRIDE = '\u202E'; 2459 const OVERRIDE_END = '\u202C'; 2460 if (direction_is_ltr) { 2461 return LTR_OVERRIDE + text + OVERRIDE_END; 2462 } 2463 return RTL_OVERRIDE + text + OVERRIDE_END; 2464 } 2465 2466 ctx.font = '50px sans-serif'; 2467 ctx.direction = 'ltr'; 2468 ctx.textAlign = 'center'; 2469 ctx.letterSpacing = '10px'; 2470 2471 const kTexts = [ 2472 'UNAVAILABLE', 2473 '🏁🎶🏁', 2474 ')(あ)(', 2475 '-abcd_', 2476 'איפה הספרייה?', 2477 'bidiמתמטיקה' 2478 ] 2479 2480 for (text of kTexts) { 2481 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 2482 const tm = ctx.measureText(text); 2483 // First character. 2484 checkRectListsMatch( 2485 tm.getSelectionRects(0, 1), 2486 placeAndSelectTextInDOM(text, 0, 1) 2487 ); 2488 // Last character. 2489 checkRectListsMatch( 2490 tm.getSelectionRects(text.length - 1, text.length), 2491 placeAndSelectTextInDOM(text, text.length - 1, text.length) 2492 ); 2493 // Whole string. 2494 checkRectListsMatch( 2495 tm.getSelectionRects(0, text.length), 2496 placeAndSelectTextInDOM(text, 0, text.length) 2497 ); 2498 // Intermediate string. 2499 checkRectListsMatch( 2500 tm.getSelectionRects(1, text.length - 1), 2501 placeAndSelectTextInDOM(text, 1, text.length - 1) 2502 ); 2503 // Invalid start > end string. Creates 0 width rectangle. 2504 checkRectListsMatch( 2505 tm.getSelectionRects(3, 2), 2506 placeAndSelectTextInDOM(text, 3, 2) 2507 ); 2508 checkRectListsMatch( 2509 tm.getSelectionRects(1, 0), 2510 placeAndSelectTextInDOM(text, 1, 0) 2511 ); 2512 } 2513 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override."); 2514 2515 test(t => { 2516 const canvas = new OffscreenCanvas(100, 50); 2517 const ctx = canvas.getContext('2d'); 2518 2519 function placeAndSelectTextInDOM(text, from, to) { 2520 const el = document.createElement("div"); 2521 el.innerHTML = text; 2522 el.style.font = '50px sans-serif'; 2523 el.style.direction = 'rtl'; 2524 el.style.textAlign = 'center'; 2525 el.style.letterSpacing = '10px'; 2526 document.body.appendChild(el); 2527 2528 let range = document.createRange(); 2529 2530 range.setStart(el.childNodes[0], 0); 2531 range.setEnd(el.childNodes[0], text.length); 2532 const parent = range.getClientRects()[0]; 2533 let width = 0; 2534 for (const rect of range.getClientRects()) { 2535 width += rect.width; 2536 } 2537 2538 range.setStart(el.childNodes[0], from); 2539 range.setEnd(el.childNodes[0], to); 2540 2541 let sel = window.getSelection(); 2542 sel.removeAllRanges(); 2543 sel.addRange(range); 2544 2545 let sel_rects = sel.getRangeAt(0).getClientRects(); 2546 2547 document.body.removeChild(el); 2548 2549 // Offset to the alignment point determined by textAlign. 2550 let text_align_dx; 2551 switch (el.style.textAlign) { 2552 case 'right': 2553 text_align_dx = width; 2554 break; 2555 case 'center': 2556 text_align_dx = width / 2; 2557 break; 2558 default: 2559 text_align_dx = 0; 2560 } 2561 2562 for(let i = 0 ; i < sel_rects.length ; ++i) { 2563 sel_rects[i].x -= parent.x + text_align_dx; 2564 sel_rects[i].y -= parent.y; 2565 } 2566 2567 return sel_rects; 2568 } 2569 2570 function checkRectListsMatch(list_a, list_b) { 2571 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 2572 for(let i = 0 ; i < list_a.length ; ++i) { 2573 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 2574 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 2575 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 2576 // Y-position not tested here as getting the baseline for text in the 2577 // DOM is not straightforward. 2578 } 2579 } 2580 2581 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 2582 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 2583 const LTR_OVERRIDE = '\u202D'; 2584 const RTL_OVERRIDE = '\u202E'; 2585 const OVERRIDE_END = '\u202C'; 2586 if (direction_is_ltr) { 2587 return LTR_OVERRIDE + text + OVERRIDE_END; 2588 } 2589 return RTL_OVERRIDE + text + OVERRIDE_END; 2590 } 2591 2592 ctx.font = '50px sans-serif'; 2593 ctx.direction = 'rtl'; 2594 ctx.textAlign = 'center'; 2595 ctx.letterSpacing = '10px'; 2596 2597 const kTexts = [ 2598 'UNAVAILABLE', 2599 '🏁🎶🏁', 2600 ')(あ)(', 2601 '-abcd_', 2602 'איפה הספרייה?', 2603 'bidiמתמטיקה' 2604 ] 2605 2606 for (text of kTexts) { 2607 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 2608 const tm = ctx.measureText(text); 2609 // First character. 2610 checkRectListsMatch( 2611 tm.getSelectionRects(0, 1), 2612 placeAndSelectTextInDOM(text, 0, 1) 2613 ); 2614 // Last character. 2615 checkRectListsMatch( 2616 tm.getSelectionRects(text.length - 1, text.length), 2617 placeAndSelectTextInDOM(text, text.length - 1, text.length) 2618 ); 2619 // Whole string. 2620 checkRectListsMatch( 2621 tm.getSelectionRects(0, text.length), 2622 placeAndSelectTextInDOM(text, 0, text.length) 2623 ); 2624 // Intermediate string. 2625 checkRectListsMatch( 2626 tm.getSelectionRects(1, text.length - 1), 2627 placeAndSelectTextInDOM(text, 1, text.length - 1) 2628 ); 2629 // Invalid start > end string. Creates 0 width rectangle. 2630 checkRectListsMatch( 2631 tm.getSelectionRects(3, 2), 2632 placeAndSelectTextInDOM(text, 3, 2) 2633 ); 2634 checkRectListsMatch( 2635 tm.getSelectionRects(1, 0), 2636 placeAndSelectTextInDOM(text, 1, 0) 2637 ); 2638 } 2639 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override."); 2640 2641 test(t => { 2642 const canvas = new OffscreenCanvas(100, 50); 2643 const ctx = canvas.getContext('2d'); 2644 2645 function placeAndSelectTextInDOM(text, from, to) { 2646 const el = document.createElement("div"); 2647 el.innerHTML = text; 2648 el.style.font = '50px sans-serif'; 2649 el.style.direction = 'ltr'; 2650 el.style.textAlign = 'right'; 2651 el.style.letterSpacing = '10px'; 2652 document.body.appendChild(el); 2653 2654 let range = document.createRange(); 2655 2656 range.setStart(el.childNodes[0], 0); 2657 range.setEnd(el.childNodes[0], text.length); 2658 const parent = range.getClientRects()[0]; 2659 let width = 0; 2660 for (const rect of range.getClientRects()) { 2661 width += rect.width; 2662 } 2663 2664 range.setStart(el.childNodes[0], from); 2665 range.setEnd(el.childNodes[0], to); 2666 2667 let sel = window.getSelection(); 2668 sel.removeAllRanges(); 2669 sel.addRange(range); 2670 2671 let sel_rects = sel.getRangeAt(0).getClientRects(); 2672 2673 document.body.removeChild(el); 2674 2675 // Offset to the alignment point determined by textAlign. 2676 let text_align_dx; 2677 switch (el.style.textAlign) { 2678 case 'right': 2679 text_align_dx = width; 2680 break; 2681 case 'center': 2682 text_align_dx = width / 2; 2683 break; 2684 default: 2685 text_align_dx = 0; 2686 } 2687 2688 for(let i = 0 ; i < sel_rects.length ; ++i) { 2689 sel_rects[i].x -= parent.x + text_align_dx; 2690 sel_rects[i].y -= parent.y; 2691 } 2692 2693 return sel_rects; 2694 } 2695 2696 function checkRectListsMatch(list_a, list_b) { 2697 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 2698 for(let i = 0 ; i < list_a.length ; ++i) { 2699 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 2700 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 2701 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 2702 // Y-position not tested here as getting the baseline for text in the 2703 // DOM is not straightforward. 2704 } 2705 } 2706 2707 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 2708 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 2709 const LTR_OVERRIDE = '\u202D'; 2710 const RTL_OVERRIDE = '\u202E'; 2711 const OVERRIDE_END = '\u202C'; 2712 if (direction_is_ltr) { 2713 return LTR_OVERRIDE + text + OVERRIDE_END; 2714 } 2715 return RTL_OVERRIDE + text + OVERRIDE_END; 2716 } 2717 2718 ctx.font = '50px sans-serif'; 2719 ctx.direction = 'ltr'; 2720 ctx.textAlign = 'right'; 2721 ctx.letterSpacing = '10px'; 2722 2723 const kTexts = [ 2724 'UNAVAILABLE', 2725 '🏁🎶🏁', 2726 ')(あ)(', 2727 '-abcd_', 2728 'איפה הספרייה?', 2729 'bidiמתמטיקה' 2730 ] 2731 2732 for (text of kTexts) { 2733 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 2734 const tm = ctx.measureText(text); 2735 // First character. 2736 checkRectListsMatch( 2737 tm.getSelectionRects(0, 1), 2738 placeAndSelectTextInDOM(text, 0, 1) 2739 ); 2740 // Last character. 2741 checkRectListsMatch( 2742 tm.getSelectionRects(text.length - 1, text.length), 2743 placeAndSelectTextInDOM(text, text.length - 1, text.length) 2744 ); 2745 // Whole string. 2746 checkRectListsMatch( 2747 tm.getSelectionRects(0, text.length), 2748 placeAndSelectTextInDOM(text, 0, text.length) 2749 ); 2750 // Intermediate string. 2751 checkRectListsMatch( 2752 tm.getSelectionRects(1, text.length - 1), 2753 placeAndSelectTextInDOM(text, 1, text.length - 1) 2754 ); 2755 // Invalid start > end string. Creates 0 width rectangle. 2756 checkRectListsMatch( 2757 tm.getSelectionRects(3, 2), 2758 placeAndSelectTextInDOM(text, 3, 2) 2759 ); 2760 checkRectListsMatch( 2761 tm.getSelectionRects(1, 0), 2762 placeAndSelectTextInDOM(text, 1, 0) 2763 ); 2764 } 2765 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override."); 2766 2767 test(t => { 2768 const canvas = new OffscreenCanvas(100, 50); 2769 const ctx = canvas.getContext('2d'); 2770 2771 function placeAndSelectTextInDOM(text, from, to) { 2772 const el = document.createElement("div"); 2773 el.innerHTML = text; 2774 el.style.font = '50px sans-serif'; 2775 el.style.direction = 'rtl'; 2776 el.style.textAlign = 'right'; 2777 el.style.letterSpacing = '10px'; 2778 document.body.appendChild(el); 2779 2780 let range = document.createRange(); 2781 2782 range.setStart(el.childNodes[0], 0); 2783 range.setEnd(el.childNodes[0], text.length); 2784 const parent = range.getClientRects()[0]; 2785 let width = 0; 2786 for (const rect of range.getClientRects()) { 2787 width += rect.width; 2788 } 2789 2790 range.setStart(el.childNodes[0], from); 2791 range.setEnd(el.childNodes[0], to); 2792 2793 let sel = window.getSelection(); 2794 sel.removeAllRanges(); 2795 sel.addRange(range); 2796 2797 let sel_rects = sel.getRangeAt(0).getClientRects(); 2798 2799 document.body.removeChild(el); 2800 2801 // Offset to the alignment point determined by textAlign. 2802 let text_align_dx; 2803 switch (el.style.textAlign) { 2804 case 'right': 2805 text_align_dx = width; 2806 break; 2807 case 'center': 2808 text_align_dx = width / 2; 2809 break; 2810 default: 2811 text_align_dx = 0; 2812 } 2813 2814 for(let i = 0 ; i < sel_rects.length ; ++i) { 2815 sel_rects[i].x -= parent.x + text_align_dx; 2816 sel_rects[i].y -= parent.y; 2817 } 2818 2819 return sel_rects; 2820 } 2821 2822 function checkRectListsMatch(list_a, list_b) { 2823 _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length"); 2824 for(let i = 0 ; i < list_a.length ; ++i) { 2825 assert_approx_equals(list_a[i].x, list_b[i].x, 1.0); 2826 assert_approx_equals(list_a[i].width, list_b[i].width, 1.0); 2827 assert_approx_equals(list_a[i].height, list_b[i].height, 1.0); 2828 // Y-position not tested here as getting the baseline for text in the 2829 // DOM is not straightforward. 2830 } 2831 } 2832 2833 function addDirectionalOverrideCharacters(text, direction_is_ltr) { 2834 // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en 2835 const LTR_OVERRIDE = '\u202D'; 2836 const RTL_OVERRIDE = '\u202E'; 2837 const OVERRIDE_END = '\u202C'; 2838 if (direction_is_ltr) { 2839 return LTR_OVERRIDE + text + OVERRIDE_END; 2840 } 2841 return RTL_OVERRIDE + text + OVERRIDE_END; 2842 } 2843 2844 ctx.font = '50px sans-serif'; 2845 ctx.direction = 'rtl'; 2846 ctx.textAlign = 'right'; 2847 ctx.letterSpacing = '10px'; 2848 2849 const kTexts = [ 2850 'UNAVAILABLE', 2851 '🏁🎶🏁', 2852 ')(あ)(', 2853 '-abcd_', 2854 'איפה הספרייה?', 2855 'bidiמתמטיקה' 2856 ] 2857 2858 for (text of kTexts) { 2859 text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr'); 2860 const tm = ctx.measureText(text); 2861 // First character. 2862 checkRectListsMatch( 2863 tm.getSelectionRects(0, 1), 2864 placeAndSelectTextInDOM(text, 0, 1) 2865 ); 2866 // Last character. 2867 checkRectListsMatch( 2868 tm.getSelectionRects(text.length - 1, text.length), 2869 placeAndSelectTextInDOM(text, text.length - 1, text.length) 2870 ); 2871 // Whole string. 2872 checkRectListsMatch( 2873 tm.getSelectionRects(0, text.length), 2874 placeAndSelectTextInDOM(text, 0, text.length) 2875 ); 2876 // Intermediate string. 2877 checkRectListsMatch( 2878 tm.getSelectionRects(1, text.length - 1), 2879 placeAndSelectTextInDOM(text, 1, text.length - 1) 2880 ); 2881 // Invalid start > end string. Creates 0 width rectangle. 2882 checkRectListsMatch( 2883 tm.getSelectionRects(3, 2), 2884 placeAndSelectTextInDOM(text, 3, 2) 2885 ); 2886 checkRectListsMatch( 2887 tm.getSelectionRects(1, 0), 2888 placeAndSelectTextInDOM(text, 1, 0) 2889 ); 2890 } 2891 }, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override."); 2892 2893 </script>