@@ -307,6 +307,246 @@ void testMain() async {
307307 );
308308 });
309309 });
310+
311+ group ('$CanvasParagraph .getPositionForOffset' , () {
312+ test ('handles single-line multi-span paragraphs' , () {
313+ final CanvasParagraph paragraph = rich (ahemStyle, (builder) {
314+ builder.pushStyle (EngineTextStyle .only (color: blue));
315+ builder.addText ('Lorem ' );
316+ builder.pushStyle (EngineTextStyle .only (color: green));
317+ builder.addText ('ipsum ' );
318+ builder.pop ();
319+ builder.addText ('.' );
320+ })
321+ ..layout (constrain (double .infinity));
322+
323+ // Above the line.
324+ expect (
325+ paragraph.getPositionForOffset (ui.Offset (0 , - 5 )),
326+ pos (0 , ui.TextAffinity .downstream),
327+ );
328+ // At the top left corner of the line.
329+ expect (
330+ paragraph.getPositionForOffset (ui.Offset (0 , 0 )),
331+ pos (0 , ui.TextAffinity .downstream),
332+ );
333+ // At the beginning of the line.
334+ expect (
335+ paragraph.getPositionForOffset (ui.Offset (0 , 5 )),
336+ pos (0 , ui.TextAffinity .downstream),
337+ );
338+ // Below the line.
339+ expect (
340+ paragraph.getPositionForOffset (ui.Offset (0 , 12 )),
341+ pos (13 , ui.TextAffinity .upstream),
342+ );
343+ // At the end of the line.
344+ expect (
345+ paragraph.getPositionForOffset (ui.Offset (130 , 5 )),
346+ pos (13 , ui.TextAffinity .upstream),
347+ );
348+ // On the left half of "p" in "ipsum".
349+ expect (
350+ paragraph.getPositionForOffset (ui.Offset (74 , 5 )),
351+ pos (7 , ui.TextAffinity .downstream),
352+ );
353+ // On the right half of "p" in "ipsum".
354+ expect (
355+ paragraph.getPositionForOffset (ui.Offset (76 , 5 )),
356+ pos (8 , ui.TextAffinity .upstream),
357+ );
358+ // At the top of the line, on the left half of "p" in "ipsum".
359+ expect (
360+ paragraph.getPositionForOffset (ui.Offset (74 , 0 )),
361+ pos (7 , ui.TextAffinity .downstream),
362+ );
363+ // At the top of the line, on the right half of "p" in "ipsum".
364+ expect (
365+ paragraph.getPositionForOffset (ui.Offset (76 , 0 )),
366+ pos (8 , ui.TextAffinity .upstream),
367+ );
368+ });
369+
370+ test ('handles multi-line single-span paragraphs' , () {
371+ final CanvasParagraph paragraph = rich (ahemStyle, (builder) {
372+ builder.addText ('Lorem ipsum dolor sit' );
373+ })
374+ ..layout (constrain (90.0 ));
375+
376+ // Lines:
377+ // "Lorem "
378+ // "ipsum "
379+ // "dolor sit"
380+
381+ // Above the first line.
382+ expect (
383+ paragraph.getPositionForOffset (ui.Offset (0 , - 5 )),
384+ pos (0 , ui.TextAffinity .downstream),
385+ );
386+ // At the top left corner of the line.
387+ expect (
388+ paragraph.getPositionForOffset (ui.Offset (0 , 0 )),
389+ pos (0 , ui.TextAffinity .downstream),
390+ );
391+ // At the beginning of the first line.
392+ expect (
393+ paragraph.getPositionForOffset (ui.Offset (0 , 5 )),
394+ pos (0 , ui.TextAffinity .downstream),
395+ );
396+ // At the end of the first line.
397+ expect (
398+ paragraph.getPositionForOffset (ui.Offset (60 , 5 )),
399+ pos (6 , ui.TextAffinity .upstream),
400+ );
401+ // After the end of the first line to the right.
402+ expect (
403+ paragraph.getPositionForOffset (ui.Offset (70 , 5 )),
404+ pos (6 , ui.TextAffinity .upstream),
405+ );
406+ // On the left half of " " in "Lorem ".
407+ expect (
408+ paragraph.getPositionForOffset (ui.Offset (54 , 5 )),
409+ pos (5 , ui.TextAffinity .downstream),
410+ );
411+ // On the right half of " " in "Lorem ".
412+ expect (
413+ paragraph.getPositionForOffset (ui.Offset (56 , 5 )),
414+ pos (6 , ui.TextAffinity .upstream),
415+ );
416+
417+ // At the beginning of the second line "ipsum ".
418+ expect (
419+ paragraph.getPositionForOffset (ui.Offset (0 , 15 )),
420+ pos (6 , ui.TextAffinity .downstream),
421+ );
422+ // At the end of the second line.
423+ expect (
424+ paragraph.getPositionForOffset (ui.Offset (60 , 15 )),
425+ pos (12 , ui.TextAffinity .upstream),
426+ );
427+ // After the end of the second line to the right.
428+ expect (
429+ paragraph.getPositionForOffset (ui.Offset (70 , 15 )),
430+ pos (12 , ui.TextAffinity .upstream),
431+ );
432+
433+ // Below the third line "dolor sit".
434+ expect (
435+ paragraph.getPositionForOffset (ui.Offset (0 , 40 )),
436+ pos (21 , ui.TextAffinity .upstream),
437+ );
438+ // At the end of the third line.
439+ expect (
440+ paragraph.getPositionForOffset (ui.Offset (90 , 25 )),
441+ pos (21 , ui.TextAffinity .upstream),
442+ );
443+ // After the end of the third line to the right.
444+ expect (
445+ paragraph.getPositionForOffset (ui.Offset (100 , 25 )),
446+ pos (21 , ui.TextAffinity .upstream),
447+ );
448+ // On the left half of " " in "dolor sit".
449+ expect (
450+ paragraph.getPositionForOffset (ui.Offset (54 , 25 )),
451+ pos (17 , ui.TextAffinity .downstream),
452+ );
453+ // On the right half of " " in "dolor sit".
454+ expect (
455+ paragraph.getPositionForOffset (ui.Offset (56 , 25 )),
456+ pos (18 , ui.TextAffinity .upstream),
457+ );
458+ });
459+
460+ test ('handles multi-line multi-span paragraphs' , () {
461+ final CanvasParagraph paragraph = rich (ahemStyle, (builder) {
462+ builder.pushStyle (EngineTextStyle .only (color: blue));
463+ builder.addText ('Lorem ipsum ' );
464+ builder.pushStyle (EngineTextStyle .only (color: green));
465+ builder.addText ('dolor ' );
466+ builder.pop ();
467+ builder.addText ('sit' );
468+ })
469+ ..layout (constrain (90.0 ));
470+
471+ // Lines:
472+ // "Lorem "
473+ // "ipsum "
474+ // "dolor sit"
475+
476+ // Above the first line.
477+ expect (
478+ paragraph.getPositionForOffset (ui.Offset (0 , - 5 )),
479+ pos (0 , ui.TextAffinity .downstream),
480+ );
481+ // At the beginning of the first line.
482+ expect (
483+ paragraph.getPositionForOffset (ui.Offset (0 , 5 )),
484+ pos (0 , ui.TextAffinity .downstream),
485+ );
486+ // At the end of the first line.
487+ expect (
488+ paragraph.getPositionForOffset (ui.Offset (60 , 5 )),
489+ pos (6 , ui.TextAffinity .upstream),
490+ );
491+ // After the end of the first line to the right.
492+ expect (
493+ paragraph.getPositionForOffset (ui.Offset (70 , 5 )),
494+ pos (6 , ui.TextAffinity .upstream),
495+ );
496+ // On the left half of " " in "Lorem ".
497+ expect (
498+ paragraph.getPositionForOffset (ui.Offset (54 , 5 )),
499+ pos (5 , ui.TextAffinity .downstream),
500+ );
501+ // On the right half of " " in "Lorem ".
502+ expect (
503+ paragraph.getPositionForOffset (ui.Offset (56 , 5 )),
504+ pos (6 , ui.TextAffinity .upstream),
505+ );
506+
507+ // At the beginning of the second line "ipsum ".
508+ expect (
509+ paragraph.getPositionForOffset (ui.Offset (0 , 15 )),
510+ pos (6 , ui.TextAffinity .downstream),
511+ );
512+ // At the end of the second line.
513+ expect (
514+ paragraph.getPositionForOffset (ui.Offset (60 , 15 )),
515+ pos (12 , ui.TextAffinity .upstream),
516+ );
517+ // After the end of the second line to the right.
518+ expect (
519+ paragraph.getPositionForOffset (ui.Offset (70 , 15 )),
520+ pos (12 , ui.TextAffinity .upstream),
521+ );
522+
523+ // Below the third line "dolor sit".
524+ expect (
525+ paragraph.getPositionForOffset (ui.Offset (0 , 40 )),
526+ pos (21 , ui.TextAffinity .upstream),
527+ );
528+ // At the end of the third line.
529+ expect (
530+ paragraph.getPositionForOffset (ui.Offset (90 , 25 )),
531+ pos (21 , ui.TextAffinity .upstream),
532+ );
533+ // After the end of the third line to the right.
534+ expect (
535+ paragraph.getPositionForOffset (ui.Offset (100 , 25 )),
536+ pos (21 , ui.TextAffinity .upstream),
537+ );
538+ // On the left half of " " in "dolor sit".
539+ expect (
540+ paragraph.getPositionForOffset (ui.Offset (54 , 25 )),
541+ pos (17 , ui.TextAffinity .downstream),
542+ );
543+ // On the right half of " " in "dolor sit".
544+ expect (
545+ paragraph.getPositionForOffset (ui.Offset (56 , 25 )),
546+ pos (18 , ui.TextAffinity .upstream),
547+ );
548+ });
549+ });
310550}
311551
312552/// Shortcut to create a [ui.TextBox] with an optional [ui.TextDirection] .
@@ -319,3 +559,8 @@ ui.TextBox box(
319559]) {
320560 return ui.TextBox .fromLTRBD (left, top, right, bottom, direction);
321561}
562+
563+ /// Shortcut to create a [ui.TextPosition] .
564+ ui.TextPosition pos (int offset, ui.TextAffinity affinity) {
565+ return ui.TextPosition (offset: offset, affinity: affinity);
566+ }
0 commit comments