1""" 2 weasyprint.tests.layout 3 ----------------------- 4 5 Tests for floating boxes layout. 6 7 :copyright: Copyright 2011-2019 Simon Sapin and contributors, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9 10""" 11 12import pytest 13 14from ..formatting_structure import boxes 15from .test_boxes import render_pages 16from .testing_utils import assert_no_logs 17 18 19def outer_area(box): 20 """Return the (x, y, w, h) rectangle for the outer area of a box.""" 21 return (box.position_x, box.position_y, 22 box.margin_width(), box.margin_height()) 23 24 25@assert_no_logs 26def test_floats_1(): 27 # adjacent-floats-001 28 page, = render_pages(''' 29 <style> 30 div { float: left } 31 img { width: 100px; vertical-align: top } 32 </style> 33 <div><img src=pattern.png /></div> 34 <div><img src=pattern.png /></div>''') 35 html, = page.children 36 body, = html.children 37 div_1, div_2 = body.children 38 assert outer_area(div_1) == (0, 0, 100, 100) 39 assert outer_area(div_2) == (100, 0, 100, 100) 40 41 42@assert_no_logs 43def test_floats_2(): 44 # c414-flt-fit-000 45 page, = render_pages(''' 46 <style> 47 body { width: 290px } 48 div { float: left; width: 100px; } 49 img { width: 60px; vertical-align: top } 50 </style> 51 <div><img src=pattern.png /><!-- 1 --></div> 52 <div><img src=pattern.png /><!-- 2 --></div> 53 <div><img src=pattern.png /><!-- 4 --></div> 54 <img src=pattern.png /><!-- 3 55 --><img src=pattern.png /><!-- 5 -->''') 56 html, = page.children 57 body, = html.children 58 div_1, div_2, div_4, anon_block = body.children 59 line_3, line_5 = anon_block.children 60 img_3, = line_3.children 61 img_5, = line_5.children 62 assert outer_area(div_1) == (0, 0, 100, 60) 63 assert outer_area(div_2) == (100, 0, 100, 60) 64 assert outer_area(img_3) == (200, 0, 60, 60) 65 66 assert outer_area(div_4) == (0, 60, 100, 60) 67 assert outer_area(img_5) == (100, 60, 60, 60) 68 69 70@assert_no_logs 71def test_floats_3(): 72 # c414-flt-fit-002 73 page, = render_pages(''' 74 <style type="text/css"> 75 body { width: 200px } 76 p { width: 70px; height: 20px } 77 .left { float: left } 78 .right { float: right } 79 </style> 80 <p class="left"> ⇦ A 1 </p> 81 <p class="left"> ⇦ B 2 </p> 82 <p class="left"> ⇦ A 3 </p> 83 <p class="right"> B 4 ⇨ </p> 84 <p class="left"> ⇦ A 5 </p> 85 <p class="right"> B 6 ⇨ </p> 86 <p class="right"> B 8 ⇨ </p> 87 <p class="left"> ⇦ A 7 </p> 88 <p class="left"> ⇦ A 9 </p> 89 <p class="left"> ⇦ B 10 </p> 90 ''') 91 html, = page.children 92 body, = html.children 93 positions = [(paragraph.position_x, paragraph.position_y) 94 for paragraph in body.children] 95 assert positions == [ 96 (0, 0), (70, 0), (0, 20), (130, 20), (0, 40), (130, 40), 97 (130, 60), (0, 60), (0, 80), (70, 80), ] 98 99 100@assert_no_logs 101def test_floats_4(): 102 # c414-flt-wrap-000 ... more or less 103 page, = render_pages(''' 104 <style> 105 body { width: 100px } 106 p { float: left; height: 100px } 107 img { width: 60px; vertical-align: top } 108 </style> 109 <p style="width: 20px"></p> 110 <p style="width: 100%"></p> 111 <img src=pattern.png /><img src=pattern.png /> 112 ''') 113 html, = page.children 114 body, = html.children 115 p_1, p_2, anon_block = body.children 116 line_1, line_2 = anon_block.children 117 assert anon_block.position_y == 0 118 assert (line_1.position_x, line_1.position_y) == (20, 0) 119 assert (line_2.position_x, line_2.position_y) == (0, 200) 120 121 122@assert_no_logs 123def test_floats_5(): 124 # c414-flt-wrap-000 with text ... more or less 125 page, = render_pages(''' 126 <style> 127 body { width: 100px; font: 60px ahem; } 128 p { float: left; height: 100px } 129 img { width: 60px; vertical-align: top } 130 </style> 131 <p style="width: 20px"></p> 132 <p style="width: 100%"></p> 133 A B 134 ''') 135 html, = page.children 136 body, = html.children 137 p_1, p_2, anon_block = body.children 138 line_1, line_2 = anon_block.children 139 assert anon_block.position_y == 0 140 assert (line_1.position_x, line_1.position_y) == (20, 0) 141 assert (line_2.position_x, line_2.position_y) == (0, 200) 142 143 144@assert_no_logs 145def test_floats_6(): 146 # floats-placement-vertical-001b 147 page, = render_pages(''' 148 <style> 149 body { width: 90px; font-size: 0 } 150 img { vertical-align: top } 151 </style> 152 <body> 153 <span> 154 <img src=pattern.png style="width: 50px" /> 155 <img src=pattern.png style="width: 50px" /> 156 <img src=pattern.png style="float: left; width: 30px" /> 157 </span> 158 ''') 159 html, = page.children 160 body, = html.children 161 line_1, line_2 = body.children 162 span_1, = line_1.children 163 span_2, = line_2.children 164 img_1, = span_1.children 165 img_2, img_3 = span_2.children 166 assert outer_area(img_1) == (0, 0, 50, 50) 167 assert outer_area(img_2) == (30, 50, 50, 50) 168 assert outer_area(img_3) == (0, 50, 30, 30) 169 170 171@assert_no_logs 172def test_floats_7(): 173 # Variant of the above: no <span> 174 page, = render_pages(''' 175 <style> 176 body { width: 90px; font-size: 0 } 177 img { vertical-align: top } 178 </style> 179 <body> 180 <img src=pattern.png style="width: 50px" /> 181 <img src=pattern.png style="width: 50px" /> 182 <img src=pattern.png style="float: left; width: 30px" /> 183 ''') 184 html, = page.children 185 body, = html.children 186 line_1, line_2 = body.children 187 img_1, = line_1.children 188 img_2, img_3 = line_2.children 189 assert outer_area(img_1) == (0, 0, 50, 50) 190 assert outer_area(img_2) == (30, 50, 50, 50) 191 assert outer_area(img_3) == (0, 50, 30, 30) 192 193 194@assert_no_logs 195def test_floats_8(): 196 # Floats do no affect other pages 197 page_1, page_2 = render_pages(''' 198 <style> 199 body { width: 90px; font-size: 0 } 200 img { vertical-align: top } 201 </style> 202 <body> 203 <img src=pattern.png style="float: left; width: 30px" /> 204 <img src=pattern.png style="width: 50px" /> 205 <div style="page-break-before: always"></div> 206 <img src=pattern.png style="width: 50px" /> 207 ''') 208 html, = page_1.children 209 body, = html.children 210 float_img, anon_block, = body.children 211 line, = anon_block.children 212 img_1, = line.children 213 assert outer_area(float_img) == (0, 0, 30, 30) 214 assert outer_area(img_1) == (30, 0, 50, 50) 215 216 html, = page_2.children 217 body, = html.children 218 div, anon_block = body.children 219 line, = anon_block.children 220 img_2, = line.children 221 222 223@assert_no_logs 224def test_floats_9(): 225 # Regression test 226 # https://github.com/Kozea/WeasyPrint/issues/263 227 page, = render_pages('''<div style="top:100%; float:left">''') 228 229 230@assert_no_logs 231def test_floats_page_breaks_1(): 232 # Tests floated images shorter than the page 233 pages = render_pages(''' 234 <style> 235 @page { size: 100px; margin: 10px } 236 img { height: 45px; width:70px; float: left;} 237 </style> 238 <body> 239 <img src=pattern.png> 240 <!-- page break should be here !!! --> 241 <img src=pattern.png> 242 ''') 243 244 assert len(pages) == 2 245 246 page_images = [] 247 for page in pages: 248 images = [d for d in page.descendants() if d.element_tag == 'img'] 249 assert all([img.element_tag == 'img' for img in images]) 250 assert all([img.position_x == 10 for img in images]) 251 page_images.append(images) 252 del images 253 positions_y = [[img.position_y for img in images] 254 for images in page_images] 255 assert positions_y == [[10], [10]] 256 257 258@assert_no_logs 259def test_floats_page_breaks_2(): 260 # Tests floated images taller than the page 261 pages = render_pages(''' 262 <style> 263 @page { size: 100px; margin: 10px } 264 img { height: 81px; width:70px; float: left;} 265 </style> 266 <body> 267 <img src=pattern.png> 268 <!-- page break should be here !!! --> 269 <img src=pattern.png> 270 ''') 271 272 assert len(pages) == 2 273 274 page_images = [] 275 for page in pages: 276 images = [d for d in page.descendants() if d.element_tag == 'img'] 277 assert all([img.element_tag == 'img' for img in images]) 278 assert all([img.position_x == 10 for img in images]) 279 page_images.append(images) 280 del images 281 positions_y = [[img.position_y for img in images] 282 for images in page_images] 283 assert positions_y == [[10], [10]] 284 285 286@assert_no_logs 287def test_floats_page_breaks_3(): 288 # Tests floated images shorter than the page 289 pages = render_pages(''' 290 <style> 291 @page { size: 100px; margin: 10px } 292 img { height: 30px; width:70px; float: left;} 293 </style> 294 <body> 295 <img src=pattern.png> 296 <img src=pattern.png> 297 <!-- page break should be here !!! --> 298 <img src=pattern.png> 299 <img src=pattern.png> 300 <!-- page break should be here !!! --> 301 <img src=pattern.png> 302 ''') 303 304 assert len(pages) == 3 305 306 page_images = [] 307 for page in pages: 308 images = [d for d in page.descendants() if d.element_tag == 'img'] 309 assert all([img.element_tag == 'img' for img in images]) 310 assert all([img.position_x == 10 for img in images]) 311 page_images.append(images) 312 del images 313 positions_y = [[img.position_y for img in images] 314 for images in page_images] 315 assert positions_y == [[10, 40], [10, 40], [10]] 316 317 318@assert_no_logs 319def test_floats_page_breaks_4(): 320 # last float does not fit, pushed to next page 321 pages = render_pages(''' 322 <style> 323 @page{ 324 size: 110px; 325 margin: 10px; 326 padding: 0; 327 } 328 .large { 329 width: 10px; 330 height: 60px; 331 } 332 .small { 333 width: 10px; 334 height: 20px; 335 } 336 </style> 337 <body> 338 <div class="large"></div> 339 <div class="small"></div> 340 <div class="large"></div> 341 ''') 342 343 assert len(pages) == 2 344 page_divs = [] 345 for page in pages: 346 divs = [div for div in page.descendants() if div.element_tag == 'div'] 347 assert all([div.element_tag == 'div' for div in divs]) 348 page_divs.append(divs) 349 del divs 350 351 positions_y = [[div.position_y for div in divs] for divs in page_divs] 352 assert positions_y == [[10, 70], [10]] 353 354 355@assert_no_logs 356def test_floats_page_breaks_5(): 357 # last float does not fit, pushed to next page 358 # center div must not 359 pages = render_pages(''' 360 <style> 361 @page{ 362 size: 110px; 363 margin: 10px; 364 padding: 0; 365 } 366 .large { 367 width: 10px; 368 height: 60px; 369 } 370 .small { 371 width: 10px; 372 height: 20px; 373 page-break-after: avoid; 374 } 375 </style> 376 <body> 377 <div class="large"></div> 378 <div class="small"></div> 379 <div class="large"></div> 380 ''') 381 382 assert len(pages) == 2 383 page_divs = [] 384 for page in pages: 385 divs = [div for div in page.descendants() if div.element_tag == 'div'] 386 assert all([div.element_tag == 'div' for div in divs]) 387 page_divs.append(divs) 388 del divs 389 390 positions_y = [[div.position_y for div in divs] for divs in page_divs] 391 assert positions_y == [[10], [10, 30]] 392 393 394@assert_no_logs 395def test_floats_page_breaks_6(): 396 # center div must be the last element, 397 # but float won't fit and will get pushed anyway 398 pages = render_pages(''' 399 <style> 400 @page{ 401 size: 110px; 402 margin: 10px; 403 padding: 0; 404 } 405 .large { 406 width: 10px; 407 height: 80px; 408 } 409 .small { 410 width: 10px; 411 height: 20px; 412 page-break-after: avoid; 413 } 414 </style> 415 <body> 416 <div class="large"></div> 417 <div class="small"></div> 418 <div class="large"></div> 419 ''') 420 421 assert len(pages) == 3 422 page_divs = [] 423 for page in pages: 424 divs = [div for div in page.descendants() if div.element_tag == 'div'] 425 assert all([div.element_tag == 'div' for div in divs]) 426 page_divs.append(divs) 427 del divs 428 429 positions_y = [[div.position_y for div in divs] for divs in page_divs] 430 assert positions_y == [[10], [10], [10]] 431 432 433@assert_no_logs 434def test_preferred_widths_1(): 435 def get_float_width(body_width): 436 page, = render_pages(''' 437 <style> 438 @font-face { src: url(AHEM____.TTF); font-family: ahem } 439 </style> 440 <body style="width: %spx; font-family: ahem"> 441 <p style="white-space: pre-line; float: left"> 442 Lorem ipsum dolor sit amet, 443 consectetur elit 444 </p> 445 <!-- ^ No-break space here --> 446 ''' % body_width) 447 html, = page.children 448 body, = html.children 449 paragraph, = body.children 450 return paragraph.width 451 # Preferred minimum width: 452 assert get_float_width(10) == len('consectetur elit') * 16 453 # Preferred width: 454 assert get_float_width(1000000) == len('Lorem ipsum dolor sit amet,') * 16 455 456 457@assert_no_logs 458def test_preferred_widths_2(): 459 # Non-regression test: 460 # Incorrect whitespace handling in preferred width used to cause 461 # unnecessary line break. 462 page, = render_pages(''' 463 <p style="float: left">Lorem <em>ipsum</em> dolor.</p> 464 ''') 465 html, = page.children 466 body, = html.children 467 paragraph, = body.children 468 assert len(paragraph.children) == 1 469 assert isinstance(paragraph.children[0], boxes.LineBox) 470 471 472@assert_no_logs 473def test_preferred_widths_3(): 474 page, = render_pages(''' 475 <style>img { width: 20px }</style> 476 <p style="float: left"> 477 <img src=pattern.png><img src=pattern.png><br> 478 <img src=pattern.png></p> 479 ''') 480 html, = page.children 481 body, = html.children 482 paragraph, = body.children 483 assert paragraph.width == 40 484 485 486@assert_no_logs 487def test_preferred_widths_4(): 488 page, = render_pages( 489 '<style>' 490 ' @font-face { src: url(AHEM____.TTF); font-family: ahem }' 491 ' p { font: 20px ahem }' 492 '</style>' 493 '<p style="float: left">XX<br>XX<br>X</p>') 494 html, = page.children 495 body, = html.children 496 paragraph, = body.children 497 assert paragraph.width == 40 498 499 500@assert_no_logs 501def test_preferred_widths_5(): 502 # The space is the start of the line is collapsed. 503 page, = render_pages( 504 '<style>' 505 ' @font-face { src: url(AHEM____.TTF); font-family: ahem }' 506 ' p { font: 20px ahem }' 507 '</style>' 508 '<p style="float: left">XX<br> XX<br>X</p>') 509 html, = page.children 510 body, = html.children 511 paragraph, = body.children 512 assert paragraph.width == 40 513 514 515@assert_no_logs 516def test_float_in_inline(): 517 page, = render_pages(''' 518 <style> 519 @font-face { src: url(AHEM____.TTF); font-family: ahem } 520 body { 521 font-family: ahem; 522 font-size: 20px; 523 } 524 p { 525 width: 14em; 526 text-align: justify; 527 } 528 span { 529 float: right; 530 } 531 </style> 532 <p> 533 aa bb <a><span>cc</span> ddd</a> ee ff 534 </p> 535 ''') 536 html, = page.children 537 body, = html.children 538 paragraph, = body.children 539 line1, line2 = paragraph.children 540 541 p1, a, p2 = line1.children 542 assert p1.width == 6 * 20 543 assert p1.text == 'aa bb ' 544 assert p1.position_x == 0 * 20 545 assert p2.width == 3 * 20 546 assert p2.text == ' ee' 547 assert p2.position_x == 9 * 20 548 span, a_text = a.children 549 assert a_text.width == 3 * 20 # leading space collapse 550 assert a_text.text == 'ddd' 551 assert a_text.position_x == 6 * 20 552 assert span.width == 2 * 20 553 assert span.children[0].children[0].text == 'cc' 554 assert span.position_x == 12 * 20 555 556 p3, = line2.children 557 assert p3.width == 2 * 20 558 559 560@assert_no_logs 561def test_float_next_line(): 562 page, = render_pages(''' 563 <style> 564 @font-face { src: url(AHEM____.TTF); font-family: ahem } 565 body { 566 font-family: ahem; 567 font-size: 20px; 568 } 569 p { 570 text-align: justify; 571 width: 13em; 572 } 573 span { 574 float: left; 575 } 576 </style> 577 <p>pp pp pp pp <a><span>ppppp</span> aa</a> pp pp pp pp pp</p>''') 578 html, = page.children 579 body, = html.children 580 paragraph, = body.children 581 line1, line2, line3 = paragraph.children 582 assert len(line1.children) == 1 583 assert len(line3.children) == 1 584 a, p = line2.children 585 span, a_text = a.children 586 assert span.position_x == 0 587 assert span.width == 5 * 20 588 assert a_text.position_x == a.position_x == 5 * 20 589 assert a_text.width == a.width == 2 * 20 590 assert p.position_x == 7 * 20 591 592 593@assert_no_logs 594def test_float_text_indent_1(): 595 page, = render_pages(''' 596 <style> 597 @font-face { src: url(AHEM____.TTF); font-family: ahem } 598 body { 599 font-family: ahem; 600 font-size: 20px; 601 } 602 p { 603 text-align: justify; 604 text-indent: 1em; 605 width: 14em; 606 } 607 span { 608 float: left; 609 } 610 </style> 611 <p><a>aa <span>float</span> aa</a></p>''') 612 html, = page.children 613 body, = html.children 614 paragraph, = body.children 615 line1, = paragraph.children 616 a, = line1.children 617 a1, span, a2 = a.children 618 span_text, = span.children 619 assert span.position_x == span_text.position_x == 0 620 assert span.width == span_text.width == ( 621 (1 + 5) * 20) # text-indent + span text 622 assert a1.width == 3 * 20 623 assert a1.position_x == (1 + 5 + 1) * 20 # span + a1 text-indent 624 assert a2.width == 2 * 20 # leading space collapse 625 assert a2.position_x == (1 + 5 + 1 + 3) * 20 # span + a1 t-i + a1 626 627 628@assert_no_logs 629def test_float_text_indent_2(): 630 page, = render_pages(''' 631 <style> 632 @font-face { src: url(AHEM____.TTF); font-family: ahem } 633 body { 634 font-family: ahem; 635 font-size: 20px; 636 } 637 p { 638 text-align: justify; 639 text-indent: 1em; 640 width: 14em; 641 } 642 span { 643 float: left; 644 } 645 </style> 646 <p> 647 oooooooooooo 648 <a>aa <span>float</span> aa</a></p>''') 649 html, = page.children 650 body, = html.children 651 paragraph, = body.children 652 line1, line2 = paragraph.children 653 654 p1, = line1.children 655 assert p1.position_x == 1 * 20 # text-indent 656 assert p1.width == 12 * 20 # p text 657 658 a, = line2.children 659 a1, span, a2 = a.children 660 span_text, = span.children 661 assert span.position_x == span_text.position_x == 0 662 assert span.width == span_text.width == ( 663 (1 + 5) * 20) # text-indent + span text 664 assert a1.width == 3 * 20 665 assert a1.position_x == (1 + 5) * 20 # span 666 assert a2.width == 2 * 20 # leading space collapse 667 assert a2.position_x == (1 + 5 + 3) * 20 # span + a1 668 669 670@assert_no_logs 671def test_float_text_indent_3(): 672 page, = render_pages(''' 673 <style> 674 @font-face { src: url(AHEM____.TTF); font-family: ahem } 675 body { 676 font-family: ahem; 677 font-size: 20px; 678 } 679 p { 680 text-align: justify; 681 text-indent: 1em; 682 width: 14em; 683 } 684 span { 685 float: right; 686 } 687 </style> 688 <p> 689 oooooooooooo 690 <a>aa <span>float</span> aa</a> 691 oooooooooooo 692 </p>''') 693 html, = page.children 694 body, = html.children 695 paragraph, = body.children 696 line1, line2, line3 = paragraph.children 697 698 p1, = line1.children 699 assert p1.position_x == 1 * 20 # text-indent 700 assert p1.width == 12 * 20 # p text 701 702 a, = line2.children 703 a1, span, a2 = a.children 704 span_text, = span.children 705 assert span.position_x == span_text.position_x == (14 - 5 - 1) * 20 706 assert span.width == span_text.width == ( 707 (1 + 5) * 20) # text-indent + span text 708 assert a1.position_x == 0 # span 709 assert a2.width == 2 * 20 # leading space collapse 710 assert a2.position_x == (14 - 5 - 1 - 2) * 20 711 712 p2, = line3.children 713 assert p2.position_x == 0 714 assert p2.width == 12 * 20 # p text 715 716 717@pytest.mark.xfail 718@assert_no_logs 719def test_float_fail(): 720 page, = render_pages(''' 721 <style> 722 @font-face { src: url(AHEM____.TTF); font-family: ahem } 723 body { 724 font-family: ahem; 725 font-size: 20px; 726 } 727 p { 728 text-align: justify; 729 width: 12em; 730 } 731 span { 732 float: left; 733 background: red; 734 } 735 a { 736 background: yellow; 737 } 738 </style> 739 <p>bb bb pp bb pp pb <a><span>pp pp</span> apa</a> bb bb</p>''') 740 html, = page.children 741 body, = html.children 742 paragraph, = body.children 743 line1, line2, line3 = paragraph.children 744