diff --git a/Tests/LibWeb/Layout/expected/block-and-inline/float-left-and-right-with-justified-text-in-between.txt b/Tests/LibWeb/Layout/expected/block-and-inline/float-left-and-right-with-justified-text-in-between.txt index c5cebd2cceb..2b3a1eed387 100644 --- a/Tests/LibWeb/Layout/expected/block-and-inline/float-left-and-right-with-justified-text-in-between.txt +++ b/Tests/LibWeb/Layout/expected/block-and-inline/float-left-and-right-with-justified-text-in-between.txt @@ -15,168 +15,192 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline " " frag 6 from TextNode start: 19, length: 3, rect: [763.59375,10 25.96875x22] baseline: 17 "sit" - frag 7 from TextNode start: 23, length: 5, rect: [554,32 52.734375x22] baseline: 17 - "amet," - frag 8 from TextNode start: 28, length: 1, rect: [607,32 62.1875x22] baseline: 17 + frag 7 from TextNode start: 23, length: 4, rect: [554,32 46.421875x22] baseline: 17 + "amet" + frag 8 from TextNode start: 27, length: 1, rect: [600,32 6.3125x22] baseline: 17 + "," + frag 9 from TextNode start: 28, length: 1, rect: [607,32 62.1875x22] baseline: 17 " " - frag 9 from TextNode start: 29, length: 11, rect: [669.1875,32 121.078125x22] baseline: 17 + frag 10 from TextNode start: 29, length: 11, rect: [669.1875,32 121.078125x22] baseline: 17 "consectetur" - frag 10 from TextNode start: 41, length: 10, rect: [554,54 94.671875x22] baseline: 17 + frag 11 from TextNode start: 41, length: 10, rect: [554,54 94.671875x22] baseline: 17 "adipiscing" - frag 11 from TextNode start: 51, length: 1, rect: [649,54 105.40625x22] baseline: 17 + frag 12 from TextNode start: 51, length: 1, rect: [649,54 105.40625x22] baseline: 17 " " - frag 12 from TextNode start: 52, length: 5, rect: [754.40625,54 35.921875x22] baseline: 17 - "elit." - frag 13 from TextNode start: 58, length: 11, rect: [554,76 123.375x22] baseline: 17 + frag 13 from TextNode start: 52, length: 4, rect: [754.40625,54 30.484375x22] baseline: 17 + "elit" + frag 14 from TextNode start: 56, length: 1, rect: [784.40625,54 5.4375x22] baseline: 17 + "." + frag 15 from TextNode start: 58, length: 11, rect: [554,76 123.375x22] baseline: 17 "Suspendisse" - frag 14 from TextNode start: 69, length: 1, rect: [677,76 100.9375x22] baseline: 17 + frag 16 from TextNode start: 69, length: 1, rect: [677,76 100.9375x22] baseline: 17 " " - frag 15 from TextNode start: 70, length: 1, rect: [777.9375,76 11.6875x22] baseline: 17 + frag 17 from TextNode start: 70, length: 1, rect: [777.9375,76 11.6875x22] baseline: 17 "a" - frag 16 from TextNode start: 72, length: 8, rect: [554,98 82.078125x22] baseline: 17 + frag 18 from TextNode start: 72, length: 8, rect: [554,98 82.078125x22] baseline: 17 "placerat" - frag 17 from TextNode start: 80, length: 1, rect: [636,98 29.625x22] baseline: 17 + frag 19 from TextNode start: 80, length: 1, rect: [636,98 29.625x22] baseline: 17 " " - frag 18 from TextNode start: 81, length: 7, rect: [665.625,98 73.875x22] baseline: 17 - "mauris," - frag 19 from TextNode start: 88, length: 1, rect: [739.625,98 29.625x22] baseline: 17 + frag 20 from TextNode start: 81, length: 6, rect: [665.625,98 67.5625x22] baseline: 17 + "mauris" + frag 21 from TextNode start: 87, length: 1, rect: [733.625,98 6.3125x22] baseline: 17 + "," + frag 22 from TextNode start: 88, length: 1, rect: [739.625,98 29.625x22] baseline: 17 " " - frag 20 from TextNode start: 89, length: 2, rect: [769.25,98 20.78125x22] baseline: 17 + frag 23 from TextNode start: 89, length: 2, rect: [769.25,98 20.78125x22] baseline: 17 "ut" - frag 21 from TextNode start: 92, length: 9, rect: [554,120 101.3125x22] baseline: 17 + frag 24 from TextNode start: 92, length: 9, rect: [554,120 101.3125x22] baseline: 17 "elementum" - frag 22 from TextNode start: 101, length: 1, rect: [655,120 10.421875x22] baseline: 17 + frag 25 from TextNode start: 101, length: 1, rect: [655,120 10.421875x22] baseline: 17 " " - frag 23 from TextNode start: 102, length: 3, rect: [665.421875,120 26.390625x22] baseline: 17 - "mi." - frag 24 from TextNode start: 105, length: 1, rect: [692.421875,120 10.421875x22] baseline: 17 + frag 26 from TextNode start: 102, length: 2, rect: [665.421875,120 20.953125x22] baseline: 17 + "mi" + frag 27 from TextNode start: 104, length: 1, rect: [686.421875,120 5.4375x22] baseline: 17 + "." + frag 28 from TextNode start: 105, length: 1, rect: [692.421875,120 10.421875x22] baseline: 17 " " - frag 25 from TextNode start: 106, length: 5, rect: [702.84375,120 56.234375x22] baseline: 17 + frag 29 from TextNode start: 106, length: 5, rect: [702.84375,120 56.234375x22] baseline: 17 "Morbi" - frag 26 from TextNode start: 111, length: 1, rect: [758.84375,120 10.421875x22] baseline: 17 + frag 30 from TextNode start: 111, length: 1, rect: [758.84375,120 10.421875x22] baseline: 17 " " - frag 27 from TextNode start: 112, length: 2, rect: [769.265625,120 20.78125x22] baseline: 17 + frag 31 from TextNode start: 112, length: 2, rect: [769.265625,120 20.78125x22] baseline: 17 "ut" - frag 28 from TextNode start: 115, length: 8, rect: [554,142 78.78125x22] baseline: 17 + frag 32 from TextNode start: 115, length: 8, rect: [554,142 78.78125x22] baseline: 17 "vehicula" - frag 29 from TextNode start: 123, length: 1, rect: [633,142 27.21875x22] baseline: 17 + frag 33 from TextNode start: 123, length: 1, rect: [633,142 27.21875x22] baseline: 17 " " - frag 30 from TextNode start: 124, length: 6, rect: [660.21875,142 62.9375x22] baseline: 17 - "ipsum," - frag 31 from TextNode start: 130, length: 1, rect: [723.21875,142 27.21875x22] baseline: 17 + frag 34 from TextNode start: 124, length: 5, rect: [660.21875,142 56.625x22] baseline: 17 + "ipsum" + frag 35 from TextNode start: 129, length: 1, rect: [716.21875,142 6.3125x22] baseline: 17 + "," + frag 36 from TextNode start: 130, length: 1, rect: [723.21875,142 27.21875x22] baseline: 17 " " - frag 32 from TextNode start: 131, length: 4, rect: [750.4375,142 39.84375x22] baseline: 17 + frag 37 from TextNode start: 131, length: 4, rect: [750.4375,142 39.84375x22] baseline: 17 "eget" - frag 33 from TextNode start: 136, length: 8, rect: [554,164 82.078125x22] baseline: 17 + frag 38 from TextNode start: 136, length: 8, rect: [554,164 82.078125x22] baseline: 17 "placerat" - frag 34 from TextNode start: 144, length: 1, rect: [636,164 11.6875x22] baseline: 17 + frag 39 from TextNode start: 144, length: 1, rect: [636,164 11.6875x22] baseline: 17 " " - frag 35 from TextNode start: 145, length: 6, rect: [647.6875,164 61.890625x22] baseline: 17 - "augue." - frag 36 from TextNode start: 151, length: 1, rect: [709.6875,164 11.6875x22] baseline: 17 + frag 40 from TextNode start: 145, length: 5, rect: [647.6875,164 56.453125x22] baseline: 17 + "augue" + frag 41 from TextNode start: 150, length: 1, rect: [704.6875,164 5.4375x22] baseline: 17 + "." + frag 42 from TextNode start: 151, length: 1, rect: [709.6875,164 11.6875x22] baseline: 17 " " - frag 37 from TextNode start: 152, length: 7, rect: [721.375,164 68.640625x22] baseline: 17 + frag 43 from TextNode start: 152, length: 7, rect: [721.375,164 68.640625x22] baseline: 17 "Integer" - frag 38 from TextNode start: 160, length: 6, rect: [554,186 70.296875x22] baseline: 17 + frag 44 from TextNode start: 160, length: 6, rect: [554,186 70.296875x22] baseline: 17 "rutrum" - frag 39 from TextNode start: 166, length: 1, rect: [624,186 21x22] baseline: 17 + frag 45 from TextNode start: 166, length: 1, rect: [624,186 21x22] baseline: 17 " " - frag 40 from TextNode start: 167, length: 4, rect: [645,186 35.109375x22] baseline: 17 + frag 46 from TextNode start: 167, length: 4, rect: [645,186 35.109375x22] baseline: 17 "nisi" - frag 41 from TextNode start: 171, length: 1, rect: [680,186 21x22] baseline: 17 + frag 47 from TextNode start: 171, length: 1, rect: [680,186 21x22] baseline: 17 " " - frag 42 from TextNode start: 172, length: 4, rect: [701,186 39.84375x22] baseline: 17 + frag 48 from TextNode start: 172, length: 4, rect: [701,186 39.84375x22] baseline: 17 "eget" - frag 43 from TextNode start: 176, length: 1, rect: [741,186 21x22] baseline: 17 + frag 49 from TextNode start: 176, length: 1, rect: [741,186 21x22] baseline: 17 " " - frag 44 from TextNode start: 177, length: 3, rect: [762,186 27.734375x22] baseline: 17 + frag 50 from TextNode start: 177, length: 3, rect: [762,186 27.734375x22] baseline: 17 "dui" - frag 45 from TextNode start: 181, length: 7, rect: [252,212 68.984375x22] baseline: 17 - "dictum," - frag 46 from TextNode start: 188, length: 1, rect: [321,212 23.578125x22] baseline: 17 + frag 51 from TextNode start: 181, length: 6, rect: [252,212 62.671875x22] baseline: 17 + "dictum" + frag 52 from TextNode start: 187, length: 1, rect: [315,212 6.3125x22] baseline: 17 + "," + frag 53 from TextNode start: 188, length: 1, rect: [321,212 23.578125x22] baseline: 17 " " - frag 47 from TextNode start: 189, length: 2, rect: [344.578125,212 23.109375x22] baseline: 17 + frag 54 from TextNode start: 189, length: 2, rect: [344.578125,212 23.109375x22] baseline: 17 "eu" - frag 48 from TextNode start: 191, length: 1, rect: [367.578125,212 23.578125x22] baseline: 17 + frag 55 from TextNode start: 191, length: 1, rect: [367.578125,212 23.578125x22] baseline: 17 " " - frag 49 from TextNode start: 192, length: 8, rect: [391.15625,212 96.75x22] baseline: 17 + frag 56 from TextNode start: 192, length: 8, rect: [391.15625,212 96.75x22] baseline: 17 "accumsan" - frag 50 from TextNode start: 201, length: 4, rect: [252,234 43.875x22] baseline: 17 + frag 57 from TextNode start: 201, length: 4, rect: [252,234 43.875x22] baseline: 17 "enim" - frag 51 from TextNode start: 205, length: 1, rect: [296,234 37.875x22] baseline: 17 + frag 58 from TextNode start: 205, length: 1, rect: [296,234 37.875x22] baseline: 17 " " - frag 52 from TextNode start: 206, length: 10, rect: [333.875,234 93.65625x22] baseline: 17 - "tristique." - frag 53 from TextNode start: 216, length: 1, rect: [427.875,234 37.875x22] baseline: 17 + frag 59 from TextNode start: 206, length: 9, rect: [333.875,234 88.21875x22] baseline: 17 + "tristique" + frag 60 from TextNode start: 215, length: 1, rect: [421.875,234 5.4375x22] baseline: 17 + "." + frag 61 from TextNode start: 216, length: 1, rect: [427.875,234 37.875x22] baseline: 17 " " - frag 54 from TextNode start: 217, length: 2, rect: [465.75,234 22.703125x22] baseline: 17 + frag 62 from TextNode start: 217, length: 2, rect: [465.75,234 22.703125x22] baseline: 17 "Ut" - frag 55 from TextNode start: 220, length: 8, rect: [252,256 80.046875x22] baseline: 17 + frag 63 from TextNode start: 220, length: 8, rect: [252,256 80.046875x22] baseline: 17 "lobortis" - frag 56 from TextNode start: 228, length: 1, rect: [332,256 30.328125x22] baseline: 17 + frag 64 from TextNode start: 228, length: 1, rect: [332,256 30.328125x22] baseline: 17 " " - frag 57 from TextNode start: 229, length: 5, rect: [362.328125,256 55.4375x22] baseline: 17 + frag 65 from TextNode start: 229, length: 5, rect: [362.328125,256 55.4375x22] baseline: 17 "lorem" - frag 58 from TextNode start: 234, length: 1, rect: [417.328125,256 30.328125x22] baseline: 17 + frag 66 from TextNode start: 234, length: 1, rect: [417.328125,256 30.328125x22] baseline: 17 " " - frag 59 from TextNode start: 235, length: 4, rect: [447.65625,256 39.84375x22] baseline: 17 + frag 67 from TextNode start: 235, length: 4, rect: [447.65625,256 39.84375x22] baseline: 17 "eget" - frag 60 from TextNode start: 240, length: 3, rect: [252,278 31.171875x22] baseline: 17 + frag 68 from TextNode start: 240, length: 3, rect: [252,278 31.171875x22] baseline: 17 "est" - frag 61 from TextNode start: 243, length: 1, rect: [283,278 16.5x22] baseline: 17 + frag 69 from TextNode start: 243, length: 1, rect: [283,278 16.5x22] baseline: 17 " " - frag 62 from TextNode start: 244, length: 9, rect: [299.5,278 91.484375x22] baseline: 17 + frag 70 from TextNode start: 244, length: 9, rect: [299.5,278 91.484375x22] baseline: 17 "vulputate" - frag 63 from TextNode start: 253, length: 1, rect: [391.5,278 16.5x22] baseline: 17 + frag 71 from TextNode start: 253, length: 1, rect: [391.5,278 16.5x22] baseline: 17 " " - frag 64 from TextNode start: 254, length: 8, rect: [408,278 80.34375x22] baseline: 17 - "egestas." - frag 65 from TextNode start: 263, length: 7, rect: [252,300 68.640625x22] baseline: 17 + frag 72 from TextNode start: 254, length: 7, rect: [408,278 74.90625x22] baseline: 17 + "egestas" + frag 73 from TextNode start: 261, length: 1, rect: [483,278 5.4375x22] baseline: 17 + "." + frag 74 from TextNode start: 263, length: 7, rect: [252,300 68.640625x22] baseline: 17 "Integer" - frag 66 from TextNode start: 270, length: 1, rect: [321,300 16.390625x22] baseline: 17 + frag 75 from TextNode start: 270, length: 1, rect: [321,300 16.390625x22] baseline: 17 " " - frag 67 from TextNode start: 271, length: 7, rect: [337.390625,300 71.359375x22] baseline: 17 + frag 76 from TextNode start: 271, length: 7, rect: [337.390625,300 71.359375x22] baseline: 17 "laoreet" - frag 68 from TextNode start: 278, length: 1, rect: [408.390625,300 16.390625x22] baseline: 17 + frag 77 from TextNode start: 278, length: 1, rect: [408.390625,300 16.390625x22] baseline: 17 " " - frag 69 from TextNode start: 279, length: 7, rect: [424.78125,300 63.203125x22] baseline: 17 + frag 78 from TextNode start: 279, length: 7, rect: [424.78125,300 63.203125x22] baseline: 17 "lacinia" - frag 70 from TextNode start: 287, length: 4, rect: [252,322 43.1875x22] baseline: 17 + frag 79 from TextNode start: 287, length: 4, rect: [252,322 43.1875x22] baseline: 17 "ante" - frag 71 from TextNode start: 291, length: 1, rect: [295,322 16.640625x22] baseline: 17 + frag 80 from TextNode start: 291, length: 1, rect: [295,322 16.640625x22] baseline: 17 " " - frag 72 from TextNode start: 292, length: 7, rect: [311.640625,322 74.046875x22] baseline: 17 + frag 81 from TextNode start: 292, length: 7, rect: [311.640625,322 74.046875x22] baseline: 17 "sodales" - frag 73 from TextNode start: 299, length: 1, rect: [385.640625,322 16.640625x22] baseline: 17 + frag 82 from TextNode start: 299, length: 1, rect: [385.640625,322 16.640625x22] baseline: 17 " " - frag 74 from TextNode start: 300, length: 9, rect: [402.28125,322 85.484375x22] baseline: 17 - "lobortis." - frag 75 from TextNode start: 310, length: 5, rect: [252,344 60.90625x22] baseline: 17 + frag 83 from TextNode start: 300, length: 8, rect: [402.28125,322 80.046875x22] baseline: 17 + "lobortis" + frag 84 from TextNode start: 308, length: 1, rect: [482.28125,322 5.4375x22] baseline: 17 + "." + frag 85 from TextNode start: 310, length: 5, rect: [252,344 60.90625x22] baseline: 17 "Donec" - frag 76 from TextNode start: 315, length: 1, rect: [313,344 38.828125x22] baseline: 17 + frag 86 from TextNode start: 315, length: 1, rect: [313,344 38.828125x22] baseline: 17 " " - frag 77 from TextNode start: 316, length: 1, rect: [351.828125,344 11.6875x22] baseline: 17 + frag 87 from TextNode start: 316, length: 1, rect: [351.828125,344 11.6875x22] baseline: 17 "a" - frag 78 from TextNode start: 317, length: 1, rect: [363.828125,344 38.828125x22] baseline: 17 + frag 88 from TextNode start: 317, length: 1, rect: [363.828125,344 38.828125x22] baseline: 17 " " - frag 79 from TextNode start: 318, length: 9, rect: [402.65625,344 85.734375x22] baseline: 17 + frag 89 from TextNode start: 318, length: 9, rect: [402.65625,344 85.734375x22] baseline: 17 "tincidunt" - frag 80 from TextNode start: 328, length: 5, rect: [252,366 48.625x22] baseline: 17 - "ante." - frag 81 from TextNode start: 333, length: 1, rect: [301,366 11.609375x22] baseline: 17 + frag 90 from TextNode start: 328, length: 4, rect: [252,366 43.1875x22] baseline: 17 + "ante" + frag 91 from TextNode start: 332, length: 1, rect: [295,366 5.4375x22] baseline: 17 + "." + frag 92 from TextNode start: 333, length: 1, rect: [301,366 11.609375x22] baseline: 17 " " - frag 82 from TextNode start: 334, length: 9, rect: [312.609375,366 94.8125x22] baseline: 17 + frag 93 from TextNode start: 334, length: 9, rect: [312.609375,366 94.8125x22] baseline: 17 "Phasellus" - frag 83 from TextNode start: 343, length: 1, rect: [406.609375,366 11.609375x22] baseline: 17 + frag 94 from TextNode start: 343, length: 1, rect: [406.609375,366 11.609375x22] baseline: 17 " " - frag 84 from TextNode start: 344, length: 1, rect: [418.21875,366 11.6875x22] baseline: 17 + frag 95 from TextNode start: 344, length: 1, rect: [418.21875,366 11.6875x22] baseline: 17 "a" - frag 85 from TextNode start: 345, length: 1, rect: [430.21875,366 11.609375x22] baseline: 17 + frag 96 from TextNode start: 345, length: 1, rect: [430.21875,366 11.609375x22] baseline: 17 " " - frag 86 from TextNode start: 346, length: 4, rect: [441.828125,366 46.03125x22] baseline: 17 + frag 97 from TextNode start: 346, length: 4, rect: [441.828125,366 46.03125x22] baseline: 17 "arcu" - frag 87 from TextNode start: 351, length: 7, rect: [252,388 70.5625x22] baseline: 17 - "tortor." + frag 98 from TextNode start: 351, length: 6, rect: [252,388 65.125x22] baseline: 17 + "tortor" + frag 99 from TextNode start: 357, length: 1, rect: [317,388 5.4375x22] baseline: 17 + "." BlockContainer at (253,11) content-size 300x200 floating [BFC] children: not-inline TextNode <#text> BlockContainer at (489,213) content-size 300x200 floating [BFC] children: not-inline @@ -184,7 +208,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline ViewportPaintable (Viewport<#document>) [0,0 800x600] overflow: [0,0 800x602] PaintableWithLines (BlockContainer) [0,0 800x602] - PaintableWithLines (BlockContainer) [251,9 540x402] overflow: [252,10 538.328125x400] + PaintableWithLines (BlockContainer) [251,9 540x402] overflow: [252,10 538.28125x400] PaintableWithLines (BlockContainer
.left) [252,10 302x202] PaintableWithLines (BlockContainer
.right) [488,212 302x202] TextPaintable (TextNode<#text>) diff --git a/Tests/LibWeb/Layout/expected/div_align.txt b/Tests/LibWeb/Layout/expected/div_align.txt index 471d74c8605..e5d9aa6c1f4 100644 --- a/Tests/LibWeb/Layout/expected/div_align.txt +++ b/Tests/LibWeb/Layout/expected/div_align.txt @@ -39,36 +39,46 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline "is" frag 5 from TextNode start: 12, length: 1, rect: [106,479 8x17] baseline: 13.296875 " " - frag 6 from TextNode start: 13, length: 16, rect: [114,479 102.96875x17] baseline: 13.296875 - "'full-justified'" - frag 7 from TextNode start: 29, length: 1, rect: [217,479 8x17] baseline: 13.296875 + frag 6 from TextNode start: 13, length: 1, rect: [114,479 3.625x17] baseline: 13.296875 + "'" + frag 7 from TextNode start: 14, length: 4, rect: [117,479 24.671875x17] baseline: 13.296875 + "full" + frag 8 from TextNode start: 18, length: 1, rect: [142,479 6.484375x17] baseline: 13.296875 + "-" + frag 9 from TextNode start: 19, length: 9, rect: [148,479 64.5625x17] baseline: 13.296875 + "justified" + frag 10 from TextNode start: 28, length: 1, rect: [213,479 3.625x17] baseline: 13.296875 + "'" + frag 11 from TextNode start: 29, length: 1, rect: [217,479 8x17] baseline: 13.296875 " " - frag 8 from TextNode start: 30, length: 3, rect: [225,479 26.8125x17] baseline: 13.296875 + frag 12 from TextNode start: 30, length: 3, rect: [225,479 26.8125x17] baseline: 13.296875 "and" - frag 9 from TextNode start: 33, length: 1, rect: [251,479 8x17] baseline: 13.296875 + frag 13 from TextNode start: 33, length: 1, rect: [251,479 8x17] baseline: 13.296875 " " - frag 10 from TextNode start: 34, length: 3, rect: [259,479 24.875x17] baseline: 13.296875 + frag 14 from TextNode start: 34, length: 3, rect: [259,479 24.875x17] baseline: 13.296875 "the" - frag 11 from TextNode start: 37, length: 1, rect: [284,479 8x17] baseline: 13.296875 + frag 15 from TextNode start: 37, length: 1, rect: [284,479 8x17] baseline: 13.296875 " " - frag 12 from TextNode start: 38, length: 5, rect: [292,479 43.4375x17] baseline: 13.296875 + frag 16 from TextNode start: 38, length: 5, rect: [292,479 43.4375x17] baseline: 13.296875 "green" - frag 13 from TextNode start: 43, length: 1, rect: [336,479 8x17] baseline: 13.296875 + frag 17 from TextNode start: 43, length: 1, rect: [336,479 8x17] baseline: 13.296875 " " - frag 14 from TextNode start: 44, length: 6, rect: [344,479 57.0625x17] baseline: 13.296875 + frag 18 from TextNode start: 44, length: 6, rect: [344,479 57.0625x17] baseline: 13.296875 "square" - frag 15 from TextNode start: 50, length: 1, rect: [401,479 8x17] baseline: 13.296875 + frag 19 from TextNode start: 50, length: 1, rect: [401,479 8x17] baseline: 13.296875 " " - frag 16 from TextNode start: 51, length: 2, rect: [409,479 13.90625x17] baseline: 13.296875 + frag 20 from TextNode start: 51, length: 2, rect: [409,479 13.90625x17] baseline: 13.296875 "is" - frag 17 from TextNode start: 53, length: 1, rect: [423,479 8x17] baseline: 13.296875 + frag 21 from TextNode start: 53, length: 1, rect: [423,479 8x17] baseline: 13.296875 " " - frag 18 from TextNode start: 54, length: 4, rect: [431,479 26.25x17] baseline: 13.296875 + frag 22 from TextNode start: 54, length: 4, rect: [431,479 26.25x17] baseline: 13.296875 "left" - frag 19 from TextNode start: 58, length: 1, rect: [457,479 8x17] baseline: 13.296875 + frag 23 from TextNode start: 58, length: 1, rect: [457,479 8x17] baseline: 13.296875 " " - frag 20 from TextNode start: 59, length: 8, rect: [465,479 55.671875x17] baseline: 13.296875 - "aligned:" + frag 24 from TextNode start: 59, length: 7, rect: [465,479 51.890625x17] baseline: 13.296875 + "aligned" + frag 25 from TextNode start: 66, length: 1, rect: [517,479 3.78125x17] baseline: 13.296875 + ":" TextNode <#text> BlockContainer at (28,516) content-size 100x100 children: not-inline BlockContainer <(anonymous)> at (8,636) content-size 784x0 children: inline diff --git a/Tests/LibWeb/Screenshot/images/text-direction-ref.png b/Tests/LibWeb/Screenshot/images/text-direction-ref.png new file mode 100644 index 00000000000..a6cb860b88a Binary files /dev/null and b/Tests/LibWeb/Screenshot/images/text-direction-ref.png differ diff --git a/Tests/LibWeb/Screenshot/reference/text-direction-ref.html b/Tests/LibWeb/Screenshot/reference/text-direction-ref.html new file mode 100644 index 00000000000..db173eac534 --- /dev/null +++ b/Tests/LibWeb/Screenshot/reference/text-direction-ref.html @@ -0,0 +1,10 @@ + + diff --git a/Tests/LibWeb/Screenshot/text-direction.html b/Tests/LibWeb/Screenshot/text-direction.html new file mode 100644 index 00000000000..e7000d0c8cd --- /dev/null +++ b/Tests/LibWeb/Screenshot/text-direction.html @@ -0,0 +1,18 @@ + + +
hello test 1, 2, 3!
+
hello test 1, 2, 3!
+
hello test 1, 2, 3!
+
hello test 1, 2, 3!
+ +
אא aaa bbb ccc מממ
+
אא aaa bbb ccc מממ
+ +
אא 1 2 3 מממ
+
אא 1 2 3 מממ
+ +
aa....!!!
+
aa....!!!
+ +
حسنًا ، hello friends مرحباً أيها ا test لأصدقاء end
+
حسنًا ، hello friends مرحباً أيها ا test لأصدقاء end
diff --git a/Userland/Libraries/LibGfx/TextLayout.h b/Userland/Libraries/LibGfx/TextLayout.h index 7e452b59b2b..d1dffb198e7 100644 --- a/Userland/Libraries/LibGfx/TextLayout.h +++ b/Userland/Libraries/LibGfx/TextLayout.h @@ -44,13 +44,23 @@ using DrawGlyphOrEmoji = Variant; class GlyphRun : public RefCounted { public: - GlyphRun(Vector&& glyphs, NonnullRefPtr font) + enum class TextType { + Common, + ContextDependent, + EndPadding, + Ltr, + Rtl, + }; + + GlyphRun(Vector&& glyphs, NonnullRefPtr font, TextType text_type) : m_glyphs(move(glyphs)) , m_font(move(font)) + , m_text_type(text_type) { } [[nodiscard]] Font const& font() const { return m_font; } + [[nodiscard]] TextType text_type() const { return m_text_type; } [[nodiscard]] Vector const& glyphs() const { return m_glyphs; } [[nodiscard]] Vector& glyphs() { return m_glyphs; } [[nodiscard]] bool is_empty() const { return m_glyphs.is_empty(); } @@ -60,6 +70,7 @@ public: private: Vector m_glyphs; NonnullRefPtr m_font; + TextType m_text_type; }; void for_each_glyph_position(FloatPoint baseline_start, Utf8View string, Gfx::Font const& font, Function callback, Optional width = {}); diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 4793d20eef4..64514cfaea5 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -249,8 +249,10 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) auto& line_boxes = m_containing_block_used_values.line_boxes; line_boxes.clear_with_capacity(); + auto direction = m_context_box->computed_values().direction(); + InlineLevelIterator iterator(*this, m_state, containing_block(), m_containing_block_used_values, layout_mode); - LineBuilder line_builder(*this, m_state, m_containing_block_used_values); + LineBuilder line_builder(*this, m_state, m_containing_block_used_values, direction); // NOTE: When we ignore collapsible whitespace chunks at the start of a line, // we have to remember how much start margin that chunk had in the inline diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp index 7c3ee7ceff0..29d6438474d 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.cpp @@ -165,6 +165,39 @@ CSSPixels InlineLevelIterator::next_non_whitespace_sequence_width() return next_width; } +Gfx::GlyphRun::TextType InlineLevelIterator::resolve_text_direction_from_context() +{ + VERIFY(m_text_node_context.has_value()); + + Optional next_known_direction; + for (size_t i = 0;; ++i) { + auto peek = m_text_node_context->chunk_iterator.peek(i); + if (!peek.has_value()) + break; + if (peek->text_type == Gfx::GlyphRun::TextType::Ltr || peek->text_type == Gfx::GlyphRun::TextType::Rtl) { + next_known_direction = peek->text_type; + break; + } + } + + auto last_known_direction = m_text_node_context->last_known_direction; + if (last_known_direction.has_value() && next_known_direction.has_value() && *last_known_direction != *next_known_direction) { + switch (m_containing_block->computed_values().direction()) { + case CSS::Direction::Ltr: + return Gfx::GlyphRun::TextType::Ltr; + case CSS::Direction::Rtl: + return Gfx::GlyphRun::TextType::Rtl; + } + } + + if (last_known_direction.has_value()) + return *last_known_direction; + if (next_known_direction.has_value()) + return *next_known_direction; + + return Gfx::GlyphRun::TextType::ContextDependent; +} + Optional InlineLevelIterator::next_without_lookahead() { if (!m_current_node) @@ -176,18 +209,29 @@ Optional InlineLevelIterator::next_without_lookahead( if (!m_text_node_context.has_value()) enter_text_node(text_node); - auto chunk_opt = m_text_node_context->next_chunk; + auto chunk_opt = m_text_node_context->chunk_iterator.next(); if (!chunk_opt.has_value()) { m_text_node_context = {}; skip_to_next(); return next_without_lookahead(); } - m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next(); - if (!m_text_node_context->next_chunk.has_value()) + if (!m_text_node_context->chunk_iterator.peek(0).has_value()) m_text_node_context->is_last_chunk = true; auto& chunk = chunk_opt.value(); + auto text_type = chunk.text_type; + if (text_type == Gfx::GlyphRun::TextType::Ltr || text_type == Gfx::GlyphRun::TextType::Rtl) + m_text_node_context->last_known_direction = text_type; + + if (m_text_node_context->do_respect_linebreaks && chunk.has_breaking_newline) { + m_text_node_context->is_last_chunk = true; + if (chunk.is_all_whitespace) + text_type = Gfx::GlyphRun::TextType::EndPadding; + } + + if (text_type == Gfx::GlyphRun::TextType::ContextDependent) + text_type = resolve_text_direction_from_context(); if (m_text_node_context->do_respect_linebreaks && chunk.has_breaking_newline) { return Item { @@ -211,7 +255,7 @@ Optional InlineLevelIterator::next_without_lookahead( Item item { .type = Item::Type::Text, .node = &text_node, - .glyph_run = adopt_ref(*new Gfx::GlyphRun(move(glyph_run), chunk.font)), + .glyph_run = adopt_ref(*new Gfx::GlyphRun(move(glyph_run), chunk.font, text_type)), .offset_in_node = chunk.start, .length_in_node = chunk.length, .width = chunk_width, @@ -322,7 +366,6 @@ void InlineLevelIterator::enter_text_node(Layout::TextNode const& text_node) .is_last_chunk = false, .chunk_iterator = TextNode::ChunkIterator { text_node.text_for_rendering(), do_wrap_lines, do_respect_linebreaks, text_node.computed_values().font_list() }, }; - m_text_node_context->next_chunk = m_text_node_context->chunk_iterator.next(); } void InlineLevelIterator::add_extra_box_model_metrics_to_item(Item& item, bool add_leading_metrics, bool add_trailing_metrics) diff --git a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h index 43fd444db33..bc5ba0c0c81 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h +++ b/Userland/Libraries/LibWeb/Layout/InlineLevelIterator.h @@ -57,6 +57,7 @@ public: private: Optional next_without_lookahead(); + Gfx::GlyphRun::TextType resolve_text_direction_from_context(); void skip_to_next(); void compute_next(); @@ -84,7 +85,7 @@ private: bool is_first_chunk {}; bool is_last_chunk {}; TextNode::ChunkIterator chunk_iterator; - Optional next_chunk {}; + Optional last_known_direction {}; }; Optional m_text_node_context; diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.cpp b/Userland/Libraries/LibWeb/Layout/LineBox.cpp index 360efab2a35..22e0d4cd642 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBox.cpp @@ -19,19 +19,14 @@ void LineBox::add_fragment(Node const& layout_node, int start, int length, CSSPi { bool text_align_is_justify = layout_node.computed_values().text_align() == CSS::TextAlign::Justify; if (glyph_run && !text_align_is_justify && !m_fragments.is_empty() && &m_fragments.last().layout_node() == &layout_node && &m_fragments.last().m_glyph_run->font() == &glyph_run->font()) { - auto const fragment_width = m_fragments.last().width(); // The fragment we're adding is from the last Layout::Node on the line. // Expand the last fragment instead of adding a new one with the same Layout::Node. m_fragments.last().m_length = (start - m_fragments.last().m_start) + length; - m_fragments.last().set_width(m_fragments.last().width() + content_width); - for (auto& glyph : glyph_run->glyphs()) { - glyph.visit([&](auto& glyph) { glyph.position.translate_by(fragment_width.to_float(), 0); }); - m_fragments.last().m_glyph_run->append(glyph); - } + m_fragments.last().append_glyph_run(glyph_run, content_width); } else { CSSPixels x_offset = leading_margin + leading_size + m_width; CSSPixels y_offset = 0; - m_fragments.append(LineBoxFragment { layout_node, start, length, CSSPixelPoint(x_offset, y_offset), CSSPixelSize(content_width, content_height), border_box_top, move(glyph_run) }); + m_fragments.append(LineBoxFragment { layout_node, start, length, CSSPixelPoint(x_offset, y_offset), CSSPixelSize(content_width, content_height), border_box_top, m_direction, move(glyph_run) }); } m_width += leading_margin + leading_size + content_width + trailing_size + trailing_margin; m_height = max(m_height, content_height + border_box_top + border_box_bottom); diff --git a/Userland/Libraries/LibWeb/Layout/LineBox.h b/Userland/Libraries/LibWeb/Layout/LineBox.h index bc4debde8ff..f46fc1b39ce 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBox.h +++ b/Userland/Libraries/LibWeb/Layout/LineBox.h @@ -13,7 +13,10 @@ namespace Web::Layout { class LineBox { public: - LineBox() = default; + LineBox(CSS::Direction direction) + : m_direction(direction) + { + } CSSPixels width() const { return m_width; } CSSPixels height() const { return m_height; } @@ -42,6 +45,7 @@ private: CSSPixels m_height { 0 }; CSSPixels m_bottom { 0 }; CSSPixels m_baseline { 0 }; + CSS::Direction m_direction { CSS::Direction::Ltr }; // The amount of available width that was originally available when creating this line box. Used for text justification. AvailableSize m_original_available_width { AvailableSize::make_indefinite() }; diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp index c60e2919369..52a1272054e 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp @@ -12,6 +12,23 @@ namespace Web::Layout { +LineBoxFragment::LineBoxFragment(Node const& layout_node, int start, int length, CSSPixelPoint offset, CSSPixelSize size, CSSPixels border_box_top, CSS::Direction direction, RefPtr glyph_run) + : m_layout_node(layout_node) + , m_start(start) + , m_length(length) + , m_offset(offset) + , m_size(size) + , m_border_box_top(border_box_top) + , m_direction(direction) + , m_glyph_run(move(glyph_run)) +{ + if (m_glyph_run) { + m_current_insert_direction = resolve_glyph_run_direction(m_glyph_run->text_type()); + if (m_direction == CSS::Direction::Rtl) + m_insert_position = m_size.width().to_float(); + } +} + bool LineBoxFragment::ends_in_whitespace() const { auto text = this->text(); @@ -45,4 +62,105 @@ bool LineBoxFragment::is_atomic_inline() const return layout_node().is_replaced_box() || (layout_node().display().is_inline_outside() && !layout_node().display().is_flow_inside()); } +CSS::Direction LineBoxFragment::resolve_glyph_run_direction(Gfx::GlyphRun::TextType text_type) const +{ + switch (text_type) { + case Gfx::GlyphRun::TextType::Common: + case Gfx::GlyphRun::TextType::ContextDependent: + case Gfx::GlyphRun::TextType::EndPadding: + return m_direction; + case Gfx::GlyphRun::TextType::Ltr: + return CSS::Direction::Ltr; + case Gfx::GlyphRun::TextType::Rtl: + return CSS::Direction::Rtl; + default: + VERIFY_NOT_REACHED(); + } +} + +void LineBoxFragment::append_glyph_run(RefPtr const& glyph_run, CSSPixels run_width) +{ + switch (m_direction) { + case CSS::Direction::Ltr: + append_glyph_run_ltr(glyph_run, run_width); + break; + case CSS::Direction::Rtl: + append_glyph_run_rtl(glyph_run, run_width); + break; + } +} + +void LineBoxFragment::append_glyph_run_ltr(RefPtr const& glyph_run, CSSPixels run_width) +{ + auto run_direction = resolve_glyph_run_direction(glyph_run->text_type()); + + if (m_current_insert_direction != run_direction) { + if (run_direction == CSS::Direction::Rtl) + m_insert_position = width().to_float(); + m_current_insert_direction = run_direction; + } + + switch (run_direction) { + case CSS::Direction::Ltr: + for (auto& glyph : glyph_run->glyphs()) { + glyph.visit([&](auto& glyph) { glyph.position.translate_by(width().to_float(), 0); }); + m_glyph_run->append(glyph); + } + break; + case CSS::Direction::Rtl: + for (auto& glyph : m_glyph_run->glyphs()) { + glyph.visit([&](auto& glyph) { + if (glyph.position.x() >= m_insert_position) + glyph.position.translate_by(run_width.to_float(), 0); + }); + } + for (auto& glyph : glyph_run->glyphs()) { + glyph.visit([&](auto& glyph) { glyph.position.translate_by(m_insert_position, 0); }); + m_glyph_run->append(glyph); + } + break; + } + + m_size.set_width(width() + run_width); +} + +void LineBoxFragment::append_glyph_run_rtl(RefPtr const& glyph_run, CSSPixels run_width) +{ + auto run_direction = resolve_glyph_run_direction(glyph_run->text_type()); + + if (m_current_insert_direction != run_direction) { + if (run_direction == CSS::Direction::Ltr) + m_insert_position = 0; + m_current_insert_direction = run_direction; + } + + switch (run_direction) { + case CSS::Direction::Ltr: + for (auto& glyph : m_glyph_run->glyphs()) { + glyph.visit([&](auto& glyph) { + if (glyph.position.x() >= m_insert_position) + glyph.position.translate_by(run_width.to_float(), 0); + }); + } + for (auto& glyph : glyph_run->glyphs()) { + glyph.visit([&](auto& glyph) { glyph.position.translate_by(m_insert_position, 0); }); + m_glyph_run->append(glyph); + } + break; + case CSS::Direction::Rtl: + if (glyph_run->text_type() != Gfx::GlyphRun::TextType::EndPadding) { + for (auto& glyph : m_glyph_run->glyphs()) { + glyph.visit([&](auto& glyph) { glyph.position.translate_by(run_width.to_float(), 0); }); + } + } + for (auto& glyph : glyph_run->glyphs()) { + m_glyph_run->append(glyph); + } + break; + } + + m_size.set_width(width() + run_width); + m_insert_position += run_width.to_float(); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h index b5fb84a2aeb..4c343c17850 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h +++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h @@ -19,16 +19,7 @@ class LineBoxFragment { friend class LineBox; public: - LineBoxFragment(Node const& layout_node, int start, int length, CSSPixelPoint offset, CSSPixelSize size, CSSPixels border_box_top, RefPtr glyph_run) - : m_layout_node(layout_node) - , m_start(start) - , m_length(length) - , m_offset(offset) - , m_size(size) - , m_border_box_top(border_box_top) - , m_glyph_run(move(glyph_run)) - { - } + LineBoxFragment(Node const& layout_node, int start, int length, CSSPixelPoint offset, CSSPixelSize size, CSSPixels border_box_top, CSS::Direction, RefPtr); Node const& layout_node() const { return m_layout_node; } int start() const { return m_start; } @@ -60,8 +51,13 @@ public: bool is_atomic_inline() const; RefPtr glyph_run() const { return m_glyph_run; } + void append_glyph_run(RefPtr const&, CSSPixels run_width); private: + CSS::Direction resolve_glyph_run_direction(Gfx::GlyphRun::TextType) const; + void append_glyph_run_ltr(RefPtr const&, CSSPixels run_width); + void append_glyph_run_rtl(RefPtr const&, CSSPixels run_width); + JS::NonnullGCPtr m_layout_node; int m_start { 0 }; int m_length { 0 }; @@ -69,7 +65,11 @@ private: CSSPixelSize m_size; CSSPixels m_border_box_top { 0 }; CSSPixels m_baseline { 0 }; + CSS::Direction m_direction { CSS::Direction::Ltr }; + RefPtr m_glyph_run; + float m_insert_position { 0 }; + CSS::Direction m_current_insert_direction { CSS::Direction::Ltr }; }; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp index 35bfc39f30f..9f8531d596e 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp @@ -10,10 +10,11 @@ namespace Web::Layout { -LineBuilder::LineBuilder(InlineFormattingContext& context, LayoutState& layout_state, LayoutState::UsedValues& containing_block_used_values) +LineBuilder::LineBuilder(InlineFormattingContext& context, LayoutState& layout_state, LayoutState::UsedValues& containing_block_used_values, CSS::Direction direction) : m_context(context) , m_layout_state(layout_state) , m_containing_block_used_values(containing_block_used_values) + , m_direction(direction) { m_text_indent = m_context.containing_block().computed_values().text_indent().to_px(m_context.containing_block(), m_containing_block_used_values.content_width()); begin_new_line(false); @@ -35,7 +36,7 @@ void LineBuilder::break_line(ForcedBreak forced_break, Optional next_ size_t break_count = 0; bool floats_intrude_at_current_y = false; do { - m_containing_block_used_values.line_boxes.append(LineBox()); + m_containing_block_used_values.line_boxes.append(LineBox(m_direction)); begin_new_line(true, break_count == 0); break_count++; floats_intrude_at_current_y = m_context.any_floats_intrude_at_y(m_current_y); @@ -80,7 +81,7 @@ LineBox& LineBuilder::ensure_last_line_box() { auto& line_boxes = m_containing_block_used_values.line_boxes; if (line_boxes.is_empty()) - line_boxes.append(LineBox {}); + line_boxes.append(LineBox(m_direction)); return line_boxes.last(); } diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.h b/Userland/Libraries/LibWeb/Layout/LineBuilder.h index 7b3f6ff8aee..178c45bac14 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.h +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.h @@ -15,7 +15,7 @@ class LineBuilder { AK_MAKE_NONMOVABLE(LineBuilder); public: - LineBuilder(InlineFormattingContext&, LayoutState&, LayoutState::UsedValues& containing_block_used_values); + LineBuilder(InlineFormattingContext&, LayoutState&, LayoutState::UsedValues& containing_block_used_values, CSS::Direction); ~LineBuilder(); enum class ForcedBreak { @@ -63,6 +63,7 @@ private: CSSPixels m_current_y { 0 }; CSSPixels m_max_height_on_current_line { 0 }; CSSPixels m_text_indent { 0 }; + CSS::Direction m_direction { CSS::Direction::Ltr }; bool m_last_line_needs_update { false }; }; diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.cpp b/Userland/Libraries/LibWeb/Layout/TextNode.cpp index aee8cb6720a..39ba6c9a434 100644 --- a/Userland/Libraries/LibWeb/Layout/TextNode.cpp +++ b/Userland/Libraries/LibWeb/Layout/TextNode.cpp @@ -400,7 +400,67 @@ TextNode::ChunkIterator::ChunkIterator(StringView text, bool wrap_lines, bool re { } +static Gfx::GlyphRun::TextType text_type_for_code_point(u32 code_point) +{ + switch (Unicode::bidirectional_class(code_point)) { + case Unicode::BidiClass::WhiteSpaceNeutral: + + case Unicode::BidiClass::BlockSeparator: + case Unicode::BidiClass::SegmentSeparator: + case Unicode::BidiClass::CommonNumberSeparator: + case Unicode::BidiClass::DirNonSpacingMark: + + case Unicode::BidiClass::ArabicNumber: + case Unicode::BidiClass::EuropeanNumber: + case Unicode::BidiClass::EuropeanNumberSeparator: + case Unicode::BidiClass::EuropeanNumberTerminator: + return Gfx::GlyphRun::TextType::ContextDependent; + + case Unicode::BidiClass::BoundaryNeutral: + case Unicode::BidiClass::OtherNeutral: + case Unicode::BidiClass::FirstStrongIsolate: + case Unicode::BidiClass::PopDirectionalFormat: + case Unicode::BidiClass::PopDirectionalIsolate: + return Gfx::GlyphRun::TextType::Common; + + case Unicode::BidiClass::LeftToRight: + case Unicode::BidiClass::LeftToRightEmbedding: + case Unicode::BidiClass::LeftToRightIsolate: + case Unicode::BidiClass::LeftToRightOverride: + return Gfx::GlyphRun::TextType::Ltr; + + case Unicode::BidiClass::RightToLeft: + case Unicode::BidiClass::RightToLeftArabic: + case Unicode::BidiClass::RightToLeftEmbedding: + case Unicode::BidiClass::RightToLeftIsolate: + case Unicode::BidiClass::RightToLeftOverride: + return Gfx::GlyphRun::TextType::Rtl; + + default: + VERIFY_NOT_REACHED(); + } +} + Optional TextNode::ChunkIterator::next() +{ + if (!m_peek_queue.is_empty()) + return m_peek_queue.take_first(); + return next_without_peek(); +} + +Optional TextNode::ChunkIterator::peek(size_t count) +{ + while (m_peek_queue.size() <= count) { + auto next = next_without_peek(); + if (!next.has_value()) + return {}; + m_peek_queue.append(*next); + } + + return m_peek_queue[count]; +} + +Optional TextNode::ChunkIterator::next_without_peek() { if (m_iterator == m_utf8_view.end()) return {}; @@ -408,35 +468,41 @@ Optional TextNode::ChunkIterator::next() auto start_of_chunk = m_iterator; Gfx::Font const& font = m_font_cascade_list.font_for_code_point(*m_iterator); + auto text_type = text_type_for_code_point(*m_iterator); while (m_iterator != m_utf8_view.end()) { if (&font != &m_font_cascade_list.font_for_code_point(*m_iterator)) { - if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font, text_type); result.has_value()) return result.release_value(); } if (m_respect_linebreaks && *m_iterator == '\n') { // Newline encountered, and we're supposed to preserve them. // If we have accumulated some code points in the current chunk, commit them now and continue with the newline next time. - if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font, text_type); result.has_value()) return result.release_value(); // Otherwise, commit the newline! ++m_iterator; - auto result = try_commit_chunk(start_of_chunk, m_iterator, true, font); + auto result = try_commit_chunk(start_of_chunk, m_iterator, true, font, text_type); VERIFY(result.has_value()); return result.release_value(); } if (m_wrap_lines) { + if (text_type != text_type_for_code_point(*m_iterator)) { + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font, text_type); result.has_value()) + return result.release_value(); + } + if (is_ascii_space(*m_iterator)) { // Whitespace encountered, and we're allowed to break on whitespace. // If we have accumulated some code points in the current chunk, commit them now and continue with the whitespace next time. - if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font, text_type); result.has_value()) return result.release_value(); // Otherwise, commit the whitespace! ++m_iterator; - if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_iterator, false, font, text_type); result.has_value()) return result.release_value(); continue; } @@ -447,14 +513,14 @@ Optional TextNode::ChunkIterator::next() if (start_of_chunk != m_utf8_view.end()) { // Try to output whatever's left at the end of the text node. - if (auto result = try_commit_chunk(start_of_chunk, m_utf8_view.end(), false, font); result.has_value()) + if (auto result = try_commit_chunk(start_of_chunk, m_utf8_view.end(), false, font, text_type); result.has_value()) return result.release_value(); } return {}; } -Optional TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, Gfx::Font const& font) const +Optional TextNode::ChunkIterator::try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, Gfx::Font const& font, Gfx::GlyphRun::TextType text_type) const { auto byte_offset = m_utf8_view.byte_offset_of(start); auto byte_length = m_utf8_view.byte_offset_of(end) - byte_offset; @@ -468,6 +534,7 @@ Optional TextNode::ChunkIterator::try_commit_chunk(Utf8View::It .length = byte_length, .has_breaking_newline = has_breaking_newline, .is_all_whitespace = is_all_whitespace(chunk_view.as_string()), + .text_type = text_type, }; } diff --git a/Userland/Libraries/LibWeb/Layout/TextNode.h b/Userland/Libraries/LibWeb/Layout/TextNode.h index 72c3b09033e..c6f3d691cfb 100644 --- a/Userland/Libraries/LibWeb/Layout/TextNode.h +++ b/Userland/Libraries/LibWeb/Layout/TextNode.h @@ -33,21 +33,26 @@ public: size_t length { 0 }; bool has_breaking_newline { false }; bool is_all_whitespace { false }; + Gfx::GlyphRun::TextType text_type; }; class ChunkIterator { public: ChunkIterator(StringView text, bool wrap_lines, bool respect_linebreaks, Gfx::FontCascadeList const&); Optional next(); + Optional peek(size_t); private: - Optional try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, Gfx::Font const&) const; + Optional next_without_peek(); + Optional try_commit_chunk(Utf8View::Iterator const& start, Utf8View::Iterator const& end, bool has_breaking_newline, Gfx::Font const&, Gfx::GlyphRun::TextType) const; bool const m_wrap_lines; bool const m_respect_linebreaks; Utf8View m_utf8_view; Utf8View::Iterator m_iterator; Gfx::FontCascadeList const& m_font_cascade_list; + + Vector m_peek_queue; }; void invalidate_text_for_rendering(); diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index ced6f483038..c139b6c47f9 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -233,7 +233,7 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, String raw_text, G if (rect.is_empty()) return; - auto glyph_run = adopt_ref(*new Gfx::GlyphRun({}, font)); + auto glyph_run = adopt_ref(*new Gfx::GlyphRun({}, font, Gfx::GlyphRun::TextType::Ltr)); float glyph_run_width = 0; Gfx::for_each_glyph_position( { 0, 0 }, raw_text.code_points(), font, [&](Gfx::DrawGlyphOrEmoji const& glyph_or_emoji) {