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