From e04a6044dad9855a2ac63df310038e151034d0af Mon Sep 17 00:00:00 2001 From: Gitea Action Date: Wed, 12 Nov 2025 12:03:27 +0000 Subject: [PATCH] Update coverage badges [skip ci] --- .coveragerc | 31 + .gitea/workflows/ci.yml | 179 + .gitignore | 56 + ARCHITECTURE.md | 233 + FONT_SWITCHING_FEATURE.md | 139 + LICENSE | 21 + MANIFEST.in | 11 + README.md | 240 + cov_info/coverage-docs.svg | 58 + cov_info/coverage-summary.txt | 1 + cov_info/coverage.json | 1 + cov_info/coverage.svg | 21 + cov_info/coverage.xml | 5373 +++++++++++++ cov_info/htmlcov/.gitignore | 2 + cov_info/htmlcov/class_index.html | 1453 ++++ cov_info/htmlcov/coverage_html_cb_6fb7b396.js | 733 ++ cov_info/htmlcov/favicon_32_cb_58284776.png | Bin 0 -> 1732 bytes cov_info/htmlcov/function_index.html | 6983 +++++++++++++++++ cov_info/htmlcov/index.html | 443 ++ cov_info/htmlcov/keybd_closed_cb_ce680311.png | Bin 0 -> 9004 bytes cov_info/htmlcov/status.json | 1 + cov_info/htmlcov/style_cb_6b508a39.css | 377 + .../z_20e398e67121d457___init___py.html | 121 + .../z_263f2e628cef8c50___init___py.html | 116 + .../z_263f2e628cef8c50_epub_reader_py.html | 702 ++ ...z_263f2e628cef8c50_html_extraction_py.html | 1031 +++ .../z_40407af872b0cf37___init___py.html | 133 + .../htmlcov/z_40407af872b0cf37_base_py.html | 547 ++ ...40407af872b0cf37_callback_registry_py.html | 377 + .../z_40407af872b0cf37_highlight_py.html | 348 + .../htmlcov/z_40407af872b0cf37_query_py.html | 185 + .../z_427cc3035faf7633___init___py.html | 110 + ...427cc3035faf7633_document_layouter_py.html | 822 ++ .../z_427cc3035faf7633_ereader_layout_py.html | 853 ++ ...z_427cc3035faf7633_ereader_manager_py.html | 987 +++ .../z_427cc3035faf7633_page_buffer_py.html | 619 ++ ...z_427cc3035faf7633_table_optimizer_py.html | 495 ++ .../z_7d48e1f4c6486fa2___init___py.html | 124 + .../htmlcov/z_7d48e1f4c6486fa2_box_py.html | 139 + .../z_7d48e1f4c6486fa2_dynamic_page_py.html | 517 ++ .../z_7d48e1f4c6486fa2_functional_py.html | 560 ++ .../htmlcov/z_7d48e1f4c6486fa2_image_py.html | 379 + ...48e1f4c6486fa2_interaction_handler_py.html | 409 + .../htmlcov/z_7d48e1f4c6486fa2_page_py.html | 636 ++ .../htmlcov/z_7d48e1f4c6486fa2_table_py.html | 805 ++ .../htmlcov/z_7d48e1f4c6486fa2_text_py.html | 846 ++ .../z_af715639580e2d86___init___py.html | 121 + .../htmlcov/z_af715639580e2d86_block_py.html | 1514 ++++ .../z_af715639580e2d86_document_py.html | 694 ++ .../z_af715639580e2d86_functional_py.html | 444 ++ .../htmlcov/z_af715639580e2d86_inline_py.html | 519 ++ ...af715639580e2d86_interactive_image_py.html | 262 + .../z_ba7f6bdeb0188088___init___py.html | 122 + .../z_ba7f6bdeb0188088_abstract_style_py.html | 441 ++ .../z_ba7f6bdeb0188088_alignment_py.html | 124 + .../z_ba7f6bdeb0188088_concrete_style_py.html | 583 ++ .../htmlcov/z_ba7f6bdeb0188088_fonts_py.html | 505 ++ .../z_ba7f6bdeb0188088_page_style_py.html | 157 + .../z_fc521de9aff00981___init___py.html | 107 + docs/images/README.md | 213 + docs/images/demo_08_bundled_fonts.png | Bin 0 -> 97208 bytes docs/images/example_01_page_rendering.png | Bin 0 -> 8202 bytes docs/images/example_02_text_and_layout.png | Bin 0 -> 23455 bytes docs/images/example_03_page_layouts.png | Bin 0 -> 11923 bytes docs/images/example_04_table_rendering.png | Bin 0 -> 30030 bytes .../example_05_html_table_with_images.png | Bin 0 -> 120550 bytes .../images/example_06_functional_elements.png | Bin 0 -> 18076 bytes docs/images/example_07_button_animation.gif | Bin 0 -> 20864 bytes docs/images/example_07_pressed_state.png | Bin 0 -> 16522 bytes docs/images/example_08_pagination_auto.png | Bin 0 -> 88831 bytes .../images/example_08_pagination_explicit.png | Bin 0 -> 111249 bytes docs/images/example_09_link_navigation.png | Bin 0 -> 60726 bytes docs/images/example_10_forms.png | Bin 0 -> 31592 bytes .../images/example_11_table_text_wrapping.png | Bin 0 -> 91317 bytes docs/images/example_11b_simple_wrapping.png | Bin 0 -> 28640 bytes .../example_12_optimized_table_layout.png | Bin 0 -> 126618 bytes docs/images/example_13_table_pagination.png | Bin 0 -> 112179 bytes docs/images/example_14_interactive_table.png | Bin 0 -> 24437 bytes docs/images/font_family_switching.png | Bin 0 -> 63976 bytes .../images/font_family_switching_vertical.png | Bin 0 -> 62818 bytes docs/images/functional_elements_demo.png | Bin 0 -> 18076 bytes examples/01_simple_page_rendering.py | 217 + examples/02_text_and_layout.py | 215 + examples/03_page_layouts.py | 247 + examples/04_table_rendering.py | 364 + examples/05_html_table_with_images.py | 272 + examples/06_functional_elements_demo.py | 292 + examples/07_pressed_state_demo.py | 452 ++ examples/08_bundled_fonts_demo.py | 227 + examples/08_pagination_demo.py | 367 + examples/09_link_navigation_demo.py | 390 + examples/10_forms_demo.py | 374 + examples/11_font_family_switching_demo.py | 240 + examples/11_table_text_wrapping_demo.py | 312 + examples/11b_simple_table_wrapping.py | 121 + examples/12_optimized_table_layout_demo.py | 254 + examples/13_table_pagination_demo.py | 291 + examples/14_interactive_table.py | 312 + examples/README.md | 310 + examples/generate_readme_font_demo.py | 252 + pyWebLayout/__init__.py | 22 + pyWebLayout/abstract/__init__.py | 22 + pyWebLayout/abstract/block.py | 1415 ++++ pyWebLayout/abstract/document.py | 595 ++ pyWebLayout/abstract/functional.py | 345 + pyWebLayout/abstract/inline.py | 420 + pyWebLayout/abstract/interactive_image.py | 163 + pyWebLayout/assets/fonts/DEJAVU_LICENSE.txt | 187 + pyWebLayout/assets/fonts/DEJAVU_README.md | 67 + pyWebLayout/assets/fonts/DejaVuSans-Bold.ttf | Bin 0 -> 705684 bytes .../assets/fonts/DejaVuSans-BoldOblique.ttf | Bin 0 -> 643292 bytes .../assets/fonts/DejaVuSans-Oblique.ttf | Bin 0 -> 635416 bytes pyWebLayout/assets/fonts/DejaVuSans.ttf | Bin 0 -> 757076 bytes .../assets/fonts/DejaVuSansMono-Bold.ttf | Bin 0 -> 331992 bytes .../fonts/DejaVuSansMono-BoldOblique.ttf | Bin 0 -> 253580 bytes .../assets/fonts/DejaVuSansMono-Oblique.ttf | Bin 0 -> 251932 bytes pyWebLayout/assets/fonts/DejaVuSansMono.ttf | Bin 0 -> 340712 bytes pyWebLayout/assets/fonts/DejaVuSerif-Bold.ttf | Bin 0 -> 356088 bytes .../assets/fonts/DejaVuSerif-BoldItalic.ttf | Bin 0 -> 347460 bytes .../assets/fonts/DejaVuSerif-Italic.ttf | Bin 0 -> 345996 bytes pyWebLayout/assets/fonts/DejaVuSerif.ttf | Bin 0 -> 380132 bytes pyWebLayout/assets/fonts/README.md | 129 + pyWebLayout/concrete/__init__.py | 25 + pyWebLayout/concrete/box.py | 40 + pyWebLayout/concrete/dynamic_page.py | 418 + pyWebLayout/concrete/functional.py | 461 ++ pyWebLayout/concrete/image.py | 280 + pyWebLayout/concrete/interaction_handler.py | 310 + pyWebLayout/concrete/page.py | 537 ++ pyWebLayout/concrete/table.py | 706 ++ pyWebLayout/concrete/text.py | 747 ++ pyWebLayout/core/__init__.py | 34 + pyWebLayout/core/base.py | 448 ++ pyWebLayout/core/callback_registry.py | 278 + pyWebLayout/core/highlight.py | 249 + pyWebLayout/core/query.py | 86 + pyWebLayout/io/__init__.py | 8 + pyWebLayout/io/readers/__init__.py | 17 + pyWebLayout/io/readers/epub_reader.py | 603 ++ pyWebLayout/io/readers/html_extraction.py | 932 +++ pyWebLayout/layout/README_EREADER_API.md | 207 + pyWebLayout/layout/__init__.py | 11 + pyWebLayout/layout/document_layouter.py | 723 ++ pyWebLayout/layout/ereader_layout.py | 754 ++ pyWebLayout/layout/ereader_manager.py | 888 +++ pyWebLayout/layout/page_buffer.py | 520 ++ pyWebLayout/layout/table_optimizer.py | 396 + pyWebLayout/style/__init__.py | 23 + pyWebLayout/style/abstract_style.py | 342 + pyWebLayout/style/alignment.py | 25 + pyWebLayout/style/concrete_style.py | 484 ++ pyWebLayout/style/fonts.py | 406 + pyWebLayout/style/page_style.py | 58 + pyproject.toml | 62 + scripts/debug_text_positioning.py | 76 + scripts/epub_page_renderer.py | 431 + .../epub_page_renderer_documentlayouter.py | 391 + scripts/run_coverage.py | 126 + scripts/run_coverage_gutters.py | 63 + scripts/update_coverage_badges.py | 83 + scripts/update_coverage_gutters.py | 103 + setup.cfg | 37 + setup.py | 32 + tests/__init__.py | 6 + tests/abstract/__init__.py | 0 tests/abstract/test_abstract_blocks.py | 591 ++ tests/abstract/test_abstract_document.py | 696 ++ tests/abstract/test_abstract_functional.py | 529 ++ tests/abstract/test_abstract_inline.py | 859 ++ tests/abstract/test_document_mixins.py | 60 + tests/abstract/test_linked_elements.py | 194 + tests/concrete/__init__.py | 0 tests/concrete/test_alignment_handlers.py | 337 + tests/concrete/test_concrete_box.py | 101 + tests/concrete/test_concrete_functional.py | 496 ++ tests/concrete/test_concrete_image.py | 386 + tests/concrete/test_concrete_text.py | 321 + tests/concrete/test_dynamic_page.py | 313 + tests/concrete/test_linkedword_hyphenation.py | 140 + .../concrete/test_new_page_implementation.py | 329 + tests/concrete/test_table_rendering.py | 652 ++ tests/core/__init__.py | 1 + tests/core/test_highlight.py | 352 + tests/core/test_query_system.py | 433 + tests/data/Kimi Räikkönen - Wikipedia.html | 6892 ++++++++++++++++ .../Ambox_important.svg.webp | Bin 0 -> 4356 bytes .../Ambox_important.svg_002.webp | Bin 0 -> 1920 bytes .../Ambox_important.svg_003.webp | Bin 0 -> 3242 bytes .../Commons-logo.svg.webp | Bin 0 -> 608 bytes .../Commons-logo.svg_002.webp | Bin 0 -> 1114 bytes .../Commons-logo.svg_003.webp | Bin 0 -> 1770 bytes .../Edit-clear.svg.webp | Bin 0 -> 2012 bytes .../Edit-clear.svg_002.webp | Bin 0 -> 4902 bytes .../Edit-clear.svg_003.webp | Bin 0 -> 3410 bytes .../Flag_of_Argentina.svg.webp | Bin 0 -> 140 bytes .../Flag_of_Argentina.svg_002.webp | Bin 0 -> 200 bytes .../Flag_of_Argentina.svg_003.webp | Bin 0 -> 378 bytes .../Flag_of_Australia_(converted).svg.webp | Bin 0 -> 438 bytes .../Flag_of_Australia_(converted).svg_002.webp | Bin 0 -> 526 bytes .../Flag_of_Austria.svg.webp | Bin 0 -> 94 bytes .../Flag_of_Austria.svg_002.webp | Bin 0 -> 72 bytes .../Flag_of_Austria.svg_003.webp | Bin 0 -> 90 bytes .../Flag_of_Barbados.svg.webp | Bin 0 -> 302 bytes .../Flag_of_Belgium_(civil).svg.webp | Bin 0 -> 68 bytes .../Flag_of_Belgium_(civil).svg_002.webp | Bin 0 -> 130 bytes .../Flag_of_Belgium_(civil).svg_003.webp | Bin 0 -> 134 bytes .../Flag_of_Brazil.svg.webp | Bin 0 -> 318 bytes .../Flag_of_Brazil.svg_002.webp | Bin 0 -> 888 bytes .../Flag_of_Brazil.svg_003.webp | Bin 0 -> 568 bytes .../Flag_of_Canada_(Pantone).svg.webp | Bin 0 -> 336 bytes .../Flag_of_Canada_(Pantone).svg_002.webp | Bin 0 -> 266 bytes .../Flag_of_Finland.svg.webp | Bin 0 -> 164 bytes .../Flag_of_Finland.svg_002.webp | Bin 0 -> 164 bytes .../Flag_of_Finland.svg_003.webp | Bin 0 -> 160 bytes .../Flag_of_France.svg.webp | Bin 0 -> 138 bytes .../Flag_of_France.svg_002.webp | Bin 0 -> 72 bytes .../Flag_of_Germany.svg.webp | Bin 0 -> 56 bytes .../Flag_of_Germany.svg_002.webp | Bin 0 -> 56 bytes .../Flag_of_Germany.svg_003.webp | Bin 0 -> 54 bytes .../Flag_of_Ireland.svg.webp | Bin 0 -> 70 bytes .../Flag_of_Ireland.svg_002.webp | Bin 0 -> 88 bytes .../Flag_of_Italy.svg.webp | Bin 0 -> 122 bytes .../Flag_of_Italy.svg_002.webp | Bin 0 -> 126 bytes .../Flag_of_Italy.svg_003.webp | Bin 0 -> 72 bytes .../Flag_of_Japan.svg.webp | Bin 0 -> 260 bytes .../Flag_of_Japan.svg_002.webp | Bin 0 -> 266 bytes .../Flag_of_Mexico.svg.webp | Bin 0 -> 416 bytes .../Flag_of_Mexico.svg_002.webp | Bin 0 -> 676 bytes .../Flag_of_Monaco.svg.webp | Bin 0 -> 52 bytes .../Flag_of_Monaco.svg_002.webp | Bin 0 -> 50 bytes .../Flag_of_Norway.svg.webp | Bin 0 -> 222 bytes .../Flag_of_Norway.svg_002.webp | Bin 0 -> 218 bytes .../Flag_of_Poland.svg.webp | Bin 0 -> 70 bytes .../Flag_of_Poland.svg_002.webp | Bin 0 -> 88 bytes .../Flag_of_South_Africa_(1928-1982).svg.webp | Bin 0 -> 178 bytes ...lag_of_South_Africa_(1928-1982).svg_002.webp | Bin 0 -> 292 bytes .../Flag_of_Sweden.svg.webp | Bin 0 -> 138 bytes .../Flag_of_Sweden.svg_002.webp | Bin 0 -> 126 bytes .../Flag_of_Sweden.svg_003.webp | Bin 0 -> 68 bytes .../Flag_of_Switzerland_(Pantone).svg.webp | Bin 0 -> 130 bytes .../Flag_of_Switzerland_(Pantone).svg_002.webp | Bin 0 -> 120 bytes .../Flag_of_Venezuela.svg.webp | Bin 0 -> 246 bytes .../Flag_of_Venezuela.svg_002.webp | Bin 0 -> 334 bytes ...g_of_the_People's_Republic_of_China.svg.webp | Bin 0 -> 220 bytes ..._the_People's_Republic_of_China.svg_002.webp | Bin 0 -> 262 bytes .../Flag_of_the_United_Kingdom.svg.webp | Bin 0 -> 316 bytes .../Flag_of_the_United_Kingdom.svg_002.webp | Bin 0 -> 334 bytes .../Flag_of_the_United_States.svg.webp | Bin 0 -> 196 bytes .../Flag_of_the_United_States.svg_002.webp | Bin 0 -> 336 bytes .../Flag_of_the_United_States.svg_003.webp | Bin 0 -> 290 bytes .../OOjs_UI_icon_edit-ltr-progressive.svg.webp | Bin 0 -> 202 bytes .../Symbol_category_class.svg.webp | Bin 0 -> 632 bytes .../Symbol_category_class.svg_002.webp | Bin 0 -> 1512 bytes .../Kimi Räikkönen - Wikipedia_files/load.css | 1 + .../Kimi Räikkönen - Wikipedia_files/load.js | 23 + .../load_002.css | 1 + tests/data/test.epub | Bin 0 -> 189381 bytes tests/examples/__init__.py | 0 tests/examples/test_08_pagination_demo.py | 221 + .../examples/test_09_link_navigation_demo.py | 179 + tests/examples/test_10_forms_demo.py | 159 + tests/io_tests/__init__.py | 0 tests/io_tests/test_epub_reader.py | 1041 +++ tests/io_tests/test_html_extraction.py | 687 ++ .../test_html_extraction_functions.py | 524 ++ tests/io_tests/test_html_file_loader.py | 136 + tests/io_tests/test_html_link_end_to_end.py | 164 + .../io_tests/test_html_link_interactivity.py | 211 + tests/io_tests/test_html_links.py | 184 + tests/layout/__init__.py | 9 + tests/layout/test_ereader_image_rendering.py | 545 ++ tests/layout/test_ereader_layout.py | 903 +++ tests/layout/test_ereader_manager.py | 784 ++ tests/layout/test_html_links_in_ereader.py | 148 + tests/layout/test_navigation_consistency.py | 359 + tests/layout/test_table_optimizer.py | 397 + tests/layouter/__init__.py | 3 + tests/layouter/test_document_layouter.py | 792 ++ .../test_document_layouter_integration.py | 384 + tests/mixins/__init__.py | 0 tests/mixins/font_registry_tests.py | 126 + tests/mixins/metadata_tests.py | 71 + tests/style/__init__.py | 0 tests/style/test_html_style.py | 189 + tests/style/test_new_style_system.py | 233 + tests/style/test_word_spacing_constraints.py | 149 + tests/test_callback_registry.py | 211 + tests/test_interactive_image.py | 199 + tests/utils/__init__.py | 4 + tests/utils/test_font_utilities.py | 143 + tests/utils/test_fonts.py | 168 + 291 files changed, 78353 insertions(+) create mode 100644 .coveragerc create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitignore create mode 100644 ARCHITECTURE.md create mode 100644 FONT_SWITCHING_FEATURE.md create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 cov_info/coverage-docs.svg create mode 100644 cov_info/coverage-summary.txt create mode 100644 cov_info/coverage.json create mode 100644 cov_info/coverage.svg create mode 100644 cov_info/coverage.xml create mode 100644 cov_info/htmlcov/.gitignore create mode 100644 cov_info/htmlcov/class_index.html create mode 100644 cov_info/htmlcov/coverage_html_cb_6fb7b396.js create mode 100644 cov_info/htmlcov/favicon_32_cb_58284776.png create mode 100644 cov_info/htmlcov/function_index.html create mode 100644 cov_info/htmlcov/index.html create mode 100644 cov_info/htmlcov/keybd_closed_cb_ce680311.png create mode 100644 cov_info/htmlcov/status.json create mode 100644 cov_info/htmlcov/style_cb_6b508a39.css create mode 100644 cov_info/htmlcov/z_20e398e67121d457___init___py.html create mode 100644 cov_info/htmlcov/z_263f2e628cef8c50___init___py.html create mode 100644 cov_info/htmlcov/z_263f2e628cef8c50_epub_reader_py.html create mode 100644 cov_info/htmlcov/z_263f2e628cef8c50_html_extraction_py.html create mode 100644 cov_info/htmlcov/z_40407af872b0cf37___init___py.html create mode 100644 cov_info/htmlcov/z_40407af872b0cf37_base_py.html create mode 100644 cov_info/htmlcov/z_40407af872b0cf37_callback_registry_py.html create mode 100644 cov_info/htmlcov/z_40407af872b0cf37_highlight_py.html create mode 100644 cov_info/htmlcov/z_40407af872b0cf37_query_py.html create mode 100644 cov_info/htmlcov/z_427cc3035faf7633___init___py.html create mode 100644 cov_info/htmlcov/z_427cc3035faf7633_document_layouter_py.html create mode 100644 cov_info/htmlcov/z_427cc3035faf7633_ereader_layout_py.html create mode 100644 cov_info/htmlcov/z_427cc3035faf7633_ereader_manager_py.html create mode 100644 cov_info/htmlcov/z_427cc3035faf7633_page_buffer_py.html create mode 100644 cov_info/htmlcov/z_427cc3035faf7633_table_optimizer_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2___init___py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_box_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_dynamic_page_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_functional_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_image_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_interaction_handler_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_page_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_table_py.html create mode 100644 cov_info/htmlcov/z_7d48e1f4c6486fa2_text_py.html create mode 100644 cov_info/htmlcov/z_af715639580e2d86___init___py.html create mode 100644 cov_info/htmlcov/z_af715639580e2d86_block_py.html create mode 100644 cov_info/htmlcov/z_af715639580e2d86_document_py.html create mode 100644 cov_info/htmlcov/z_af715639580e2d86_functional_py.html create mode 100644 cov_info/htmlcov/z_af715639580e2d86_inline_py.html create mode 100644 cov_info/htmlcov/z_af715639580e2d86_interactive_image_py.html create mode 100644 cov_info/htmlcov/z_ba7f6bdeb0188088___init___py.html create mode 100644 cov_info/htmlcov/z_ba7f6bdeb0188088_abstract_style_py.html create mode 100644 cov_info/htmlcov/z_ba7f6bdeb0188088_alignment_py.html create mode 100644 cov_info/htmlcov/z_ba7f6bdeb0188088_concrete_style_py.html create mode 100644 cov_info/htmlcov/z_ba7f6bdeb0188088_fonts_py.html create mode 100644 cov_info/htmlcov/z_ba7f6bdeb0188088_page_style_py.html create mode 100644 cov_info/htmlcov/z_fc521de9aff00981___init___py.html create mode 100644 docs/images/README.md create mode 100644 docs/images/demo_08_bundled_fonts.png create mode 100644 docs/images/example_01_page_rendering.png create mode 100644 docs/images/example_02_text_and_layout.png create mode 100644 docs/images/example_03_page_layouts.png create mode 100644 docs/images/example_04_table_rendering.png create mode 100644 docs/images/example_05_html_table_with_images.png create mode 100644 docs/images/example_06_functional_elements.png create mode 100644 docs/images/example_07_button_animation.gif create mode 100644 docs/images/example_07_pressed_state.png create mode 100644 docs/images/example_08_pagination_auto.png create mode 100644 docs/images/example_08_pagination_explicit.png create mode 100644 docs/images/example_09_link_navigation.png create mode 100644 docs/images/example_10_forms.png create mode 100644 docs/images/example_11_table_text_wrapping.png create mode 100644 docs/images/example_11b_simple_wrapping.png create mode 100644 docs/images/example_12_optimized_table_layout.png create mode 100644 docs/images/example_13_table_pagination.png create mode 100644 docs/images/example_14_interactive_table.png create mode 100644 docs/images/font_family_switching.png create mode 100644 docs/images/font_family_switching_vertical.png create mode 100644 docs/images/functional_elements_demo.png create mode 100644 examples/01_simple_page_rendering.py create mode 100644 examples/02_text_and_layout.py create mode 100644 examples/03_page_layouts.py create mode 100644 examples/04_table_rendering.py create mode 100644 examples/05_html_table_with_images.py create mode 100644 examples/06_functional_elements_demo.py create mode 100644 examples/07_pressed_state_demo.py create mode 100644 examples/08_bundled_fonts_demo.py create mode 100644 examples/08_pagination_demo.py create mode 100644 examples/09_link_navigation_demo.py create mode 100644 examples/10_forms_demo.py create mode 100644 examples/11_font_family_switching_demo.py create mode 100644 examples/11_table_text_wrapping_demo.py create mode 100644 examples/11b_simple_table_wrapping.py create mode 100644 examples/12_optimized_table_layout_demo.py create mode 100644 examples/13_table_pagination_demo.py create mode 100644 examples/14_interactive_table.py create mode 100644 examples/README.md create mode 100644 examples/generate_readme_font_demo.py create mode 100644 pyWebLayout/__init__.py create mode 100644 pyWebLayout/abstract/__init__.py create mode 100644 pyWebLayout/abstract/block.py create mode 100644 pyWebLayout/abstract/document.py create mode 100644 pyWebLayout/abstract/functional.py create mode 100644 pyWebLayout/abstract/inline.py create mode 100644 pyWebLayout/abstract/interactive_image.py create mode 100644 pyWebLayout/assets/fonts/DEJAVU_LICENSE.txt create mode 100644 pyWebLayout/assets/fonts/DEJAVU_README.md create mode 100644 pyWebLayout/assets/fonts/DejaVuSans-Bold.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSans-BoldOblique.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSans-Oblique.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSans.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSansMono-Bold.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSansMono-BoldOblique.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSansMono-Oblique.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSansMono.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSerif-Bold.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSerif-BoldItalic.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSerif-Italic.ttf create mode 100644 pyWebLayout/assets/fonts/DejaVuSerif.ttf create mode 100644 pyWebLayout/assets/fonts/README.md create mode 100644 pyWebLayout/concrete/__init__.py create mode 100644 pyWebLayout/concrete/box.py create mode 100644 pyWebLayout/concrete/dynamic_page.py create mode 100644 pyWebLayout/concrete/functional.py create mode 100644 pyWebLayout/concrete/image.py create mode 100644 pyWebLayout/concrete/interaction_handler.py create mode 100644 pyWebLayout/concrete/page.py create mode 100644 pyWebLayout/concrete/table.py create mode 100644 pyWebLayout/concrete/text.py create mode 100644 pyWebLayout/core/__init__.py create mode 100644 pyWebLayout/core/base.py create mode 100644 pyWebLayout/core/callback_registry.py create mode 100644 pyWebLayout/core/highlight.py create mode 100644 pyWebLayout/core/query.py create mode 100644 pyWebLayout/io/__init__.py create mode 100644 pyWebLayout/io/readers/__init__.py create mode 100644 pyWebLayout/io/readers/epub_reader.py create mode 100644 pyWebLayout/io/readers/html_extraction.py create mode 100644 pyWebLayout/layout/README_EREADER_API.md create mode 100644 pyWebLayout/layout/__init__.py create mode 100644 pyWebLayout/layout/document_layouter.py create mode 100644 pyWebLayout/layout/ereader_layout.py create mode 100644 pyWebLayout/layout/ereader_manager.py create mode 100644 pyWebLayout/layout/page_buffer.py create mode 100644 pyWebLayout/layout/table_optimizer.py create mode 100644 pyWebLayout/style/__init__.py create mode 100644 pyWebLayout/style/abstract_style.py create mode 100644 pyWebLayout/style/alignment.py create mode 100644 pyWebLayout/style/concrete_style.py create mode 100644 pyWebLayout/style/fonts.py create mode 100644 pyWebLayout/style/page_style.py create mode 100644 pyproject.toml create mode 100644 scripts/debug_text_positioning.py create mode 100755 scripts/epub_page_renderer.py create mode 100644 scripts/epub_page_renderer_documentlayouter.py create mode 100644 scripts/run_coverage.py create mode 100644 scripts/run_coverage_gutters.py create mode 100644 scripts/update_coverage_badges.py create mode 100644 scripts/update_coverage_gutters.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/abstract/__init__.py create mode 100644 tests/abstract/test_abstract_blocks.py create mode 100644 tests/abstract/test_abstract_document.py create mode 100644 tests/abstract/test_abstract_functional.py create mode 100644 tests/abstract/test_abstract_inline.py create mode 100644 tests/abstract/test_document_mixins.py create mode 100644 tests/abstract/test_linked_elements.py create mode 100644 tests/concrete/__init__.py create mode 100644 tests/concrete/test_alignment_handlers.py create mode 100644 tests/concrete/test_concrete_box.py create mode 100644 tests/concrete/test_concrete_functional.py create mode 100644 tests/concrete/test_concrete_image.py create mode 100644 tests/concrete/test_concrete_text.py create mode 100644 tests/concrete/test_dynamic_page.py create mode 100644 tests/concrete/test_linkedword_hyphenation.py create mode 100644 tests/concrete/test_new_page_implementation.py create mode 100644 tests/concrete/test_table_rendering.py create mode 100644 tests/core/__init__.py create mode 100644 tests/core/test_highlight.py create mode 100644 tests/core/test_query_system.py create mode 100644 tests/data/Kimi Räikkönen - Wikipedia.html create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Ambox_important.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Ambox_important.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Ambox_important.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Commons-logo.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Commons-logo.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Commons-logo.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Edit-clear.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Edit-clear.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Edit-clear.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Argentina.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Argentina.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Argentina.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Australia_(converted).svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Australia_(converted).svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Austria.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Austria.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Austria.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Barbados.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Belgium_(civil).svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Belgium_(civil).svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Belgium_(civil).svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Brazil.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Brazil.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Brazil.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Canada_(Pantone).svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Canada_(Pantone).svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Finland.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Finland.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Finland.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_France.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_France.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Germany.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Germany.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Germany.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Ireland.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Ireland.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Italy.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Italy.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Italy.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Japan.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Japan.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Mexico.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Mexico.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Monaco.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Monaco.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Norway.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Norway.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Poland.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Poland.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_South_Africa_(1928-1982).svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_South_Africa_(1928-1982).svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Sweden.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Sweden.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Sweden.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Switzerland_(Pantone).svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Switzerland_(Pantone).svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Venezuela.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_Venezuela.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_the_People's_Republic_of_China.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_the_People's_Republic_of_China.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_the_United_Kingdom.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_the_United_Kingdom.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_the_United_States.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_the_United_States.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Flag_of_the_United_States.svg_003.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/OOjs_UI_icon_edit-ltr-progressive.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Symbol_category_class.svg.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/Symbol_category_class.svg_002.webp create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/load.css create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/load.js create mode 100644 tests/data/Kimi Räikkönen - Wikipedia_files/load_002.css create mode 100644 tests/data/test.epub create mode 100644 tests/examples/__init__.py create mode 100644 tests/examples/test_08_pagination_demo.py create mode 100644 tests/examples/test_09_link_navigation_demo.py create mode 100644 tests/examples/test_10_forms_demo.py create mode 100644 tests/io_tests/__init__.py create mode 100644 tests/io_tests/test_epub_reader.py create mode 100644 tests/io_tests/test_html_extraction.py create mode 100644 tests/io_tests/test_html_extraction_functions.py create mode 100644 tests/io_tests/test_html_file_loader.py create mode 100644 tests/io_tests/test_html_link_end_to_end.py create mode 100644 tests/io_tests/test_html_link_interactivity.py create mode 100644 tests/io_tests/test_html_links.py create mode 100644 tests/layout/__init__.py create mode 100644 tests/layout/test_ereader_image_rendering.py create mode 100644 tests/layout/test_ereader_layout.py create mode 100644 tests/layout/test_ereader_manager.py create mode 100644 tests/layout/test_html_links_in_ereader.py create mode 100644 tests/layout/test_navigation_consistency.py create mode 100644 tests/layout/test_table_optimizer.py create mode 100644 tests/layouter/__init__.py create mode 100644 tests/layouter/test_document_layouter.py create mode 100644 tests/layouter/test_document_layouter_integration.py create mode 100644 tests/mixins/__init__.py create mode 100644 tests/mixins/font_registry_tests.py create mode 100644 tests/mixins/metadata_tests.py create mode 100644 tests/style/__init__.py create mode 100644 tests/style/test_html_style.py create mode 100644 tests/style/test_new_style_system.py create mode 100644 tests/style/test_word_spacing_constraints.py create mode 100644 tests/test_callback_registry.py create mode 100644 tests/test_interactive_image.py create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/test_font_utilities.py create mode 100644 tests/utils/test_fonts.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..ea44045 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,31 @@ +[run] +source = pyWebLayout +branch = True +omit = + */tests/* + */test_* + setup.py + */examples/* + */__main__.py + +[report] +exclude_lines = + pragma: no cover + def __repr__ + if self.debug: + if settings.DEBUG + raise AssertionError + raise NotImplementedError + if 0: + if __name__ == .__main__.: + # Exclude docstrings + ^\s*""" + ^\s*''' + ^\s*r""" + ^\s*r''' + +[xml] +output = coverage.xml + +[html] +directory = htmlcov diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..53bfa29 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,179 @@ +name: Python CI + +on: + push: + branches: [ main, master, develop ] + paths-ignore: + - 'coverage*.svg' + - 'README.md' + pull_request: + branches: [ main, master, develop ] + +jobs: + test: + runs-on: self-hosted + strategy: + matrix: + python-version: ['3.10', '3.12', '3.13'] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + # Install package in development mode + pip install -e . + # Install test dependencies if they exist + if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi + if [ -f requirements/test.txt ]; then pip install -r requirements/test.txt; fi + # Install common test packages + pip install pytest pytest-cov flake8 coverage-badge interrogate + + - name: Download initial failed badges + run: | + echo "Downloading initial failed badges..." + + # Create cov_info directory first + mkdir -p cov_info + + # Download failed badges as defaults + curl -o cov_info/coverage.svg "https://img.shields.io/badge/coverage-failed-red.svg" + curl -o cov_info/coverage-docs.svg "https://img.shields.io/badge/docs-failed-red.svg" + + echo "Initial failed badges created:" + ls -la cov_info/coverage*.svg + + - name: Run tests with pytest + id: pytest + continue-on-error: true + run: | + # Run tests with coverage + python -m pytest tests/ -v --cov=pyWebLayout --cov-report=term-missing --cov-report=json --cov-report=html --cov-report=xml + + - name: Check documentation coverage + id: docs + continue-on-error: true + run: | + # Generate documentation coverage report + interrogate -v --ignore-init-method --ignore-init-module --ignore-magic --ignore-private --ignore-property-decorators --ignore-semiprivate --fail-under=80 pyWebLayout/ + + - name: Lint with flake8 + run: | + # Stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # Exit-zero treats all errors as warnings + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Create coverage info directory + if: always() + run: | + mkdir -p cov_info + echo "Created cov_info directory for coverage data" + + - name: Update test coverage badge on success + if: steps.pytest.outcome == 'success' && always() + run: | + echo "Tests passed! Generating successful coverage badge..." + + if [ -f coverage.json ]; then + coverage-badge -o cov_info/coverage.svg -f + echo "✅ Test coverage badge updated with actual results" + else + echo "⚠️ No coverage.json found, keeping failed badge" + fi + + - name: Update docs coverage badge on success + if: steps.docs.outcome == 'success' && always() + run: | + echo "Docs check passed! Generating successful docs badge..." + + # Remove existing badge first to avoid overwrite error + rm -f cov_info/coverage-docs.svg + interrogate --generate-badge cov_info/coverage-docs.svg pyWebLayout/ + echo "✅ Docs coverage badge updated with actual results" + + - name: Generate coverage reports + if: steps.pytest.outcome == 'success' + run: | + # Generate coverage summary for README + python -c " + import json + import os + # Read coverage data + if os.path.exists('coverage.json'): + with open('coverage.json', 'r') as f: + coverage_data = json.load(f) + total_coverage = round(coverage_data['totals']['percent_covered'], 1) + # Create coverage summary file in cov_info directory + with open('cov_info/coverage-summary.txt', 'w') as f: + f.write(f'{total_coverage}%') + print(f'Test Coverage: {total_coverage}%') + covered_lines = coverage_data['totals']['covered_lines'] + total_lines = coverage_data['totals']['num_statements'] + print(f'Lines Covered: {covered_lines}/{total_lines}') + else: + print('No coverage data found') + " + + # Copy other coverage files to cov_info + if [ -f coverage.json ]; then cp coverage.json cov_info/; fi + if [ -f coverage.xml ]; then cp coverage.xml cov_info/; fi + if [ -d htmlcov ]; then cp -r htmlcov cov_info/; fi + + - name: Final badge status + if: always() + run: | + echo "=== FINAL BADGE STATUS ===" + echo "Test outcome: ${{ steps.pytest.outcome }}" + echo "Docs outcome: ${{ steps.docs.outcome }}" + + if [ -f cov_info/coverage.svg ]; then + echo "✅ Test coverage badge: $(ls -lh cov_info/coverage.svg)" + else + echo "❌ Test coverage badge: MISSING" + fi + + if [ -f cov_info/coverage-docs.svg ]; then + echo "✅ Docs coverage badge: $(ls -lh cov_info/coverage-docs.svg)" + else + echo "❌ Docs coverage badge: MISSING" + fi + + echo "Coverage info directory contents:" + ls -la cov_info/ 2>/dev/null || echo "No cov_info directory found" + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v3 + with: + name: coverage-reports + path: | + cov_info/ + + - name: Commit badges to badges branch + if: github.ref == 'refs/heads/master' + run: | + git config --local user.email "action@gitea.local" + git config --local user.name "Gitea Action" + + # Set the remote URL to use the token + git remote set-url origin https://${{ secrets.PUSH_TOKEN }}@gitea.tourolle.paris/dtourolle/pyWebLayout.git + + # Create a new orphan branch for badges (this discards any existing badges branch) + git checkout --orphan badges + + # Remove all files except cov_info + find . -maxdepth 1 -not -name '.git' -not -name 'cov_info' -exec rm -rf {} + 2>/dev/null || true + + # Add only the coverage info directory + git add -f cov_info/ + + # Always commit (force overwrite) + echo "Force updating badges branch with new coverage data..." + git commit -m "Update coverage badges [skip ci]" + git push -f origin badges diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be58037 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*/__pycache__ +# Distribution / packaging +dist/ +build/ +*.egg-info/ + +# Environment +venv/ +env/ +.env/ +.venv/ + +# Tests +.pytest_cache/ +.coverage +htmlcov/ + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo + +# Project specific +*.png +*.jpg +*.jpeg +*.gif +*.svg + +# But allow documentation images +!docs/images/*.gif +!docs/images/*.png +!docs/images/*.jpg + +# Output directories +output/ +my_output/ +test_output/ +*_output/ +examples/output/ + +# Generated data +bookmarks/ +positions/ + +# Profiling scripts +profile_*.py + +# Debug scripts output +debug_*.png +.fish* \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..09f89f0 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,233 @@ +# pyWebLayout Architecture: Abstract vs Concrete + +This document explains the fundamental architectural separation between **Abstract** and **Concrete** layers in the pyWebLayout library. + +## Overview + +The pyWebLayout library follows a clear separation between two distinct layers: + +- **Abstract Layer**: Represents the logical structure and content of documents (HTML/EPUB text) +- **Concrete Layer**: Handles the spatial rendering and visual representation of content + +This separation provides flexibility, testability, and clean separation of concerns. + +## Abstract Layer (`pyWebLayout/abstract/`) + +The Abstract layer deals with the **logical structure** of documents without concerning itself with how content will be visually rendered. + +### Key Components + +#### `abstract/block.py` +- `Block`: Base class for all block-level content +- `Paragraph`: Represents a logical paragraph containing words +- `Heading`: Represents headings with semantic levels (H1-H6) +- `HList`: Represents ordered/unordered lists +- `Image`: Represents image references + +#### `abstract/inline.py` +- `Word`: Represents individual words with text content and styling information +- Contains methods for hyphenation and text manipulation +- Does **not** handle rendering or spatial layout + +#### `abstract/document.py` +- `Document`: Container for the overall document structure +- `Chapter`: Logical grouping of blocks (for books/long documents) + +### Characteristics of Abstract Classes + +1. **Content-focused**: Store text, structure, and semantic meaning +2. **Layout-agnostic**: No knowledge of fonts, pixels, or rendering +3. **Reusable**: Same content can be rendered in different formats/sizes +4. **Serializable**: Can be saved/loaded without rendering context + +### Example: Abstract Word + +```python +# An Abstract Word knows its text content and semantic properties +word = Word("supercalifragilisticexpialidocious", font_style) +word.hyphenate() # Logical operation - finds break points +parts = word.get_hyphenated_parts() # Returns ["super-", "cali-", "fragi-", ...] +``` + +## Concrete Layer (`pyWebLayout/concrete/`) + +The Concrete layer handles the **spatial representation** and actual rendering of content. + +### Key Components + +#### `concrete/text.py` +- `Text`: Renders a specific text fragment with precise positioning +- `Line`: Manages a line of `Text` objects with spacing and alignment +- Handles actual pixel measurements, font rendering, and positioning + +#### `concrete/page.py` +- `Page`: Top-level container for rendered content +- `Container`: Layout manager for organizing renderable objects +- Handles spatial layout, pagination, and visual composition + +#### `concrete/box.py` +- `Box`: Base class for all spatially-aware renderable objects +- Provides positioning, sizing, and rendering capabilities + +### Characteristics of Concrete Classes + +1. **Rendering-focused**: Handle pixels, fonts, images, and visual output +2. **Spatially-aware**: Know exact positions, sizes, and layout constraints +3. **Implementation-specific**: Tied to specific rendering technologies (PIL, etc.) +4. **Non-portable**: Rendering results are tied to specific display contexts + +### Example: Concrete Text + +```python +# A Concrete Text object handles actual rendering +text = Text("super-", font) # Specific text fragment +text._calculate_dimensions() # Computes exact pixel size +image = text.render() # Produces actual visual output +``` + +## The Transformation Process + +The architecture involves a clear transformation from Abstract to Concrete: + +``` +Abstract Document + ↓ + [Parser Layer] + ↓ +Abstract Blocks (Paragraph, Heading, etc.) + ↓ + [Layout Engine] + ↓ +Concrete Objects (Text, Line, Page) + ↓ + [Rendering Engine] + ↓ +Visual Output (Images, PDF, etc.) +``` + +### Example Transformation + +```python +# 1. Abstract content +paragraph = Paragraph() +paragraph.add_word(Word("This", font)) +paragraph.add_word(Word("is", font)) +paragraph.add_word(Word("a", font)) +paragraph.add_word(Word("test", font)) + +# 2. Layout transformation +layout = ParagraphLayout(line_width=200, line_height=20) +lines = layout.layout_paragraph(paragraph) # Returns List[Line] + +# 3. Each Line contains concrete Text objects +for line in lines: + for text_obj in line.text_objects: # List[Text] + print(f"Text: '{text_obj.text}' at position {text_obj._origin}") +``` + +## Key Architectural Principles + +### 1. **Single Responsibility** +- Abstract classes: Handle content and structure +- Concrete classes: Handle rendering and layout + +### 2. **Separation of Concerns** +- Text parsing/processing ≠ Text rendering +- Document structure ≠ Page layout +- Content semantics ≠ Visual presentation + +### 3. **Immutable Abstract Content** +- Abstract content remains unchanged during rendering +- Multiple concrete representations can be generated from same abstract content +- Enables pagination, different formats, responsive layouts + +### 4. **One-to-Many Relationships** +- One Abstract Word → Multiple Concrete Text objects (hyphenation) +- One Abstract Paragraph → Multiple Concrete Lines +- One Abstract Document → Multiple Concrete Pages + +## Common Anti-Patterns to Avoid + +### ❌ **Mixing Concerns** +```python +# WRONG: Abstract class knowing about pixels +class Word: + def __init__(self, text): + self.text = text + self.rendered_width = None # ❌ Concrete concern in abstract class +``` + +### ❌ **renderable_words Concept** +```python +# WRONG: Confusing abstract and concrete +line.renderable_words # ❌ This suggests Words are renderable + # Words are abstract - only Text objects render +``` + +### ✅ **Correct Separation** +```python +# CORRECT: Clear separation +abstract_word = Word("test") # Abstract content +concrete_text = Text("test", font) # Concrete rendering +line.text_objects.append(concrete_text) # Concrete objects in concrete container +``` + +## Benefits of This Architecture + +### 1. **Flexibility** +- Same content can be rendered at different sizes +- Multiple output formats from single source +- Easy to implement responsive design + +### 2. **Testability** +- Abstract logic can be tested without rendering +- Layout algorithms can be tested independently +- Visual rendering can be mocked + +### 3. **Performance** +- Abstract content can be cached and reused +- Layout can be computed once for multiple renderings +- Incremental updates possible + +### 4. **Maintainability** +- Clear boundaries between text processing and rendering +- Changes to rendering don't affect content parsing +- Easy to swap rendering backends + +## File Organization + +``` +pyWebLayout/ +├── abstract/ # Content and structure +│ ├── block.py # Document blocks (Paragraph, Heading, etc.) +│ ├── inline.py # Inline content (Word, etc.) +│ ├── document.py # Document structure +│ └── functional.py # Links, buttons, etc. +│ +├── concrete/ # Rendering and layout +│ ├── text.py # Text and Line rendering +│ ├── page.py # Page layout and containers +│ ├── box.py # Base rendering classes +│ ├── image.py # Image rendering +│ └── functional.py # Interactive elements +│ +├── typesetting/ # Layout algorithms +│ ├── paragraph_layout.py # Abstract → Concrete transformation +│ ├── flow.py # Text flow management +│ └── pagination.py # Page breaking logic +│ +└── style/ # Styling and formatting + ├── fonts.py # Font management + ├── layout.py # Layout constants + └── alignment.py # Alignment enums +``` + +## Conclusion + +The Abstract/Concrete separation is fundamental to pyWebLayout's design. It ensures clean separation between content processing and visual rendering, enabling flexible, maintainable, and testable document processing pipelines. + +**Remember**: +- **Abstract** = What to display (content, structure, semantics) +- **Concrete** = How to display it (pixels, fonts, positioning, rendering) + +This architecture enables the library to handle complex document layouts while maintaining clear, understandable code organization. diff --git a/FONT_SWITCHING_FEATURE.md b/FONT_SWITCHING_FEATURE.md new file mode 100644 index 0000000..0d22cd3 --- /dev/null +++ b/FONT_SWITCHING_FEATURE.md @@ -0,0 +1,139 @@ +# Dynamic Font Family Switching + +The pyWebLayout ereader now supports dynamic font family switching, allowing readers to change fonts on-the-fly without losing their reading position. + +## Visual Demo + +![Font Family Switching](docs/images/font_family_switching_vertical.png) + +*The same content rendered in Sans-Serif, Serif, and Monospace fonts* + +## Features + +- **Three Bundled Font Families**: Sans-Serif (DejaVu Sans), Serif (DejaVu Serif), and Monospace (DejaVu Sans Mono) +- **Dynamic Switching**: Change fonts instantly during reading +- **Position Preservation**: Your reading position is maintained across font changes +- **Attribute Preservation**: Bold, italic, size, and color are preserved when switching families +- **Automatic Cache Management**: Intelligent cache invalidation ensures optimal performance + +## Usage + +```python +from pyWebLayout.style.fonts import BundledFont +from pyWebLayout.layout.ereader_manager import create_ereader_manager + +# Create an ereader instance +manager = create_ereader_manager(blocks, page_size=(600, 800)) + +# Switch to serif font +manager.set_font_family(BundledFont.SERIF) +page = manager.get_current_page() + +# Switch to monospace font +manager.set_font_family(BundledFont.MONOSPACE) +page = manager.get_current_page() + +# Restore original fonts +manager.set_font_family(None) +page = manager.get_current_page() + +# Query current font family +current_family = manager.get_font_family() # Returns BundledFont or None +``` + +## API Reference + +### EreaderLayoutManager Methods + +#### `set_font_family(family: Optional[BundledFont]) -> Page` + +Change the font family and re-render the current page. + +**Parameters:** +- `family`: Font family to use (`BundledFont.SANS`, `BundledFont.SERIF`, `BundledFont.MONOSPACE`, or `None` for original fonts) + +**Returns:** +- Re-rendered page with the new font family + +**Example:** +```python +# Switch to serif +page = manager.set_font_family(BundledFont.SERIF) + +# Restore original fonts +page = manager.set_font_family(None) +``` + +#### `get_font_family() -> Optional[BundledFont]` + +Get the current font family override. + +**Returns:** +- Current font family (`BundledFont.SANS`, `BundledFont.SERIF`, `BundledFont.MONOSPACE`) or `None` if using original fonts + +**Example:** +```python +family = manager.get_font_family() +if family: + print(f"Currently using: {family.value}") +else: + print("Using original fonts") +``` + +## Font Families + +### Sans-Serif (BundledFont.SANS) +- **Font**: DejaVu Sans +- **Best for**: Screen reading, modern interfaces +- **Characteristics**: Clean, legible, no decorative strokes + +### Serif (BundledFont.SERIF) +- **Font**: DejaVu Serif +- **Best for**: Long-form reading, formal documents +- **Characteristics**: Traditional, classic appearance with decorative strokes + +### Monospace (BundledFont.MONOSPACE) +- **Font**: DejaVu Sans Mono +- **Best for**: Code, technical documentation +- **Characteristics**: Fixed-width characters, uniform spacing + +## Examples + +### Complete Demo +See [examples/11_font_family_switching_demo.py](examples/11_font_family_switching_demo.py) for a full demonstration including: +- Creating ereader content +- Switching between font families +- Navigating with different fonts +- Position tracking across font changes + +### Generate README Images +Run [examples/generate_readme_font_demo.py](examples/generate_readme_font_demo.py) to create comparison images: +```bash +python examples/generate_readme_font_demo.py +``` + +## Implementation Details + +The font family switching is implemented using a **hybrid approach** that combines: + +1. **FontFamilyOverride class**: Manages font preferences at render time +2. **Font transformation pipeline**: Intercepts and transforms Font objects during rendering +3. **Intelligent caching**: Automatic cache invalidation when font family changes +4. **Backward compatibility**: Works with existing Font-based content without migration + +This approach provides: +- ✅ No breaking changes to existing code +- ✅ Instant font switching without document recreation +- ✅ Preservation of font attributes (weight, style, size, color) +- ✅ Optimal performance with intelligent buffering + +## Technical Notes + +- Font family changes invalidate the page buffer cache +- Reading position is preserved using the abstract document structure +- Background rendering adapts to the new font family automatically +- All three bundled fonts are included in the package (license: Bitstream Vera / Public Domain) + +## License + +The bundled DejaVu fonts are free and open source under the [Bitstream Vera License](pyWebLayout/assets/fonts/DEJAVU_README.md). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..536b3ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Duncan Tourolle + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9950b3d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,11 @@ +include README.md +include LICENSE +include pyWebLayout/*.py +recursive-include pyWebLayout/abstract *.py +recursive-include pyWebLayout/concrete *.py +recursive-include pyWebLayout/style *.py +recursive-include pyWebLayout/core *.py +recursive-include pyWebLayout/typesetting *.py +recursive-include pyWebLayout/io *.py +recursive-include pyWebLayout/examples *.py +recursive-include pyWebLayout/assets *.ttf *.otf *.woff *.woff2 diff --git a/README.md b/README.md new file mode 100644 index 0000000..876c5c5 --- /dev/null +++ b/README.md @@ -0,0 +1,240 @@ +# PyWebLayout + +## Project Status + + +| Badge | Description | +|-------|-------------| +| ![Test Coverage](https://gitea.tourolle.paris/dtourolle/pyWebLayout/raw/branch/badges/cov_info/coverage.svg) | **Test Coverage** - Percentage of code covered by unit tests | +| ![Documentation Coverage](https://gitea.tourolle.paris/dtourolle/pyWebLayout/raw/branch/badges/cov_info/coverage-docs.svg) | **Documentation Coverage** - Percentage of code with docstrings | +| ![License](https://img.shields.io/badge/license-MIT-blue.svg) | **License** - Project licensing information | +A Python library for HTML-like layout and rendering. +> 📋 **Note**: Badges show results from the commit referenced in the URLs. Red "error" badges indicate build failures for that specific step. +## Description + +PyWebLayout is a Python library for HTML-like layout and rendering to paginated images. It provides a flexible page rendering system with support for borders, padding, text layout, and HTML parsing. + +## Key Features + +### Page Rendering System +- 📄 **Flexible Page Layouts** - Create pages with customizable sizes, borders, and padding +- 🎨 **Styling System** - Control backgrounds, border colors, and spacing +- 📐 **Multiple Layouts** - Support for portrait, landscape, and square pages +- 🖼️ **Image Output** - Render pages to PIL Images (PNG, JPEG, etc.) + +### Text and HTML Support +- 📝 **HTML Parsing** - Parse HTML content into structured document blocks +- 🔤 **Font Support** - Multiple font sizes, weights, and styles +- 🎨 **Dynamic Font Families** - Switch between Sans, Serif, and Monospace fonts on-the-fly +- ↔️ **Text Alignment** - Left, center, right, and justified text +- 📖 **Rich Content** - Headings, paragraphs, bold, italic, and more +- 📊 **Table Rendering** - Full HTML table support with headers, borders, and styling +- 🔘 **Interactive Elements** - Buttons, forms, and links with callback support + +### Architecture +- **Abstract/Concrete Separation** - Clean separation between content structure and rendering +- **Extensible Design** - Easy to extend with custom renderables +- **Type-safe** - Comprehensive type hints throughout the codebase + +## Installation + +```bash +pip install pyWebLayout +``` + +## Quick Start + +### Basic Page Rendering + +```python +from pyWebLayout.concrete.page import Page +from pyWebLayout.style.page_style import PageStyle + +# Create a styled page +page_style = PageStyle( + border_width=2, + border_color=(200, 200, 200), + padding=(30, 30, 30, 30), # top, right, bottom, left + background_color=(255, 255, 255) +) + +page = Page(size=(600, 800), style=page_style) + +# Render to image +image = page.render() +image.save("my_page.png") +``` + +### HTML Content Parsing + +```python +from pyWebLayout.io.readers.html_extraction import parse_html_string +from pyWebLayout.style import Font + +# Parse HTML to structured blocks +html = """ +

Document Title

+

First paragraph with bold text.

+

Second paragraph with more content.

+""" + +base_font = Font(font_size=14) +blocks = parse_html_string(html, base_font=base_font) + +# blocks is a list of structured content (Paragraph, Heading, etc.) +``` + +## Visual Examples + +The library supports various page layouts and configurations: + + + + + + + + + + + + + + + + + + + + + + + +
+ Page Styles
+ Page Rendering
+ Different borders, padding, and backgrounds +
+ HTML Content
+ Text Layout
+ Parsed HTML with various text styles +
+ Page Layouts
+ Page Layouts
+ Portrait, landscape, and square formats +
+ Table Rendering
+ Table Rendering
+ HTML tables with headers and styling +
+ Interactive Elements
+ Interactive Elements
+ Buttons, forms, and callback binding +
+ 🆕 Pagination & PageBreak
+ Pagination
+ Multi-page documents with explicit and automatic breaks +
+ 🆕 Link Navigation
+ Links
+ All 4 link types: Internal, External, API, Function +
+ 🆕 Comprehensive Forms
+ Forms
+ All 14 form field types with validation +
+ 🆕 Dynamic Font Family Switching
+ Font Switching
+ Switch between Sans, Serif, and Monospace fonts instantly +
+ +## Examples + +The `examples/` directory contains working demonstrations: + +### Getting Started +- **[01_simple_page_rendering.py](examples/01_simple_page_rendering.py)** - Introduction to the Page system +- **[02_text_and_layout.py](examples/02_text_and_layout.py)** - HTML parsing and text rendering +- **[03_page_layouts.py](examples/03_page_layouts.py)** - Different page configurations +- **[04_table_rendering.py](examples/04_table_rendering.py)** - HTML table rendering with styling +- **[05_html_table_with_images.py](examples/05_html_table_with_images.py)** - Tables with embedded images +- **[06_functional_elements_demo.py](examples/06_functional_elements_demo.py)** - Interactive buttons and forms with callbacks +- **[08_bundled_fonts_demo.py](examples/08_bundled_fonts_demo.py)** - Using the bundled DejaVu font families + +### 🆕 Advanced Features (NEW) +- **[08_pagination_demo.py](examples/08_pagination_demo.py)** - Multi-page documents with PageBreak ([11 tests](tests/examples/test_08_pagination_demo.py)) +- **[09_link_navigation_demo.py](examples/09_link_navigation_demo.py)** - All link types and navigation ([10 tests](tests/examples/test_09_link_navigation_demo.py)) +- **[10_forms_demo.py](examples/10_forms_demo.py)** - All 14 form field types ([9 tests](tests/examples/test_10_forms_demo.py)) +- **[11_font_family_switching_demo.py](examples/11_font_family_switching_demo.py)** - 🆕 Dynamic font switching in ereader + +Run any example: +```bash +cd examples +python 01_simple_page_rendering.py +python 08_pagination_demo.py # NEW: Multi-page documents +``` + +**All new examples include comprehensive test coverage!** Run tests with: +```bash +python -m pytest tests/examples/ -v # 30 tests, all passing ✅ +``` + +**Coverage Impact:** The new examples fill critical documentation gaps: +- **PageBreak:** 0% → 100% (had NO examples before) +- **LinkText:** 14% → 100% (all 4 link types demonstrated) +- **FormFields:** 14% → 100% (all 14 field types demonstrated) + +See **[examples/README.md](examples/README.md)** for detailed documentation. + +## Font Family Switching (NEW ✨) + +PyWebLayout now supports dynamic font family switching in the ereader, allowing readers to change fonts on-the-fly without losing their reading position! + +### Quick Example + +```python +from pyWebLayout.style.fonts import BundledFont +from pyWebLayout.layout.ereader_manager import create_ereader_manager + +# Create an ereader +manager = create_ereader_manager(blocks, page_size=(600, 800)) + +# Switch to serif font +manager.set_font_family(BundledFont.SERIF) + +# Switch to monospace font +manager.set_font_family(BundledFont.MONOSPACE) + +# Restore original fonts +manager.set_font_family(None) + +# Query current font +current = manager.get_font_family() +``` + +### Features + +- **3 Bundled Fonts**: Sans, Serif, and Monospace (DejaVu font family) +- **Instant Switching**: Change fonts without recreating the document +- **Position Preservation**: Reading position maintained across font changes +- **Attribute Preservation**: Bold, italic, size, and color are preserved +- **Smart Caching**: Automatic cache invalidation for optimal performance + +**Learn more**: See [FONT_SWITCHING_FEATURE.md](FONT_SWITCHING_FEATURE.md) for complete documentation. + +## Documentation + +- **[ARCHITECTURE.md](ARCHITECTURE.md)** - Abstract/Concrete architecture guide +- **[FONT_SWITCHING_FEATURE.md](FONT_SWITCHING_FEATURE.md)** - 🆕 Font family switching guide +- **[examples/README.md](examples/README.md)** - Complete examples guide with tests +- **[docs/images/README.md](docs/images/README.md)** - Visual documentation index +- **[pyWebLayout/layout/README_EREADER_API.md](pyWebLayout/layout/README_EREADER_API.md)** - EbookReader API reference +- **API Reference** - See docstrings in source code + +## License + +MIT License + +## Author + +Duncan Tourolle - duncan@tourolle.paris diff --git a/cov_info/coverage-docs.svg b/cov_info/coverage-docs.svg new file mode 100644 index 0000000..5fef205 --- /dev/null +++ b/cov_info/coverage-docs.svg @@ -0,0 +1,58 @@ + + interrogate: 94.8% + + + + + + + + + + + interrogate + interrogate + 94.8% + 94.8% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cov_info/coverage-summary.txt b/cov_info/coverage-summary.txt new file mode 100644 index 0000000..fa637f7 --- /dev/null +++ b/cov_info/coverage-summary.txt @@ -0,0 +1 @@ +78.9% \ No newline at end of file diff --git a/cov_info/coverage.json b/cov_info/coverage.json new file mode 100644 index 0000000..4439d71 --- /dev/null +++ b/cov_info/coverage.json @@ -0,0 +1 @@ +{"meta": {"format": 3, "version": "7.11.2", "timestamp": "2025-11-12T12:02:50.628912", "branch_coverage": true, "show_contexts": false}, "files": {"pyWebLayout/__init__.py": {"executed_lines": [1, 11], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 11], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1, 11], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/abstract/__init__.py": {"executed_lines": [1, 8, 9, 10, 11, 13], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 8, 9, 10, 11, 13], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1, 8, 9, 10, 11, 13], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/abstract/block.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 29, 30, 37, 44, 45, 47, 48, 50, 53, 54, 61, 68, 69, 70, 72, 73, 100, 107, 109, 126, 135, 148, 149, 151, 153, 160, 161, 163, 173, 174, 176, 178, 179, 184, 185, 186, 187, 188, 189, 190, 191, 194, 195, 200, 208, 209, 210, 212, 213, 245, 246, 248, 250, 251, 253, 256, 257, 261, 268, 269, 271, 272, 299, 300, 304, 305, 310, 311, 315, 322, 323, 324, 326, 327, 354, 355, 357, 359, 360, 362, 364, 371, 373, 380, 381, 383, 384, 386, 389, 390, 391, 392, 393, 396, 397, 401, 409, 410, 411, 412, 414, 415, 447, 448, 450, 452, 453, 455, 457, 458, 462, 463, 467, 474, 475, 477, 490, 497, 498, 500, 501, 503, 506, 507, 511, 519, 520, 521, 523, 524, 556, 557, 559, 561, 562, 564, 566, 567, 571, 572, 577, 578, 582, 597, 598, 599, 600, 601, 603, 604, 635, 636, 638, 640, 641, 645, 646, 648, 650, 651, 655, 656, 658, 660, 661, 665, 666, 670, 671, 676, 677, 681, 688, 689, 690, 692, 693, 725, 726, 730, 731, 735, 742, 743, 745, 765, 772, 773, 775, 776, 778, 781, 782, 786, 794, 795, 796, 797, 798, 799, 801, 802, 834, 835, 837, 839, 840, 842, 844, 845, 849, 850, 854, 862, 864, 865, 866, 867, 869, 871, 884, 891, 892, 894, 901, 902, 904, 911, 912, 914, 921, 922, 923, 924, 925, 926, 928, 929, 931, 939, 940, 944, 959, 960, 961, 962, 963, 965, 966, 1002, 1003, 1005, 1007, 1008, 1010, 1012, 1013, 1015, 1017, 1018, 1020, 1022, 1023, 1025, 1027, 1028, 1030, 1032, 1033, 1035, 1037, 1038, 1040, 1042, 1049, 1051, 1058, 1059, 1060, 1062, 1076, 1077, 1079, 1082, 1083, 1084, 1086, 1090, 1092, 1102, 1103, 1105, 1119, 1121, 1123, 1125, 1126, 1128, 1129, 1131, 1132, 1135, 1136, 1139, 1141, 1154, 1155, 1157, 1158, 1160, 1161, 1163, 1164, 1167, 1170, 1172, 1175, 1176, 1179, 1181, 1183, 1188, 1190, 1198, 1200, 1201, 1204, 1205, 1207, 1218, 1219, 1220, 1223, 1224, 1227, 1229, 1238, 1239, 1242, 1243, 1244, 1248, 1251, 1252, 1256, 1277, 1281, 1282, 1283, 1284, 1285, 1286, 1288, 1289, 1291, 1293, 1294, 1296, 1298, 1299, 1303, 1304, 1308, 1309, 1313, 1323, 1326, 1330, 1333, 1334, 1337, 1340, 1341, 1345, 1347, 1349, 1350, 1377, 1378, 1386, 1388, 1390, 1391], "summary": {"covered_lines": 390, "num_statements": 489, "percent_covered": 79.27927927927928, "percent_covered_display": "79", "missing_lines": 99, "excluded_lines": 119, "num_branches": 66, "num_partial_branches": 6, "covered_branches": 50, "missing_branches": 16}, "missing_lines": [89, 90, 93, 96, 98, 124, 133, 146, 170, 171, 234, 235, 238, 241, 243, 288, 289, 292, 295, 297, 302, 307, 342, 345, 346, 348, 352, 436, 437, 440, 443, 445, 460, 465, 488, 545, 546, 549, 552, 554, 569, 574, 624, 625, 628, 631, 633, 643, 653, 663, 668, 673, 714, 715, 718, 721, 723, 728, 733, 763, 823, 824, 827, 830, 832, 847, 852, 882, 990, 993, 994, 996, 1000, 1087, 1088, 1133, 1134, 1137, 1138, 1184, 1185, 1186, 1187, 1245, 1246, 1301, 1306, 1311, 1331, 1364, 1367, 1368, 1370, 1374, 1405, 1408, 1409, 1411, 1415], "excluded_lines": [13, 30, 38, 49, 54, 62, 74, 101, 110, 127, 136, 150, 154, 164, 175, 185, 195, 201, 218, 247, 252, 257, 262, 273, 301, 306, 311, 316, 328, 356, 361, 365, 374, 385, 390, 397, 402, 420, 449, 454, 459, 464, 468, 478, 491, 502, 507, 512, 529, 558, 563, 568, 573, 578, 588, 606, 637, 642, 647, 652, 657, 662, 667, 672, 677, 682, 698, 727, 732, 736, 751, 766, 777, 782, 787, 807, 836, 841, 846, 851, 855, 872, 885, 895, 905, 915, 930, 940, 950, 973, 1004, 1009, 1014, 1019, 1024, 1029, 1034, 1039, 1043, 1052, 1066, 1093, 1106, 1144, 1191, 1252, 1262, 1290, 1295, 1300, 1305, 1310, 1314, 1341, 1346, 1351, 1378, 1387, 1392], "executed_branches": [[160, -153], [160, 161], [380, -373], [380, 381], [497, -490], [497, 498], [772, -765], [772, 773], [864, 865], [864, 866], [866, 867], [866, 869], [891, -884], [891, 892], [901, -894], [901, 902], [911, -904], [911, 912], [921, 922], [921, 923], [923, 924], [923, 925], [925, -914], [925, 926], [1058, 1059], [1058, 1060], [1076, 1077], [1076, 1079], [1082, 1083], [1082, 1086], [1086, 1090], [1154, 1155], [1154, 1157], [1161, 1163], [1161, 1167], [1175, 1176], [1175, 1179], [1183, 1188], [1200, 1201], [1200, 1204], [1205, 1207], [1218, 1219], [1223, 1224], [1223, 1229], [1238, 1239], [1242, 1243], [1242, 1248], [1330, 1333], [1333, 1334], [1333, 1337]], "missing_branches": [[170, -163], [170, 171], [345, 346], [345, 348], [993, 994], [993, 996], [1086, 1087], [1183, 1184], [1205, 1229], [1218, 1223], [1238, 1242], [1330, 1331], [1367, 1368], [1367, 1370], [1408, 1409], [1408, 1411]], "functions": {"Block.__init__": {"executed_lines": [44, 45], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [38], "executed_branches": [], "missing_branches": []}, "Block.block_type": {"executed_lines": [50], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [49], "executed_branches": [], "missing_branches": []}, "Paragraph.__init__": {"executed_lines": [68, 69, 70], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [62], "executed_branches": [], "missing_branches": []}, "Paragraph.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [89, 90, 93, 96, 98], "excluded_lines": [74], "executed_branches": [], "missing_branches": []}, "Paragraph.add_word": {"executed_lines": [107], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [101], "executed_branches": [], "missing_branches": []}, "Paragraph.create_word": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [124], "excluded_lines": [110], "executed_branches": [], "missing_branches": []}, "Paragraph.add_span": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [133], "excluded_lines": [127], "executed_branches": [], "missing_branches": []}, "Paragraph.create_span": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [146], "excluded_lines": [136], "executed_branches": [], "missing_branches": []}, "Paragraph.words": {"executed_lines": [151], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [150], "executed_branches": [], "missing_branches": []}, "Paragraph.words_iter": {"executed_lines": [160, 161], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [154], "executed_branches": [[160, -153], [160, 161]], "missing_branches": []}, "Paragraph.spans": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [170, 171], "excluded_lines": [164], "executed_branches": [], "missing_branches": [[170, -163], [170, 171]]}, "Paragraph.word_count": {"executed_lines": [176], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [175], "executed_branches": [], "missing_branches": []}, "Paragraph.__len__": {"executed_lines": [179], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Heading.__init__": {"executed_lines": [208, 209, 210], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [201], "executed_branches": [], "missing_branches": []}, "Heading.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [234, 235, 238, 241, 243], "excluded_lines": [218], "executed_branches": [], "missing_branches": []}, "Heading.level": {"executed_lines": [253], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [252], "executed_branches": [], "missing_branches": []}, "Quote.__init__": {"executed_lines": [268, 269], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [262], "executed_branches": [], "missing_branches": []}, "Quote.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [288, 289, 292, 295, 297], "excluded_lines": [273], "executed_branches": [], "missing_branches": []}, "Quote.style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [307], "excluded_lines": [306], "executed_branches": [], "missing_branches": []}, "CodeBlock.__init__": {"executed_lines": [322, 323, 324], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [316], "executed_branches": [], "missing_branches": []}, "CodeBlock.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [342, 345, 346, 348, 352], "excluded_lines": [328], "executed_branches": [], "missing_branches": [[345, 346], [345, 348]]}, "CodeBlock.language": {"executed_lines": [362], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [361], "executed_branches": [], "missing_branches": []}, "CodeBlock.add_line": {"executed_lines": [371], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [365], "executed_branches": [], "missing_branches": []}, "CodeBlock.lines": {"executed_lines": [380, 381], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [374], "executed_branches": [[380, -373], [380, 381]], "missing_branches": []}, "CodeBlock.line_count": {"executed_lines": [386], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [385], "executed_branches": [], "missing_branches": []}, "HList.__init__": {"executed_lines": [409, 410, 411, 412], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [402], "executed_branches": [], "missing_branches": []}, "HList.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [436, 437, 440, 443, 445], "excluded_lines": [420], "executed_branches": [], "missing_branches": []}, "HList.style": {"executed_lines": [455], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [454], "executed_branches": [], "missing_branches": []}, "HList.default_style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [465], "excluded_lines": [464], "executed_branches": [], "missing_branches": []}, "HList.add_item": {"executed_lines": [474, 475], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [468], "executed_branches": [], "missing_branches": []}, "HList.create_item": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [488], "excluded_lines": [478], "executed_branches": [], "missing_branches": []}, "HList.items": {"executed_lines": [497, 498], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [491], "executed_branches": [[497, -490], [497, 498]], "missing_branches": []}, "HList.item_count": {"executed_lines": [503], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [502], "executed_branches": [], "missing_branches": []}, "ListItem.__init__": {"executed_lines": [519, 520, 521], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [512], "executed_branches": [], "missing_branches": []}, "ListItem.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [545, 546, 549, 552, 554], "excluded_lines": [529], "executed_branches": [], "missing_branches": []}, "ListItem.term": {"executed_lines": [564], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [563], "executed_branches": [], "missing_branches": []}, "ListItem.style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [574], "excluded_lines": [573], "executed_branches": [], "missing_branches": []}, "TableCell.__init__": {"executed_lines": [597, 598, 599, 600, 601], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [588], "executed_branches": [], "missing_branches": []}, "TableCell.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [624, 625, 628, 631, 633], "excluded_lines": [606], "executed_branches": [], "missing_branches": []}, "TableCell.is_header": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [643], "excluded_lines": [642], "executed_branches": [], "missing_branches": []}, "TableCell.colspan": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [653], "excluded_lines": [652], "executed_branches": [], "missing_branches": []}, "TableCell.rowspan": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [663], "excluded_lines": [662], "executed_branches": [], "missing_branches": []}, "TableCell.style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [673], "excluded_lines": [672], "executed_branches": [], "missing_branches": []}, "TableRow.__init__": {"executed_lines": [688, 689, 690], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [682], "executed_branches": [], "missing_branches": []}, "TableRow.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [714, 715, 718, 721, 723], "excluded_lines": [698], "executed_branches": [], "missing_branches": []}, "TableRow.style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [733], "excluded_lines": [732], "executed_branches": [], "missing_branches": []}, "TableRow.add_cell": {"executed_lines": [742, 743], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [736], "executed_branches": [], "missing_branches": []}, "TableRow.create_cell": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [763], "excluded_lines": [751], "executed_branches": [], "missing_branches": []}, "TableRow.cells": {"executed_lines": [772, 773], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [766], "executed_branches": [[772, -765], [772, 773]], "missing_branches": []}, "TableRow.cell_count": {"executed_lines": [778], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [777], "executed_branches": [], "missing_branches": []}, "Table.__init__": {"executed_lines": [794, 795, 796, 797, 798, 799], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [787], "executed_branches": [], "missing_branches": []}, "Table.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [823, 824, 827, 830, 832], "excluded_lines": [807], "executed_branches": [], "missing_branches": []}, "Table.caption": {"executed_lines": [842], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [841], "executed_branches": [], "missing_branches": []}, "Table.style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [852], "excluded_lines": [851], "executed_branches": [], "missing_branches": []}, "Table.add_row": {"executed_lines": [862, 864, 865, 866, 867, 869], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [855], "executed_branches": [[864, 865], [864, 866], [866, 867], [866, 869]], "missing_branches": []}, "Table.create_row": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [882], "excluded_lines": [872], "executed_branches": [], "missing_branches": []}, "Table.header_rows": {"executed_lines": [891, 892], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [885], "executed_branches": [[891, -884], [891, 892]], "missing_branches": []}, "Table.body_rows": {"executed_lines": [901, 902], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [895], "executed_branches": [[901, -894], [901, 902]], "missing_branches": []}, "Table.footer_rows": {"executed_lines": [911, 912], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [905], "executed_branches": [[911, -904], [911, 912]], "missing_branches": []}, "Table.all_rows": {"executed_lines": [921, 922, 923, 924, 925, 926], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [915], "executed_branches": [[921, 922], [921, 923], [923, 924], [923, 925], [925, -914], [925, 926]], "missing_branches": []}, "Table.row_count": {"executed_lines": [931], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [930], "executed_branches": [], "missing_branches": []}, "Image.__init__": {"executed_lines": [959, 960, 961, 962, 963], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [950], "executed_branches": [], "missing_branches": []}, "Image.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [990, 993, 994, 996, 1000], "excluded_lines": [973], "executed_branches": [], "missing_branches": [[993, 994], [993, 996]]}, "Image.source": {"executed_lines": [1010], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1009], "executed_branches": [], "missing_branches": []}, "Image.alt_text": {"executed_lines": [1020], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1019], "executed_branches": [], "missing_branches": []}, "Image.width": {"executed_lines": [1030], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1029], "executed_branches": [], "missing_branches": []}, "Image.height": {"executed_lines": [1040], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1039], "executed_branches": [], "missing_branches": []}, "Image.get_dimensions": {"executed_lines": [1049], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1043], "executed_branches": [], "missing_branches": []}, "Image.get_aspect_ratio": {"executed_lines": [1058, 1059, 1060], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1052], "executed_branches": [[1058, 1059], [1058, 1060]], "missing_branches": []}, "Image.calculate_scaled_dimensions": {"executed_lines": [1076, 1077, 1079, 1082, 1083, 1084, 1086, 1090], "summary": {"covered_lines": 8, "num_statements": 10, "percent_covered": 81.25, "percent_covered_display": "81", "missing_lines": 2, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [1087, 1088], "excluded_lines": [1066], "executed_branches": [[1076, 1077], [1076, 1079], [1082, 1083], [1082, 1086], [1086, 1090]], "missing_branches": [[1086, 1087]]}, "Image._is_url": {"executed_lines": [1102, 1103], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1093], "executed_branches": [], "missing_branches": []}, "Image._download_to_temp": {"executed_lines": [1119, 1121, 1123, 1125, 1126, 1128, 1129, 1131, 1132, 1135, 1136, 1139], "summary": {"covered_lines": 12, "num_statements": 16, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 4, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [1133, 1134, 1137, 1138], "excluded_lines": [1106], "executed_branches": [], "missing_branches": []}, "Image.load_image_data": {"executed_lines": [1154, 1155, 1157, 1158, 1160, 1161, 1163, 1164, 1167, 1170, 1172, 1175, 1176, 1179, 1181, 1183, 1188], "summary": {"covered_lines": 17, "num_statements": 21, "percent_covered": 82.75862068965517, "percent_covered_display": "83", "missing_lines": 4, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [1184, 1185, 1186, 1187], "excluded_lines": [1144], "executed_branches": [[1154, 1155], [1154, 1157], [1161, 1163], [1161, 1167], [1175, 1176], [1175, 1179], [1183, 1188]], "missing_branches": [[1183, 1184]]}, "Image.get_image_info": {"executed_lines": [1198, 1200, 1201, 1204, 1205, 1207, 1218, 1219, 1220, 1223, 1224, 1227, 1229, 1238, 1239, 1242, 1243, 1244, 1248], "summary": {"covered_lines": 19, "num_statements": 21, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 2, "excluded_lines": 1, "num_branches": 12, "num_partial_branches": 3, "covered_branches": 9, "missing_branches": 3}, "missing_lines": [1245, 1246], "excluded_lines": [1191], "executed_branches": [[1200, 1201], [1200, 1204], [1205, 1207], [1218, 1219], [1223, 1224], [1223, 1229], [1238, 1239], [1242, 1243], [1242, 1248]], "missing_branches": [[1205, 1229], [1218, 1223], [1238, 1242]]}, "LinkedImage.__init__": {"executed_lines": [1277, 1281, 1282, 1283, 1284, 1285, 1286], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1262], "executed_branches": [], "missing_branches": []}, "LinkedImage.location": {"executed_lines": [1291], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1290], "executed_branches": [], "missing_branches": []}, "LinkedImage.link_type": {"executed_lines": [1296], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1295], "executed_branches": [], "missing_branches": []}, "LinkedImage.link_callback": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [1301], "excluded_lines": [1300], "executed_branches": [], "missing_branches": []}, "LinkedImage.params": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [1306], "excluded_lines": [1305], "executed_branches": [], "missing_branches": []}, "LinkedImage.link_title": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [1311], "excluded_lines": [1310], "executed_branches": [], "missing_branches": []}, "LinkedImage.execute_link": {"executed_lines": [1323, 1326, 1330, 1333, 1334, 1337], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [1331], "excluded_lines": [1314], "executed_branches": [[1330, 1333], [1333, 1334], [1333, 1337]], "missing_branches": [[1330, 1331]]}, "HorizontalRule.__init__": {"executed_lines": [1347], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1346], "executed_branches": [], "missing_branches": []}, "HorizontalRule.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [1364, 1367, 1368, 1370, 1374], "excluded_lines": [1351], "executed_branches": [], "missing_branches": [[1367, 1368], [1367, 1370]]}, "PageBreak.__init__": {"executed_lines": [1388], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1387], "executed_branches": [], "missing_branches": []}, "PageBreak.create_and_add_to": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [1405, 1408, 1409, 1411, 1415], "excluded_lines": [1392], "executed_branches": [], "missing_branches": [[1408, 1409], [1408, 1411]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 29, 30, 37, 47, 48, 53, 54, 61, 72, 73, 100, 109, 126, 135, 148, 149, 153, 163, 173, 174, 178, 184, 185, 186, 187, 188, 189, 190, 191, 194, 195, 200, 212, 213, 245, 246, 250, 251, 256, 257, 261, 271, 272, 299, 300, 304, 305, 310, 311, 315, 326, 327, 354, 355, 359, 360, 364, 373, 383, 384, 389, 390, 391, 392, 393, 396, 397, 401, 414, 415, 447, 448, 452, 453, 457, 458, 462, 463, 467, 477, 490, 500, 501, 506, 507, 511, 523, 524, 556, 557, 561, 562, 566, 567, 571, 572, 577, 578, 582, 603, 604, 635, 636, 640, 641, 645, 646, 650, 651, 655, 656, 660, 661, 665, 666, 670, 671, 676, 677, 681, 692, 693, 725, 726, 730, 731, 735, 745, 765, 775, 776, 781, 782, 786, 801, 802, 834, 835, 839, 840, 844, 845, 849, 850, 854, 871, 884, 894, 904, 914, 928, 929, 939, 940, 944, 965, 966, 1002, 1003, 1007, 1008, 1012, 1013, 1017, 1018, 1022, 1023, 1027, 1028, 1032, 1033, 1037, 1038, 1042, 1051, 1062, 1092, 1105, 1141, 1190, 1251, 1252, 1256, 1288, 1289, 1293, 1294, 1298, 1299, 1303, 1304, 1308, 1309, 1313, 1340, 1341, 1345, 1349, 1350, 1377, 1378, 1386, 1390, 1391], "summary": {"covered_lines": 211, "num_statements": 211, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 17, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [13, 30, 54, 185, 195, 257, 311, 390, 397, 507, 578, 677, 782, 940, 1252, 1341, 1378], "executed_branches": [], "missing_branches": []}}, "classes": {"BlockType": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Block": {"executed_lines": [44, 45, 50], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [38, 49], "executed_branches": [], "missing_branches": []}, "Paragraph": {"executed_lines": [68, 69, 70, 107, 151, 160, 161, 176, 179], "summary": {"covered_lines": 9, "num_statements": 19, "percent_covered": 47.82608695652174, "percent_covered_display": "48", "missing_lines": 10, "excluded_lines": 10, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [89, 90, 93, 96, 98, 124, 133, 146, 170, 171], "excluded_lines": [62, 74, 101, 110, 127, 136, 150, 154, 164, 175], "executed_branches": [[160, -153], [160, 161]], "missing_branches": [[170, -163], [170, 171]]}, "HeadingLevel": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Heading": {"executed_lines": [208, 209, 210, 248, 253], "summary": {"covered_lines": 5, "num_statements": 10, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 5, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [234, 235, 238, 241, 243], "excluded_lines": [201, 218, 247, 252], "executed_branches": [], "missing_branches": []}, "Quote": {"executed_lines": [268, 269], "summary": {"covered_lines": 2, "num_statements": 9, "percent_covered": 22.22222222222222, "percent_covered_display": "22", "missing_lines": 7, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [288, 289, 292, 295, 297, 302, 307], "excluded_lines": [262, 273, 301, 306], "executed_branches": [], "missing_branches": []}, "CodeBlock": {"executed_lines": [322, 323, 324, 357, 362, 371, 380, 381, 386], "summary": {"covered_lines": 9, "num_statements": 14, "percent_covered": 61.111111111111114, "percent_covered_display": "61", "missing_lines": 5, "excluded_lines": 7, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [342, 345, 346, 348, 352], "excluded_lines": [316, 328, 356, 361, 365, 374, 385], "executed_branches": [[380, -373], [380, 381]], "missing_branches": [[345, 346], [345, 348]]}, "ListStyle": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "HList": {"executed_lines": [409, 410, 411, 412, 450, 455, 474, 475, 497, 498, 503], "summary": {"covered_lines": 11, "num_statements": 19, "percent_covered": 61.904761904761905, "percent_covered_display": "62", "missing_lines": 8, "excluded_lines": 10, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [436, 437, 440, 443, 445, 460, 465, 488], "excluded_lines": [402, 420, 449, 454, 459, 464, 468, 478, 491, 502], "executed_branches": [[497, -490], [497, 498]], "missing_branches": []}, "ListItem": {"executed_lines": [519, 520, 521, 559, 564], "summary": {"covered_lines": 5, "num_statements": 12, "percent_covered": 41.666666666666664, "percent_covered_display": "42", "missing_lines": 7, "excluded_lines": 6, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [545, 546, 549, 552, 554, 569, 574], "excluded_lines": [512, 529, 558, 563, 568, 573], "executed_branches": [], "missing_branches": []}, "TableCell": {"executed_lines": [597, 598, 599, 600, 601, 638, 648, 658], "summary": {"covered_lines": 8, "num_statements": 18, "percent_covered": 44.44444444444444, "percent_covered_display": "44", "missing_lines": 10, "excluded_lines": 10, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [624, 625, 628, 631, 633, 643, 653, 663, 668, 673], "excluded_lines": [588, 606, 637, 642, 647, 652, 657, 662, 667, 672], "executed_branches": [], "missing_branches": []}, "TableRow": {"executed_lines": [688, 689, 690, 742, 743, 772, 773, 778], "summary": {"covered_lines": 8, "num_statements": 16, "percent_covered": 55.55555555555556, "percent_covered_display": "56", "missing_lines": 8, "excluded_lines": 8, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [714, 715, 718, 721, 723, 728, 733, 763], "excluded_lines": [682, 698, 727, 732, 736, 751, 766, 777], "executed_branches": [[772, -765], [772, 773]], "missing_branches": []}, "Table": {"executed_lines": [794, 795, 796, 797, 798, 799, 837, 842, 862, 864, 865, 866, 867, 869, 891, 892, 901, 902, 911, 912, 921, 922, 923, 924, 925, 926, 931], "summary": {"covered_lines": 27, "num_statements": 35, "percent_covered": 84.31372549019608, "percent_covered_display": "84", "missing_lines": 8, "excluded_lines": 13, "num_branches": 16, "num_partial_branches": 0, "covered_branches": 16, "missing_branches": 0}, "missing_lines": [823, 824, 827, 830, 832, 847, 852, 882], "excluded_lines": [787, 807, 836, 841, 846, 851, 855, 872, 885, 895, 905, 915, 930], "executed_branches": [[864, 865], [864, 866], [866, 867], [866, 869], [891, -884], [891, 892], [901, -894], [901, 902], [911, -904], [911, 912], [921, 922], [921, 923], [923, 924], [923, 925], [925, -914], [925, 926]], "missing_branches": []}, "Image": {"executed_lines": [959, 960, 961, 962, 963, 1005, 1010, 1015, 1020, 1025, 1030, 1035, 1040, 1049, 1058, 1059, 1060, 1076, 1077, 1079, 1082, 1083, 1084, 1086, 1090, 1102, 1103, 1119, 1121, 1123, 1125, 1126, 1128, 1129, 1131, 1132, 1135, 1136, 1139, 1154, 1155, 1157, 1158, 1160, 1161, 1163, 1164, 1167, 1170, 1172, 1175, 1176, 1179, 1181, 1183, 1188, 1198, 1200, 1201, 1204, 1205, 1207, 1218, 1219, 1220, 1223, 1224, 1227, 1229, 1238, 1239, 1242, 1243, 1244, 1248], "summary": {"covered_lines": 75, "num_statements": 92, "percent_covered": 80.32786885245902, "percent_covered_display": "80", "missing_lines": 17, "excluded_lines": 17, "num_branches": 30, "num_partial_branches": 5, "covered_branches": 23, "missing_branches": 7}, "missing_lines": [990, 993, 994, 996, 1000, 1087, 1088, 1133, 1134, 1137, 1138, 1184, 1185, 1186, 1187, 1245, 1246], "excluded_lines": [950, 973, 1004, 1009, 1014, 1019, 1024, 1029, 1034, 1039, 1043, 1052, 1066, 1093, 1106, 1144, 1191], "executed_branches": [[1058, 1059], [1058, 1060], [1076, 1077], [1076, 1079], [1082, 1083], [1082, 1086], [1086, 1090], [1154, 1155], [1154, 1157], [1161, 1163], [1161, 1167], [1175, 1176], [1175, 1179], [1183, 1188], [1200, 1201], [1200, 1204], [1205, 1207], [1218, 1219], [1223, 1224], [1223, 1229], [1238, 1239], [1242, 1243], [1242, 1248]], "missing_branches": [[993, 994], [993, 996], [1086, 1087], [1183, 1184], [1205, 1229], [1218, 1223], [1238, 1242]]}, "LinkedImage": {"executed_lines": [1277, 1281, 1282, 1283, 1284, 1285, 1286, 1291, 1296, 1323, 1326, 1330, 1333, 1334, 1337], "summary": {"covered_lines": 15, "num_statements": 19, "percent_covered": 78.26086956521739, "percent_covered_display": "78", "missing_lines": 4, "excluded_lines": 7, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [1301, 1306, 1311, 1331], "excluded_lines": [1262, 1290, 1295, 1300, 1305, 1310, 1314], "executed_branches": [[1330, 1333], [1333, 1334], [1333, 1337]], "missing_branches": [[1330, 1331]]}, "HorizontalRule": {"executed_lines": [1347], "summary": {"covered_lines": 1, "num_statements": 6, "percent_covered": 12.5, "percent_covered_display": "12", "missing_lines": 5, "excluded_lines": 2, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [1364, 1367, 1368, 1370, 1374], "excluded_lines": [1346, 1351], "executed_branches": [], "missing_branches": [[1367, 1368], [1367, 1370]]}, "PageBreak": {"executed_lines": [1388], "summary": {"covered_lines": 1, "num_statements": 6, "percent_covered": 12.5, "percent_covered_display": "12", "missing_lines": 5, "excluded_lines": 2, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [1405, 1408, 1409, 1411, 1415], "excluded_lines": [1387, 1392], "executed_branches": [], "missing_branches": [[1408, 1409], [1408, 1411]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 29, 30, 37, 47, 48, 53, 54, 61, 72, 73, 100, 109, 126, 135, 148, 149, 153, 163, 173, 174, 178, 184, 185, 186, 187, 188, 189, 190, 191, 194, 195, 200, 212, 213, 245, 246, 250, 251, 256, 257, 261, 271, 272, 299, 300, 304, 305, 310, 311, 315, 326, 327, 354, 355, 359, 360, 364, 373, 383, 384, 389, 390, 391, 392, 393, 396, 397, 401, 414, 415, 447, 448, 452, 453, 457, 458, 462, 463, 467, 477, 490, 500, 501, 506, 507, 511, 523, 524, 556, 557, 561, 562, 566, 567, 571, 572, 577, 578, 582, 603, 604, 635, 636, 640, 641, 645, 646, 650, 651, 655, 656, 660, 661, 665, 666, 670, 671, 676, 677, 681, 692, 693, 725, 726, 730, 731, 735, 745, 765, 775, 776, 781, 782, 786, 801, 802, 834, 835, 839, 840, 844, 845, 849, 850, 854, 871, 884, 894, 904, 914, 928, 929, 939, 940, 944, 965, 966, 1002, 1003, 1007, 1008, 1012, 1013, 1017, 1018, 1022, 1023, 1027, 1028, 1032, 1033, 1037, 1038, 1042, 1051, 1062, 1092, 1105, 1141, 1190, 1251, 1252, 1256, 1288, 1289, 1293, 1294, 1298, 1299, 1303, 1304, 1308, 1309, 1313, 1340, 1341, 1345, 1349, 1350, 1377, 1378, 1386, 1390, 1391], "summary": {"covered_lines": 211, "num_statements": 211, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 17, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [13, 30, 54, 185, 195, 257, 311, 390, 397, 507, 578, 677, 782, 940, 1252, 1341, 1378], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/abstract/document.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 26, 27, 35, 48, 49, 50, 51, 52, 53, 56, 57, 58, 59, 62, 64, 75, 78, 79, 80, 82, 83, 85, 87, 88, 92, 93, 97, 104, 106, 122, 142, 164, 172, 174, 184, 186, 194, 196, 206, 208, 215, 217, 224, 226, 233, 235, 242, 244, 245, 252, 254, 255, 264, 274, 276, 277, 278, 279, 282, 284, 287, 288, 290, 297, 298, 300, 307, 309, 310, 312, 313, 314, 315, 318, 319, 321, 323, 350, 362, 364, 374, 376, 385, 387, 389, 396, 397, 405, 420, 421, 422, 423, 424, 425, 427, 428, 430, 432, 433, 435, 437, 438, 440, 442, 443, 445, 447, 448, 452, 453, 457, 464, 466, 482, 506, 507, 512, 523, 524, 526, 527, 529, 530, 532, 534, 541, 543, 559, 560, 561, 562, 563, 565, 572, 574, 581, 583, 590, 591, 592, 593, 595], "summary": {"covered_lines": 159, "num_statements": 194, "percent_covered": 77.82608695652173, "percent_covered_display": "78", "missing_lines": 35, "excluded_lines": 47, "num_branches": 36, "num_partial_branches": 4, "covered_branches": 20, "missing_branches": 16}, "missing_lines": [65, 67, 73, 90, 95, 116, 117, 118, 119, 120, 136, 137, 138, 139, 140, 158, 159, 160, 262, 283, 285, 383, 391, 450, 455, 476, 477, 478, 479, 480, 496, 497, 498, 499, 500], "excluded_lines": [12, 27, 40, 84, 89, 94, 98, 107, 126, 147, 165, 175, 187, 197, 209, 218, 227, 236, 246, 256, 265, 291, 301, 333, 365, 377, 386, 390, 397, 411, 429, 434, 439, 444, 449, 454, 458, 467, 486, 507, 514, 531, 535, 548, 566, 575, 584], "executed_branches": [[62, 64], [78, 79], [78, 80], [277, -276], [277, 278], [278, 279], [278, 282], [282, 284], [284, 277], [310, 312], [310, 321], [313, 314], [313, 315], [526, -512], [526, 527], [559, 560], [591, 592], [591, 595], [592, 591], [592, 593]], "missing_branches": [[62, 65], [65, 67], [65, 75], [116, 117], [116, 118], [136, 137], [136, 138], [158, 159], [158, 160], [282, 283], [284, 285], [476, 477], [476, 478], [496, 497], [496, 498], [559, 561]], "functions": {"Document.__init__": {"executed_lines": [48, 49, 50, 51, 52, 53, 56, 57, 58, 59, 62, 64, 75, 78, 79, 80], "summary": {"covered_lines": 16, "num_statements": 19, "percent_covered": 76.0, "percent_covered_display": "76", "missing_lines": 3, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 3}, "missing_lines": [65, 67, 73], "excluded_lines": [40], "executed_branches": [[62, 64], [78, 79], [78, 80]], "missing_branches": [[62, 65], [65, 67], [65, 75]]}, "Document.blocks": {"executed_lines": [85], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [84], "executed_branches": [], "missing_branches": []}, "Document.default_style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [95], "excluded_lines": [94], "executed_branches": [], "missing_branches": []}, "Document.add_block": {"executed_lines": [104], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [98], "executed_branches": [], "missing_branches": []}, "Document.create_paragraph": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [116, 117, 118, 119, 120], "excluded_lines": [107], "executed_branches": [], "missing_branches": [[116, 117], [116, 118]]}, "Document.create_heading": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [136, 137, 138, 139, 140], "excluded_lines": [126], "executed_branches": [], "missing_branches": [[136, 137], [136, 138]]}, "Document.create_chapter": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [158, 159, 160], "excluded_lines": [147], "executed_branches": [], "missing_branches": [[158, 159], [158, 160]]}, "Document.add_anchor": {"executed_lines": [172], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [165], "executed_branches": [], "missing_branches": []}, "Document.get_anchor": {"executed_lines": [184], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [175], "executed_branches": [], "missing_branches": []}, "Document.add_resource": {"executed_lines": [194], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [187], "executed_branches": [], "missing_branches": []}, "Document.get_resource": {"executed_lines": [206], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [197], "executed_branches": [], "missing_branches": []}, "Document.add_stylesheet": {"executed_lines": [215], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [209], "executed_branches": [], "missing_branches": []}, "Document.add_script": {"executed_lines": [224], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [218], "executed_branches": [], "missing_branches": []}, "Document.get_title": {"executed_lines": [233], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [227], "executed_branches": [], "missing_branches": []}, "Document.set_title": {"executed_lines": [242], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [236], "executed_branches": [], "missing_branches": []}, "Document.title": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [262], "excluded_lines": [256], "executed_branches": [], "missing_branches": []}, "Document.find_blocks_by_type": {"executed_lines": [274, 276, 287, 288], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [265], "executed_branches": [], "missing_branches": []}, "Document.find_blocks_by_type._find_recursive": {"executed_lines": [277, 278, 279, 282, 284], "summary": {"covered_lines": 5, "num_statements": 7, "percent_covered": 73.33333333333333, "percent_covered_display": "73", "missing_lines": 2, "excluded_lines": 0, "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2}, "missing_lines": [283, 285], "excluded_lines": [], "executed_branches": [[277, -276], [277, 278], [278, 279], [278, 282], [282, 284], [284, 277]], "missing_branches": [[282, 283], [284, 285]]}, "Document.find_headings": {"executed_lines": [297, 298], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [291], "executed_branches": [], "missing_branches": []}, "Document.generate_table_of_contents": {"executed_lines": [307, 309, 310, 312, 313, 314, 315, 318, 319, 321], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [301], "executed_branches": [[310, 312], [310, 321], [313, 314], [313, 315]], "missing_branches": []}, "Document.get_or_create_style": {"executed_lines": [350, 362], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [333], "executed_branches": [], "missing_branches": []}, "Document.get_font_for_style": {"executed_lines": [374], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [365], "executed_branches": [], "missing_branches": []}, "Document.update_rendering_context": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [383], "excluded_lines": [377], "executed_branches": [], "missing_branches": []}, "Document.get_style_registry": {"executed_lines": [387], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [386], "executed_branches": [], "missing_branches": []}, "Document.get_concrete_style_registry": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [391], "excluded_lines": [390], "executed_branches": [], "missing_branches": []}, "Chapter.__init__": {"executed_lines": [420, 421, 422, 423, 424, 425], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [411], "executed_branches": [], "missing_branches": []}, "Chapter.title": {"executed_lines": [435], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [434], "executed_branches": [], "missing_branches": []}, "Chapter.level": {"executed_lines": [440], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [439], "executed_branches": [], "missing_branches": []}, "Chapter.blocks": {"executed_lines": [445], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [444], "executed_branches": [], "missing_branches": []}, "Chapter.style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [455], "excluded_lines": [454], "executed_branches": [], "missing_branches": []}, "Chapter.add_block": {"executed_lines": [464], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [458], "executed_branches": [], "missing_branches": []}, "Chapter.create_paragraph": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [476, 477, 478, 479, 480], "excluded_lines": [467], "executed_branches": [], "missing_branches": [[476, 477], [476, 478]]}, "Chapter.create_heading": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [496, 497, 498, 499, 500], "excluded_lines": [486], "executed_branches": [], "missing_branches": [[496, 497], [496, 498]]}, "Book.__init__": {"executed_lines": [523, 524, 526, 527], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [514], "executed_branches": [[526, -512], [526, 527]], "missing_branches": []}, "Book.chapters": {"executed_lines": [532], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [531], "executed_branches": [], "missing_branches": []}, "Book.add_chapter": {"executed_lines": [541], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [535], "executed_branches": [], "missing_branches": []}, "Book.create_chapter": {"executed_lines": [559, 560, 561, 562, 563], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [548], "executed_branches": [[559, 560]], "missing_branches": [[559, 561]]}, "Book.get_author": {"executed_lines": [572], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [566], "executed_branches": [], "missing_branches": []}, "Book.set_author": {"executed_lines": [581], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [575], "executed_branches": [], "missing_branches": []}, "Book.generate_table_of_contents": {"executed_lines": [590, 591, 592, 593, 595], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [584], "executed_branches": [[591, 592], [591, 595], [592, 591], [592, 593]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 26, 27, 35, 82, 83, 87, 88, 92, 93, 97, 106, 122, 142, 164, 174, 186, 196, 208, 217, 226, 235, 244, 245, 254, 255, 264, 290, 300, 323, 364, 376, 385, 389, 396, 397, 405, 427, 428, 432, 433, 437, 438, 442, 443, 447, 448, 452, 453, 457, 466, 482, 506, 507, 512, 529, 530, 534, 543, 565, 574, 583], "summary": {"covered_lines": 78, "num_statements": 78, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [12, 27, 397, 507], "executed_branches": [], "missing_branches": []}}, "classes": {"MetadataType": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Document": {"executed_lines": [48, 49, 50, 51, 52, 53, 56, 57, 58, 59, 62, 64, 75, 78, 79, 80, 85, 104, 172, 184, 194, 206, 215, 224, 233, 242, 252, 274, 276, 277, 278, 279, 282, 284, 287, 288, 297, 298, 307, 309, 310, 312, 313, 314, 315, 318, 319, 321, 350, 362, 374, 387], "summary": {"covered_lines": 52, "num_statements": 75, "percent_covered": 65.65656565656566, "percent_covered_display": "66", "missing_lines": 23, "excluded_lines": 26, "num_branches": 24, "num_partial_branches": 3, "covered_branches": 13, "missing_branches": 11}, "missing_lines": [65, 67, 73, 90, 95, 116, 117, 118, 119, 120, 136, 137, 138, 139, 140, 158, 159, 160, 262, 283, 285, 383, 391], "excluded_lines": [40, 84, 89, 94, 98, 107, 126, 147, 165, 175, 187, 197, 209, 218, 227, 236, 246, 256, 265, 291, 301, 333, 365, 377, 386, 390], "executed_branches": [[62, 64], [78, 79], [78, 80], [277, -276], [277, 278], [278, 279], [278, 282], [282, 284], [284, 277], [310, 312], [310, 321], [313, 314], [313, 315]], "missing_branches": [[62, 65], [65, 67], [65, 75], [116, 117], [116, 118], [136, 137], [136, 138], [158, 159], [158, 160], [282, 283], [284, 285]]}, "Chapter": {"executed_lines": [420, 421, 422, 423, 424, 425, 430, 435, 440, 445, 464], "summary": {"covered_lines": 11, "num_statements": 23, "percent_covered": 40.74074074074074, "percent_covered_display": "41", "missing_lines": 12, "excluded_lines": 10, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [450, 455, 476, 477, 478, 479, 480, 496, 497, 498, 499, 500], "excluded_lines": [411, 429, 434, 439, 444, 449, 454, 458, 467, 486], "executed_branches": [], "missing_branches": [[476, 477], [476, 478], [496, 497], [496, 498]]}, "Book": {"executed_lines": [523, 524, 526, 527, 532, 541, 559, 560, 561, 562, 563, 572, 581, 590, 591, 592, 593, 595], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 96.15384615384616, "percent_covered_display": "96", "missing_lines": 0, "excluded_lines": 7, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [514, 531, 535, 548, 566, 575, 584], "executed_branches": [[526, -512], [526, 527], [559, 560], [591, 592], [591, 595], [592, 591], [592, 593]], "missing_branches": [[559, 561]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 26, 27, 35, 82, 83, 87, 88, 92, 93, 97, 106, 122, 142, 164, 174, 186, 196, 208, 217, 226, 235, 244, 245, 254, 255, 264, 290, 300, 323, 364, 376, 385, 389, 396, 397, 405, 427, 428, 432, 433, 437, 438, 442, 443, 447, 448, 452, 453, 457, 466, 482, 506, 507, 512, 529, 530, 534, 543, 565, 574, 583], "summary": {"covered_lines": 78, "num_statements": 78, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [12, 27, 397, 507], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/abstract/functional.py": {"executed_lines": [1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 15, 16, 22, 40, 41, 42, 43, 44, 45, 47, 48, 50, 52, 53, 55, 57, 58, 60, 62, 63, 65, 67, 68, 72, 85, 86, 90, 93, 94, 99, 115, 116, 117, 118, 119, 121, 122, 124, 126, 127, 129, 131, 132, 134, 136, 137, 139, 141, 142, 144, 146, 147, 151, 161, 162, 163, 166, 167, 172, 186, 187, 188, 189, 190, 192, 193, 195, 197, 198, 200, 202, 203, 207, 214, 215, 217, 227, 229, 236, 238, 245, 247, 248, 250, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 271, 272, 276, 294, 295, 296, 297, 298, 299, 300, 302, 303, 305, 307, 308, 310, 312, 313, 315, 317, 318, 320, 322, 323, 325, 327, 328, 330, 332, 333, 335, 337, 338, 340, 342, 343, 345], "summary": {"covered_lines": 141, "num_statements": 144, "percent_covered": 98.0, "percent_covered_display": "98", "missing_lines": 3, "excluded_lines": 39, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [70, 149, 205], "excluded_lines": [8, 16, 29, 49, 54, 59, 64, 69, 73, 94, 105, 123, 128, 133, 138, 143, 148, 152, 167, 177, 194, 199, 204, 208, 218, 230, 239, 254, 272, 283, 304, 309, 314, 319, 324, 329, 334, 339, 344], "executed_branches": [[85, 86], [85, 90], [161, 162], [161, 163], [247, 248], [247, 250]], "missing_branches": [], "functions": {"Link.__init__": {"executed_lines": [40, 41, 42, 43, 44, 45], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [29], "executed_branches": [], "missing_branches": []}, "Link.location": {"executed_lines": [50], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [49], "executed_branches": [], "missing_branches": []}, "Link.link_type": {"executed_lines": [55], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [54], "executed_branches": [], "missing_branches": []}, "Link.params": {"executed_lines": [60], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [59], "executed_branches": [], "missing_branches": []}, "Link.title": {"executed_lines": [65], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [64], "executed_branches": [], "missing_branches": []}, "Link.html_id": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [70], "excluded_lines": [69], "executed_branches": [], "missing_branches": []}, "Link.execute": {"executed_lines": [85, 86, 90], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [73], "executed_branches": [[85, 86], [85, 90]], "missing_branches": []}, "Button.__init__": {"executed_lines": [115, 116, 117, 118, 119], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [105], "executed_branches": [], "missing_branches": []}, "Button.label": {"executed_lines": [129], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [128], "executed_branches": [], "missing_branches": []}, "Button.enabled": {"executed_lines": [139], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [138], "executed_branches": [], "missing_branches": []}, "Button.params": {"executed_lines": [144], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [143], "executed_branches": [], "missing_branches": []}, "Button.html_id": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [149], "excluded_lines": [148], "executed_branches": [], "missing_branches": []}, "Button.execute": {"executed_lines": [161, 162, 163], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [152], "executed_branches": [[161, 162], [161, 163]], "missing_branches": []}, "Form.__init__": {"executed_lines": [186, 187, 188, 189, 190], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [177], "executed_branches": [], "missing_branches": []}, "Form.form_id": {"executed_lines": [195], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [194], "executed_branches": [], "missing_branches": []}, "Form.action": {"executed_lines": [200], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [199], "executed_branches": [], "missing_branches": []}, "Form.html_id": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [205], "excluded_lines": [204], "executed_branches": [], "missing_branches": []}, "Form.add_field": {"executed_lines": [214, 215], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [208], "executed_branches": [], "missing_branches": []}, "Form.get_field": {"executed_lines": [227], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [218], "executed_branches": [], "missing_branches": []}, "Form.get_values": {"executed_lines": [236], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [230], "executed_branches": [], "missing_branches": []}, "Form.execute": {"executed_lines": [245, 247, 248, 250], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [239], "executed_branches": [[247, 248], [247, 250]], "missing_branches": []}, "FormField.__init__": {"executed_lines": [294, 295, 296, 297, 298, 299, 300], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [283], "executed_branches": [], "missing_branches": []}, "FormField.name": {"executed_lines": [305], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [304], "executed_branches": [], "missing_branches": []}, "FormField.field_type": {"executed_lines": [310], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [309], "executed_branches": [], "missing_branches": []}, "FormField.label": {"executed_lines": [315], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [314], "executed_branches": [], "missing_branches": []}, "FormField.value": {"executed_lines": [325], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [324], "executed_branches": [], "missing_branches": []}, "FormField.required": {"executed_lines": [330], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [329], "executed_branches": [], "missing_branches": []}, "FormField.options": {"executed_lines": [335], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [334], "executed_branches": [], "missing_branches": []}, "FormField.form": {"executed_lines": [345], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [344], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 15, 16, 22, 47, 48, 52, 53, 57, 58, 62, 63, 67, 68, 72, 93, 94, 99, 121, 122, 126, 127, 131, 132, 136, 137, 141, 142, 146, 147, 151, 166, 167, 172, 192, 193, 197, 198, 202, 203, 207, 217, 229, 238, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 271, 272, 276, 302, 303, 307, 308, 312, 313, 317, 318, 322, 323, 327, 328, 332, 333, 337, 338, 342, 343], "summary": {"covered_lines": 84, "num_statements": 84, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 6, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [8, 16, 94, 167, 254, 272], "executed_branches": [], "missing_branches": []}}, "classes": {"LinkType": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Link": {"executed_lines": [40, 41, 42, 43, 44, 45, 50, 55, 60, 65, 85, 86, 90], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 93.75, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 7, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [70], "excluded_lines": [29, 49, 54, 59, 64, 69, 73], "executed_branches": [[85, 86], [85, 90]], "missing_branches": []}, "Button": {"executed_lines": [115, 116, 117, 118, 119, 124, 129, 134, 139, 144, 161, 162, 163], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 93.75, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 8, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [149], "excluded_lines": [105, 123, 128, 133, 138, 143, 148, 152], "executed_branches": [[161, 162], [161, 163]], "missing_branches": []}, "Form": {"executed_lines": [186, 187, 188, 189, 190, 195, 200, 214, 215, 227, 236, 245, 247, 248, 250], "summary": {"covered_lines": 15, "num_statements": 16, "percent_covered": 94.44444444444444, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 8, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [205], "excluded_lines": [177, 194, 199, 204, 208, 218, 230, 239], "executed_branches": [[247, 248], [247, 250]], "missing_branches": []}, "FormFieldType": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "FormField": {"executed_lines": [294, 295, 296, 297, 298, 299, 300, 305, 310, 315, 320, 325, 330, 335, 340, 345], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 10, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [283, 304, 309, 314, 319, 324, 329, 334, 339, 344], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 15, 16, 22, 47, 48, 52, 53, 57, 58, 62, 63, 67, 68, 72, 93, 94, 99, 121, 122, 126, 127, 131, 132, 136, 137, 141, 142, 146, 147, 151, 166, 167, 172, 192, 193, 197, 198, 202, 203, 207, 217, 229, 238, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 271, 272, 276, 302, 303, 307, 308, 312, 313, 317, 318, 322, 323, 327, 328, 332, 333, 337, 338, 342, 343], "summary": {"covered_lines": 84, "num_statements": 84, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 6, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [8, 16, 94, 167, 254, 272], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/abstract/inline.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 9, 12, 13, 21, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 70, 71, 72, 74, 78, 79, 82, 83, 85, 86, 88, 90, 91, 92, 93, 96, 99, 100, 103, 105, 106, 107, 109, 111, 112, 114, 120, 121, 124, 127, 129, 132, 134, 135, 137, 138, 140, 142, 143, 145, 147, 148, 150, 152, 153, 155, 157, 158, 160, 162, 164, 166, 177, 178, 181, 184, 185, 190, 198, 199, 200, 202, 203, 224, 225, 226, 228, 232, 233, 236, 239, 240, 242, 245, 247, 248, 250, 252, 253, 255, 257, 258, 260, 262, 273, 276, 279, 280, 283, 285, 288, 289, 296, 317, 320, 321, 322, 323, 324, 326, 327, 329, 331, 332, 334, 336, 337, 339, 341, 342, 344, 346, 347, 349, 351, 362, 363, 366, 367, 370, 373, 374, 382, 384, 386, 387, 389, 390, 392, 394, 395, 406, 409, 410, 411, 412, 413, 415, 418, 420], "summary": {"covered_lines": 156, "num_statements": 157, "percent_covered": 99.0049751243781, "percent_covered_display": "99", "missing_lines": 1, "excluded_lines": 29, "num_branches": 44, "num_partial_branches": 1, "covered_branches": 43, "missing_branches": 1}, "missing_lines": [364], "excluded_lines": [13, 28, 49, 139, 144, 149, 154, 159, 163, 167, 185, 191, 208, 249, 254, 259, 263, 289, 302, 328, 333, 338, 343, 348, 352, 374, 383, 391, 396], "executed_branches": [[43, -21], [43, 44], [70, 71], [70, 78], [71, 72], [71, 74], [78, 79], [78, 82], [83, 85], [83, 86], [86, 88], [86, 96], [90, 91], [90, 96], [99, 100], [99, 103], [103, 105], [103, 129], [109, 111], [109, 127], [112, 114], [112, 120], [120, 121], [120, 124], [224, 225], [224, 232], [225, 226], [225, 228], [232, 233], [232, 236], [239, 240], [239, 242], [279, 280], [279, 283], [363, 366], [366, 367], [366, 370], [409, 410], [409, 411], [411, 412], [411, 413], [413, 415], [413, 418]], "missing_branches": [[363, 364]], "functions": {"Word.__init__": {"executed_lines": [37, 38, 39, 40, 41, 42, 43, 44], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [28], "executed_branches": [[43, -21], [43, 44]], "missing_branches": []}, "Word.create_and_add_to": {"executed_lines": [70, 71, 72, 74, 78, 79, 82, 83, 85, 86, 88, 90, 91, 92, 93, 96, 99, 100, 103, 105, 106, 107, 109, 111, 112, 114, 120, 121, 124, 127, 129, 132], "summary": {"covered_lines": 32, "num_statements": 32, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 22, "num_partial_branches": 0, "covered_branches": 22, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [49], "executed_branches": [[70, 71], [70, 78], [71, 72], [71, 74], [78, 79], [78, 82], [83, 85], [83, 86], [86, 88], [86, 96], [90, 91], [90, 96], [99, 100], [99, 103], [103, 105], [103, 129], [109, 111], [109, 127], [112, 114], [112, 120], [120, 121], [120, 124]], "missing_branches": []}, "Word.add_concete": {"executed_lines": [135], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Word.text": {"executed_lines": [140], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [139], "executed_branches": [], "missing_branches": []}, "Word.style": {"executed_lines": [145], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [144], "executed_branches": [], "missing_branches": []}, "Word.background": {"executed_lines": [150], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [149], "executed_branches": [], "missing_branches": []}, "Word.previous": {"executed_lines": [155], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [154], "executed_branches": [], "missing_branches": []}, "Word.next": {"executed_lines": [160], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [159], "executed_branches": [], "missing_branches": []}, "Word.add_next": {"executed_lines": [164], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [163], "executed_branches": [], "missing_branches": []}, "Word.possible_hyphenation": {"executed_lines": [177, 178], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [167], "executed_branches": [], "missing_branches": []}, "FormattedSpan.__init__": {"executed_lines": [198, 199, 200], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [191], "executed_branches": [], "missing_branches": []}, "FormattedSpan.create_and_add_to": {"executed_lines": [224, 225, 226, 228, 232, 233, 236, 239, 240, 242, 245], "summary": {"covered_lines": 11, "num_statements": 11, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [208], "executed_branches": [[224, 225], [224, 232], [225, 226], [225, 228], [232, 233], [232, 236], [239, 240], [239, 242]], "missing_branches": []}, "FormattedSpan.style": {"executed_lines": [250], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [249], "executed_branches": [], "missing_branches": []}, "FormattedSpan.background": {"executed_lines": [255], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [254], "executed_branches": [], "missing_branches": []}, "FormattedSpan.words": {"executed_lines": [260], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [259], "executed_branches": [], "missing_branches": []}, "FormattedSpan.add_word": {"executed_lines": [273, 276, 279, 280, 283, 285], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [263], "executed_branches": [[279, 280], [279, 283]], "missing_branches": []}, "LinkedWord.__init__": {"executed_lines": [317, 320, 321, 322, 323, 324], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [302], "executed_branches": [], "missing_branches": []}, "LinkedWord.location": {"executed_lines": [329], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [328], "executed_branches": [], "missing_branches": []}, "LinkedWord.link_type": {"executed_lines": [334], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [333], "executed_branches": [], "missing_branches": []}, "LinkedWord.link_callback": {"executed_lines": [339], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [338], "executed_branches": [], "missing_branches": []}, "LinkedWord.params": {"executed_lines": [344], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [343], "executed_branches": [], "missing_branches": []}, "LinkedWord.link_title": {"executed_lines": [349], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [348], "executed_branches": [], "missing_branches": []}, "LinkedWord.execute_link": {"executed_lines": [362, 363, 366, 367, 370], "summary": {"covered_lines": 5, "num_statements": 6, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [364], "excluded_lines": [352], "executed_branches": [[363, 366], [366, 367], [366, 370]], "missing_branches": [[363, 364]]}, "LineBreak.__init__": {"executed_lines": [384, 386, 387], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [383], "executed_branches": [], "missing_branches": []}, "LineBreak.block_type": {"executed_lines": [392], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [391], "executed_branches": [], "missing_branches": []}, "LineBreak.create_and_add_to": {"executed_lines": [406, 409, 410, 411, 412, 413, 415, 418, 420], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [396], "executed_branches": [[409, 410], [409, 411], [411, 412], [411, 413], [413, 415], [413, 418]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 9, 12, 13, 21, 46, 47, 134, 137, 138, 142, 143, 147, 148, 152, 153, 157, 158, 162, 166, 181, 184, 185, 190, 202, 203, 247, 248, 252, 253, 257, 258, 262, 288, 289, 296, 326, 327, 331, 332, 336, 337, 341, 342, 346, 347, 351, 373, 374, 382, 389, 390, 394, 395], "summary": {"covered_lines": 55, "num_statements": 55, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [13, 185, 289, 374], "executed_branches": [], "missing_branches": []}}, "classes": {"Word": {"executed_lines": [37, 38, 39, 40, 41, 42, 43, 44, 70, 71, 72, 74, 78, 79, 82, 83, 85, 86, 88, 90, 91, 92, 93, 96, 99, 100, 103, 105, 106, 107, 109, 111, 112, 114, 120, 121, 124, 127, 129, 132, 135, 140, 145, 150, 155, 160, 164, 177, 178], "summary": {"covered_lines": 49, "num_statements": 49, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 9, "num_branches": 24, "num_partial_branches": 0, "covered_branches": 24, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [28, 49, 139, 144, 149, 154, 159, 163, 167], "executed_branches": [[43, -21], [43, 44], [70, 71], [70, 78], [71, 72], [71, 74], [78, 79], [78, 82], [83, 85], [83, 86], [86, 88], [86, 96], [90, 91], [90, 96], [99, 100], [99, 103], [103, 105], [103, 129], [109, 111], [109, 127], [112, 114], [112, 120], [120, 121], [120, 124]], "missing_branches": []}, "FormattedSpan": {"executed_lines": [198, 199, 200, 224, 225, 226, 228, 232, 233, 236, 239, 240, 242, 245, 250, 255, 260, 273, 276, 279, 280, 283, 285], "summary": {"covered_lines": 23, "num_statements": 23, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 6, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [191, 208, 249, 254, 259, 263], "executed_branches": [[224, 225], [224, 232], [225, 226], [225, 228], [232, 233], [232, 236], [239, 240], [239, 242], [279, 280], [279, 283]], "missing_branches": []}, "LinkedWord": {"executed_lines": [317, 320, 321, 322, 323, 324, 329, 334, 339, 344, 349, 362, 363, 366, 367, 370], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 90.47619047619048, "percent_covered_display": "90", "missing_lines": 1, "excluded_lines": 7, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [364], "excluded_lines": [302, 328, 333, 338, 343, 348, 352], "executed_branches": [[363, 366], [366, 367], [366, 370]], "missing_branches": [[363, 364]]}, "LineBreak": {"executed_lines": [384, 386, 387, 392, 406, 409, 410, 411, 412, 413, 415, 418, 420], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [383, 391, 396], "executed_branches": [[409, 410], [409, 411], [411, 412], [411, 413], [413, 415], [413, 418]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 9, 12, 13, 21, 46, 47, 134, 137, 138, 142, 143, 147, 148, 152, 153, 157, 158, 162, 166, 181, 184, 185, 190, 202, 203, 247, 248, 252, 253, 257, 258, 262, 288, 289, 296, 326, 327, 331, 332, 336, 337, 341, 342, 346, 347, 351, 373, 374, 382, 389, 390, 394, 395], "summary": {"covered_lines": 55, "num_statements": 55, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [13, 185, 289, 374], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/abstract/interactive_image.py": {"executed_lines": [1, 9, 10, 12, 13, 16, 17, 38, 57, 65, 68, 69, 71, 84, 86, 87, 89, 91, 101, 102, 103, 105, 106, 132, 141, 143, 145, 146, 150, 152, 162, 163], "summary": {"covered_lines": 30, "num_statements": 34, "percent_covered": 80.43478260869566, "percent_covered_display": "80", "missing_lines": 4, "excluded_lines": 7, "num_branches": 12, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 5}, "missing_lines": [142, 144, 147, 148], "excluded_lines": [1, 17, 46, 72, 92, 115, 153], "executed_branches": [[84, 86], [84, 89], [86, 87], [86, 89], [141, 143], [143, 145], [145, 146]], "missing_branches": [[141, 142], [143, 144], [145, 147], [147, 148], [147, 150]], "functions": {"InteractiveImage.__init__": {"executed_lines": [57, 65, 68, 69], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [46], "executed_branches": [], "missing_branches": []}, "InteractiveImage.interact": {"executed_lines": [84, 86, 87, 89], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [72], "executed_branches": [[84, 86], [84, 89], [86, 87], [86, 89]], "missing_branches": []}, "InteractiveImage.in_object": {"executed_lines": [101, 102, 103], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [92], "executed_branches": [], "missing_branches": []}, "InteractiveImage.create_and_add_to": {"executed_lines": [132, 141, 143, 145, 146, 150], "summary": {"covered_lines": 6, "num_statements": 10, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 4, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 3, "covered_branches": 3, "missing_branches": 5}, "missing_lines": [142, 144, 147, 148], "excluded_lines": [115], "executed_branches": [[141, 143], [143, 145], [145, 146]], "missing_branches": [[141, 142], [143, 144], [145, 147], [147, 148], [147, 150]]}, "InteractiveImage.set_rendered_bounds": {"executed_lines": [162, 163], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [153], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 9, 10, 12, 13, 16, 17, 38, 71, 91, 105, 106, 152], "summary": {"covered_lines": 11, "num_statements": 11, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17], "executed_branches": [], "missing_branches": []}}, "classes": {"InteractiveImage": {"executed_lines": [57, 65, 68, 69, 84, 86, 87, 89, 101, 102, 103, 132, 141, 143, 145, 146, 150, 162, 163], "summary": {"covered_lines": 19, "num_statements": 23, "percent_covered": 74.28571428571429, "percent_covered_display": "74", "missing_lines": 4, "excluded_lines": 5, "num_branches": 12, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 5}, "missing_lines": [142, 144, 147, 148], "excluded_lines": [46, 72, 92, 115, 153], "executed_branches": [[84, 86], [84, 89], [86, 87], [86, 89], [141, 143], [143, 145], [145, 146]], "missing_branches": [[141, 142], [143, 144], [145, 147], [147, 148], [147, 150]]}, "": {"executed_lines": [1, 9, 10, 12, 13, 16, 17, 38, 71, 91, 105, 106, 152], "summary": {"covered_lines": 11, "num_statements": 11, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/__init__.py": {"executed_lines": [1, 7, 8, 9, 10, 11, 12, 14], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 7, 8, 9, 10, 11, 12, 14], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1, 7, 8, 9, 10, 11, 12, 14], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/box.py": {"executed_lines": [1, 2, 3, 5, 6, 7, 10, 11, 17, 26, 27, 28, 29, 30, 31, 33, 34, 35, 39, 40], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [11], "executed_branches": [[30, 31], [30, 33]], "missing_branches": [], "functions": {"Box.__init__": {"executed_lines": [26, 27, 28, 29, 30, 31, 33, 34, 35], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [[30, 31], [30, 33]], "missing_branches": []}, "Box.in_shape": {"executed_lines": [40], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 5, 6, 7, 10, 11, 17, 39], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [11], "executed_branches": [], "missing_branches": []}}, "classes": {"Box": {"executed_lines": [26, 27, 28, 29, 30, 31, 33, 34, 35, 40], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [[30, 31], [30, 33]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 5, 6, 7, 10, 11, 17, 39], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [11], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/dynamic_page.py": {"executed_lines": [1, 14, 15, 16, 17, 19, 20, 21, 24, 25, 26, 27, 28, 29, 30, 35, 36, 46, 57, 58, 61, 62, 63, 64, 65, 68, 69, 71, 72, 74, 76, 89, 90, 93, 94, 96, 97, 101, 102, 104, 118, 119, 122, 123, 124, 126, 127, 128, 131, 132, 134, 136, 147, 151, 153, 156, 157, 160, 161, 162, 164, 166, 168, 169, 179, 182, 185, 186, 188, 199, 203, 205, 207, 208, 210, 211, 212, 213, 214, 216, 217, 220, 222, 224, 226, 235, 238, 240, 243, 244, 246, 256, 257, 259, 261, 272, 274, 275, 277, 288, 292, 293, 295, 296, 301, 302, 304, 314, 315, 317, 319, 320, 326, 331, 345, 346, 348, 368, 370, 372, 379, 380, 382, 384, 386, 388, 389, 390, 391, 392, 393, 395, 405, 406, 407, 409, 416, 417, 418], "summary": {"covered_lines": 136, "num_statements": 178, "percent_covered": 68.3206106870229, "percent_covered_display": "68", "missing_lines": 42, "excluded_lines": 17, "num_branches": 84, "num_partial_branches": 19, "covered_branches": 43, "missing_branches": 41}, "missing_lines": [95, 105, 107, 108, 111, 112, 114, 115, 125, 129, 148, 170, 172, 173, 174, 176, 183, 200, 227, 228, 229, 230, 232, 239, 241, 262, 263, 264, 265, 267, 269, 323, 350, 351, 352, 353, 356, 358, 360, 361, 362, 365], "excluded_lines": [1, 26, 36, 49, 73, 77, 137, 189, 247, 278, 305, 332, 373, 383, 387, 396, 410], "executed_branches": [[89, 90], [89, 93], [93, 94], [93, 101], [94, 96], [96, 97], [96, 101], [104, 118], [122, 123], [122, 124], [124, 126], [126, 127], [126, 128], [128, 131], [147, 151], [156, 157], [156, 179], [157, 160], [160, 156], [160, 161], [161, 162], [166, 168], [182, 185], [199, 203], [207, 208], [207, 235], [208, 210], [211, 212], [213, 214], [213, 226], [214, 216], [220, 213], [220, 222], [238, 240], [240, 243], [256, 257], [256, 259], [261, 272], [314, 315], [314, 326], [315, 317], [319, 320], [348, 368]], "missing_branches": [[94, 95], [104, 105], [105, 107], [105, 111], [124, 125], [128, 129], [147, 148], [157, 170], [161, 160], [166, 160], [170, 172], [170, 174], [174, 156], [174, 176], [182, 183], [199, 200], [208, 227], [211, 207], [214, 213], [227, 228], [227, 230], [230, 207], [230, 232], [238, 239], [240, 241], [261, 262], [262, 263], [262, 264], [264, 265], [264, 267], [315, 319], [319, 323], [348, 350], [350, 351], [350, 356], [351, 352], [351, 353], [358, 360], [358, 365], [360, 361], [360, 362]], "functions": {"DynamicPage.__init__": {"executed_lines": [57, 58, 61, 62, 63, 64, 65, 68, 69], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [49], "executed_branches": [], "missing_branches": []}, "DynamicPage.constraints": {"executed_lines": [74], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [73], "executed_branches": [], "missing_branches": []}, "DynamicPage.measure": {"executed_lines": [89, 90, 93, 94, 96, 97, 101, 102, 104, 118, 119, 122, 123, 124, 126, 127, 128, 131, 132, 134], "summary": {"covered_lines": 20, "num_statements": 30, "percent_covered": 68.0, "percent_covered_display": "68", "missing_lines": 10, "excluded_lines": 1, "num_branches": 20, "num_partial_branches": 4, "covered_branches": 14, "missing_branches": 6}, "missing_lines": [95, 105, 107, 108, 111, 112, 114, 115, 125, 129], "excluded_lines": [77], "executed_branches": [[89, 90], [89, 93], [93, 94], [93, 101], [94, 96], [96, 97], [96, 101], [104, 118], [122, 123], [122, 124], [124, 126], [126, 127], [126, 128], [128, 131]], "missing_branches": [[94, 95], [104, 105], [105, 107], [105, 111], [124, 125], [128, 129]]}, "DynamicPage.get_min_width": {"executed_lines": [147, 151, 153, 156, 157, 160, 161, 162, 164, 166, 168, 169, 179, 182, 185, 186], "summary": {"covered_lines": 16, "num_statements": 23, "percent_covered": 60.97560975609756, "percent_covered_display": "61", "missing_lines": 7, "excluded_lines": 1, "num_branches": 18, "num_partial_branches": 5, "covered_branches": 9, "missing_branches": 9}, "missing_lines": [148, 170, 172, 173, 174, 176, 183], "excluded_lines": [137], "executed_branches": [[147, 151], [156, 157], [156, 179], [157, 160], [160, 156], [160, 161], [161, 162], [166, 168], [182, 185]], "missing_branches": [[147, 148], [157, 170], [161, 160], [166, 160], [170, 172], [170, 174], [174, 156], [174, 176], [182, 183]]}, "DynamicPage.get_preferred_width": {"executed_lines": [199, 203, 205, 207, 208, 210, 211, 212, 213, 214, 216, 217, 220, 222, 224, 226, 235, 238, 240, 243, 244], "summary": {"covered_lines": 21, "num_statements": 29, "percent_covered": 64.70588235294117, "percent_covered_display": "65", "missing_lines": 8, "excluded_lines": 1, "num_branches": 22, "num_partial_branches": 6, "covered_branches": 12, "missing_branches": 10}, "missing_lines": [200, 227, 228, 229, 230, 232, 239, 241], "excluded_lines": [189], "executed_branches": [[199, 203], [207, 208], [207, 235], [208, 210], [211, 212], [213, 214], [213, 226], [214, 216], [220, 213], [220, 222], [238, 240], [240, 243]], "missing_branches": [[199, 200], [208, 227], [211, 207], [214, 213], [227, 228], [227, 230], [230, 207], [230, 232], [238, 239], [240, 241]]}, "DynamicPage.measure_content_height": {"executed_lines": [256, 257, 259, 261, 272, 274, 275], "summary": {"covered_lines": 7, "num_statements": 13, "percent_covered": 47.61904761904762, "percent_covered_display": "48", "missing_lines": 6, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 5}, "missing_lines": [262, 263, 264, 265, 267, 269], "excluded_lines": [247], "executed_branches": [[256, 257], [256, 259], [261, 272]], "missing_branches": [[261, 262], [262, 263], [262, 264], [264, 265], [264, 267]]}, "DynamicPage.layout": {"executed_lines": [288, 292, 293, 295, 296, 301, 302], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [278], "executed_branches": [], "missing_branches": []}, "DynamicPage.render": {"executed_lines": [314, 315, 317, 319, 320, 326], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 76.92307692307692, "percent_covered_display": "77", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2}, "missing_lines": [323], "excluded_lines": [305], "executed_branches": [[314, 315], [314, 326], [315, 317], [319, 320]], "missing_branches": [[315, 319], [319, 323]]}, "DynamicPage.render_partial": {"executed_lines": [345, 346, 348, 368, 370], "summary": {"covered_lines": 5, "num_statements": 15, "percent_covered": 24.0, "percent_covered_display": "24", "missing_lines": 10, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 9}, "missing_lines": [350, 351, 352, 353, 356, 358, 360, 361, 362, 365], "excluded_lines": [332], "executed_branches": [[348, 368]], "missing_branches": [[348, 350], [350, 351], [350, 356], [351, 352], [351, 353], [358, 360], [358, 365], [360, 361], [360, 362]]}, "DynamicPage.has_more_content": {"executed_lines": [379, 380], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [373], "executed_branches": [], "missing_branches": []}, "DynamicPage.reset_pagination": {"executed_lines": [384], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [383], "executed_branches": [], "missing_branches": []}, "DynamicPage.invalidate_caches": {"executed_lines": [388, 389, 390, 391, 392, 393], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [387], "executed_branches": [], "missing_branches": []}, "DynamicPage.add_child": {"executed_lines": [405, 406, 407], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [396], "executed_branches": [], "missing_branches": []}, "DynamicPage.clear_children": {"executed_lines": [416, 417, 418], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [410], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 14, 15, 16, 17, 19, 20, 21, 24, 25, 26, 27, 28, 29, 30, 35, 36, 46, 71, 72, 76, 136, 188, 246, 277, 304, 331, 372, 382, 386, 395, 409], "summary": {"covered_lines": 29, "num_statements": 29, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 26, 36], "executed_branches": [], "missing_branches": []}}, "classes": {"SizeConstraints": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "DynamicPage": {"executed_lines": [57, 58, 61, 62, 63, 64, 65, 68, 69, 74, 89, 90, 93, 94, 96, 97, 101, 102, 104, 118, 119, 122, 123, 124, 126, 127, 128, 131, 132, 134, 147, 151, 153, 156, 157, 160, 161, 162, 164, 166, 168, 169, 179, 182, 185, 186, 199, 203, 205, 207, 208, 210, 211, 212, 213, 214, 216, 217, 220, 222, 224, 226, 235, 238, 240, 243, 244, 256, 257, 259, 261, 272, 274, 275, 288, 292, 293, 295, 296, 301, 302, 314, 315, 317, 319, 320, 326, 345, 346, 348, 368, 370, 379, 380, 384, 388, 389, 390, 391, 392, 393, 405, 406, 407, 416, 417, 418], "summary": {"covered_lines": 107, "num_statements": 149, "percent_covered": 64.37768240343348, "percent_covered_display": "64", "missing_lines": 42, "excluded_lines": 14, "num_branches": 84, "num_partial_branches": 19, "covered_branches": 43, "missing_branches": 41}, "missing_lines": [95, 105, 107, 108, 111, 112, 114, 115, 125, 129, 148, 170, 172, 173, 174, 176, 183, 200, 227, 228, 229, 230, 232, 239, 241, 262, 263, 264, 265, 267, 269, 323, 350, 351, 352, 353, 356, 358, 360, 361, 362, 365], "excluded_lines": [49, 73, 77, 137, 189, 247, 278, 305, 332, 373, 383, 387, 396, 410], "executed_branches": [[89, 90], [89, 93], [93, 94], [93, 101], [94, 96], [96, 97], [96, 101], [104, 118], [122, 123], [122, 124], [124, 126], [126, 127], [126, 128], [128, 131], [147, 151], [156, 157], [156, 179], [157, 160], [160, 156], [160, 161], [161, 162], [166, 168], [182, 185], [199, 203], [207, 208], [207, 235], [208, 210], [211, 212], [213, 214], [213, 226], [214, 216], [220, 213], [220, 222], [238, 240], [240, 243], [256, 257], [256, 259], [261, 272], [314, 315], [314, 326], [315, 317], [319, 320], [348, 368]], "missing_branches": [[94, 95], [104, 105], [105, 107], [105, 111], [124, 125], [128, 129], [147, 148], [157, 170], [161, 160], [166, 160], [170, 172], [170, 174], [174, 156], [174, 176], [182, 183], [199, 200], [208, 227], [211, 207], [214, 213], [227, 228], [227, 230], [230, 207], [230, 232], [238, 239], [240, 241], [261, 262], [262, 263], [262, 264], [264, 265], [264, 267], [315, 319], [319, 323], [348, 350], [350, 351], [350, 356], [351, 352], [351, 353], [358, 360], [358, 365], [360, 361], [360, 362]]}, "": {"executed_lines": [1, 14, 15, 16, 17, 19, 20, 21, 24, 25, 26, 27, 28, 29, 30, 35, 36, 46, 71, 72, 76, 136, 188, 246, 277, 304, 331, 372, 382, 386, 395, 409], "summary": {"covered_lines": 29, "num_statements": 29, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 26, 36], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/functional.py": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 12, 13, 18, 33, 34, 35, 36, 37, 39, 40, 41, 42, 45, 48, 51, 52, 53, 54, 57, 60, 61, 63, 65, 67, 68, 70, 75, 77, 80, 89, 90, 94, 97, 103, 107, 113, 116, 117, 122, 138, 141, 144, 145, 146, 147, 148, 152, 155, 156, 158, 159, 161, 163, 164, 166, 168, 170, 171, 173, 175, 176, 178, 187, 189, 192, 197, 199, 200, 201, 202, 207, 214, 215, 216, 220, 226, 230, 231, 235, 238, 243, 244, 247, 248, 251, 254, 256, 266, 267, 270, 274, 275, 280, 294, 298, 301, 302, 303, 306, 310, 313, 315, 316, 318, 320, 321, 323, 325, 327, 329, 334, 337, 338, 341, 342, 344, 346, 349, 350, 353, 354, 357, 361, 365, 366, 367, 370, 373, 384, 387, 389, 390, 392, 394, 404, 405, 408, 413, 427, 430, 444, 447, 461], "summary": {"covered_lines": 148, "num_statements": 165, "percent_covered": 86.80203045685279, "percent_covered_display": "87", "missing_lines": 17, "excluded_lines": 28, "num_branches": 32, "num_partial_branches": 9, "covered_branches": 23, "missing_branches": 9}, "missing_lines": [58, 72, 73, 78, 92, 105, 106, 109, 110, 185, 190, 204, 205, 206, 209, 210, 211], "excluded_lines": [13, 20, 62, 66, 71, 76, 81, 117, 125, 160, 165, 169, 174, 179, 188, 193, 257, 275, 282, 317, 322, 326, 330, 374, 395, 415, 432, 449], "executed_branches": [[34, 35], [34, 36], [36, 37], [36, 39], [39, 40], [39, 41], [41, 42], [57, -18], [77, -75], [90, 94], [103, 107], [107, 113], [189, -187], [197, 199], [197, 202], [202, 207], [207, 214], [349, -329], [349, 350], [353, 354], [353, 357], [387, 389], [387, 392]], "missing_branches": [[41, 45], [57, 58], [77, 78], [90, 92], [103, 105], [107, 109], [189, 190], [202, 204], [207, 209]], "functions": {"LinkText.__init__": {"executed_lines": [33, 34, 35, 36, 37, 39, 40, 41, 42, 45, 48, 51, 52, 53, 54, 57], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 88.88888888888889, "percent_covered_display": "89", "missing_lines": 1, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 2}, "missing_lines": [58], "excluded_lines": [20], "executed_branches": [[34, 35], [34, 36], [36, 37], [36, 39], [39, 40], [39, 41], [41, 42], [57, -18]], "missing_branches": [[41, 45], [57, 58]]}, "LinkText.link": {"executed_lines": [63], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [62], "executed_branches": [], "missing_branches": []}, "LinkText.set_hovered": {"executed_lines": [67, 68], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [66], "executed_branches": [], "missing_branches": []}, "LinkText.set_pressed": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [72, 73], "excluded_lines": [71], "executed_branches": [], "missing_branches": []}, "LinkText._mark_page_dirty": {"executed_lines": [77], "summary": {"covered_lines": 1, "num_statements": 2, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 1, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [78], "excluded_lines": [76], "executed_branches": [[77, -75]], "missing_branches": [[77, 78]]}, "LinkText.render": {"executed_lines": [89, 90, 94, 97, 103, 107, 113], "summary": {"covered_lines": 7, "num_statements": 12, "percent_covered": 55.55555555555556, "percent_covered_display": "56", "missing_lines": 5, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 3, "covered_branches": 3, "missing_branches": 3}, "missing_lines": [92, 105, 106, 109, 110], "excluded_lines": [81], "executed_branches": [[90, 94], [103, 107], [107, 113]], "missing_branches": [[90, 92], [103, 105], [107, 109]]}, "ButtonText.__init__": {"executed_lines": [138, 141, 144, 145, 146, 147, 148, 152, 155, 156], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [125], "executed_branches": [], "missing_branches": []}, "ButtonText.button": {"executed_lines": [161], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [160], "executed_branches": [], "missing_branches": []}, "ButtonText.size": {"executed_lines": [166], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [165], "executed_branches": [], "missing_branches": []}, "ButtonText.set_pressed": {"executed_lines": [170, 171], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [169], "executed_branches": [], "missing_branches": []}, "ButtonText.set_hovered": {"executed_lines": [175, 176], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [174], "executed_branches": [], "missing_branches": []}, "ButtonText.set_page": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [185], "excluded_lines": [179], "executed_branches": [], "missing_branches": []}, "ButtonText._mark_page_dirty": {"executed_lines": [189], "summary": {"covered_lines": 1, "num_statements": 2, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 1, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [190], "excluded_lines": [188], "executed_branches": [[189, -187]], "missing_branches": [[189, 190]]}, "ButtonText.render": {"executed_lines": [197, 199, 200, 201, 202, 207, 214, 215, 216, 220, 226, 230, 231, 235, 238, 243, 244, 247, 248, 251, 254], "summary": {"covered_lines": 21, "num_statements": 27, "percent_covered": 75.75757575757575, "percent_covered_display": "76", "missing_lines": 6, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2}, "missing_lines": [204, 205, 206, 209, 210, 211], "excluded_lines": [193], "executed_branches": [[197, 199], [197, 202], [202, 207], [207, 214]], "missing_branches": [[202, 204], [207, 209]]}, "ButtonText.in_object": {"executed_lines": [266, 267, 270], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [257], "executed_branches": [], "missing_branches": []}, "FormFieldText.__init__": {"executed_lines": [294, 298, 301, 302, 303, 306, 310, 313], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [282], "executed_branches": [], "missing_branches": []}, "FormFieldText.field": {"executed_lines": [318], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [317], "executed_branches": [], "missing_branches": []}, "FormFieldText.size": {"executed_lines": [323], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [322], "executed_branches": [], "missing_branches": []}, "FormFieldText.set_focused": {"executed_lines": [327], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [326], "executed_branches": [], "missing_branches": []}, "FormFieldText.render": {"executed_lines": [334, 337, 338, 341, 342, 344, 346, 349, 350, 353, 354, 357, 361, 365, 366, 367, 370], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [330], "executed_branches": [[349, -329], [349, 350], [353, 354], [353, 357]], "missing_branches": []}, "FormFieldText.handle_click": {"executed_lines": [384, 387, 389, 390, 392], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [374], "executed_branches": [[387, 389], [387, 392]], "missing_branches": []}, "FormFieldText.in_object": {"executed_lines": [404, 405, 408], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [395], "executed_branches": [], "missing_branches": []}, "create_link_text": {"executed_lines": [427], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [415], "executed_branches": [], "missing_branches": []}, "create_button_text": {"executed_lines": [444], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [432], "executed_branches": [], "missing_branches": []}, "create_form_field_text": {"executed_lines": [461], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [449], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 12, 13, 18, 60, 61, 65, 70, 75, 80, 116, 117, 122, 158, 159, 163, 164, 168, 173, 178, 187, 192, 256, 274, 275, 280, 315, 316, 320, 321, 325, 329, 373, 394, 413, 430, 447], "summary": {"covered_lines": 41, "num_statements": 41, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [13, 117, 275], "executed_branches": [], "missing_branches": []}}, "classes": {"LinkText": {"executed_lines": [33, 34, 35, 36, 37, 39, 40, 41, 42, 45, 48, 51, 52, 53, 54, 57, 63, 67, 68, 77, 89, 90, 94, 97, 103, 107, 113], "summary": {"covered_lines": 27, "num_statements": 36, "percent_covered": 72.22222222222223, "percent_covered_display": "72", "missing_lines": 9, "excluded_lines": 6, "num_branches": 18, "num_partial_branches": 6, "covered_branches": 12, "missing_branches": 6}, "missing_lines": [58, 72, 73, 78, 92, 105, 106, 109, 110], "excluded_lines": [20, 62, 66, 71, 76, 81], "executed_branches": [[34, 35], [34, 36], [36, 37], [36, 39], [39, 40], [39, 41], [41, 42], [57, -18], [77, -75], [90, 94], [103, 107], [107, 113]], "missing_branches": [[41, 45], [57, 58], [77, 78], [90, 92], [103, 105], [107, 109]]}, "ButtonText": {"executed_lines": [138, 141, 144, 145, 146, 147, 148, 152, 155, 156, 161, 166, 170, 171, 175, 176, 189, 197, 199, 200, 201, 202, 207, 214, 215, 216, 220, 226, 230, 231, 235, 238, 243, 244, 247, 248, 251, 254, 266, 267, 270], "summary": {"covered_lines": 41, "num_statements": 49, "percent_covered": 80.70175438596492, "percent_covered_display": "81", "missing_lines": 8, "excluded_lines": 9, "num_branches": 8, "num_partial_branches": 3, "covered_branches": 5, "missing_branches": 3}, "missing_lines": [185, 190, 204, 205, 206, 209, 210, 211], "excluded_lines": [125, 160, 165, 169, 174, 179, 188, 193, 257], "executed_branches": [[189, -187], [197, 199], [197, 202], [202, 207], [207, 214]], "missing_branches": [[189, 190], [202, 204], [207, 209]]}, "FormFieldText": {"executed_lines": [294, 298, 301, 302, 303, 306, 310, 313, 318, 323, 327, 334, 337, 338, 341, 342, 344, 346, 349, 350, 353, 354, 357, 361, 365, 366, 367, 370, 384, 387, 389, 390, 392, 404, 405, 408], "summary": {"covered_lines": 36, "num_statements": 36, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 7, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [282, 317, 322, 326, 330, 374, 395], "executed_branches": [[349, -329], [349, 350], [353, 354], [353, 357], [387, 389], [387, 392]], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 6, 7, 8, 9, 12, 13, 18, 60, 61, 65, 70, 75, 80, 116, 117, 122, 158, 159, 163, 164, 168, 173, 178, 187, 192, 256, 274, 275, 280, 315, 316, 320, 321, 325, 329, 373, 394, 413, 427, 430, 444, 447, 461], "summary": {"covered_lines": 44, "num_statements": 44, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 6, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [13, 117, 275, 415, 432, 449], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/image.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 10, 11, 15, 35, 36, 37, 38, 39, 40, 41, 44, 47, 50, 51, 53, 54, 57, 60, 62, 63, 65, 67, 68, 70, 72, 73, 75, 77, 79, 81, 83, 85, 91, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113, 119, 123, 125, 128, 129, 132, 133, 134, 135, 137, 140, 141, 142, 143, 145, 148, 149, 153, 163, 165, 172, 173, 176, 179, 180, 183, 186, 187, 190, 193, 194, 197, 201, 204, 206, 211, 212, 213, 214, 216, 218, 222, 223, 226, 227, 229, 232, 235, 236, 237, 239, 240, 241, 242, 244, 245, 247, 248, 250, 251, 254, 255, 256, 257, 258, 261, 264, 267, 273, 275, 276, 279], "summary": {"covered_lines": 126, "num_statements": 134, "percent_covered": 92.94117647058823, "percent_covered_display": "93", "missing_lines": 8, "excluded_lines": 11, "num_branches": 36, "num_partial_branches": 4, "covered_branches": 32, "missing_branches": 4}, "missing_lines": [88, 89, 115, 116, 117, 198, 269, 271], "excluded_lines": [11, 19, 64, 69, 74, 78, 82, 120, 166, 207, 274], "executed_branches": [[50, 51], [50, 57], [53, 54], [53, 57], [85, 91], [94, 96], [94, 98], [98, 100], [98, 113], [105, 106], [105, 109], [123, 125], [123, 163], [132, 133], [132, 134], [134, 135], [134, 137], [140, 141], [140, 142], [142, 143], [142, 145], [172, 173], [172, 176], [197, 201], [226, 227], [239, 240], [239, 250], [244, 245], [244, 247], [250, 251], [255, -206], [255, 256]], "missing_branches": [[85, 88], [197, 198], [226, -206], [250, 254]], "functions": {"RenderableImage.__init__": {"executed_lines": [35, 36, 37, 38, 39, 40, 41, 44, 47, 50, 51, 53, 54, 57, 60], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [19], "executed_branches": [[50, 51], [50, 57], [53, 54], [53, 57]], "missing_branches": []}, "RenderableImage.origin": {"executed_lines": [65], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [64], "executed_branches": [], "missing_branches": []}, "RenderableImage.size": {"executed_lines": [70], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [69], "executed_branches": [], "missing_branches": []}, "RenderableImage.width": {"executed_lines": [75], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [74], "executed_branches": [], "missing_branches": []}, "RenderableImage.set_origin": {"executed_lines": [79], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [78], "executed_branches": [], "missing_branches": []}, "RenderableImage._load_image": {"executed_lines": [83, 85, 91, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113], "summary": {"covered_lines": 18, "num_statements": 23, "percent_covered": 80.64516129032258, "percent_covered_display": "81", "missing_lines": 5, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [88, 89, 115, 116, 117], "excluded_lines": [82], "executed_branches": [[85, 91], [94, 96], [94, 98], [98, 100], [98, 113], [105, 106], [105, 109]], "missing_branches": [[85, 88]]}, "RenderableImage.render": {"executed_lines": [123, 125, 128, 129, 132, 133, 134, 135, 137, 140, 141, 142, 143, 145, 148, 149, 153, 163], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [120], "executed_branches": [[123, 125], [123, 163], [132, 133], [132, 134], [134, 135], [134, 137], [140, 141], [140, 142], [142, 143], [142, 145]], "missing_branches": []}, "RenderableImage._resize_image": {"executed_lines": [172, 173, 176, 179, 180, 183, 186, 187, 190, 193, 194, 197, 201, 204], "summary": {"covered_lines": 14, "num_statements": 15, "percent_covered": 89.47368421052632, "percent_covered_display": "89", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [198], "excluded_lines": [166], "executed_branches": [[172, 173], [172, 176], [197, 201]], "missing_branches": [[197, 198]]}, "RenderableImage._draw_error_placeholder": {"executed_lines": [211, 212, 213, 214, 216, 218, 222, 223, 226, 227, 229, 232, 235, 236, 237, 239, 240, 241, 242, 244, 245, 247, 248, 250, 251, 254, 255, 256, 257, 258, 261, 264, 267], "summary": {"covered_lines": 33, "num_statements": 35, "percent_covered": 91.11111111111111, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 2}, "missing_lines": [269, 271], "excluded_lines": [207], "executed_branches": [[226, 227], [239, 240], [239, 250], [244, 245], [244, 247], [250, 251], [255, -206], [255, 256]], "missing_branches": [[226, -206], [250, 254]]}, "RenderableImage.in_object": {"executed_lines": [275, 276, 279], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [274], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 10, 11, 15, 62, 63, 67, 68, 72, 73, 77, 81, 119, 165, 206, 273], "summary": {"covered_lines": 21, "num_statements": 21, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [11], "executed_branches": [], "missing_branches": []}}, "classes": {"RenderableImage": {"executed_lines": [35, 36, 37, 38, 39, 40, 41, 44, 47, 50, 51, 53, 54, 57, 60, 65, 70, 75, 79, 83, 85, 91, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113, 123, 125, 128, 129, 132, 133, 134, 135, 137, 140, 141, 142, 143, 145, 148, 149, 153, 163, 172, 173, 176, 179, 180, 183, 186, 187, 190, 193, 194, 197, 201, 204, 211, 212, 213, 214, 216, 218, 222, 223, 226, 227, 229, 232, 235, 236, 237, 239, 240, 241, 242, 244, 245, 247, 248, 250, 251, 254, 255, 256, 257, 258, 261, 264, 267, 275, 276, 279], "summary": {"covered_lines": 105, "num_statements": 113, "percent_covered": 91.94630872483222, "percent_covered_display": "92", "missing_lines": 8, "excluded_lines": 10, "num_branches": 36, "num_partial_branches": 4, "covered_branches": 32, "missing_branches": 4}, "missing_lines": [88, 89, 115, 116, 117, 198, 269, 271], "excluded_lines": [19, 64, 69, 74, 78, 82, 120, 166, 207, 274], "executed_branches": [[50, 51], [50, 57], [53, 54], [53, 57], [85, 91], [94, 96], [94, 98], [98, 100], [98, 113], [105, 106], [105, 109], [123, 125], [123, 163], [132, 133], [132, 134], [134, 135], [134, 137], [140, 141], [140, 142], [142, 143], [142, 145], [172, 173], [172, 176], [197, 201], [226, 227], [239, 240], [239, 250], [244, 245], [244, 247], [250, 251], [255, -206], [255, 256]], "missing_branches": [[85, 88], [197, 198], [226, -206], [250, 254]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 10, 11, 15, 62, 63, 67, 68, 72, 73, 77, 81, 119, 165, 206, 273], "summary": {"covered_lines": 21, "num_statements": 21, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [11], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/interaction_handler.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 99, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 99, "excluded_lines": 14, "num_branches": 40, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 40}, "missing_lines": [8, 9, 10, 11, 13, 14, 17, 42, 50, 51, 53, 61, 63, 64, 65, 67, 70, 78, 80, 81, 82, 84, 87, 94, 96, 119, 120, 123, 126, 127, 128, 129, 130, 133, 134, 136, 138, 158, 159, 162, 163, 164, 165, 166, 167, 171, 172, 176, 178, 181, 189, 196, 197, 198, 200, 214, 216, 218, 220, 221, 222, 223, 224, 227, 228, 231, 233, 236, 237, 239, 240, 242, 244, 254, 256, 257, 259, 260, 261, 262, 263, 265, 267, 282, 283, 286, 287, 288, 292, 293, 295, 297, 299, 301, 303, 305, 307, 309, 310], "excluded_lines": [1, 18, 43, 54, 71, 88, 101, 142, 182, 190, 201, 245, 272, 300], "executed_branches": [], "missing_branches": [[61, 63], [61, 67], [63, 64], [63, 65], [78, 80], [78, 84], [80, 81], [80, 82], [127, 128], [127, 129], [129, 130], [129, 133], [164, 165], [164, 166], [216, 218], [216, 227], [218, 220], [218, 224], [220, 221], [220, 222], [228, 231], [228, 242], [231, 233], [231, 236], [236, 237], [236, 239], [256, 257], [256, 259], [260, 261], [260, 265], [282, 283], [282, 286], [287, 288], [287, 292], [292, 293], [292, 295], [301, 303], [301, 305], [305, 307], [305, 309]], "functions": {"InteractionHandler.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [50, 51], "excluded_lines": [43], "executed_branches": [], "missing_branches": []}, "InteractionHandler.set_pressed_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [61, 63, 64, 65, 67], "excluded_lines": [54], "executed_branches": [], "missing_branches": [[61, 63], [61, 67], [63, 64], [63, 65]]}, "InteractionHandler.set_hovered_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [78, 80, 81, 82, 84], "excluded_lines": [71], "executed_branches": [], "missing_branches": [[78, 80], [78, 84], [80, 81], [80, 82]]}, "InteractionHandler.render_current_state": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [94], "excluded_lines": [88], "executed_branches": [], "missing_branches": []}, "InteractionHandler.execute_with_feedback": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 11, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 11, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [119, 120, 123, 126, 127, 128, 129, 130, 133, 134, 136], "excluded_lines": [101], "executed_branches": [], "missing_branches": [[127, 128], [127, 129], [129, 130], [129, 133]]}, "InteractionHandler.execute_async_with_feedback": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [158, 159, 162, 171, 172, 176, 178], "excluded_lines": [142], "executed_branches": [], "missing_branches": []}, "InteractionHandler.execute_async_with_feedback.execute_callback": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [163, 164, 165, 166, 167], "excluded_lines": [], "executed_branches": [], "missing_branches": [[164, 165], [164, 166]]}, "InteractionStateManager.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [196, 197, 198], "excluded_lines": [190], "executed_branches": [], "missing_branches": []}, "InteractionStateManager.update_hover": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 17, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 17, "excluded_lines": 1, "num_branches": 12, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 12}, "missing_lines": [214, 216, 218, 220, 221, 222, 223, 224, 227, 228, 231, 233, 236, 237, 239, 240, 242], "excluded_lines": [201], "executed_branches": [], "missing_branches": [[216, 218], [216, 227], [218, 220], [218, 224], [220, 221], [220, 222], [228, 231], [228, 242], [231, 233], [231, 236], [236, 237], [236, 239]]}, "InteractionStateManager.handle_mouse_down": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 9, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 9, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [254, 256, 257, 259, 260, 261, 262, 263, 265], "excluded_lines": [245], "executed_branches": [], "missing_branches": [[256, 257], [256, 259], [260, 261], [260, 265]]}, "InteractionStateManager.handle_mouse_up": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 9, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 9, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6}, "missing_lines": [282, 283, 286, 287, 288, 292, 293, 295, 297], "excluded_lines": [272], "executed_branches": [], "missing_branches": [[282, 283], [282, 286], [287, 288], [287, 292], [292, 293], [292, 295]]}, "InteractionStateManager.reset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [301, 303, 305, 307, 309, 310], "excluded_lines": [300], "executed_branches": [], "missing_branches": [[301, 303], [301, 305], [305, 307], [305, 309]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 19, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 19, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [8, 9, 10, 11, 13, 14, 17, 42, 53, 70, 87, 96, 138, 181, 189, 200, 244, 267, 299], "excluded_lines": [1, 18, 182], "executed_branches": [], "missing_branches": []}}, "classes": {"InteractionHandler": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 36, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 36, "excluded_lines": 6, "num_branches": 14, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 14}, "missing_lines": [50, 51, 61, 63, 64, 65, 67, 78, 80, 81, 82, 84, 94, 119, 120, 123, 126, 127, 128, 129, 130, 133, 134, 136, 158, 159, 162, 163, 164, 165, 166, 167, 171, 172, 176, 178], "excluded_lines": [43, 54, 71, 88, 101, 142], "executed_branches": [], "missing_branches": [[61, 63], [61, 67], [63, 64], [63, 65], [78, 80], [78, 84], [80, 81], [80, 82], [127, 128], [127, 129], [129, 130], [129, 133], [164, 165], [164, 166]]}, "InteractionStateManager": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 44, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 44, "excluded_lines": 5, "num_branches": 26, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 26}, "missing_lines": [196, 197, 198, 214, 216, 218, 220, 221, 222, 223, 224, 227, 228, 231, 233, 236, 237, 239, 240, 242, 254, 256, 257, 259, 260, 261, 262, 263, 265, 282, 283, 286, 287, 288, 292, 293, 295, 297, 301, 303, 305, 307, 309, 310], "excluded_lines": [190, 201, 245, 272, 300], "executed_branches": [], "missing_branches": [[216, 218], [216, 227], [218, 220], [218, 224], [220, 221], [220, 222], [228, 231], [228, 242], [231, 233], [231, 236], [236, 237], [236, 239], [256, 257], [256, 259], [260, 261], [260, 265], [282, 283], [282, 286], [287, 288], [287, 292], [292, 293], [292, 295], [301, 303], [301, 305], [305, 307], [305, 309]]}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 19, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 19, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [8, 9, 10, 11, 13, 14, 17, 42, 53, 70, 87, 96, 138, 181, 189, 200, 244, 267, 299], "excluded_lines": [1, 18, 182], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/page.py": {"executed_lines": [1, 2, 3, 5, 6, 7, 8, 11, 12, 18, 26, 27, 28, 29, 30, 34, 35, 37, 39, 41, 43, 45, 62, 65, 66, 70, 73, 75, 76, 78, 80, 81, 83, 84, 89, 90, 92, 93, 98, 99, 101, 103, 104, 106, 108, 109, 111, 113, 114, 116, 118, 119, 123, 127, 131, 132, 134, 136, 137, 138, 140, 150, 151, 153, 154, 156, 166, 167, 168, 169, 170, 171, 173, 180, 181, 183, 185, 186, 188, 189, 191, 193, 233, 256, 261, 263, 264, 266, 268, 269, 271, 279, 280, 283, 286, 288, 290, 298, 301, 302, 303, 306, 307, 312, 314, 331, 342, 345, 347, 349, 350, 351, 352, 353, 358, 361, 367, 398, 425, 436, 437, 440, 441, 442, 450, 451, 459, 468, 469, 476, 482, 495, 496, 498, 499, 501, 505, 507, 508, 509, 511, 512, 514, 515, 516, 518, 519, 520, 522, 524, 534], "summary": {"covered_lines": 150, "num_statements": 204, "percent_covered": 66.18705035971223, "percent_covered_display": "66", "missing_lines": 54, "excluded_lines": 31, "num_branches": 74, "num_partial_branches": 8, "covered_branches": 34, "missing_branches": 40}, "missing_lines": [121, 125, 129, 210, 211, 212, 213, 214, 215, 216, 217, 220, 221, 222, 223, 224, 225, 226, 227, 229, 231, 244, 245, 246, 249, 250, 251, 254, 267, 325, 327, 329, 355, 379, 380, 381, 382, 383, 386, 387, 389, 390, 393, 409, 410, 413, 414, 417, 418, 420, 421, 423, 460, 502], "excluded_lines": [12, 19, 42, 50, 77, 82, 91, 100, 105, 110, 115, 120, 124, 128, 133, 141, 157, 174, 190, 196, 234, 257, 272, 291, 315, 332, 368, 399, 426, 484, 525], "executed_branches": [[65, 66], [65, 70], [134, 136], [134, 138], [261, -256], [261, 263], [263, 264], [263, 266], [266, 268], [268, 269], [301, 302], [301, 312], [345, 347], [345, 361], [347, 345], [347, 349], [349, 350], [349, 358], [351, 352], [450, 451], [450, 459], [459, 468], [468, 469], [468, 476], [501, 505], [507, 508], [507, 522], [508, 509], [509, 511], [511, 512], [511, 514], [514, 515], [518, 509], [518, 519]], "missing_branches": [[210, 211], [210, 220], [212, 213], [212, 220], [213, 214], [213, 220], [214, 215], [214, 216], [216, 217], [216, 220], [220, 221], [220, 231], [222, 223], [222, 231], [223, 224], [223, 229], [224, 225], [224, 226], [226, 227], [226, 231], [245, 246], [245, 249], [250, 251], [250, 254], [266, 267], [268, 261], [351, 355], [379, 380], [379, 386], [389, 390], [389, 393], [413, 414], [413, 417], [420, 421], [420, 423], [459, 460], [501, 502], [508, 507], [509, 507], [514, 518]], "functions": {"Page.__init__": {"executed_lines": [26, 27, 28, 29, 30, 34, 35, 37, 39], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [19], "executed_branches": [], "missing_branches": []}, "Page.free_space": {"executed_lines": [43], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [42], "executed_branches": [], "missing_branches": []}, "Page.can_fit_line": {"executed_lines": [62, 65, 66, 70, 73], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [50], "executed_branches": [[65, 66], [65, 70]], "missing_branches": []}, "Page.size": {"executed_lines": [78], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [77], "executed_branches": [], "missing_branches": []}, "Page.canvas_size": {"executed_lines": [83, 84], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [82], "executed_branches": [], "missing_branches": []}, "Page.content_size": {"executed_lines": [92, 93], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [91], "executed_branches": [], "missing_branches": []}, "Page.border_size": {"executed_lines": [101], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [100], "executed_branches": [], "missing_branches": []}, "Page.available_width": {"executed_lines": [106], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [105], "executed_branches": [], "missing_branches": []}, "Page.style": {"executed_lines": [111], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [110], "executed_branches": [], "missing_branches": []}, "Page.callbacks": {"executed_lines": [116], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [115], "executed_branches": [], "missing_branches": []}, "Page.is_dirty": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [121], "excluded_lines": [120], "executed_branches": [], "missing_branches": []}, "Page.mark_dirty": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [125], "excluded_lines": [124], "executed_branches": [], "missing_branches": []}, "Page.mark_clean": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [129], "excluded_lines": [128], "executed_branches": [], "missing_branches": []}, "Page.draw": {"executed_lines": [134, 136, 137, 138], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [133], "executed_branches": [[134, 136], [134, 138]], "missing_branches": []}, "Page.add_child": {"executed_lines": [150, 151, 153, 154], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [141], "executed_branches": [], "missing_branches": []}, "Page.remove_child": {"executed_lines": [166, 167, 168, 169, 170, 171], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [157], "executed_branches": [], "missing_branches": []}, "Page.clear_children": {"executed_lines": [180, 181, 183, 185, 186], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [174], "executed_branches": [], "missing_branches": []}, "Page.children": {"executed_lines": [191], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [190], "executed_branches": [], "missing_branches": []}, "Page._get_child_property": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 18, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 18, "excluded_lines": 1, "num_branches": 20, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 20}, "missing_lines": [210, 211, 212, 213, 214, 215, 216, 217, 220, 221, 222, 223, 224, 225, 226, 227, 229, 231], "excluded_lines": [196], "executed_branches": [], "missing_branches": [[210, 211], [210, 220], [212, 213], [212, 220], [213, 214], [213, 220], [214, 215], [214, 216], [216, 217], [216, 220], [220, 221], [220, 231], [222, 223], [222, 231], [223, 224], [223, 229], [224, 225], [224, 226], [226, 227], [226, 231]]}, "Page._get_child_height": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [244, 245, 246, 249, 250, 251, 254], "excluded_lines": [234], "executed_branches": [], "missing_branches": [[245, 246], [245, 249], [250, 251], [250, 254]]}, "Page.render_children": {"executed_lines": [261, 263, 264, 266, 268, 269], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2}, "missing_lines": [267], "excluded_lines": [257], "executed_branches": [[261, -256], [261, 263], [263, 264], [263, 266], [266, 268], [268, 269]], "missing_branches": [[266, 267], [268, 261]]}, "Page.render": {"executed_lines": [279, 280, 283, 286, 288], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [272], "executed_branches": [], "missing_branches": []}, "Page._create_canvas": {"executed_lines": [298, 301, 302, 303, 306, 307, 312], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [291], "executed_branches": [[301, 302], [301, 312]], "missing_branches": []}, "Page._get_child_position": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [325, 327, 329], "excluded_lines": [315], "executed_branches": [], "missing_branches": []}, "Page.query_point": {"executed_lines": [342, 345, 347, 349, 350, 351, 352, 353, 358, 361], "summary": {"covered_lines": 10, "num_statements": 11, "percent_covered": 89.47368421052632, "percent_covered_display": "89", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [355], "excluded_lines": [332], "executed_branches": [[345, 347], [345, 361], [347, 345], [347, 349], [349, 350], [349, 358], [351, 352]], "missing_branches": [[351, 355]]}, "Page._point_in_child": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 10, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 10, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [379, 380, 381, 382, 383, 386, 387, 389, 390, 393], "excluded_lines": [368], "executed_branches": [], "missing_branches": [[379, 380], [379, 386], [389, 390], [389, 393]]}, "Page._get_child_size": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 9, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 9, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [409, 410, 413, 414, 417, 418, 420, 421, 423], "excluded_lines": [399], "executed_branches": [], "missing_branches": [[413, 414], [413, 417], [420, 421], [420, 423]]}, "Page._make_query_result": {"executed_lines": [436, 437, 440, 441, 442, 450, 451, 459, 468, 469, 476], "summary": {"covered_lines": 11, "num_statements": 12, "percent_covered": 88.88888888888889, "percent_covered_display": "89", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [460], "excluded_lines": [426], "executed_branches": [[450, 451], [450, 459], [459, 468], [468, 469], [468, 476]], "missing_branches": [[459, 460]]}, "Page.query_range": {"executed_lines": [495, 496, 498, 499, 501, 505, 507, 508, 509, 511, 512, 514, 515, 516, 518, 519, 520, 522], "summary": {"covered_lines": 18, "num_statements": 19, "percent_covered": 84.84848484848484, "percent_covered_display": "85", "missing_lines": 1, "excluded_lines": 1, "num_branches": 14, "num_partial_branches": 4, "covered_branches": 10, "missing_branches": 4}, "missing_lines": [502], "excluded_lines": [484], "executed_branches": [[501, 505], [507, 508], [507, 522], [508, 509], [509, 511], [511, 512], [511, 514], [514, 515], [518, 509], [518, 519]], "missing_branches": [[501, 502], [508, 507], [509, 507], [514, 518]]}, "Page.in_object": {"executed_lines": [534], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [525], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 3, 5, 6, 7, 8, 11, 12, 18, 41, 45, 75, 76, 80, 81, 89, 90, 98, 99, 103, 104, 108, 109, 113, 114, 118, 119, 123, 127, 131, 132, 140, 156, 173, 188, 189, 193, 233, 256, 271, 290, 314, 331, 367, 398, 425, 482, 524], "summary": {"covered_lines": 48, "num_statements": 48, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [12], "executed_branches": [], "missing_branches": []}}, "classes": {"Page": {"executed_lines": [26, 27, 28, 29, 30, 34, 35, 37, 39, 43, 62, 65, 66, 70, 73, 78, 83, 84, 92, 93, 101, 106, 111, 116, 134, 136, 137, 138, 150, 151, 153, 154, 166, 167, 168, 169, 170, 171, 180, 181, 183, 185, 186, 191, 261, 263, 264, 266, 268, 269, 279, 280, 283, 286, 288, 298, 301, 302, 303, 306, 307, 312, 342, 345, 347, 349, 350, 351, 352, 353, 358, 361, 436, 437, 440, 441, 442, 450, 451, 459, 468, 469, 476, 495, 496, 498, 499, 501, 505, 507, 508, 509, 511, 512, 514, 515, 516, 518, 519, 520, 522, 534], "summary": {"covered_lines": 102, "num_statements": 156, "percent_covered": 59.130434782608695, "percent_covered_display": "59", "missing_lines": 54, "excluded_lines": 30, "num_branches": 74, "num_partial_branches": 8, "covered_branches": 34, "missing_branches": 40}, "missing_lines": [121, 125, 129, 210, 211, 212, 213, 214, 215, 216, 217, 220, 221, 222, 223, 224, 225, 226, 227, 229, 231, 244, 245, 246, 249, 250, 251, 254, 267, 325, 327, 329, 355, 379, 380, 381, 382, 383, 386, 387, 389, 390, 393, 409, 410, 413, 414, 417, 418, 420, 421, 423, 460, 502], "excluded_lines": [19, 42, 50, 77, 82, 91, 100, 105, 110, 115, 120, 124, 128, 133, 141, 157, 174, 190, 196, 234, 257, 272, 291, 315, 332, 368, 399, 426, 484, 525], "executed_branches": [[65, 66], [65, 70], [134, 136], [134, 138], [261, -256], [261, 263], [263, 264], [263, 266], [266, 268], [268, 269], [301, 302], [301, 312], [345, 347], [345, 361], [347, 345], [347, 349], [349, 350], [349, 358], [351, 352], [450, 451], [450, 459], [459, 468], [468, 469], [468, 476], [501, 505], [507, 508], [507, 522], [508, 509], [509, 511], [511, 512], [511, 514], [514, 515], [518, 509], [518, 519]], "missing_branches": [[210, 211], [210, 220], [212, 213], [212, 220], [213, 214], [213, 220], [214, 215], [214, 216], [216, 217], [216, 220], [220, 221], [220, 231], [222, 223], [222, 231], [223, 224], [223, 229], [224, 225], [224, 226], [226, 227], [226, 231], [245, 246], [245, 249], [250, 251], [250, 254], [266, 267], [268, 261], [351, 355], [379, 380], [379, 386], [389, 390], [389, 393], [413, 414], [413, 417], [420, 421], [420, 423], [459, 460], [501, 502], [508, 507], [509, 507], [514, 518]]}, "": {"executed_lines": [1, 2, 3, 5, 6, 7, 8, 11, 12, 18, 41, 45, 75, 76, 80, 81, 89, 90, 98, 99, 103, 104, 108, 109, 113, 114, 118, 119, 123, 127, 131, 132, 140, 156, 173, 188, 189, 193, 233, 256, 271, 290, 314, 331, 367, 398, 425, 482, 524], "summary": {"covered_lines": 48, "num_statements": 48, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [12], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/table.py": {"executed_lines": [1, 10, 11, 12, 13, 15, 16, 17, 18, 21, 22, 23, 26, 27, 30, 33, 34, 37, 38, 41, 44, 45, 50, 72, 73, 74, 75, 76, 77, 78, 80, 83, 84, 86, 89, 90, 91, 99, 100, 101, 102, 103, 106, 108, 110, 112, 113, 114, 116, 117, 120, 121, 122, 123, 125, 132, 133, 134, 137, 138, 141, 142, 146, 148, 150, 151, 153, 158, 159, 161, 164, 167, 170, 171, 174, 175, 177, 179, 183, 193, 194, 195, 198, 199, 201, 202, 203, 211, 221, 222, 223, 225, 229, 244, 336, 337, 341, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 375, 377, 378, 381, 382, 383, 384, 387, 389, 390, 393, 402, 403, 405, 407, 410, 411, 416, 435, 436, 437, 438, 439, 442, 443, 445, 448, 449, 451, 460, 462, 464, 465, 468, 475, 480, 484, 487, 491, 497, 499, 515, 516, 517, 520, 521, 522, 523, 524, 526, 528, 529, 530, 532, 534, 535, 539, 540, 547, 549, 552, 553, 556, 558, 560, 562, 566, 568, 569, 572, 574, 576, 578, 594, 595, 598, 599, 602, 604, 605, 607, 609, 612, 615, 619, 622, 623, 624, 629, 632, 637, 639, 641, 643, 644, 647, 648, 649, 652, 653, 654, 655, 656, 658, 660, 662, 672, 673, 675, 677, 679, 681, 683, 684, 690, 691, 692, 694, 696, 698, 699, 701, 703, 704, 706], "summary": {"covered_lines": 227, "num_statements": 303, "percent_covered": 70.37037037037037, "percent_covered_display": "70", "missing_lines": 76, "excluded_lines": 19, "num_branches": 102, "num_partial_branches": 20, "covered_branches": 58, "missing_branches": 44}, "missing_lines": [144, 154, 162, 180, 207, 208, 214, 217, 218, 226, 231, 232, 233, 234, 235, 237, 252, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 272, 276, 277, 278, 280, 282, 283, 285, 286, 289, 292, 293, 294, 296, 299, 306, 307, 308, 310, 311, 313, 314, 315, 316, 317, 318, 323, 324, 329, 331, 333, 477, 536, 542, 544, 555, 563, 610, 626, 627, 634, 635, 686, 687], "excluded_lines": [1, 23, 45, 60, 81, 111, 246, 337, 351, 376, 411, 424, 452, 504, 583, 642, 680, 700, 705], "executed_branches": [[83, 84], [83, 86], [122, 123], [122, 125], [141, 142], [141, 229], [142, 146], [146, 148], [153, 158], [159, 161], [159, 174], [161, 164], [177, 179], [177, 225], [179, 183], [194, 195], [194, 221], [201, 202], [203, 211], [221, 222], [225, 141], [229, -110], [382, 383], [382, 407], [383, 384], [387, 389], [387, 393], [464, 465], [464, 468], [475, 480], [528, 529], [528, 576], [529, 530], [529, 532], [534, 535], [534, 574], [535, 539], [540, 547], [552, 553], [552, 572], [553, 556], [556, 558], [562, 566], [607, 609], [607, 639], [609, 612], [622, 623], [622, 632], [624, 629], [632, 637], [647, 648], [647, 652], [652, 653], [652, 677], [653, 654], [653, 655], [655, 656], [655, 658]], "missing_branches": [[142, 144], [146, 225], [153, 154], [161, 162], [179, 180], [201, 214], [203, 207], [214, 217], [214, 218], [221, 177], [225, 226], [229, 231], [255, 256], [255, 257], [257, 258], [257, 259], [259, 260], [259, 261], [261, 262], [261, 263], [263, 264], [263, 265], [265, 266], [265, 268], [268, 269], [268, 272], [285, 286], [285, 289], [292, 293], [292, 299], [293, 294], [293, 296], [323, 324], [323, 329], [383, 382], [475, 477], [535, 536], [540, 542], [553, 555], [556, 552], [562, 563], [609, 610], [624, 626], [632, 634]], "functions": {"TableCellRenderer.__init__": {"executed_lines": [72, 73, 74, 75, 76, 77, 78], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [60], "executed_branches": [], "missing_branches": []}, "TableCellRenderer.render": {"executed_lines": [83, 84, 86, 89, 90, 91, 99, 100, 101, 102, 103, 106, 108], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [81], "executed_branches": [[83, 84], [83, 86]], "missing_branches": []}, "TableCellRenderer._render_cell_content": {"executed_lines": [112, 113, 114, 116, 117, 120, 121, 122, 123, 125, 132, 133, 134, 137, 138, 141, 142, 146, 148, 150, 151, 153, 158, 159, 161, 164, 167, 170, 171, 174, 175, 177, 179, 183, 193, 194, 195, 198, 199, 201, 202, 203, 211, 221, 222, 223, 225, 229], "summary": {"covered_lines": 48, "num_statements": 64, "percent_covered": 70.83333333333333, "percent_covered_display": "71", "missing_lines": 16, "excluded_lines": 1, "num_branches": 32, "num_partial_branches": 10, "covered_branches": 20, "missing_branches": 12}, "missing_lines": [144, 154, 162, 180, 207, 208, 214, 217, 218, 226, 231, 232, 233, 234, 235, 237], "excluded_lines": [111], "executed_branches": [[122, 123], [122, 125], [141, 142], [141, 229], [142, 146], [146, 148], [153, 158], [159, 161], [159, 174], [161, 164], [177, 179], [177, 225], [179, 183], [194, 195], [194, 221], [201, 202], [203, 211], [221, 222], [225, 141], [229, -110]], "missing_branches": [[142, 144], [146, 225], [153, 154], [161, 162], [179, 180], [201, 214], [203, 207], [214, 217], [214, 218], [221, 177], [225, 226], [229, 231]]}, "TableCellRenderer._render_image_in_cell": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 47, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 47, "excluded_lines": 1, "num_branches": 22, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 22}, "missing_lines": [252, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 272, 276, 277, 278, 280, 282, 283, 285, 286, 289, 292, 293, 294, 296, 299, 306, 307, 308, 310, 311, 313, 314, 315, 316, 317, 318, 323, 324, 329, 331, 333], "excluded_lines": [246], "executed_branches": [], "missing_branches": [[255, 256], [255, 257], [257, 258], [257, 259], [259, 260], [259, 261], [261, 262], [261, 263], [263, 264], [263, 265], [265, 266], [265, 268], [268, 269], [268, 272], [285, 286], [285, 289], [292, 293], [292, 299], [293, 294], [293, 296], [323, 324], [323, 329]]}, "TableRowRenderer.__init__": {"executed_lines": [364, 365, 366, 367, 368, 369, 370, 371, 372, 373], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [351], "executed_branches": [], "missing_branches": []}, "TableRowRenderer.render": {"executed_lines": [377, 378, 381, 382, 383, 384, 387, 389, 390, 393, 402, 403, 405, 407], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 95.0, "percent_covered_display": "95", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [376], "executed_branches": [[382, 383], [382, 407], [383, 384], [387, 389], [387, 393]], "missing_branches": [[383, 382]]}, "TableRenderer.__init__": {"executed_lines": [435, 436, 437, 438, 439, 442, 443, 445, 448, 449], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [424], "executed_branches": [], "missing_branches": []}, "TableRenderer._calculate_dimensions": {"executed_lines": [460, 462, 464, 465, 468, 475, 480, 484, 487, 491, 497], "summary": {"covered_lines": 11, "num_statements": 12, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [477], "excluded_lines": [452], "executed_branches": [[464, 465], [464, 468], [475, 480]], "missing_branches": [[475, 477]]}, "TableRenderer._calculate_row_height_for_section": {"executed_lines": [515, 516, 517, 520, 521, 522, 523, 524, 526, 528, 529, 530, 532, 534, 535, 539, 540, 547, 549, 552, 553, 556, 558, 560, 562, 566, 568, 569, 572, 574, 576], "summary": {"covered_lines": 31, "num_statements": 36, "percent_covered": 81.48148148148148, "percent_covered_display": "81", "missing_lines": 5, "excluded_lines": 1, "num_branches": 18, "num_partial_branches": 5, "covered_branches": 13, "missing_branches": 5}, "missing_lines": [536, 542, 544, 555, 563], "excluded_lines": [504], "executed_branches": [[528, 529], [528, 576], [529, 530], [529, 532], [534, 535], [534, 574], [535, 539], [540, 547], [552, 553], [552, 572], [553, 556], [556, 558], [562, 566]], "missing_branches": [[535, 536], [540, 542], [553, 555], [556, 552], [562, 563]]}, "TableRenderer._estimate_wrapped_lines": {"executed_lines": [594, 595, 598, 599, 602, 604, 605, 607, 609, 612, 615, 619, 622, 623, 624, 629, 632, 637, 639], "summary": {"covered_lines": 19, "num_statements": 24, "percent_covered": 76.47058823529412, "percent_covered_display": "76", "missing_lines": 5, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 3}, "missing_lines": [610, 626, 627, 634, 635], "excluded_lines": [583], "executed_branches": [[607, 609], [607, 639], [609, 612], [622, 623], [622, 632], [624, 629], [632, 637]], "missing_branches": [[609, 610], [624, 626], [632, 634]]}, "TableRenderer.render": {"executed_lines": [643, 644, 647, 648, 649, 652, 653, 654, 655, 656, 658, 660, 662, 672, 673, 675, 677], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [642], "executed_branches": [[647, 648], [647, 652], [652, 653], [652, 677], [653, 654], [653, 655], [655, 656], [655, 658]], "missing_branches": []}, "TableRenderer._render_caption": {"executed_lines": [681, 683, 684, 690, 691, 692, 694, 696], "summary": {"covered_lines": 8, "num_statements": 10, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [686, 687], "excluded_lines": [680], "executed_branches": [], "missing_branches": []}, "TableRenderer.height": {"executed_lines": [701], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [700], "executed_branches": [], "missing_branches": []}, "TableRenderer.width": {"executed_lines": [706], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [705], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 10, 11, 12, 13, 15, 16, 17, 18, 21, 22, 23, 26, 27, 30, 33, 34, 37, 38, 41, 44, 45, 50, 80, 110, 244, 336, 337, 341, 375, 410, 411, 416, 451, 499, 578, 641, 679, 698, 699, 703, 704], "summary": {"covered_lines": 37, "num_statements": 37, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 23, 45, 337, 411], "executed_branches": [], "missing_branches": []}}, "classes": {"TableStyle": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "TableCellRenderer": {"executed_lines": [72, 73, 74, 75, 76, 77, 78, 83, 84, 86, 89, 90, 91, 99, 100, 101, 102, 103, 106, 108, 112, 113, 114, 116, 117, 120, 121, 122, 123, 125, 132, 133, 134, 137, 138, 141, 142, 146, 148, 150, 151, 153, 158, 159, 161, 164, 167, 170, 171, 174, 175, 177, 179, 183, 193, 194, 195, 198, 199, 201, 202, 203, 211, 221, 222, 223, 225, 229], "summary": {"covered_lines": 68, "num_statements": 131, "percent_covered": 48.1283422459893, "percent_covered_display": "48", "missing_lines": 63, "excluded_lines": 4, "num_branches": 56, "num_partial_branches": 10, "covered_branches": 22, "missing_branches": 34}, "missing_lines": [144, 154, 162, 180, 207, 208, 214, 217, 218, 226, 231, 232, 233, 234, 235, 237, 252, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 272, 276, 277, 278, 280, 282, 283, 285, 286, 289, 292, 293, 294, 296, 299, 306, 307, 308, 310, 311, 313, 314, 315, 316, 317, 318, 323, 324, 329, 331, 333], "excluded_lines": [60, 81, 111, 246], "executed_branches": [[83, 84], [83, 86], [122, 123], [122, 125], [141, 142], [141, 229], [142, 146], [146, 148], [153, 158], [159, 161], [159, 174], [161, 164], [177, 179], [177, 225], [179, 183], [194, 195], [194, 221], [201, 202], [203, 211], [221, 222], [225, 141], [229, -110]], "missing_branches": [[142, 144], [146, 225], [153, 154], [161, 162], [179, 180], [201, 214], [203, 207], [214, 217], [214, 218], [221, 177], [225, 226], [229, 231], [255, 256], [255, 257], [257, 258], [257, 259], [259, 260], [259, 261], [261, 262], [261, 263], [263, 264], [263, 265], [265, 266], [265, 268], [268, 269], [268, 272], [285, 286], [285, 289], [292, 293], [292, 299], [293, 294], [293, 296], [323, 324], [323, 329]]}, "TableRowRenderer": {"executed_lines": [364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 377, 378, 381, 382, 383, 384, 387, 389, 390, 393, 402, 403, 405, 407], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 96.66666666666667, "percent_covered_display": "97", "missing_lines": 0, "excluded_lines": 2, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [351, 376], "executed_branches": [[382, 383], [382, 407], [383, 384], [387, 389], [387, 393]], "missing_branches": [[383, 382]]}, "TableRenderer": {"executed_lines": [435, 436, 437, 438, 439, 442, 443, 445, 448, 449, 460, 462, 464, 465, 468, 475, 480, 484, 487, 491, 497, 515, 516, 517, 520, 521, 522, 523, 524, 526, 528, 529, 530, 532, 534, 535, 539, 540, 547, 549, 552, 553, 556, 558, 560, 562, 566, 568, 569, 572, 574, 576, 594, 595, 598, 599, 602, 604, 605, 607, 609, 612, 615, 619, 622, 623, 624, 629, 632, 637, 639, 643, 644, 647, 648, 649, 652, 653, 654, 655, 656, 658, 660, 662, 672, 673, 675, 677, 681, 683, 684, 690, 691, 692, 694, 696, 701, 706], "summary": {"covered_lines": 98, "num_statements": 111, "percent_covered": 85.43046357615894, "percent_covered_display": "85", "missing_lines": 13, "excluded_lines": 8, "num_branches": 40, "num_partial_branches": 9, "covered_branches": 31, "missing_branches": 9}, "missing_lines": [477, 536, 542, 544, 555, 563, 610, 626, 627, 634, 635, 686, 687], "excluded_lines": [424, 452, 504, 583, 642, 680, 700, 705], "executed_branches": [[464, 465], [464, 468], [475, 480], [528, 529], [528, 576], [529, 530], [529, 532], [534, 535], [534, 574], [535, 539], [540, 547], [552, 553], [552, 572], [553, 556], [556, 558], [562, 566], [607, 609], [607, 639], [609, 612], [622, 623], [622, 632], [624, 629], [632, 637], [647, 648], [647, 652], [652, 653], [652, 677], [653, 654], [653, 655], [655, 656], [655, 658]], "missing_branches": [[475, 477], [535, 536], [540, 542], [553, 555], [556, 552], [562, 563], [609, 610], [624, 626], [632, 634]]}, "": {"executed_lines": [1, 10, 11, 12, 13, 15, 16, 17, 18, 21, 22, 23, 26, 27, 30, 33, 34, 37, 38, 41, 44, 45, 50, 80, 110, 244, 336, 337, 341, 375, 410, 411, 416, 451, 499, 578, 641, 679, 698, 699, 703, 704], "summary": {"covered_lines": 37, "num_statements": 37, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 23, 45, 337, 411], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/concrete/text.py": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 21, 22, 39, 40, 42, 61, 62, 65, 68, 71, 75, 76, 79, 82, 84, 85, 86, 90, 93, 94, 96, 97, 99, 103, 104, 107, 108, 109, 111, 112, 114, 115, 116, 117, 119, 120, 121, 123, 125, 126, 128, 131, 132, 134, 136, 138, 149, 150, 151, 154, 155, 158, 165, 166, 167, 170, 172, 175, 176, 181, 195, 196, 197, 198, 199, 200, 201, 204, 206, 209, 210, 211, 212, 213, 215, 216, 217, 219, 220, 222, 224, 225, 227, 229, 230, 232, 234, 235, 237, 239, 240, 242, 244, 245, 247, 249, 250, 253, 254, 255, 257, 259, 261, 263, 265, 278, 281, 282, 287, 290, 298, 300, 301, 304, 307, 311, 313, 316, 334, 347, 353, 362, 365, 366, 371, 405, 406, 407, 408, 409, 410, 411, 412, 413, 415, 416, 417, 418, 421, 422, 423, 426, 428, 438, 439, 440, 441, 443, 445, 446, 448, 450, 454, 471, 472, 473, 474, 478, 480, 485, 492, 500, 501, 502, 505, 507, 508, 509, 510, 511, 512, 515, 518, 519, 521, 523, 524, 525, 528, 530, 534, 540, 548, 549, 551, 553, 555, 558, 560, 561, 564, 565, 566, 567, 568, 569, 570, 573, 575, 576, 577, 579, 581, 583, 585, 590, 591, 594, 595, 598, 602, 603, 605, 611, 619, 620, 623, 634, 637, 639, 647, 648, 650, 651, 653, 656, 657, 659, 660, 663, 667, 670, 672, 675, 677, 678, 679, 681, 692, 695, 697, 699, 700, 705, 706, 714, 716, 717, 726, 737, 744, 745, 747], "summary": {"covered_lines": 261, "num_statements": 283, "percent_covered": 90.41095890410959, "percent_covered_display": "90", "missing_lines": 22, "excluded_lines": 31, "num_branches": 82, "num_partial_branches": 11, "covered_branches": 69, "missing_branches": 13}, "missing_lines": [88, 160, 161, 168, 318, 319, 322, 325, 329, 331, 348, 452, 529, 531, 625, 626, 627, 628, 629, 630, 631, 727], "excluded_lines": [16, 25, 40, 47, 94, 102, 132, 141, 176, 188, 207, 221, 226, 231, 236, 241, 246, 251, 258, 262, 266, 291, 335, 366, 387, 429, 447, 451, 458, 640, 682], "executed_branches": [[61, 62], [61, 65], [75, 76], [75, 79], [84, 85], [84, 86], [86, 90], [107, 108], [107, 114], [108, 109], [108, 111], [116, 117], [116, 119], [120, 121], [120, 123], [125, 126], [125, 128], [158, 165], [166, 167], [166, 172], [167, 170], [298, 300], [298, 316], [307, 311], [307, 313], [316, -290], [347, 353], [438, 439], [438, 440], [440, 441], [440, 443], [471, 472], [471, 478], [478, 480], [478, 500], [505, 507], [505, 515], [521, 523], [521, 558], [523, 524], [523, 558], [528, 530], [530, 534], [553, 523], [553, 555], [558, 560], [558, 573], [573, 575], [573, 637], [579, 581], [579, 637], [583, 585], [598, 602], [623, 634], [647, 648], [647, 653], [657, -639], [657, 659], [667, 670], [667, 672], [678, 657], [678, 679], [695, 697], [695, 747], [697, 695], [697, 699], [716, 717], [716, 726], [726, 737]], "missing_branches": [[86, 88], [158, 160], [167, 168], [316, 318], [325, 329], [325, 331], [347, 348], [528, 529], [530, 531], [583, 637], [598, 637], [623, 625], [726, 727]], "functions": {"AlignmentHandler.calculate_spacing_and_position": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [25], "executed_branches": [], "missing_branches": []}, "LeftAlignmentHandler.calculate_spacing_and_position": {"executed_lines": [61, 62, 65, 68, 71, 75, 76, 79, 82, 84, 85, 86, 90], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 90.9090909090909, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [88], "excluded_lines": [47], "executed_branches": [[61, 62], [61, 65], [75, 76], [75, 79], [84, 85], [84, 86], [86, 90]], "missing_branches": [[86, 88]]}, "CenterRightAlignmentHandler.__init__": {"executed_lines": [97], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "CenterRightAlignmentHandler.calculate_spacing_and_position": {"executed_lines": [103, 104, 107, 108, 109, 111, 112, 114, 115, 116, 117, 119, 120, 121, 123, 125, 126, 128], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [102], "executed_branches": [[107, 108], [107, 114], [108, 109], [108, 111], [116, 117], [116, 119], [120, 121], [120, 123], [125, 126], [125, 128]], "missing_branches": []}, "JustifyAlignmentHandler.__init__": {"executed_lines": [136], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "JustifyAlignmentHandler.calculate_spacing_and_position": {"executed_lines": [149, 150, 151, 154, 155, 158, 165, 166, 167, 170, 172], "summary": {"covered_lines": 11, "num_statements": 14, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 3, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2}, "missing_lines": [160, 161, 168], "excluded_lines": [141], "executed_branches": [[158, 165], [166, 167], [166, 172], [167, 170]], "missing_branches": [[158, 160], [167, 168]]}, "Text.__init__": {"executed_lines": [195, 196, 197, 198, 199, 200, 201, 204], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [188], "executed_branches": [], "missing_branches": []}, "Text._calculate_dimensions": {"executed_lines": [209, 210, 211, 212, 213], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [207], "executed_branches": [], "missing_branches": []}, "Text.from_word": {"executed_lines": [217], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Text.text": {"executed_lines": [222], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [221], "executed_branches": [], "missing_branches": []}, "Text.style": {"executed_lines": [227], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [226], "executed_branches": [], "missing_branches": []}, "Text.origin": {"executed_lines": [232], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [231], "executed_branches": [], "missing_branches": []}, "Text.line": {"executed_lines": [242], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [241], "executed_branches": [], "missing_branches": []}, "Text.width": {"executed_lines": [247], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [246], "executed_branches": [], "missing_branches": []}, "Text.size": {"executed_lines": [253, 254, 255], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [251], "executed_branches": [], "missing_branches": []}, "Text.set_origin": {"executed_lines": [259], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [258], "executed_branches": [], "missing_branches": []}, "Text.add_line": {"executed_lines": [263], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [262], "executed_branches": [], "missing_branches": []}, "Text.in_object": {"executed_lines": [278, 281, 282, 287], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [266], "executed_branches": [], "missing_branches": []}, "Text._apply_decoration": {"executed_lines": [298, 300, 301, 304, 307, 311, 313, 316], "summary": {"covered_lines": 8, "num_statements": 14, "percent_covered": 59.09090909090909, "percent_covered_display": "59", "missing_lines": 6, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 3}, "missing_lines": [318, 319, 322, 325, 329, 331], "excluded_lines": [291], "executed_branches": [[298, 300], [298, 316], [307, 311], [307, 313], [316, -290]], "missing_branches": [[316, 318], [325, 329], [325, 331]]}, "Text.render": {"executed_lines": [347, 353, 362], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 1, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [348], "excluded_lines": [335], "executed_branches": [[347, 353]], "missing_branches": [[347, 348]]}, "Line.__init__": {"executed_lines": [405, 406, 407, 408, 409, 410, 411, 412, 413, 415, 416, 417, 418, 421, 422, 423, 426], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [387], "executed_branches": [], "missing_branches": []}, "Line._create_alignment_handler": {"executed_lines": [438, 439, 440, 441, 443], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [429], "executed_branches": [[438, 439], [438, 440], [440, 441], [440, 443]], "missing_branches": []}, "Line.text_objects": {"executed_lines": [448], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [447], "executed_branches": [], "missing_branches": []}, "Line.set_next": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [452], "excluded_lines": [451], "executed_branches": [], "missing_branches": []}, "Line.add_word": {"executed_lines": [471, 472, 473, 474, 478, 480, 485, 492, 500, 501, 502, 505, 507, 508, 509, 510, 511, 512, 515, 518, 519, 521, 523, 524, 525, 528, 530, 534, 540, 548, 549, 551, 553, 555, 558, 560, 561, 564, 565, 566, 567, 568, 569, 570, 573, 575, 576, 577, 579, 581, 583, 585, 590, 591, 594, 595, 598, 602, 603, 605, 611, 619, 620, 623, 634, 637], "summary": {"covered_lines": 66, "num_statements": 75, "percent_covered": 86.40776699029126, "percent_covered_display": "86", "missing_lines": 9, "excluded_lines": 1, "num_branches": 28, "num_partial_branches": 5, "covered_branches": 23, "missing_branches": 5}, "missing_lines": [529, 531, 625, 626, 627, 628, 629, 630, 631], "excluded_lines": [458], "executed_branches": [[471, 472], [471, 478], [478, 480], [478, 500], [505, 507], [505, 515], [521, 523], [521, 558], [523, 524], [523, 558], [528, 530], [530, 534], [553, 523], [553, 555], [558, 560], [558, 573], [573, 575], [573, 637], [579, 581], [579, 637], [583, 585], [598, 602], [623, 634]], "missing_branches": [[528, 529], [530, 531], [583, 637], [598, 637], [623, 625]]}, "Line.render": {"executed_lines": [647, 648, 650, 651, 653, 656, 657, 659, 660, 663, 667, 670, 672, 675, 677, 678, 679], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [640], "executed_branches": [[647, 648], [647, 653], [657, -639], [657, 659], [667, 670], [667, 672], [678, 657], [678, 679]], "missing_branches": []}, "Line.query_point": {"executed_lines": [692, 695, 697, 699, 700, 705, 706, 714, 716, 717, 726, 737, 744, 745, 747], "summary": {"covered_lines": 15, "num_statements": 16, "percent_covered": 91.66666666666667, "percent_covered_display": "92", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [727], "excluded_lines": [682], "executed_branches": [[695, 697], [695, 747], [697, 695], [697, 699], [716, 717], [716, 726], [726, 737]], "missing_branches": [[726, 727]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 21, 22, 39, 40, 42, 93, 94, 96, 99, 131, 132, 134, 138, 175, 176, 181, 206, 215, 216, 219, 220, 224, 225, 229, 230, 234, 235, 239, 240, 244, 245, 249, 250, 257, 261, 265, 290, 334, 365, 366, 371, 428, 445, 446, 450, 454, 639, 681], "summary": {"covered_lines": 56, "num_statements": 56, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 6, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [16, 40, 94, 132, 176, 366], "executed_branches": [], "missing_branches": []}}, "classes": {"AlignmentHandler": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [25], "executed_branches": [], "missing_branches": []}, "LeftAlignmentHandler": {"executed_lines": [61, 62, 65, 68, 71, 75, 76, 79, 82, 84, 85, 86, 90], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 90.9090909090909, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [88], "excluded_lines": [47], "executed_branches": [[61, 62], [61, 65], [75, 76], [75, 79], [84, 85], [84, 86], [86, 90]], "missing_branches": [[86, 88]]}, "CenterRightAlignmentHandler": {"executed_lines": [97, 103, 104, 107, 108, 109, 111, 112, 114, 115, 116, 117, 119, 120, 121, 123, 125, 126, 128], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [102], "executed_branches": [[107, 108], [107, 114], [108, 109], [108, 111], [116, 117], [116, 119], [120, 121], [120, 123], [125, 126], [125, 128]], "missing_branches": []}, "JustifyAlignmentHandler": {"executed_lines": [136, 149, 150, 151, 154, 155, 158, 165, 166, 167, 170, 172], "summary": {"covered_lines": 12, "num_statements": 15, "percent_covered": 76.19047619047619, "percent_covered_display": "76", "missing_lines": 3, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2}, "missing_lines": [160, 161, 168], "excluded_lines": [141], "executed_branches": [[158, 165], [166, 167], [166, 172], [167, 170]], "missing_branches": [[158, 160], [167, 168]]}, "Text": {"executed_lines": [195, 196, 197, 198, 199, 200, 201, 204, 209, 210, 211, 212, 213, 217, 222, 227, 232, 237, 242, 247, 253, 254, 255, 259, 263, 278, 281, 282, 287, 298, 300, 301, 304, 307, 311, 313, 316, 347, 353, 362], "summary": {"covered_lines": 40, "num_statements": 47, "percent_covered": 80.70175438596492, "percent_covered_display": "81", "missing_lines": 7, "excluded_lines": 14, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 4}, "missing_lines": [318, 319, 322, 325, 329, 331, 348], "excluded_lines": [188, 207, 221, 226, 231, 236, 241, 246, 251, 258, 262, 266, 291, 335], "executed_branches": [[298, 300], [298, 316], [307, 311], [307, 313], [316, -290], [347, 353]], "missing_branches": [[316, 318], [325, 329], [325, 331], [347, 348]]}, "Line": {"executed_lines": [405, 406, 407, 408, 409, 410, 411, 412, 413, 415, 416, 417, 418, 421, 422, 423, 426, 438, 439, 440, 441, 443, 448, 471, 472, 473, 474, 478, 480, 485, 492, 500, 501, 502, 505, 507, 508, 509, 510, 511, 512, 515, 518, 519, 521, 523, 524, 525, 528, 530, 534, 540, 548, 549, 551, 553, 555, 558, 560, 561, 564, 565, 566, 567, 568, 569, 570, 573, 575, 576, 577, 579, 581, 583, 585, 590, 591, 594, 595, 598, 602, 603, 605, 611, 619, 620, 623, 634, 637, 647, 648, 650, 651, 653, 656, 657, 659, 660, 663, 667, 670, 672, 675, 677, 678, 679, 692, 695, 697, 699, 700, 705, 706, 714, 716, 717, 726, 737, 744, 745, 747], "summary": {"covered_lines": 121, "num_statements": 132, "percent_covered": 90.55555555555556, "percent_covered_display": "91", "missing_lines": 11, "excluded_lines": 7, "num_branches": 48, "num_partial_branches": 6, "covered_branches": 42, "missing_branches": 6}, "missing_lines": [452, 529, 531, 625, 626, 627, 628, 629, 630, 631, 727], "excluded_lines": [387, 429, 447, 451, 458, 640, 682], "executed_branches": [[438, 439], [438, 440], [440, 441], [440, 443], [471, 472], [471, 478], [478, 480], [478, 500], [505, 507], [505, 515], [521, 523], [521, 558], [523, 524], [523, 558], [528, 530], [530, 534], [553, 523], [553, 555], [558, 560], [558, 573], [573, 575], [573, 637], [579, 581], [579, 637], [583, 585], [598, 602], [623, 634], [647, 648], [647, 653], [657, -639], [657, 659], [667, 670], [667, 672], [678, 657], [678, 679], [695, 697], [695, 747], [697, 695], [697, 699], [716, 717], [716, 726], [726, 737]], "missing_branches": [[528, 529], [530, 531], [583, 637], [598, 637], [623, 625], [726, 727]]}, "": {"executed_lines": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 21, 22, 39, 40, 42, 93, 94, 96, 99, 131, 132, 134, 138, 175, 176, 181, 206, 215, 216, 219, 220, 224, 225, 229, 230, 234, 235, 239, 240, 244, 245, 249, 250, 257, 261, 265, 290, 334, 365, 366, 371, 428, 445, 446, 450, 454, 639, 681], "summary": {"covered_lines": 56, "num_statements": 56, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 6, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [16, 40, 94, 132, 176, 366], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/core/__init__.py": {"executed_lines": [1, 8, 22], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 8, 22], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1, 8, 22], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/core/base.py": {"executed_lines": [1, 2, 3, 6, 10, 11, 16, 24, 25, 26, 29, 30, 35, 42, 44, 54, 56, 59, 60, 65, 72, 74, 78, 79, 80, 88, 89, 95, 96, 97, 99, 100, 102, 104, 105, 107, 110, 111, 118, 119, 120, 121, 123, 124, 126, 128, 129, 133, 134, 136, 138, 139, 143, 148, 149, 156, 157, 158, 160, 161, 163, 165, 166, 171, 172, 182, 183, 184, 186, 217, 220, 221, 222, 223, 224, 225, 228, 233, 247, 248, 250, 263, 266, 267, 270, 282, 283, 286, 287, 293, 294, 295, 297, 305, 307, 317, 320, 321, 330, 331, 332, 334, 343, 345, 352, 353, 354, 356, 375, 399, 400, 411, 412, 428, 429], "summary": {"covered_lines": 105, "num_statements": 134, "percent_covered": 71.95121951219512, "percent_covered_display": "72", "missing_lines": 29, "excluded_lines": 33, "num_branches": 30, "num_partial_branches": 3, "covered_branches": 13, "missing_branches": 17}, "missing_lines": [7, 55, 131, 141, 145, 168, 366, 368, 369, 371, 372, 373, 386, 388, 389, 391, 392, 394, 395, 396, 423, 424, 440, 441, 443, 444, 445, 446, 448], "excluded_lines": [11, 17, 30, 36, 45, 60, 66, 75, 89, 101, 106, 111, 125, 130, 135, 140, 144, 149, 162, 167, 172, 196, 287, 298, 308, 321, 335, 346, 357, 376, 400, 413, 430], "executed_branches": [[6, 10], [54, 56], [220, 221], [220, 222], [222, 223], [222, 224], [224, 225], [224, 228], [228, 233], [228, 247], [266, 267], [266, 270], [353, 354]], "missing_branches": [[6, 7], [54, 55], [353, -345], [368, 369], [368, 371], [388, 389], [388, 391], [391, 392], [391, 394], [423, -411], [423, 424], [440, 441], [440, 443], [443, 444], [443, 445], [445, 446], [445, 448]], "functions": {"Renderable.render": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [17], "executed_branches": [], "missing_branches": []}, "Renderable.origin": {"executed_lines": [26], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Interactable.__init__": {"executed_lines": [42], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [36], "executed_branches": [], "missing_branches": []}, "Interactable.interact": {"executed_lines": [54, 56], "summary": {"covered_lines": 2, "num_statements": 3, "percent_covered": 60.0, "percent_covered_display": "60", "missing_lines": 1, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [55], "excluded_lines": [45], "executed_branches": [[54, 56]], "missing_branches": [[54, 55]]}, "Layoutable.layout": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [66], "executed_branches": [], "missing_branches": []}, "Queriable.in_object": {"executed_lines": [78, 79, 80], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [75], "executed_branches": [], "missing_branches": []}, "Hierarchical.__init__": {"executed_lines": [96, 97], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Hierarchical.parent": {"executed_lines": [107], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [106], "executed_branches": [], "missing_branches": []}, "Geometric.__init__": {"executed_lines": [119, 120, 121], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Geometric.origin": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [131], "excluded_lines": [130], "executed_branches": [], "missing_branches": []}, "Geometric.size": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [141], "excluded_lines": [140], "executed_branches": [], "missing_branches": []}, "Geometric.set_origin": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [145], "excluded_lines": [144], "executed_branches": [], "missing_branches": []}, "Styleable.__init__": {"executed_lines": [157, 158], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Styleable.style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [168], "excluded_lines": [167], "executed_branches": [], "missing_branches": []}, "FontRegistry.__init__": {"executed_lines": [183, 184], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "FontRegistry.get_or_create_font": {"executed_lines": [217, 220, 221, 222, 223, 224, 225, 228, 233, 247, 248, 250, 263, 266, 267, 270, 282, 283], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [196], "executed_branches": [[220, 221], [220, 222], [222, 223], [222, 224], [224, 225], [224, 228], [228, 233], [228, 247], [266, 267], [266, 270]], "missing_branches": []}, "MetadataContainer.__init__": {"executed_lines": [294, 295], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "MetadataContainer.set_metadata": {"executed_lines": [305], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [298], "executed_branches": [], "missing_branches": []}, "MetadataContainer.get_metadata": {"executed_lines": [317], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [308], "executed_branches": [], "missing_branches": []}, "BlockContainer.__init__": {"executed_lines": [331, 332], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "BlockContainer.blocks": {"executed_lines": [343], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [335], "executed_branches": [], "missing_branches": []}, "BlockContainer.add_block": {"executed_lines": [352, 353, 354], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [346], "executed_branches": [[353, 354]], "missing_branches": [[353, -345]]}, "BlockContainer.create_paragraph": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [366, 368, 369, 371, 372, 373], "excluded_lines": [357], "executed_branches": [], "missing_branches": [[368, 369], [368, 371]]}, "BlockContainer.create_heading": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 8, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 8, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [386, 388, 389, 391, 392, 394, 395, 396], "excluded_lines": [376], "executed_branches": [], "missing_branches": [[388, 389], [388, 391], [391, 392], [391, 394]]}, "ContainerAware._validate_container": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [423, 424], "excluded_lines": [413], "executed_branches": [], "missing_branches": [[423, -411], [423, 424]]}, "ContainerAware._inherit_style": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 6}, "missing_lines": [440, 441, 443, 444, 445, 446, 448], "excluded_lines": [430], "executed_branches": [], "missing_branches": [[440, 441], [440, 443], [443, 444], [443, 445], [445, 446], [445, 448]]}, "": {"executed_lines": [1, 2, 3, 6, 10, 11, 16, 24, 25, 29, 30, 35, 44, 59, 60, 65, 72, 74, 88, 89, 95, 99, 100, 104, 105, 110, 111, 118, 123, 124, 128, 129, 133, 134, 138, 139, 143, 148, 149, 156, 160, 161, 165, 166, 171, 172, 182, 186, 286, 287, 293, 297, 307, 320, 321, 330, 334, 345, 356, 375, 399, 400, 411, 412, 428, 429], "summary": {"covered_lines": 56, "num_statements": 57, "percent_covered": 96.61016949152543, "percent_covered_display": "97", "missing_lines": 1, "excluded_lines": 10, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [7], "excluded_lines": [11, 30, 60, 89, 111, 149, 172, 287, 321, 400], "executed_branches": [[6, 10]], "missing_branches": [[6, 7]]}}, "classes": {"Renderable": {"executed_lines": [26], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [17], "executed_branches": [], "missing_branches": []}, "Interactable": {"executed_lines": [42, 54, 56], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 1, "excluded_lines": 2, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [55], "excluded_lines": [36, 45], "executed_branches": [[54, 56]], "missing_branches": [[54, 55]]}, "Layoutable": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [66], "executed_branches": [], "missing_branches": []}, "Queriable": {"executed_lines": [78, 79, 80], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [75], "executed_branches": [], "missing_branches": []}, "Hierarchical": {"executed_lines": [96, 97, 102, 107], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [101, 106], "executed_branches": [], "missing_branches": []}, "Geometric": {"executed_lines": [119, 120, 121, 126, 136], "summary": {"covered_lines": 5, "num_statements": 8, "percent_covered": 62.5, "percent_covered_display": "62", "missing_lines": 3, "excluded_lines": 5, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [131, 141, 145], "excluded_lines": [125, 130, 135, 140, 144], "executed_branches": [], "missing_branches": []}, "Styleable": {"executed_lines": [157, 158, 163], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 1, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [168], "excluded_lines": [162, 167], "executed_branches": [], "missing_branches": []}, "FontRegistry": {"executed_lines": [183, 184, 217, 220, 221, 222, 223, 224, 225, 228, 233, 247, 248, 250, 263, 266, 267, 270, 282, 283], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [196], "executed_branches": [[220, 221], [220, 222], [222, 223], [222, 224], [224, 225], [224, 228], [228, 233], [228, 247], [266, 267], [266, 270]], "missing_branches": []}, "MetadataContainer": {"executed_lines": [294, 295, 305, 317], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [298, 308], "executed_branches": [], "missing_branches": []}, "BlockContainer": {"executed_lines": [331, 332, 343, 352, 353, 354], "summary": {"covered_lines": 6, "num_statements": 20, "percent_covered": 25.0, "percent_covered_display": "25", "missing_lines": 14, "excluded_lines": 4, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 7}, "missing_lines": [366, 368, 369, 371, 372, 373, 386, 388, 389, 391, 392, 394, 395, 396], "excluded_lines": [335, 346, 357, 376], "executed_branches": [[353, 354]], "missing_branches": [[353, -345], [368, 369], [368, 371], [388, 389], [388, 391], [391, 392], [391, 394]]}, "ContainerAware": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 9, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 9, "excluded_lines": 2, "num_branches": 8, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 8}, "missing_lines": [423, 424, 440, 441, 443, 444, 445, 446, 448], "excluded_lines": [413, 430], "executed_branches": [], "missing_branches": [[423, -411], [423, 424], [440, 441], [440, 443], [443, 444], [443, 445], [445, 446], [445, 448]]}, "": {"executed_lines": [1, 2, 3, 6, 10, 11, 16, 24, 25, 29, 30, 35, 44, 59, 60, 65, 72, 74, 88, 89, 95, 99, 100, 104, 105, 110, 111, 118, 123, 124, 128, 129, 133, 134, 138, 139, 143, 148, 149, 156, 160, 161, 165, 166, 171, 172, 182, 186, 286, 287, 293, 297, 307, 320, 321, 330, 334, 345, 356, 375, 399, 400, 411, 412, 428, 429], "summary": {"covered_lines": 56, "num_statements": 57, "percent_covered": 96.61016949152543, "percent_covered_display": "97", "missing_lines": 1, "excluded_lines": 10, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [7], "excluded_lines": [11, 30, 60, 89, 111, 149, 172, 287, 321, 400], "executed_branches": [[6, 10]], "missing_branches": [[6, 7]]}}}, "pyWebLayout/core/callback_registry.py": {"executed_lines": [1, 11, 12, 15, 16, 29, 31, 32, 33, 34, 36, 59, 60, 63, 64, 65, 66, 69, 71, 72, 75, 76, 77, 78, 80, 95, 97, 112, 114, 126, 128, 140, 142, 159, 160, 161, 162, 163, 165, 185, 186, 187, 188, 190, 200, 201, 203, 206, 207, 208, 209, 213, 216, 218, 219, 220, 221, 223, 230, 232, 242, 244, 255, 257, 258, 259, 260, 261, 262, 267, 269, 271, 273, 275, 277, 278], "summary": {"covered_lines": 71, "num_statements": 75, "percent_covered": 92.47311827956989, "percent_covered_display": "92", "missing_lines": 4, "excluded_lines": 21, "num_branches": 18, "num_partial_branches": 3, "covered_branches": 15, "missing_branches": 3}, "missing_lines": [210, 211, 214, 265], "excluded_lines": [1, 16, 30, 37, 81, 98, 115, 129, 143, 166, 191, 217, 224, 233, 245, 268, 272, 275, 276, 277, 278], "executed_branches": [[64, 65], [64, 66], [69, 71], [69, 75], [160, 161], [160, 163], [186, 187], [186, 188], [201, 203], [207, 208], [257, 258], [257, 259], [259, 260], [259, 261], [261, 262]], "missing_branches": [[201, 214], [207, 213], [261, 265]], "functions": {"CallbackRegistry.__init__": {"executed_lines": [31, 32, 33, 34], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [30], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.register": {"executed_lines": [59, 60, 63, 64, 65, 66, 69, 71, 72, 75, 76, 77, 78], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [37], "executed_branches": [[64, 65], [64, 66], [69, 71], [69, 75]], "missing_branches": []}, "CallbackRegistry.get_by_id": {"executed_lines": [95], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [81], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.get_by_type": {"executed_lines": [112], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [98], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.get_all_ids": {"executed_lines": [126], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [115], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.get_all_types": {"executed_lines": [140], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [129], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.set_callback": {"executed_lines": [159, 160, 161, 162, 163], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [143], "executed_branches": [[160, 161], [160, 163]], "missing_branches": []}, "CallbackRegistry.set_callbacks_by_type": {"executed_lines": [185, 186, 187, 188], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [166], "executed_branches": [[186, 187], [186, 188]], "missing_branches": []}, "CallbackRegistry.unregister": {"executed_lines": [200, 201, 203, 206, 207, 208, 209, 213], "summary": {"covered_lines": 8, "num_statements": 11, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 3, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [210, 211, 214], "excluded_lines": [191], "executed_branches": [[201, 203], [207, 208]], "missing_branches": [[201, 214], [207, 213]]}, "CallbackRegistry.clear": {"executed_lines": [218, 219, 220, 221], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [217], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.count": {"executed_lines": [230], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [224], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.count_by_type": {"executed_lines": [242], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [233], "executed_branches": [], "missing_branches": []}, "CallbackRegistry._get_type_name": {"executed_lines": [255, 257, 258, 259, 260, 261, 262], "summary": {"covered_lines": 7, "num_statements": 8, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [265], "excluded_lines": [245], "executed_branches": [[257, 258], [257, 259], [259, 260], [259, 261], [261, 262]], "missing_branches": [[261, 265]]}, "CallbackRegistry.__len__": {"executed_lines": [269], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [268], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.__contains__": {"executed_lines": [273], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [272], "executed_branches": [], "missing_branches": []}, "CallbackRegistry.__repr__": {"executed_lines": [277, 278], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [276, 277, 278], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 11, 12, 15, 16, 29, 36, 80, 97, 114, 128, 142, 165, 190, 216, 223, 232, 244, 267, 271, 275], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 16, 275], "executed_branches": [], "missing_branches": []}}, "classes": {"CallbackRegistry": {"executed_lines": [31, 32, 33, 34, 59, 60, 63, 64, 65, 66, 69, 71, 72, 75, 76, 77, 78, 95, 112, 126, 140, 159, 160, 161, 162, 163, 185, 186, 187, 188, 200, 201, 203, 206, 207, 208, 209, 213, 218, 219, 220, 221, 230, 242, 255, 257, 258, 259, 260, 261, 262, 269, 273, 277, 278], "summary": {"covered_lines": 53, "num_statements": 57, "percent_covered": 90.66666666666667, "percent_covered_display": "91", "missing_lines": 4, "excluded_lines": 18, "num_branches": 18, "num_partial_branches": 3, "covered_branches": 15, "missing_branches": 3}, "missing_lines": [210, 211, 214, 265], "excluded_lines": [30, 37, 81, 98, 115, 129, 143, 166, 191, 217, 224, 233, 245, 268, 272, 276, 277, 278], "executed_branches": [[64, 65], [64, 66], [69, 71], [69, 75], [160, 161], [160, 163], [186, 187], [186, 188], [201, 203], [207, 208], [257, 258], [257, 259], [259, 260], [259, 261], [261, 262]], "missing_branches": [[201, 214], [207, 213], [261, 265]]}, "": {"executed_lines": [1, 11, 12, 15, 16, 29, 36, 80, 97, 114, 128, 142, 165, 190, 216, 223, 232, 244, 267, 271, 275], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 16, 275], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/core/highlight.py": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 27, 28, 29, 36, 39, 40, 43, 44, 45, 48, 49, 50, 52, 54, 55, 57, 59, 71, 72, 74, 87, 88, 94, 102, 103, 104, 107, 110, 112, 119, 120, 122, 132, 133, 134, 135, 136, 138, 140, 142, 144, 146, 148, 149, 151, 162, 163, 165, 167, 168, 170, 171, 173, 175, 177, 179, 181, 182, 183, 188, 189, 193, 195, 196, 197, 198, 200, 201, 203, 212, 230, 231, 234, 235, 236, 238, 239, 241], "summary": {"covered_lines": 90, "num_statements": 95, "percent_covered": 95.41284403669725, "percent_covered_display": "95", "missing_lines": 5, "excluded_lines": 18, "num_branches": 14, "num_partial_branches": 0, "covered_branches": 14, "missing_branches": 0}, "missing_lines": [190, 191, 207, 208, 209], "excluded_lines": [1, 17, 29, 53, 58, 73, 88, 95, 113, 123, 139, 143, 147, 153, 176, 180, 194, 218], "executed_branches": [[54, -52], [54, 55], [132, 133], [132, 136], [165, 167], [165, 173], [167, 165], [167, 168], [168, 167], [168, 170], [197, 198], [197, 200], [234, 235], [234, 238]], "missing_branches": [], "functions": {"Highlight.__post_init__": {"executed_lines": [54, 55], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [53], "executed_branches": [[54, -52], [54, 55]], "missing_branches": []}, "Highlight.to_dict": {"executed_lines": [59], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [58], "executed_branches": [], "missing_branches": []}, "Highlight.from_dict": {"executed_lines": [74], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [73], "executed_branches": [], "missing_branches": []}, "HighlightManager.__init__": {"executed_lines": [102, 103, 104, 107, 110], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [95], "executed_branches": [], "missing_branches": []}, "HighlightManager.add_highlight": {"executed_lines": [119, 120], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [113], "executed_branches": [], "missing_branches": []}, "HighlightManager.remove_highlight": {"executed_lines": [132, 133, 134, 135, 136], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [123], "executed_branches": [[132, 133], [132, 136]], "missing_branches": []}, "HighlightManager.get_highlight": {"executed_lines": [140], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [139], "executed_branches": [], "missing_branches": []}, "HighlightManager.list_highlights": {"executed_lines": [144], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [143], "executed_branches": [], "missing_branches": []}, "HighlightManager.clear_all": {"executed_lines": [148, 149], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [147], "executed_branches": [], "missing_branches": []}, "HighlightManager.get_highlights_for_page": {"executed_lines": [162, 163, 165, 167, 168, 170, 171, 173], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [153], "executed_branches": [[165, 167], [165, 173], [167, 165], [167, 168], [168, 167], [168, 170]], "missing_branches": []}, "HighlightManager._get_filepath": {"executed_lines": [177], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [176], "executed_branches": [], "missing_branches": []}, "HighlightManager._save_highlights": {"executed_lines": [181, 182, 183, 188, 189], "summary": {"covered_lines": 5, "num_statements": 7, "percent_covered": 71.42857142857143, "percent_covered_display": "71", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [190, 191], "excluded_lines": [180], "executed_branches": [], "missing_branches": []}, "HighlightManager._load_highlights": {"executed_lines": [195, 196, 197, 198, 200, 201, 203], "summary": {"covered_lines": 7, "num_statements": 10, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 3, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [207, 208, 209], "excluded_lines": [194], "executed_branches": [[197, 198], [197, 200]], "missing_branches": []}, "create_highlight_from_query_result": {"executed_lines": [230, 231, 234, 235, 236, 238, 239, 241], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [218], "executed_branches": [[234, 235], [234, 238]], "missing_branches": []}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 27, 28, 29, 36, 39, 40, 43, 44, 45, 48, 49, 50, 52, 57, 71, 72, 87, 88, 94, 112, 122, 138, 142, 146, 151, 175, 179, 193, 212], "summary": {"covered_lines": 41, "num_statements": 41, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17, 29, 88], "executed_branches": [], "missing_branches": []}}, "classes": {"HighlightColor": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Highlight": {"executed_lines": [54, 55, 59, 74], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [53, 58, 73], "executed_branches": [[54, -52], [54, 55]], "missing_branches": []}, "HighlightManager": {"executed_lines": [102, 103, 104, 107, 110, 119, 120, 132, 133, 134, 135, 136, 140, 144, 148, 149, 162, 163, 165, 167, 168, 170, 171, 173, 177, 181, 182, 183, 188, 189, 195, 196, 197, 198, 200, 201, 203], "summary": {"covered_lines": 37, "num_statements": 42, "percent_covered": 90.38461538461539, "percent_covered_display": "90", "missing_lines": 5, "excluded_lines": 10, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [190, 191, 207, 208, 209], "excluded_lines": [95, 113, 123, 139, 143, 147, 153, 176, 180, 194], "executed_branches": [[132, 133], [132, 136], [165, 167], [165, 173], [167, 165], [167, 168], [168, 167], [168, 170], [197, 198], [197, 200]], "missing_branches": []}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 27, 28, 29, 36, 39, 40, 43, 44, 45, 48, 49, 50, 52, 57, 71, 72, 87, 88, 94, 112, 122, 138, 142, 146, 151, 175, 179, 193, 212, 230, 231, 234, 235, 236, 238, 239, 241], "summary": {"covered_lines": 49, "num_statements": 49, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17, 29, 88, 218], "executed_branches": [[234, 235], [234, 238]], "missing_branches": []}}}, "pyWebLayout/core/query.py": {"executed_lines": [1, 9, 10, 11, 13, 17, 18, 19, 26, 27, 30, 33, 34, 35, 38, 39, 40, 43, 44, 46, 48, 59, 60, 61, 64, 65, 66, 68, 69, 71, 73, 74, 76, 78, 80], "summary": {"covered_lines": 32, "num_statements": 33, "percent_covered": 94.28571428571429, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 7, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [14], "excluded_lines": [1, 19, 47, 61, 70, 75, 79], "executed_branches": [[13, 17]], "missing_branches": [[13, 14]], "functions": {"QueryResult.to_dict": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [47], "executed_branches": [], "missing_branches": []}, "SelectionRange.text": {"executed_lines": [71], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [70], "executed_branches": [], "missing_branches": []}, "SelectionRange.bounds_list": {"executed_lines": [76], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [75], "executed_branches": [], "missing_branches": []}, "SelectionRange.to_dict": {"executed_lines": [80], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [79], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 9, 10, 11, 13, 17, 18, 19, 26, 27, 30, 33, 34, 35, 38, 39, 40, 43, 44, 46, 59, 60, 61, 64, 65, 66, 68, 69, 73, 74, 78], "summary": {"covered_lines": 28, "num_statements": 29, "percent_covered": 93.54838709677419, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 3, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [14], "excluded_lines": [1, 19, 61], "executed_branches": [[13, 17]], "missing_branches": [[13, 14]]}}, "classes": {"QueryResult": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [47], "executed_branches": [], "missing_branches": []}, "SelectionRange": {"executed_lines": [71, 76, 80], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [70, 75, 79], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 9, 10, 11, 13, 17, 18, 19, 26, 27, 30, 33, 34, 35, 38, 39, 40, 43, 44, 46, 59, 60, 61, 64, 65, 66, 68, 69, 73, 74, 78], "summary": {"covered_lines": 28, "num_statements": 29, "percent_covered": 93.54838709677419, "percent_covered_display": "94", "missing_lines": 1, "excluded_lines": 3, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [14], "excluded_lines": [1, 19, 61], "executed_branches": [[13, 17]], "missing_branches": [[13, 14]]}}}, "pyWebLayout/io/__init__.py": {"executed_lines": [1], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/io/readers/__init__.py": {"executed_lines": [1, 8, 11], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 8, 11], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1, 8, 11], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/io/readers/epub_reader.py": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 22, 31, 55, 56, 63, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 86, 93, 95, 96, 97, 98, 99, 102, 105, 107, 111, 112, 113, 115, 117, 118, 121, 122, 123, 124, 127, 129, 130, 131, 133, 144, 147, 148, 149, 150, 151, 152, 153, 154, 156, 160, 161, 164, 167, 170, 172, 180, 181, 185, 186, 188, 189, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 211, 214, 215, 216, 218, 222, 230, 231, 235, 236, 237, 238, 240, 242, 243, 245, 251, 259, 260, 264, 265, 266, 269, 271, 272, 273, 275, 277, 290, 298, 299, 302, 303, 307, 309, 317, 319, 320, 323, 324, 326, 329, 330, 333, 342, 345, 348, 350, 353, 354, 356, 357, 359, 360, 362, 363, 367, 368, 372, 373, 375, 376, 378, 379, 381, 383, 384, 447, 457, 458, 459, 461, 462, 464, 465, 467, 468, 469, 471, 472, 479, 480, 482, 494, 501, 502, 504, 507, 510, 512, 513, 514, 516, 517, 518, 521, 524, 528, 529, 530, 533, 534, 535, 538, 541, 544, 545, 546, 549, 550, 553, 555, 556, 559, 562, 565, 566, 570, 592, 602, 603], "summary": {"covered_lines": 210, "num_statements": 286, "percent_covered": 70.23809523809524, "percent_covered_display": "70", "missing_lines": 76, "excluded_lines": 18, "num_branches": 134, "num_partial_branches": 27, "covered_branches": 85, "missing_branches": 49}, "missing_lines": [43, 44, 47, 50, 52, 136, 137, 138, 139, 142, 157, 182, 220, 232, 261, 282, 283, 284, 285, 286, 287, 288, 295, 304, 343, 387, 388, 391, 392, 393, 396, 398, 400, 401, 402, 407, 408, 411, 412, 415, 418, 419, 420, 421, 422, 426, 429, 430, 434, 437, 439, 440, 441, 442, 444, 445, 473, 477, 478, 485, 486, 487, 488, 489, 522, 531, 572, 573, 575, 576, 577, 578, 580, 581, 587, 589], "excluded_lines": [1, 32, 56, 65, 87, 116, 145, 173, 223, 252, 276, 310, 351, 382, 448, 495, 505, 593], "executed_branches": [[122, 123], [127, 129], [130, 131], [148, 149], [149, 150], [150, 149], [150, 151], [153, 154], [156, 160], [181, 185], [185, 186], [185, 214], [186, 185], [186, 188], [191, 192], [191, 193], [193, 194], [193, 195], [195, 196], [195, 197], [197, 198], [197, 199], [199, 200], [199, 203], [200, 201], [200, 202], [203, 204], [203, 205], [205, 206], [205, 207], [207, 208], [207, 211], [214, -172], [214, 215], [218, 214], [231, 235], [235, -222], [235, 236], [240, 242], [260, 264], [265, 266], [269, -251], [269, 271], [272, 273], [277, 290], [290, 298], [303, 307], [317, -309], [317, 319], [342, 345], [353, 354], [356, 357], [356, 359], [359, 360], [359, 362], [362, 363], [362, 367], [367, 368], [367, 372], [372, 373], [372, 375], [375, 376], [378, -350], [378, 379], [383, 384], [461, -447], [461, 462], [462, 461], [462, 464], [464, 465], [467, 468], [501, -494], [501, 502], [513, -512], [513, 514], [514, 516], [521, 513], [529, -504], [529, 530], [530, 533], [538, 541], [538, 544], [545, 546], [565, 566], [565, 570]], "missing_branches": [[43, 44], [43, 47], [122, 136], [127, 136], [130, 127], [136, 137], [136, 142], [137, 136], [137, 138], [148, 156], [149, 153], [153, 148], [156, 157], [181, 182], [218, 220], [231, 232], [240, 235], [260, 261], [265, 269], [272, 269], [277, 282], [282, 283], [282, 290], [283, 284], [283, 287], [284, 283], [284, 285], [287, 282], [287, 288], [290, 295], [303, 304], [342, 343], [353, 356], [375, 378], [383, 387], [391, 392], [391, 396], [418, 419], [418, 426], [444, -381], [444, 445], [464, 485], [467, 485], [485, 461], [485, 486], [514, 521], [521, 522], [530, 531], [545, 549]], "functions": {"default_eink_processor": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [43, 44, 47, 50, 52], "excluded_lines": [32], "executed_branches": [], "missing_branches": [[43, 44], [43, 47]]}, "EPUBReader.__init__": {"executed_lines": [75, 76, 77, 78, 79, 80, 81, 82, 83, 84], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [65], "executed_branches": [], "missing_branches": []}, "EPUBReader.read": {"executed_lines": [93, 95, 96, 97, 98, 99, 102, 105, 107, 111, 112, 113], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [87], "executed_branches": [], "missing_branches": []}, "EPUBReader._extract_epub": {"executed_lines": [117, 118, 121, 122, 123, 124, 127, 129, 130, 131, 133], "summary": {"covered_lines": 11, "num_statements": 16, "percent_covered": 53.84615384615385, "percent_covered_display": "54", "missing_lines": 5, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 3, "covered_branches": 3, "missing_branches": 7}, "missing_lines": [136, 137, 138, 139, 142], "excluded_lines": [116], "executed_branches": [[122, 123], [127, 129], [130, 131]], "missing_branches": [[122, 136], [127, 136], [130, 127], [136, 137], [136, 142], [137, 136], [137, 138]]}, "EPUBReader._parse_package_document": {"executed_lines": [147, 148, 149, 150, 151, 152, 153, 154, 156, 160, 161, 164, 167, 170], "summary": {"covered_lines": 14, "num_statements": 15, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 1, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 4, "covered_branches": 6, "missing_branches": 4}, "missing_lines": [157], "excluded_lines": [145], "executed_branches": [[148, 149], [149, 150], [150, 149], [150, 151], [153, 154], [156, 160]], "missing_branches": [[148, 156], [149, 153], [153, 148], [156, 157]]}, "EPUBReader._parse_metadata": {"executed_lines": [180, 181, 185, 186, 188, 189, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 211, 214, 215, 216, 218], "summary": {"covered_lines": 29, "num_statements": 31, "percent_covered": 93.22033898305085, "percent_covered_display": "93", "missing_lines": 2, "excluded_lines": 1, "num_branches": 28, "num_partial_branches": 2, "covered_branches": 26, "missing_branches": 2}, "missing_lines": [182, 220], "excluded_lines": [173], "executed_branches": [[181, 185], [185, 186], [185, 214], [186, 185], [186, 188], [191, 192], [191, 193], [193, 194], [193, 195], [195, 196], [195, 197], [197, 198], [197, 199], [199, 200], [199, 203], [200, 201], [200, 202], [203, 204], [203, 205], [205, 206], [205, 207], [207, 208], [207, 211], [214, -172], [214, 215], [218, 214]], "missing_branches": [[181, 182], [218, 220]]}, "EPUBReader._parse_manifest": {"executed_lines": [230, 231, 235, 236, 237, 238, 240, 242, 243, 245], "summary": {"covered_lines": 10, "num_statements": 11, "percent_covered": 82.3529411764706, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2}, "missing_lines": [232], "excluded_lines": [223], "executed_branches": [[231, 235], [235, -222], [235, 236], [240, 242]], "missing_branches": [[231, 232], [240, 235]]}, "EPUBReader._parse_spine": {"executed_lines": [259, 260, 264, 265, 266, 269, 271, 272, 273], "summary": {"covered_lines": 9, "num_statements": 10, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 3, "covered_branches": 5, "missing_branches": 3}, "missing_lines": [261], "excluded_lines": [252], "executed_branches": [[260, 264], [265, 266], [269, -251], [269, 271], [272, 273]], "missing_branches": [[260, 261], [265, 269], [272, 269]]}, "EPUBReader._parse_toc": {"executed_lines": [277, 290, 298, 299, 302, 303, 307], "summary": {"covered_lines": 7, "num_statements": 16, "percent_covered": 33.333333333333336, "percent_covered_display": "33", "missing_lines": 9, "excluded_lines": 1, "num_branches": 14, "num_partial_branches": 3, "covered_branches": 3, "missing_branches": 11}, "missing_lines": [282, 283, 284, 285, 286, 287, 288, 295, 304], "excluded_lines": [276], "executed_branches": [[277, 290], [290, 298], [303, 307]], "missing_branches": [[277, 282], [282, 283], [282, 290], [283, 284], [283, 287], [284, 283], [284, 285], [287, 282], [287, 288], [290, 295], [303, 304]]}, "EPUBReader._parse_nav_points": {"executed_lines": [317, 319, 320, 323, 324, 326, 329, 330, 333, 342, 345, 348], "summary": {"covered_lines": 12, "num_statements": 13, "percent_covered": 88.23529411764706, "percent_covered_display": "88", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [343], "excluded_lines": [310], "executed_branches": [[317, -309], [317, 319], [342, 345]], "missing_branches": [[342, 343]]}, "EPUBReader._create_book": {"executed_lines": [353, 354, 356, 357, 359, 360, 362, 363, 367, 368, 372, 373, 375, 376, 378, 379], "summary": {"covered_lines": 16, "num_statements": 16, "percent_covered": 93.75, "percent_covered_display": "94", "missing_lines": 0, "excluded_lines": 1, "num_branches": 16, "num_partial_branches": 2, "covered_branches": 14, "missing_branches": 2}, "missing_lines": [], "excluded_lines": [351], "executed_branches": [[353, 354], [356, 357], [356, 359], [359, 360], [359, 362], [362, 363], [362, 367], [367, 368], [367, 372], [372, 373], [372, 375], [375, 376], [378, -350], [378, 379]], "missing_branches": [[353, 356], [375, 378]]}, "EPUBReader._add_cover_chapter": {"executed_lines": [383, 384], "summary": {"covered_lines": 2, "num_statements": 33, "percent_covered": 7.317073170731708, "percent_covered_display": "7", "missing_lines": 31, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 7}, "missing_lines": [387, 388, 391, 392, 393, 396, 398, 400, 401, 402, 407, 408, 411, 412, 415, 418, 419, 420, 421, 422, 426, 429, 430, 434, 437, 439, 440, 441, 442, 444, 445], "excluded_lines": [382], "executed_branches": [[383, 384]], "missing_branches": [[383, 387], [391, 392], [391, 396], [418, 419], [418, 426], [444, -381], [444, 445]]}, "EPUBReader._process_chapter_images": {"executed_lines": [457, 458, 459, 461, 462, 464, 465, 467, 468, 469, 471, 472, 479, 480, 482], "summary": {"covered_lines": 15, "num_statements": 23, "percent_covered": 63.63636363636363, "percent_covered_display": "64", "missing_lines": 8, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 4}, "missing_lines": [473, 477, 478, 485, 486, 487, 488, 489], "excluded_lines": [448], "executed_branches": [[461, -447], [461, 462], [462, 461], [462, 464], [464, 465], [467, 468]], "missing_branches": [[464, 485], [467, 485], [485, 461], [485, 486]]}, "EPUBReader._process_content_images": {"executed_lines": [501, 502], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [495], "executed_branches": [[501, -494], [501, 502]], "missing_branches": []}, "EPUBReader._add_chapters": {"executed_lines": [507, 510, 512, 524, 528, 529, 530, 533, 534, 535, 538, 541, 544, 545, 546, 549, 550, 553, 555, 556, 559, 562, 565, 566, 570], "summary": {"covered_lines": 25, "num_statements": 36, "percent_covered": 71.73913043478261, "percent_covered_display": "72", "missing_lines": 11, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 2}, "missing_lines": [531, 572, 573, 575, 576, 577, 578, 580, 581, 587, 589], "excluded_lines": [505], "executed_branches": [[529, -504], [529, 530], [530, 533], [538, 541], [538, 544], [545, 546], [565, 566], [565, 570]], "missing_branches": [[530, 531], [545, 549]]}, "EPUBReader._add_chapters.add_to_toc_map": {"executed_lines": [513, 514, 516, 517, 518, 521], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 76.92307692307692, "percent_covered_display": "77", "missing_lines": 1, "excluded_lines": 0, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2}, "missing_lines": [522], "excluded_lines": [], "executed_branches": [[513, -512], [513, 514], [514, 516], [521, 513]], "missing_branches": [[514, 521], [521, 522]]}, "read_epub": {"executed_lines": [602, 603], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [593], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 22, 31, 55, 56, 63, 86, 115, 144, 172, 222, 251, 275, 309, 350, 381, 447, 494, 504, 592], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 56], "executed_branches": [], "missing_branches": []}}, "classes": {"EPUBReader": {"executed_lines": [75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 93, 95, 96, 97, 98, 99, 102, 105, 107, 111, 112, 113, 117, 118, 121, 122, 123, 124, 127, 129, 130, 131, 133, 147, 148, 149, 150, 151, 152, 153, 154, 156, 160, 161, 164, 167, 170, 180, 181, 185, 186, 188, 189, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 211, 214, 215, 216, 218, 230, 231, 235, 236, 237, 238, 240, 242, 243, 245, 259, 260, 264, 265, 266, 269, 271, 272, 273, 277, 290, 298, 299, 302, 303, 307, 317, 319, 320, 323, 324, 326, 329, 330, 333, 342, 345, 348, 353, 354, 356, 357, 359, 360, 362, 363, 367, 368, 372, 373, 375, 376, 378, 379, 383, 384, 457, 458, 459, 461, 462, 464, 465, 467, 468, 469, 471, 472, 479, 480, 482, 501, 502, 507, 510, 512, 513, 514, 516, 517, 518, 521, 524, 528, 529, 530, 533, 534, 535, 538, 541, 544, 545, 546, 549, 550, 553, 555, 556, 559, 562, 565, 566, 570], "summary": {"covered_lines": 180, "num_statements": 251, "percent_covered": 69.19060052219321, "percent_covered_display": "69", "missing_lines": 71, "excluded_lines": 14, "num_branches": 132, "num_partial_branches": 27, "covered_branches": 85, "missing_branches": 47}, "missing_lines": [136, 137, 138, 139, 142, 157, 182, 220, 232, 261, 282, 283, 284, 285, 286, 287, 288, 295, 304, 343, 387, 388, 391, 392, 393, 396, 398, 400, 401, 402, 407, 408, 411, 412, 415, 418, 419, 420, 421, 422, 426, 429, 430, 434, 437, 439, 440, 441, 442, 444, 445, 473, 477, 478, 485, 486, 487, 488, 489, 522, 531, 572, 573, 575, 576, 577, 578, 580, 581, 587, 589], "excluded_lines": [65, 87, 116, 145, 173, 223, 252, 276, 310, 351, 382, 448, 495, 505], "executed_branches": [[122, 123], [127, 129], [130, 131], [148, 149], [149, 150], [150, 149], [150, 151], [153, 154], [156, 160], [181, 185], [185, 186], [185, 214], [186, 185], [186, 188], [191, 192], [191, 193], [193, 194], [193, 195], [195, 196], [195, 197], [197, 198], [197, 199], [199, 200], [199, 203], [200, 201], [200, 202], [203, 204], [203, 205], [205, 206], [205, 207], [207, 208], [207, 211], [214, -172], [214, 215], [218, 214], [231, 235], [235, -222], [235, 236], [240, 242], [260, 264], [265, 266], [269, -251], [269, 271], [272, 273], [277, 290], [290, 298], [303, 307], [317, -309], [317, 319], [342, 345], [353, 354], [356, 357], [356, 359], [359, 360], [359, 362], [362, 363], [362, 367], [367, 368], [367, 372], [372, 373], [372, 375], [375, 376], [378, -350], [378, 379], [383, 384], [461, -447], [461, 462], [462, 461], [462, 464], [464, 465], [467, 468], [501, -494], [501, 502], [513, -512], [513, 514], [514, 516], [521, 513], [529, -504], [529, 530], [530, 533], [538, 541], [538, 544], [545, 546], [565, 566], [565, 570]], "missing_branches": [[122, 136], [127, 136], [130, 127], [136, 137], [136, 142], [137, 136], [137, 138], [148, 156], [149, 153], [153, 148], [156, 157], [181, 182], [218, 220], [231, 232], [240, 235], [260, 261], [265, 269], [272, 269], [277, 282], [282, 283], [282, 290], [283, 284], [283, 287], [284, 283], [284, 285], [287, 282], [287, 288], [290, 295], [303, 304], [342, 343], [353, 356], [375, 378], [383, 387], [391, 392], [391, 396], [418, 419], [418, 426], [444, -381], [444, 445], [464, 485], [467, 485], [485, 461], [485, 486], [514, 521], [521, 522], [530, 531], [545, 549]]}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 22, 31, 55, 56, 63, 86, 115, 144, 172, 222, 251, 275, 309, 350, 381, 447, 494, 504, 592, 602, 603], "summary": {"covered_lines": 30, "num_statements": 35, "percent_covered": 81.08108108108108, "percent_covered_display": "81", "missing_lines": 5, "excluded_lines": 4, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [43, 44, 47, 50, 52], "excluded_lines": [1, 32, 56, 593], "executed_branches": [], "missing_branches": [[43, 44], [43, 47]]}}}, "pyWebLayout/io/readers/html_extraction.py": {"executed_lines": [1, 9, 10, 11, 12, 28, 31, 32, 37, 38, 39, 40, 41, 42, 43, 44, 46, 48, 50, 54, 56, 58, 60, 62, 64, 66, 68, 70, 73, 89, 90, 91, 93, 95, 107, 118, 119, 122, 125, 126, 127, 132, 133, 136, 137, 138, 139, 140, 143, 145, 148, 149, 151, 154, 164, 165, 166, 167, 168, 169, 172, 191, 208, 209, 210, 211, 212, 213, 214, 215, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 230, 232, 233, 234, 235, 238, 244, 245, 246, 247, 251, 252, 253, 254, 255, 256, 258, 259, 260, 261, 262, 264, 265, 267, 269, 270, 277, 278, 279, 280, 281, 282, 283, 284, 289, 292, 295, 296, 297, 303, 314, 315, 330, 342, 355, 356, 357, 361, 364, 375, 376, 378, 380, 381, 383, 384, 385, 386, 387, 388, 389, 391, 392, 393, 395, 396, 397, 398, 399, 400, 402, 405, 408, 409, 411, 412, 413, 421, 424, 425, 426, 429, 449, 450, 451, 455, 456, 457, 462, 466, 469, 482, 483, 484, 492, 502, 504, 506, 510, 512, 514, 515, 516, 517, 518, 521, 522, 523, 527, 528, 529, 531, 532, 533, 534, 541, 542, 543, 544, 545, 546, 548, 551, 552, 553, 554, 555, 558, 560, 561, 562, 563, 564, 565, 566, 567, 569, 570, 573, 575, 584, 585, 586, 587, 588, 589, 592, 594, 595, 596, 597, 598, 599, 600, 604, 605, 608, 610, 611, 614, 615, 616, 618, 621, 624, 628, 631, 633, 634, 635, 636, 637, 638, 639, 640, 643, 645, 646, 647, 648, 649, 650, 651, 652, 655, 657, 659, 660, 661, 662, 663, 664, 668, 669, 671, 672, 673, 674, 675, 676, 677, 678, 680, 683, 685, 686, 687, 690, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 704, 705, 706, 707, 708, 710, 713, 715, 716, 717, 718, 719, 720, 721, 722, 725, 727, 728, 729, 732, 733, 734, 735, 736, 737, 738, 739, 741, 742, 744, 745, 746, 747, 748, 749, 750, 751, 753, 756, 758, 759, 760, 763, 764, 765, 766, 767, 768, 769, 770, 773, 774, 775, 776, 777, 778, 779, 780, 781, 783, 786, 788, 791, 794, 797, 799, 800, 802, 803, 806, 808, 810, 813, 814, 815, 816, 817, 818, 822, 825, 827, 830, 832, 836, 899, 914, 915, 917, 920, 922, 923, 924, 925, 926, 927, 928, 930, 932], "summary": {"covered_lines": 394, "num_statements": 424, "percent_covered": 88.34808259587021, "percent_covered_display": "88", "missing_lines": 30, "excluded_lines": 34, "num_branches": 254, "num_partial_branches": 35, "covered_branches": 205, "missing_branches": 49}, "missing_lines": [236, 237, 239, 240, 241, 242, 248, 249, 263, 285, 286, 300, 317, 358, 458, 459, 460, 461, 463, 464, 538, 601, 602, 625, 665, 666, 688, 772, 819, 820], "excluded_lines": [1, 32, 47, 53, 57, 61, 65, 69, 77, 108, 155, 177, 345, 365, 472, 493, 559, 574, 593, 609, 622, 632, 644, 656, 684, 714, 726, 757, 787, 792, 798, 826, 831, 902], "executed_branches": [[89, 90], [89, 95], [90, 91], [90, 93], [126, 127], [126, 133], [137, 138], [137, 140], [165, 166], [165, 169], [166, 165], [166, 167], [218, 219], [218, 230], [220, 221], [220, 222], [222, 223], [222, 224], [224, 225], [224, 226], [226, 227], [226, 230], [230, 232], [230, 244], [233, 234], [233, 238], [238, 244], [244, 245], [244, 251], [246, 247], [251, 252], [251, 258], [253, 254], [253, 255], [255, 256], [258, 259], [258, 267], [260, 261], [260, 262], [262, 264], [264, 265], [267, 269], [267, 289], [277, 278], [277, 279], [279, 280], [279, 289], [289, 292], [289, 315], [296, 297], [315, 330], [355, 356], [355, 361], [357, 361], [380, 381], [380, 466], [381, 383], [381, 389], [384, 380], [384, 385], [386, 380], [386, 387], [387, 388], [389, 391], [391, 392], [391, 429], [393, 395], [393, 424], [395, 396], [395, 397], [397, 398], [397, 399], [399, 400], [399, 402], [411, 380], [411, 412], [412, 413], [429, 449], [429, 455], [457, 462], [462, 380], [504, 506], [504, 551], [512, 514], [512, 527], [514, 515], [514, 521], [517, 518], [521, 522], [521, 523], [527, 528], [527, 541], [528, 527], [528, 529], [529, 531], [533, 534], [543, 544], [544, 545], [544, 546], [553, 554], [553, 555], [561, 562], [561, 570], [562, 561], [562, 563], [565, 561], [565, 566], [566, 567], [566, 569], [587, 588], [587, 589], [595, 596], [595, 605], [596, 595], [596, 597], [599, 600], [600, 604], [615, 616], [615, 618], [624, 628], [634, 635], [634, 640], [635, 634], [635, 636], [638, 639], [646, 647], [646, 652], [647, 646], [647, 648], [650, 651], [659, 660], [659, 680], [660, 661], [660, 669], [663, 659], [663, 664], [664, 668], [669, 671], [672, 659], [672, 673], [675, 676], [675, 678], [676, 677], [687, 690], [693, 694], [693, 710], [694, 693], [694, 695], [695, 696], [695, 700], [698, 699], [700, 701], [704, 693], [704, 705], [707, 708], [716, 717], [716, 722], [717, 716], [717, 718], [720, 721], [732, 733], [732, 753], [733, 734], [733, 742], [736, 732], [736, 737], [737, 738], [737, 741], [738, 732], [738, 739], [742, 744], [745, 732], [745, 746], [748, 749], [748, 751], [749, 750], [763, 764], [763, 783], [764, 765], [764, 773], [767, 763], [767, 768], [768, 769], [769, 763], [769, 770], [773, 774], [775, 763], [775, 776], [778, 779], [778, 781], [779, 780], [806, 808], [806, 813], [815, 816], [815, 817], [817, 818], [817, 822], [922, 923], [922, 932], [923, 922], [923, 924], [926, 922], [926, 927], [927, 928], [927, 930]], "missing_branches": [[238, 239], [246, 248], [248, 249], [248, 251], [255, 258], [262, 263], [264, 267], [296, 300], [315, 317], [357, 358], [387, 386], [389, 380], [412, 411], [457, 458], [458, 380], [458, 459], [459, 458], [459, 460], [460, 458], [460, 461], [462, 463], [463, 380], [463, 464], [517, 514], [529, 538], [533, 527], [543, 548], [599, 595], [600, 601], [601, 595], [601, 602], [624, 625], [638, 634], [650, 646], [664, 665], [665, 659], [665, 666], [669, 659], [676, 675], [687, 688], [698, 693], [700, 693], [707, 704], [720, 716], [742, 732], [749, 748], [768, 772], [773, 763], [779, 778]], "functions": {"StyleContext.with_font": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [47], "executed_branches": [], "missing_branches": []}, "StyleContext.with_background": {"executed_lines": [54], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [53], "executed_branches": [], "missing_branches": []}, "StyleContext.with_css_classes": {"executed_lines": [58], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [57], "executed_branches": [], "missing_branches": []}, "StyleContext.with_css_styles": {"executed_lines": [62], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [61], "executed_branches": [], "missing_branches": []}, "StyleContext.with_attributes": {"executed_lines": [66], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [65], "executed_branches": [], "missing_branches": []}, "StyleContext.push_element": {"executed_lines": [70], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [69], "executed_branches": [], "missing_branches": []}, "create_base_context": {"executed_lines": [89, 90, 91, 93, 95], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [77], "executed_branches": [[89, 90], [89, 95], [90, 91], [90, 93]], "missing_branches": []}, "apply_element_styling": {"executed_lines": [118, 119, 122, 125, 126, 127, 132, 133, 136, 137, 138, 139, 140, 143, 145, 148, 149, 151], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [108], "executed_branches": [[126, 127], [126, 133], [137, 138], [137, 140]], "missing_branches": []}, "parse_inline_styles": {"executed_lines": [164, 165, 166, 167, 168, 169], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [155], "executed_branches": [[165, 166], [165, 169], [166, 165], [166, 167]], "missing_branches": []}, "apply_element_font_styles": {"executed_lines": [191, 208, 209, 210, 211, 212, 213, 214, 215, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 230, 232, 233, 234, 235, 238, 244, 245, 246, 247, 251, 252, 253, 254, 255, 256, 258, 259, 260, 261, 262, 264, 265, 267, 269, 270, 277, 278, 279, 280, 281, 282, 283, 284, 289, 292, 295, 296, 297, 303, 314, 315, 330], "summary": {"covered_lines": 62, "num_statements": 75, "percent_covered": 82.11382113821138, "percent_covered_display": "82", "missing_lines": 13, "excluded_lines": 1, "num_branches": 48, "num_partial_branches": 7, "covered_branches": 39, "missing_branches": 9}, "missing_lines": [236, 237, 239, 240, 241, 242, 248, 249, 263, 285, 286, 300, 317], "excluded_lines": [177], "executed_branches": [[218, 219], [218, 230], [220, 221], [220, 222], [222, 223], [222, 224], [224, 225], [224, 226], [226, 227], [226, 230], [230, 232], [230, 244], [233, 234], [233, 238], [238, 244], [244, 245], [244, 251], [246, 247], [251, 252], [251, 258], [253, 254], [253, 255], [255, 256], [258, 259], [258, 267], [260, 261], [260, 262], [262, 264], [264, 265], [267, 269], [267, 289], [277, 278], [277, 279], [279, 280], [279, 289], [289, 292], [289, 315], [296, 297], [315, 330]], "missing_branches": [[238, 239], [246, 248], [248, 249], [248, 251], [255, 258], [262, 263], [264, 267], [296, 300], [315, 317]]}, "apply_background_styles": {"executed_lines": [355, 356, 357, 361], "summary": {"covered_lines": 4, "num_statements": 5, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [358], "excluded_lines": [345], "executed_branches": [[355, 356], [355, 361], [357, 361]], "missing_branches": [[357, 358]]}, "extract_text_content": {"executed_lines": [375, 376, 378, 380, 381, 383, 384, 385, 386, 387, 388, 389, 391, 392, 393, 395, 396, 397, 398, 399, 400, 402, 405, 408, 409, 411, 412, 413, 421, 424, 425, 426, 429, 449, 450, 451, 455, 456, 457, 462, 466], "summary": {"covered_lines": 41, "num_statements": 47, "percent_covered": 78.16091954022988, "percent_covered_display": "78", "missing_lines": 6, "excluded_lines": 1, "num_branches": 40, "num_partial_branches": 5, "covered_branches": 27, "missing_branches": 13}, "missing_lines": [458, 459, 460, 461, 463, 464], "excluded_lines": [365], "executed_branches": [[380, 381], [380, 466], [381, 383], [381, 389], [384, 380], [384, 385], [386, 380], [386, 387], [387, 388], [389, 391], [391, 392], [391, 429], [393, 395], [393, 424], [395, 396], [395, 397], [397, 398], [397, 399], [399, 400], [399, 402], [411, 380], [411, 412], [412, 413], [429, 449], [429, 455], [457, 462], [462, 380]], "missing_branches": [[387, 386], [389, 380], [412, 411], [457, 458], [458, 380], [458, 459], [459, 458], [459, 460], [460, 458], [460, 461], [462, 463], [463, 380], [463, 464]]}, "process_element": {"executed_lines": [482, 483, 484], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [472], "executed_branches": [], "missing_branches": []}, "paragraph_handler": {"executed_lines": [502, 504, 506, 510, 512, 514, 515, 516, 517, 518, 521, 522, 523, 527, 528, 529, 531, 532, 533, 534, 541, 542, 543, 544, 545, 546, 548, 551, 552, 553, 554, 555], "summary": {"covered_lines": 32, "num_statements": 33, "percent_covered": 91.2280701754386, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 1, "num_branches": 24, "num_partial_branches": 4, "covered_branches": 20, "missing_branches": 4}, "missing_lines": [538], "excluded_lines": [493], "executed_branches": [[504, 506], [504, 551], [512, 514], [512, 527], [514, 515], [514, 521], [517, 518], [521, 522], [521, 523], [527, 528], [527, 541], [528, 527], [528, 529], [529, 531], [533, 534], [543, 544], [544, 545], [544, 546], [553, 554], [553, 555]], "missing_branches": [[517, 514], [529, 538], [533, 527], [543, 548]]}, "div_handler": {"executed_lines": [560, 561, 562, 563, 564, 565, 566, 567, 569, 570], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [559], "executed_branches": [[561, 562], [561, 570], [562, 561], [562, 563], [565, 561], [565, 566], [566, 567], [566, 569]], "missing_branches": []}, "heading_handler": {"executed_lines": [575, 584, 585, 586, 587, 588, 589], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [574], "executed_branches": [[587, 588], [587, 589]], "missing_branches": []}, "blockquote_handler": {"executed_lines": [594, 595, 596, 597, 598, 599, 600, 604, 605], "summary": {"covered_lines": 9, "num_statements": 11, "percent_covered": 71.42857142857143, "percent_covered_display": "71", "missing_lines": 2, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 4}, "missing_lines": [601, 602], "excluded_lines": [593], "executed_branches": [[595, 596], [595, 605], [596, 595], [596, 597], [599, 600], [600, 604]], "missing_branches": [[599, 595], [600, 601], [601, 595], [601, 602]]}, "preformatted_handler": {"executed_lines": [610, 611, 614, 615, 616, 618], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [609], "executed_branches": [[615, 616], [615, 618]], "missing_branches": []}, "code_handler": {"executed_lines": [624, 628], "summary": {"covered_lines": 2, "num_statements": 3, "percent_covered": 60.0, "percent_covered_display": "60", "missing_lines": 1, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [625], "excluded_lines": [622], "executed_branches": [[624, 628]], "missing_branches": [[624, 625]]}, "unordered_list_handler": {"executed_lines": [633, 634, 635, 636, 637, 638, 639, 640], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [632], "executed_branches": [[634, 635], [634, 640], [635, 634], [635, 636], [638, 639]], "missing_branches": [[638, 634]]}, "ordered_list_handler": {"executed_lines": [645, 646, 647, 648, 649, 650, 651, 652], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [644], "executed_branches": [[646, 647], [646, 652], [647, 646], [647, 648], [650, 651]], "missing_branches": [[650, 646]]}, "list_item_handler": {"executed_lines": [657, 659, 660, 661, 662, 663, 664, 668, 669, 671, 672, 673, 674, 675, 676, 677, 678, 680], "summary": {"covered_lines": 18, "num_statements": 20, "percent_covered": 81.57894736842105, "percent_covered_display": "82", "missing_lines": 2, "excluded_lines": 1, "num_branches": 18, "num_partial_branches": 3, "covered_branches": 13, "missing_branches": 5}, "missing_lines": [665, 666], "excluded_lines": [656], "executed_branches": [[659, 660], [659, 680], [660, 661], [660, 669], [663, 659], [663, 664], [664, 668], [669, 671], [672, 659], [672, 673], [675, 676], [675, 678], [676, 677]], "missing_branches": [[664, 665], [665, 659], [665, 666], [669, 659], [676, 675]]}, "table_handler": {"executed_lines": [685, 686, 687, 690, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 704, 705, 706, 707, 708, 710], "summary": {"covered_lines": 20, "num_statements": 21, "percent_covered": 86.48648648648648, "percent_covered_display": "86", "missing_lines": 1, "excluded_lines": 1, "num_branches": 16, "num_partial_branches": 4, "covered_branches": 12, "missing_branches": 4}, "missing_lines": [688], "excluded_lines": [684], "executed_branches": [[687, 690], [693, 694], [693, 710], [694, 693], [694, 695], [695, 696], [695, 700], [698, 699], [700, 701], [704, 693], [704, 705], [707, 708]], "missing_branches": [[687, 688], [698, 693], [700, 693], [707, 704]]}, "table_row_handler": {"executed_lines": [715, 716, 717, 718, 719, 720, 721, 722], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [714], "executed_branches": [[716, 717], [716, 722], [717, 716], [717, 718], [720, 721]], "missing_branches": [[720, 716]]}, "table_cell_handler": {"executed_lines": [727, 728, 729, 732, 733, 734, 735, 736, 737, 738, 739, 741, 742, 744, 745, 746, 747, 748, 749, 750, 751, 753], "summary": {"covered_lines": 22, "num_statements": 22, "percent_covered": 95.0, "percent_covered_display": "95", "missing_lines": 0, "excluded_lines": 1, "num_branches": 18, "num_partial_branches": 2, "covered_branches": 16, "missing_branches": 2}, "missing_lines": [], "excluded_lines": [726], "executed_branches": [[732, 733], [732, 753], [733, 734], [733, 742], [736, 732], [736, 737], [737, 738], [737, 741], [738, 732], [738, 739], [742, 744], [745, 732], [745, 746], [748, 749], [748, 751], [749, 750]], "missing_branches": [[742, 732], [749, 748]]}, "table_header_cell_handler": {"executed_lines": [758, 759, 760, 763, 764, 765, 766, 767, 768, 769, 770, 773, 774, 775, 776, 777, 778, 779, 780, 781, 783], "summary": {"covered_lines": 21, "num_statements": 22, "percent_covered": 90.0, "percent_covered_display": "90", "missing_lines": 1, "excluded_lines": 1, "num_branches": 18, "num_partial_branches": 3, "covered_branches": 15, "missing_branches": 3}, "missing_lines": [772], "excluded_lines": [757], "executed_branches": [[763, 764], [763, 783], [764, 765], [764, 773], [767, 763], [767, 768], [768, 769], [769, 763], [769, 770], [773, 774], [775, 763], [775, 776], [778, 779], [778, 781], [779, 780]], "missing_branches": [[768, 772], [773, 763], [779, 778]]}, "horizontal_rule_handler": {"executed_lines": [788], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [787], "executed_branches": [], "missing_branches": []}, "line_break_handler": {"executed_lines": [794], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [792], "executed_branches": [], "missing_branches": []}, "image_handler": {"executed_lines": [799, 800, 802, 803, 806, 808, 810, 813, 814, 815, 816, 817, 818, 822], "summary": {"covered_lines": 14, "num_statements": 16, "percent_covered": 90.9090909090909, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [819, 820], "excluded_lines": [798], "executed_branches": [[806, 808], [806, 813], [815, 816], [815, 817], [817, 818], [817, 822]], "missing_branches": []}, "ignore_handler": {"executed_lines": [827], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [826], "executed_branches": [], "missing_branches": []}, "generic_handler": {"executed_lines": [832], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [831], "executed_branches": [], "missing_branches": []}, "parse_html_string": {"executed_lines": [914, 915, 917, 920, 922, 923, 924, 925, 926, 927, 928, 930, 932], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [902], "executed_branches": [[922, 923], [922, 932], [923, 922], [923, 924], [926, 922], [926, 927], [927, 928], [927, 930]], "missing_branches": []}, "": {"executed_lines": [1, 9, 10, 11, 12, 28, 31, 32, 37, 38, 39, 40, 41, 42, 43, 44, 46, 50, 56, 60, 64, 68, 73, 107, 154, 172, 342, 364, 469, 492, 558, 573, 592, 608, 621, 631, 643, 655, 683, 713, 725, 756, 786, 791, 797, 825, 830, 836, 899], "summary": {"covered_lines": 47, "num_statements": 47, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 32], "executed_branches": [], "missing_branches": []}}, "classes": {"StyleContext": {"executed_lines": [48, 54, 58, 62, 66, 70], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 6, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [47, 53, 57, 61, 65, 69], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 9, 10, 11, 12, 28, 31, 32, 37, 38, 39, 40, 41, 42, 43, 44, 46, 50, 56, 60, 64, 68, 73, 89, 90, 91, 93, 95, 107, 118, 119, 122, 125, 126, 127, 132, 133, 136, 137, 138, 139, 140, 143, 145, 148, 149, 151, 154, 164, 165, 166, 167, 168, 169, 172, 191, 208, 209, 210, 211, 212, 213, 214, 215, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 230, 232, 233, 234, 235, 238, 244, 245, 246, 247, 251, 252, 253, 254, 255, 256, 258, 259, 260, 261, 262, 264, 265, 267, 269, 270, 277, 278, 279, 280, 281, 282, 283, 284, 289, 292, 295, 296, 297, 303, 314, 315, 330, 342, 355, 356, 357, 361, 364, 375, 376, 378, 380, 381, 383, 384, 385, 386, 387, 388, 389, 391, 392, 393, 395, 396, 397, 398, 399, 400, 402, 405, 408, 409, 411, 412, 413, 421, 424, 425, 426, 429, 449, 450, 451, 455, 456, 457, 462, 466, 469, 482, 483, 484, 492, 502, 504, 506, 510, 512, 514, 515, 516, 517, 518, 521, 522, 523, 527, 528, 529, 531, 532, 533, 534, 541, 542, 543, 544, 545, 546, 548, 551, 552, 553, 554, 555, 558, 560, 561, 562, 563, 564, 565, 566, 567, 569, 570, 573, 575, 584, 585, 586, 587, 588, 589, 592, 594, 595, 596, 597, 598, 599, 600, 604, 605, 608, 610, 611, 614, 615, 616, 618, 621, 624, 628, 631, 633, 634, 635, 636, 637, 638, 639, 640, 643, 645, 646, 647, 648, 649, 650, 651, 652, 655, 657, 659, 660, 661, 662, 663, 664, 668, 669, 671, 672, 673, 674, 675, 676, 677, 678, 680, 683, 685, 686, 687, 690, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 704, 705, 706, 707, 708, 710, 713, 715, 716, 717, 718, 719, 720, 721, 722, 725, 727, 728, 729, 732, 733, 734, 735, 736, 737, 738, 739, 741, 742, 744, 745, 746, 747, 748, 749, 750, 751, 753, 756, 758, 759, 760, 763, 764, 765, 766, 767, 768, 769, 770, 773, 774, 775, 776, 777, 778, 779, 780, 781, 783, 786, 788, 791, 794, 797, 799, 800, 802, 803, 806, 808, 810, 813, 814, 815, 816, 817, 818, 822, 825, 827, 830, 832, 836, 899, 914, 915, 917, 920, 922, 923, 924, 925, 926, 927, 928, 930, 932], "summary": {"covered_lines": 388, "num_statements": 418, "percent_covered": 88.24404761904762, "percent_covered_display": "88", "missing_lines": 30, "excluded_lines": 28, "num_branches": 254, "num_partial_branches": 35, "covered_branches": 205, "missing_branches": 49}, "missing_lines": [236, 237, 239, 240, 241, 242, 248, 249, 263, 285, 286, 300, 317, 358, 458, 459, 460, 461, 463, 464, 538, 601, 602, 625, 665, 666, 688, 772, 819, 820], "excluded_lines": [1, 32, 77, 108, 155, 177, 345, 365, 472, 493, 559, 574, 593, 609, 622, 632, 644, 656, 684, 714, 726, 757, 787, 792, 798, 826, 831, 902], "executed_branches": [[89, 90], [89, 95], [90, 91], [90, 93], [126, 127], [126, 133], [137, 138], [137, 140], [165, 166], [165, 169], [166, 165], [166, 167], [218, 219], [218, 230], [220, 221], [220, 222], [222, 223], [222, 224], [224, 225], [224, 226], [226, 227], [226, 230], [230, 232], [230, 244], [233, 234], [233, 238], [238, 244], [244, 245], [244, 251], [246, 247], [251, 252], [251, 258], [253, 254], [253, 255], [255, 256], [258, 259], [258, 267], [260, 261], [260, 262], [262, 264], [264, 265], [267, 269], [267, 289], [277, 278], [277, 279], [279, 280], [279, 289], [289, 292], [289, 315], [296, 297], [315, 330], [355, 356], [355, 361], [357, 361], [380, 381], [380, 466], [381, 383], [381, 389], [384, 380], [384, 385], [386, 380], [386, 387], [387, 388], [389, 391], [391, 392], [391, 429], [393, 395], [393, 424], [395, 396], [395, 397], [397, 398], [397, 399], [399, 400], [399, 402], [411, 380], [411, 412], [412, 413], [429, 449], [429, 455], [457, 462], [462, 380], [504, 506], [504, 551], [512, 514], [512, 527], [514, 515], [514, 521], [517, 518], [521, 522], [521, 523], [527, 528], [527, 541], [528, 527], [528, 529], [529, 531], [533, 534], [543, 544], [544, 545], [544, 546], [553, 554], [553, 555], [561, 562], [561, 570], [562, 561], [562, 563], [565, 561], [565, 566], [566, 567], [566, 569], [587, 588], [587, 589], [595, 596], [595, 605], [596, 595], [596, 597], [599, 600], [600, 604], [615, 616], [615, 618], [624, 628], [634, 635], [634, 640], [635, 634], [635, 636], [638, 639], [646, 647], [646, 652], [647, 646], [647, 648], [650, 651], [659, 660], [659, 680], [660, 661], [660, 669], [663, 659], [663, 664], [664, 668], [669, 671], [672, 659], [672, 673], [675, 676], [675, 678], [676, 677], [687, 690], [693, 694], [693, 710], [694, 693], [694, 695], [695, 696], [695, 700], [698, 699], [700, 701], [704, 693], [704, 705], [707, 708], [716, 717], [716, 722], [717, 716], [717, 718], [720, 721], [732, 733], [732, 753], [733, 734], [733, 742], [736, 732], [736, 737], [737, 738], [737, 741], [738, 732], [738, 739], [742, 744], [745, 732], [745, 746], [748, 749], [748, 751], [749, 750], [763, 764], [763, 783], [764, 765], [764, 773], [767, 763], [767, 768], [768, 769], [769, 763], [769, 770], [773, 774], [775, 763], [775, 776], [778, 779], [778, 781], [779, 780], [806, 808], [806, 813], [815, 816], [815, 817], [817, 818], [817, 822], [922, 923], [922, 932], [923, 922], [923, 924], [926, 922], [926, 927], [927, 928], [927, 930]], "missing_branches": [[238, 239], [246, 248], [248, 249], [248, 251], [255, 258], [262, 263], [264, 267], [296, 300], [315, 317], [357, 358], [387, 386], [389, 380], [412, 411], [457, 458], [458, 380], [458, 459], [459, 458], [459, 460], [460, 458], [460, 461], [462, 463], [463, 380], [463, 464], [517, 514], [529, 538], [533, 527], [543, 548], [599, 595], [600, 601], [601, 595], [601, 602], [624, 625], [638, 634], [650, 646], [664, 665], [665, 659], [665, 666], [669, 659], [676, 675], [687, 688], [698, 693], [700, 693], [707, 704], [720, 716], [742, 732], [749, 748], [768, 772], [773, 763], [779, 778]]}}}, "pyWebLayout/layout/__init__.py": {"executed_lines": [1], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/layout/document_layouter.py": {"executed_lines": [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 43, 44, 47, 48, 54, 56, 59, 60, 61, 62, 66, 67, 69, 73, 74, 75, 76, 77, 78, 82, 85, 98, 102, 129, 131, 132, 133, 136, 138, 142, 143, 148, 153, 154, 157, 158, 159, 161, 163, 173, 174, 175, 177, 182, 185, 192, 194, 196, 198, 199, 200, 202, 203, 206, 209, 212, 213, 215, 218, 219, 221, 223, 236, 238, 239, 240, 241, 243, 247, 249, 261, 264, 267, 286, 304, 305, 308, 311, 314, 315, 317, 320, 324, 325, 328, 329, 332, 334, 346, 348, 351, 370, 371, 372, 375, 376, 379, 380, 390, 391, 393, 394, 397, 400, 402, 405, 465, 485, 489, 492, 495, 496, 500, 501, 503, 506, 507, 510, 512, 515, 536, 540, 543, 545, 546, 549, 551, 555, 562, 565, 566, 579, 586, 588, 589, 592, 593, 594, 595, 597, 614, 616, 629, 631, 642, 644, 668, 681, 683, 701, 702, 703, 704, 705, 706, 707, 708, 710, 711, 712, 713, 723], "summary": {"covered_lines": 174, "num_statements": 216, "percent_covered": 76.6025641025641, "percent_covered_display": "77", "missing_lines": 42, "excluded_lines": 16, "num_branches": 96, "num_partial_branches": 15, "covered_branches": 65, "missing_branches": 31}, "missing_lines": [71, 91, 92, 99, 104, 105, 116, 151, 252, 254, 255, 259, 283, 312, 435, 436, 439, 442, 445, 446, 447, 450, 451, 453, 456, 457, 460, 462, 486, 497, 537, 553, 666, 709, 714, 715, 716, 717, 718, 719, 720, 721], "excluded_lines": [24, 140, 268, 288, 355, 416, 467, 517, 566, 580, 603, 618, 632, 655, 670, 685], "executed_branches": [[43, 44], [43, 47], [47, 48], [47, 54], [54, 56], [54, 66], [67, 69], [85, 98], [98, 102], [102, 129], [131, 132], [131, 133], [142, 143], [142, 148], [148, 153], [157, 158], [157, 161], [174, 175], [174, 177], [185, 192], [185, 264], [194, 196], [194, 212], [196, 198], [196, 209], [200, 202], [200, 203], [213, 215], [213, 218], [219, 221], [219, 243], [221, 223], [236, 238], [249, 261], [304, 305], [304, 308], [311, 314], [314, 315], [314, 317], [324, 325], [324, 328], [393, 394], [393, 397], [485, 489], [496, 500], [536, 540], [543, 545], [543, 562], [545, 546], [545, 549], [551, 555], [588, 589], [588, 592], [701, 702], [701, 723], [702, 703], [702, 706], [704, 701], [704, 705], [706, 707], [706, 710], [708, 701], [710, 711], [712, 701], [712, 713]], "missing_branches": [[67, 71], [85, 91], [98, 99], [102, 104], [104, 105], [104, 116], [148, 151], [221, 243], [236, 243], [249, 252], [252, 254], [252, 259], [311, 312], [435, 436], [435, 439], [446, 447], [446, 450], [485, 486], [496, 497], [536, 537], [551, 553], [708, 709], [710, 714], [714, 715], [714, 718], [716, 701], [716, 717], [718, 701], [718, 719], [720, 701], [720, 721]], "functions": {"paragraph_layouter": {"executed_lines": [43, 44, 47, 48, 54, 56, 59, 60, 61, 62, 66, 67, 69, 73, 74, 75, 76, 77, 78, 82, 85, 98, 102, 129, 131, 132, 133, 136, 138, 173, 174, 175, 177, 182, 185, 192, 194, 196, 198, 199, 200, 202, 203, 206, 209, 212, 213, 215, 218, 219, 221, 223, 236, 238, 239, 240, 241, 243, 247, 249, 261, 264], "summary": {"covered_lines": 62, "num_statements": 73, "percent_covered": 80.53097345132744, "percent_covered_display": "81", "missing_lines": 11, "excluded_lines": 1, "num_branches": 40, "num_partial_branches": 7, "covered_branches": 29, "missing_branches": 11}, "missing_lines": [71, 91, 92, 99, 104, 105, 116, 252, 254, 255, 259], "excluded_lines": [24], "executed_branches": [[43, 44], [43, 47], [47, 48], [47, 54], [54, 56], [54, 66], [67, 69], [85, 98], [98, 102], [102, 129], [131, 132], [131, 133], [174, 175], [174, 177], [185, 192], [185, 264], [194, 196], [194, 212], [196, 198], [196, 209], [200, 202], [200, 203], [213, 215], [213, 218], [219, 221], [219, 243], [221, 223], [236, 238], [249, 261]], "missing_branches": [[67, 71], [85, 91], [98, 99], [102, 104], [104, 105], [104, 116], [221, 243], [236, 243], [249, 252], [252, 254], [252, 259]]}, "paragraph_layouter.create_new_line": {"executed_lines": [142, 143, 148, 153, 154, 157, 158, 159, 161, 163], "summary": {"covered_lines": 10, "num_statements": 11, "percent_covered": 88.23529411764706, "percent_covered_display": "88", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [151], "excluded_lines": [140], "executed_branches": [[142, 143], [142, 148], [148, 153], [157, 158], [157, 161]], "missing_branches": [[148, 151]]}, "pagebreak_layouter": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [283], "excluded_lines": [268], "executed_branches": [], "missing_branches": []}, "image_layouter": {"executed_lines": [304, 305, 308, 311, 314, 315, 317, 320, 324, 325, 328, 329, 332, 334, 346, 348], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 92.0, "percent_covered_display": "92", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [312], "excluded_lines": [288], "executed_branches": [[304, 305], [304, 308], [311, 314], [314, 315], [314, 317], [324, 325], [324, 328]], "missing_branches": [[311, 312]]}, "table_layouter": {"executed_lines": [370, 371, 372, 375, 376, 379, 380, 390, 391, 393, 394, 397, 400, 402], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [355], "executed_branches": [[393, 394], [393, 397]], "missing_branches": []}, "button_layouter": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 14, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 14, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [435, 436, 439, 442, 445, 446, 447, 450, 451, 453, 456, 457, 460, 462], "excluded_lines": [416], "executed_branches": [], "missing_branches": [[435, 436], [435, 439], [446, 447], [446, 450]]}, "form_field_layouter": {"executed_lines": [485, 489, 492, 495, 496, 500, 501, 503, 506, 507, 510, 512], "summary": {"covered_lines": 12, "num_statements": 14, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 2, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [486, 497], "excluded_lines": [467], "executed_branches": [[485, 489], [496, 500]], "missing_branches": [[485, 486], [496, 497]]}, "form_layouter": {"executed_lines": [536, 540, 543, 545, 546, 549, 551, 555, 562], "summary": {"covered_lines": 9, "num_statements": 11, "percent_covered": 78.94736842105263, "percent_covered_display": "79", "missing_lines": 2, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2}, "missing_lines": [537, 553], "excluded_lines": [517], "executed_branches": [[536, 540], [543, 545], [543, 562], [545, 546], [545, 549], [551, 555]], "missing_branches": [[536, 537], [551, 553]]}, "DocumentLayouter.__init__": {"executed_lines": [586, 588, 589, 592, 593, 594, 595], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [580], "executed_branches": [[588, 589], [588, 592]], "missing_branches": []}, "DocumentLayouter.layout_paragraph": {"executed_lines": [614], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [603], "executed_branches": [], "missing_branches": []}, "DocumentLayouter.layout_image": {"executed_lines": [629], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [618], "executed_branches": [], "missing_branches": []}, "DocumentLayouter.layout_table": {"executed_lines": [642], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [632], "executed_branches": [], "missing_branches": []}, "DocumentLayouter.layout_button": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [666], "excluded_lines": [655], "executed_branches": [], "missing_branches": []}, "DocumentLayouter.layout_form": {"executed_lines": [681], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [670], "executed_branches": [], "missing_branches": []}, "DocumentLayouter.layout_document": {"executed_lines": [701, 702, 703, 704, 705, 706, 707, 708, 710, 711, 712, 713, 723], "summary": {"covered_lines": 13, "num_statements": 22, "percent_covered": 56.81818181818182, "percent_covered_display": "57", "missing_lines": 9, "excluded_lines": 1, "num_branches": 22, "num_partial_branches": 2, "covered_branches": 12, "missing_branches": 10}, "missing_lines": [709, 714, 715, 716, 717, 718, 719, 720, 721], "excluded_lines": [685], "executed_branches": [[701, 702], [701, 723], [702, 703], [702, 706], [704, 701], [704, 705], [706, 707], [706, 710], [708, 701], [710, 711], [712, 701], [712, 713]], "missing_branches": [[708, 709], [710, 714], [714, 715], [714, 718], [716, 701], [716, 717], [718, 701], [718, 719], [720, 701], [720, 721]]}, "": {"executed_lines": [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 267, 286, 351, 405, 465, 515, 565, 566, 579, 597, 616, 631, 644, 668, 683], "summary": {"covered_lines": 27, "num_statements": 27, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [566], "executed_branches": [], "missing_branches": []}}, "classes": {"DocumentLayouter": {"executed_lines": [586, 588, 589, 592, 593, 594, 595, 614, 629, 642, 681, 701, 702, 703, 704, 705, 706, 707, 708, 710, 711, 712, 713, 723], "summary": {"covered_lines": 24, "num_statements": 34, "percent_covered": 65.51724137931035, "percent_covered_display": "66", "missing_lines": 10, "excluded_lines": 7, "num_branches": 24, "num_partial_branches": 2, "covered_branches": 14, "missing_branches": 10}, "missing_lines": [666, 709, 714, 715, 716, 717, 718, 719, 720, 721], "excluded_lines": [580, 603, 618, 632, 655, 670, 685], "executed_branches": [[588, 589], [588, 592], [701, 702], [701, 723], [702, 703], [702, 706], [704, 701], [704, 705], [706, 707], [706, 710], [708, 701], [710, 711], [712, 701], [712, 713]], "missing_branches": [[708, 709], [710, 714], [714, 715], [714, 718], [716, 701], [716, 717], [718, 701], [718, 719], [720, 701], [720, 721]]}, "": {"executed_lines": [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 43, 44, 47, 48, 54, 56, 59, 60, 61, 62, 66, 67, 69, 73, 74, 75, 76, 77, 78, 82, 85, 98, 102, 129, 131, 132, 133, 136, 138, 142, 143, 148, 153, 154, 157, 158, 159, 161, 163, 173, 174, 175, 177, 182, 185, 192, 194, 196, 198, 199, 200, 202, 203, 206, 209, 212, 213, 215, 218, 219, 221, 223, 236, 238, 239, 240, 241, 243, 247, 249, 261, 264, 267, 286, 304, 305, 308, 311, 314, 315, 317, 320, 324, 325, 328, 329, 332, 334, 346, 348, 351, 370, 371, 372, 375, 376, 379, 380, 390, 391, 393, 394, 397, 400, 402, 405, 465, 485, 489, 492, 495, 496, 500, 501, 503, 506, 507, 510, 512, 515, 536, 540, 543, 545, 546, 549, 551, 555, 562, 565, 566, 579, 597, 616, 631, 644, 668, 683], "summary": {"covered_lines": 150, "num_statements": 182, "percent_covered": 79.13385826771653, "percent_covered_display": "79", "missing_lines": 32, "excluded_lines": 9, "num_branches": 72, "num_partial_branches": 13, "covered_branches": 51, "missing_branches": 21}, "missing_lines": [71, 91, 92, 99, 104, 105, 116, 151, 252, 254, 255, 259, 283, 312, 435, 436, 439, 442, 445, 446, 447, 450, 451, 453, 456, 457, 460, 462, 486, 497, 537, 553], "excluded_lines": [24, 140, 268, 288, 355, 416, 467, 517, 566], "executed_branches": [[43, 44], [43, 47], [47, 48], [47, 54], [54, 56], [54, 66], [67, 69], [85, 98], [98, 102], [102, 129], [131, 132], [131, 133], [142, 143], [142, 148], [148, 153], [157, 158], [157, 161], [174, 175], [174, 177], [185, 192], [185, 264], [194, 196], [194, 212], [196, 198], [196, 209], [200, 202], [200, 203], [213, 215], [213, 218], [219, 221], [219, 243], [221, 223], [236, 238], [249, 261], [304, 305], [304, 308], [311, 314], [314, 315], [314, 317], [324, 325], [324, 328], [393, 394], [393, 397], [485, 489], [496, 500], [536, 540], [543, 545], [543, 562], [545, 546], [545, 549], [551, 555]], "missing_branches": [[67, 71], [85, 91], [98, 99], [102, 104], [104, 105], [104, 116], [148, 151], [221, 243], [236, 243], [249, 252], [252, 254], [252, 259], [311, 312], [435, 436], [435, 439], [446, 447], [446, 450], [485, 486], [496, 497], [536, 537], [551, 553]]}}}, "pyWebLayout/layout/ereader_layout.py": {"executed_lines": [1, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 34, 35, 37, 38, 39, 40, 41, 42, 44, 46, 48, 49, 51, 53, 55, 57, 59, 60, 61, 63, 65, 68, 69, 71, 77, 78, 79, 80, 83, 84, 89, 90, 91, 92, 94, 96, 99, 100, 109, 116, 118, 119, 121, 131, 133, 140, 143, 144, 146, 148, 149, 150, 151, 152, 154, 157, 160, 162, 163, 164, 165, 167, 169, 170, 173, 175, 176, 178, 179, 180, 185, 186, 191, 200, 239, 240, 245, 246, 259, 260, 264, 265, 267, 269, 281, 282, 285, 286, 288, 289, 295, 296, 301, 309, 310, 311, 312, 313, 314, 316, 328, 329, 332, 334, 337, 340, 343, 346, 348, 352, 353, 356, 358, 359, 361, 363, 365, 384, 388, 391, 392, 393, 394, 396, 398, 401, 405, 407, 408, 417, 418, 419, 420, 421, 424, 428, 434, 435, 439, 465, 467, 470, 471, 475, 476, 477, 478, 480, 483, 484, 485, 488, 489, 493, 505, 506, 507, 509, 511, 513, 514, 517, 518, 519, 521, 541, 542, 544, 553, 562, 564, 566, 567, 568, 569, 572, 574, 577, 580, 582, 588, 596, 598, 607, 608, 609, 610, 611, 613, 622, 623, 624, 625, 627, 648, 655, 657, 659, 660, 661, 665, 667, 674, 677, 678, 680, 682, 684, 693, 696, 698, 700, 703, 704, 706, 709, 710, 713, 714, 717, 719, 721, 724, 725, 726, 727, 728, 729, 730, 734, 736, 744, 754], "summary": {"covered_lines": 241, "num_statements": 279, "percent_covered": 83.12020460358056, "percent_covered_display": "83", "missing_lines": 38, "excluded_lines": 36, "num_branches": 112, "num_partial_branches": 18, "covered_branches": 84, "missing_branches": 28}, "missing_lines": [182, 198, 210, 211, 215, 222, 223, 226, 261, 335, 385, 411, 414, 429, 430, 441, 443, 447, 448, 455, 458, 459, 463, 491, 508, 510, 512, 578, 586, 707, 737, 739, 740, 742, 745, 746, 748, 750], "excluded_lines": [1, 30, 45, 50, 54, 58, 64, 69, 84, 95, 147, 156, 161, 168, 186, 192, 201, 240, 247, 284, 296, 318, 369, 468, 499, 527, 594, 604, 619, 633, 671, 689, 723, 735, 738, 747], "executed_branches": [[59, 60], [59, 61], [99, 100], [99, 118], [118, -94], [118, 119], [119, 118], [119, 121], [143, 118], [143, 144], [149, 150], [149, 152], [150, 151], [162, 163], [162, 165], [163, 162], [163, 164], [169, 170], [169, 173], [173, 175], [175, 176], [175, 178], [179, 173], [179, 180], [260, 264], [264, 265], [264, 267], [285, 286], [285, 288], [332, 334], [332, 363], [334, 337], [346, 348], [346, 352], [352, 353], [352, 356], [356, 358], [356, 361], [384, 388], [396, 398], [396, 434], [405, 407], [405, 417], [407, 408], [418, 419], [418, 424], [428, 396], [439, 465], [470, 471], [470, 475], [475, 476], [477, 478], [477, 480], [483, 484], [483, 489], [484, 485], [505, 506], [505, 507], [507, 509], [509, 511], [511, 513], [513, 514], [513, 517], [542, 544], [542, 553], [564, 566], [564, 572], [572, 574], [577, 580], [657, 659], [657, 665], [700, 703], [700, 710], [706, 709], [710, 713], [710, 717], [724, 725], [724, 726], [726, 727], [726, 728], [728, 729], [728, 730], [736, 744], [744, -734]], "missing_branches": [[150, 149], [173, 182], [210, 211], [210, 215], [222, 223], [222, 226], [260, 261], [334, 335], [384, 385], [407, 411], [411, 414], [411, 417], [428, 429], [439, 441], [441, 443], [441, 447], [458, 459], [458, 463], [475, 491], [484, 483], [507, 508], [509, 510], [511, 512], [572, 586], [577, 578], [706, 707], [736, 737], [744, 745]], "functions": {"RenderingPosition.to_dict": {"executed_lines": [46], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [45], "executed_branches": [], "missing_branches": []}, "RenderingPosition.from_dict": {"executed_lines": [51], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [50], "executed_branches": [], "missing_branches": []}, "RenderingPosition.copy": {"executed_lines": [55], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [54], "executed_branches": [], "missing_branches": []}, "RenderingPosition.__eq__": {"executed_lines": [59, 60, 61], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [58], "executed_branches": [[59, 60], [59, 61]], "missing_branches": []}, "RenderingPosition.__hash__": {"executed_lines": [65], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [64], "executed_branches": [], "missing_branches": []}, "ChapterInfo.__init__": {"executed_lines": [77, 78, 79, 80], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "ChapterNavigator.__init__": {"executed_lines": [90, 91, 92], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "ChapterNavigator._build_chapter_map": {"executed_lines": [96, 99, 100, 109, 116, 118, 119, 121, 131, 133, 140, 143, 144], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 0, "covered_branches": 8, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [95], "executed_branches": [[99, 100], [99, 118], [118, -94], [118, 119], [119, 118], [119, 121], [143, 118], [143, 144]], "missing_branches": []}, "ChapterNavigator._extract_heading_text": {"executed_lines": [148, 149, 150, 151, 152], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 88.88888888888889, "percent_covered_display": "89", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [147], "executed_branches": [[149, 150], [149, 152], [150, 151]], "missing_branches": [[150, 149]]}, "ChapterNavigator.get_table_of_contents": {"executed_lines": [157], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [156], "executed_branches": [], "missing_branches": []}, "ChapterNavigator.get_chapter_position": {"executed_lines": [162, 163, 164, 165], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [161], "executed_branches": [[162, 163], [162, 165], [163, 162], [163, 164]], "missing_branches": []}, "ChapterNavigator.get_current_chapter": {"executed_lines": [169, 170, 173, 175, 176, 178, 179, 180], "summary": {"covered_lines": 8, "num_statements": 9, "percent_covered": 88.23529411764706, "percent_covered_display": "88", "missing_lines": 1, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [182], "excluded_lines": [168], "executed_branches": [[169, 170], [169, 173], [173, 175], [175, 176], [175, 178], [179, 173], [179, 180]], "missing_branches": [[173, 182]]}, "FontFamilyOverride.__init__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [198], "excluded_lines": [192], "executed_branches": [], "missing_branches": []}, "FontFamilyOverride.override_font": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [210, 211, 215, 222, 223, 226], "excluded_lines": [201], "executed_branches": [], "missing_branches": [[210, 211], [210, 215], [222, 223], [222, 226]]}, "FontScaler.scale_font": {"executed_lines": [259, 260, 264, 265, 267, 269], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 81.81818181818181, "percent_covered_display": "82", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [261], "excluded_lines": [247], "executed_branches": [[260, 264], [264, 265], [264, 267]], "missing_branches": [[260, 261]]}, "FontScaler.scale_word_spacing": {"executed_lines": [285, 286, 288, 289], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [284], "executed_branches": [[285, 286], [285, 288]], "missing_branches": []}, "BidirectionalLayouter.__init__": {"executed_lines": [309, 310, 311, 312, 313, 314], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "BidirectionalLayouter.render_page_forward": {"executed_lines": [328, 329, 332, 334, 337, 340, 343, 346, 348, 352, 353, 356, 358, 359, 361, 363], "summary": {"covered_lines": 16, "num_statements": 17, "percent_covered": 92.5925925925926, "percent_covered_display": "93", "missing_lines": 1, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 1, "covered_branches": 9, "missing_branches": 1}, "missing_lines": [335], "excluded_lines": [318], "executed_branches": [[332, 334], [332, 363], [334, 337], [346, 348], [346, 352], [352, 353], [352, 356], [356, 358], [356, 361]], "missing_branches": [[334, 335]]}, "BidirectionalLayouter.render_page_backward": {"executed_lines": [384, 388, 391, 392, 393, 394, 396, 398, 401, 405, 407, 408, 417, 418, 419, 420, 421, 424, 428, 434, 435, 439, 465], "summary": {"covered_lines": 23, "num_statements": 36, "percent_covered": 58.92857142857143, "percent_covered_display": "59", "missing_lines": 13, "excluded_lines": 1, "num_branches": 20, "num_partial_branches": 4, "covered_branches": 10, "missing_branches": 10}, "missing_lines": [385, 411, 414, 429, 430, 441, 443, 447, 448, 455, 458, 459, 463], "excluded_lines": [369], "executed_branches": [[384, 388], [396, 398], [396, 434], [405, 407], [405, 417], [407, 408], [418, 419], [418, 424], [428, 396], [439, 465]], "missing_branches": [[384, 385], [407, 411], [411, 414], [411, 417], [428, 429], [439, 441], [441, 443], [441, 447], [458, 459], [458, 463]]}, "BidirectionalLayouter._scale_block_fonts": {"executed_lines": [470, 471, 475, 476, 477, 478, 480, 483, 484, 485, 488, 489], "summary": {"covered_lines": 12, "num_statements": 13, "percent_covered": 86.95652173913044, "percent_covered_display": "87", "missing_lines": 1, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 2}, "missing_lines": [491], "excluded_lines": [468], "executed_branches": [[470, 471], [470, 475], [475, 476], [477, 478], [477, 480], [483, 484], [483, 489], [484, 485]], "missing_branches": [[475, 491], [484, 483]]}, "BidirectionalLayouter._layout_block_on_page": {"executed_lines": [505, 506, 507, 509, 511, 513, 514, 517, 518, 519], "summary": {"covered_lines": 10, "num_statements": 13, "percent_covered": 73.91304347826087, "percent_covered_display": "74", "missing_lines": 3, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 3}, "missing_lines": [508, 510, 512], "excluded_lines": [499], "executed_branches": [[505, 506], [505, 507], [507, 509], [509, 511], [511, 513], [513, 514], [513, 517]], "missing_branches": [[507, 508], [509, 510], [511, 512]]}, "BidirectionalLayouter._layout_paragraph_on_page": {"executed_lines": [541, 542, 544, 553, 562, 564, 566, 567, 568, 569, 572, 574, 577, 580, 582], "summary": {"covered_lines": 15, "num_statements": 17, "percent_covered": 84.0, "percent_covered_display": "84", "missing_lines": 2, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2}, "missing_lines": [578, 586], "excluded_lines": [527], "executed_branches": [[542, 544], [542, 553], [564, 566], [564, 572], [572, 574], [577, 580]], "missing_branches": [[572, 586], [577, 578]]}, "BidirectionalLayouter._layout_heading_on_page": {"executed_lines": [596], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [594], "executed_branches": [], "missing_branches": []}, "BidirectionalLayouter._layout_table_on_page": {"executed_lines": [607, 608, 609, 610, 611], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [604], "executed_branches": [], "missing_branches": []}, "BidirectionalLayouter._layout_list_on_page": {"executed_lines": [622, 623, 624, 625], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [619], "executed_branches": [], "missing_branches": []}, "BidirectionalLayouter._layout_image_on_page": {"executed_lines": [648, 655, 657, 659, 660, 661, 665], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [633], "executed_branches": [[657, 659], [657, 665]], "missing_branches": []}, "BidirectionalLayouter._estimate_page_start": {"executed_lines": [674, 677, 678, 680, 682], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [671], "executed_branches": [], "missing_branches": []}, "BidirectionalLayouter._adjust_start_estimate": {"executed_lines": [693, 696, 698, 700, 703, 704, 706, 709, 710, 713, 714, 717, 719], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 90.0, "percent_covered_display": "90", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [707], "excluded_lines": [689], "executed_branches": [[700, 703], [700, 710], [706, 709], [710, 713], [710, 717]], "missing_branches": [[706, 707]]}, "BidirectionalLayouter._position_compare": {"executed_lines": [724, 725, 726, 727, 728, 729, 730], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [723], "executed_branches": [[724, 725], [724, 726], [726, 727], [726, 728], [728, 729], [728, 730]], "missing_branches": []}, "_add_page_methods": {"executed_lines": [736, 744], "summary": {"covered_lines": 2, "num_statements": 7, "percent_covered": 36.36363636363637, "percent_covered_display": "36", "missing_lines": 5, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [737, 742, 745, 746, 750], "excluded_lines": [735], "executed_branches": [[736, 744], [744, -734]], "missing_branches": [[736, 737], [744, 745]]}, "_add_page_methods.can_fit_line": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [739, 740], "excluded_lines": [738], "executed_branches": [], "missing_branches": []}, "_add_page_methods.available_width": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [748], "excluded_lines": [747], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 34, 35, 37, 38, 39, 40, 41, 42, 44, 48, 49, 53, 57, 63, 68, 69, 71, 83, 84, 89, 94, 146, 154, 160, 167, 185, 186, 191, 200, 239, 240, 245, 246, 281, 282, 295, 296, 301, 316, 365, 467, 493, 521, 588, 598, 613, 627, 667, 684, 721, 734, 754], "summary": {"covered_lines": 60, "num_statements": 60, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 7, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 30, 69, 84, 186, 240, 296], "executed_branches": [], "missing_branches": []}}, "classes": {"RenderingPosition": {"executed_lines": [46, 51, 55, 59, 60, 61, 65], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [45, 50, 54, 58, 64], "executed_branches": [[59, 60], [59, 61]], "missing_branches": []}, "ChapterInfo": {"executed_lines": [77, 78, 79, 80], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "ChapterNavigator": {"executed_lines": [90, 91, 92, 96, 99, 100, 109, 116, 118, 119, 121, 131, 133, 140, 143, 144, 148, 149, 150, 151, 152, 157, 162, 163, 164, 165, 169, 170, 173, 175, 176, 178, 179, 180], "summary": {"covered_lines": 34, "num_statements": 35, "percent_covered": 94.91525423728814, "percent_covered_display": "95", "missing_lines": 1, "excluded_lines": 5, "num_branches": 24, "num_partial_branches": 2, "covered_branches": 22, "missing_branches": 2}, "missing_lines": [182], "excluded_lines": [95, 147, 156, 161, 168], "executed_branches": [[99, 100], [99, 118], [118, -94], [118, 119], [119, 118], [119, 121], [143, 118], [143, 144], [149, 150], [149, 152], [150, 151], [162, 163], [162, 165], [163, 162], [163, 164], [169, 170], [169, 173], [173, 175], [175, 176], [175, 178], [179, 173], [179, 180]], "missing_branches": [[150, 149], [173, 182]]}, "FontFamilyOverride": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 2, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [198, 210, 211, 215, 222, 223, 226], "excluded_lines": [192, 201], "executed_branches": [], "missing_branches": [[210, 211], [210, 215], [222, 223], [222, 226]]}, "FontScaler": {"executed_lines": [259, 260, 264, 265, 267, 269, 285, 286, 288, 289], "summary": {"covered_lines": 10, "num_statements": 11, "percent_covered": 88.23529411764706, "percent_covered_display": "88", "missing_lines": 1, "excluded_lines": 2, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [261], "excluded_lines": [247, 284], "executed_branches": [[260, 264], [264, 265], [264, 267], [285, 286], [285, 288]], "missing_branches": [[260, 261]]}, "BidirectionalLayouter": {"executed_lines": [309, 310, 311, 312, 313, 314, 328, 329, 332, 334, 337, 340, 343, 346, 348, 352, 353, 356, 358, 359, 361, 363, 384, 388, 391, 392, 393, 394, 396, 398, 401, 405, 407, 408, 417, 418, 419, 420, 421, 424, 428, 434, 435, 439, 465, 470, 471, 475, 476, 477, 478, 480, 483, 484, 485, 488, 489, 505, 506, 507, 509, 511, 513, 514, 517, 518, 519, 541, 542, 544, 553, 562, 564, 566, 567, 568, 569, 572, 574, 577, 580, 582, 596, 607, 608, 609, 610, 611, 622, 623, 624, 625, 648, 655, 657, 659, 660, 661, 665, 674, 677, 678, 680, 682, 693, 696, 698, 700, 703, 704, 706, 709, 710, 713, 714, 717, 719, 724, 725, 726, 727, 728, 729, 730], "summary": {"covered_lines": 124, "num_statements": 145, "percent_covered": 81.5668202764977, "percent_covered_display": "82", "missing_lines": 21, "excluded_lines": 12, "num_branches": 72, "num_partial_branches": 13, "covered_branches": 53, "missing_branches": 19}, "missing_lines": [335, 385, 411, 414, 429, 430, 441, 443, 447, 448, 455, 458, 459, 463, 491, 508, 510, 512, 578, 586, 707], "excluded_lines": [318, 369, 468, 499, 527, 594, 604, 619, 633, 671, 689, 723], "executed_branches": [[332, 334], [332, 363], [334, 337], [346, 348], [346, 352], [352, 353], [352, 356], [356, 358], [356, 361], [384, 388], [396, 398], [396, 434], [405, 407], [405, 417], [407, 408], [418, 419], [418, 424], [428, 396], [439, 465], [470, 471], [470, 475], [475, 476], [477, 478], [477, 480], [483, 484], [483, 489], [484, 485], [505, 506], [505, 507], [507, 509], [509, 511], [511, 513], [513, 514], [513, 517], [542, 544], [542, 553], [564, 566], [564, 572], [572, 574], [577, 580], [657, 659], [657, 665], [700, 703], [700, 710], [706, 709], [710, 713], [710, 717], [724, 725], [724, 726], [726, 727], [726, 728], [728, 729], [728, 730]], "missing_branches": [[334, 335], [384, 385], [407, 411], [411, 414], [411, 417], [428, 429], [439, 441], [441, 443], [441, 447], [458, 459], [458, 463], [475, 491], [484, 483], [507, 508], [509, 510], [511, 512], [572, 586], [577, 578], [706, 707]]}, "": {"executed_lines": [1, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 34, 35, 37, 38, 39, 40, 41, 42, 44, 48, 49, 53, 57, 63, 68, 69, 71, 83, 84, 89, 94, 146, 154, 160, 167, 185, 186, 191, 200, 239, 240, 245, 246, 281, 282, 295, 296, 301, 316, 365, 467, 493, 521, 588, 598, 613, 627, 667, 684, 721, 734, 736, 744, 754], "summary": {"covered_lines": 62, "num_statements": 70, "percent_covered": 86.48648648648648, "percent_covered_display": "86", "missing_lines": 8, "excluded_lines": 10, "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [737, 739, 740, 742, 745, 746, 748, 750], "excluded_lines": [1, 30, 69, 84, 186, 240, 296, 735, 738, 747], "executed_branches": [[736, 744], [744, -734]], "missing_branches": [[736, 737], [744, 745]]}}}, "pyWebLayout/layout/ereader_manager.py": {"executed_lines": [1, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 24, 25, 29, 37, 38, 39, 41, 42, 44, 45, 47, 49, 50, 51, 52, 53, 57, 58, 59, 61, 63, 64, 68, 69, 73, 81, 82, 84, 94, 95, 96, 97, 98, 100, 110, 112, 119, 121, 128, 129, 130, 134, 141, 142, 143, 144, 145, 146, 147, 148, 151, 152, 165, 183, 184, 185, 188, 189, 190, 193, 194, 195, 198, 199, 202, 203, 207, 208, 211, 212, 213, 214, 217, 219, 222, 225, 227, 230, 232, 243, 244, 247, 248, 249, 251, 253, 264, 266, 270, 274, 275, 276, 279, 286, 287, 293, 295, 297, 298, 301, 303, 304, 307, 309, 320, 321, 323, 324, 326, 337, 338, 340, 341, 344, 345, 348, 350, 354, 355, 356, 357, 359, 361, 373, 374, 375, 376, 379, 382, 383, 386, 388, 390, 391, 392, 396, 399, 401, 403, 404, 405, 409, 417, 419, 423, 433, 434, 435, 436, 438, 448, 449, 450, 451, 453, 463, 464, 465, 466, 468, 477, 481, 484, 487, 505, 506, 509, 513, 522, 523, 525, 527, 529, 531, 543, 544, 546, 549, 551, 553, 555, 583, 590, 592, 610, 628, 646, 665, 683, 701, 709, 711, 718, 720, 730, 731, 732, 736, 746, 748, 758, 759, 760, 761, 763, 770, 772, 779, 780, 784, 785, 787, 789, 796, 798, 805, 807, 814, 817, 818, 819, 821, 828, 829, 831, 846, 853, 855, 861, 864, 866, 868, 872, 888], "summary": {"covered_lines": 244, "num_statements": 292, "percent_covered": 84.18079096045197, "percent_covered_display": "84", "missing_lines": 48, "excluded_lines": 53, "num_branches": 62, "num_partial_branches": 8, "covered_branches": 54, "missing_branches": 8}, "missing_lines": [70, 71, 131, 132, 268, 289, 291, 343, 380, 407, 485, 510, 576, 579, 581, 604, 605, 606, 607, 608, 622, 623, 624, 625, 626, 640, 641, 642, 643, 644, 658, 660, 661, 662, 663, 677, 678, 679, 680, 681, 695, 696, 697, 698, 699, 733, 734, 815], "excluded_lines": [1, 25, 30, 48, 62, 74, 85, 101, 113, 122, 135, 152, 172, 224, 229, 233, 254, 296, 310, 327, 362, 410, 424, 439, 454, 469, 491, 528, 532, 552, 556, 584, 593, 611, 629, 647, 666, 684, 703, 712, 721, 737, 749, 764, 773, 790, 799, 808, 822, 847, 856, 867, 876], "executed_branches": [[49, -47], [49, 50], [94, 95], [94, 98], [141, 142], [141, 148], [188, 189], [188, 190], [212, 213], [212, 217], [243, 244], [243, 247], [248, 249], [248, 251], [266, 270], [286, 287], [297, 298], [297, 301], [303, 304], [303, 307], [320, 321], [320, 323], [337, 338], [337, 348], [340, 341], [354, 355], [354, 359], [373, 374], [373, 379], [379, 382], [382, 383], [382, 386], [388, 390], [388, 396], [399, 401], [449, 450], [449, 451], [464, 465], [464, 466], [477, -468], [477, 481], [484, -468], [505, 506], [505, 525], [509, 513], [513, 505], [513, 522], [543, 544], [543, 549], [759, 760], [759, 761], [779, 780], [779, 784], [814, 817]], "missing_branches": [[266, 268], [286, 293], [340, 343], [379, 380], [399, 407], [484, 485], [509, 510], [814, 815]], "functions": {"BookmarkManager.__init__": {"executed_lines": [37, 38, 39, 41, 42, 44, 45], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [30], "executed_branches": [], "missing_branches": []}, "BookmarkManager._load_bookmarks": {"executed_lines": [49, 50, 51, 52, 53, 57, 58, 59], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [48], "executed_branches": [[49, -47], [49, 50]], "missing_branches": []}, "BookmarkManager._save_bookmarks": {"executed_lines": [63, 64, 68, 69], "summary": {"covered_lines": 4, "num_statements": 6, "percent_covered": 66.66666666666667, "percent_covered_display": "67", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [70, 71], "excluded_lines": [62], "executed_branches": [], "missing_branches": []}, "BookmarkManager.add_bookmark": {"executed_lines": [81, 82], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [74], "executed_branches": [], "missing_branches": []}, "BookmarkManager.remove_bookmark": {"executed_lines": [94, 95, 96, 97, 98], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [85], "executed_branches": [[94, 95], [94, 98]], "missing_branches": []}, "BookmarkManager.get_bookmark": {"executed_lines": [110], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [101], "executed_branches": [], "missing_branches": []}, "BookmarkManager.list_bookmarks": {"executed_lines": [119], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [113], "executed_branches": [], "missing_branches": []}, "BookmarkManager.save_reading_position": {"executed_lines": [128, 129, 130], "summary": {"covered_lines": 3, "num_statements": 5, "percent_covered": 60.0, "percent_covered_display": "60", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [131, 132], "excluded_lines": [122], "executed_branches": [], "missing_branches": []}, "BookmarkManager.load_reading_position": {"executed_lines": [141, 142, 143, 144, 145, 146, 147, 148], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [135], "executed_branches": [[141, 142], [141, 148]], "missing_branches": []}, "EreaderLayoutManager.__init__": {"executed_lines": [183, 184, 185, 188, 189, 190, 193, 194, 195, 198, 199, 202, 203, 207, 208, 211, 212, 213, 214, 217, 219], "summary": {"covered_lines": 21, "num_statements": 21, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [172], "executed_branches": [[188, 189], [188, 190], [212, 213], [212, 217]], "missing_branches": []}, "EreaderLayoutManager.set_position_changed_callback": {"executed_lines": [225], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [224], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.set_chapter_changed_callback": {"executed_lines": [230], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [229], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager._detect_cover": {"executed_lines": [243, 244, 247, 248, 249, 251], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [233], "executed_branches": [[243, 244], [243, 247], [248, 249], [248, 251]], "missing_branches": []}, "EreaderLayoutManager._render_cover_page": {"executed_lines": [264, 266, 270, 274, 275, 276, 279, 286, 287, 293], "summary": {"covered_lines": 10, "num_statements": 13, "percent_covered": 70.58823529411765, "percent_covered_display": "71", "missing_lines": 3, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [268, 289, 291], "excluded_lines": [254], "executed_branches": [[266, 270], [286, 287]], "missing_branches": [[266, 268], [286, 293]]}, "EreaderLayoutManager._notify_position_changed": {"executed_lines": [297, 298, 301, 303, 304, 307], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [296], "executed_branches": [[297, 298], [297, 301], [303, 304], [303, 307]], "missing_branches": []}, "EreaderLayoutManager.get_current_page": {"executed_lines": [320, 321, 323, 324], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [310], "executed_branches": [[320, 321], [320, 323]], "missing_branches": []}, "EreaderLayoutManager.next_page": {"executed_lines": [337, 338, 340, 341, 344, 345, 348, 350, 354, 355, 356, 357, 359], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 90.0, "percent_covered_display": "90", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [343], "excluded_lines": [327], "executed_branches": [[337, 338], [337, 348], [340, 341], [354, 355], [354, 359]], "missing_branches": [[340, 343]]}, "EreaderLayoutManager.previous_page": {"executed_lines": [373, 374, 375, 376, 379, 382, 383, 386, 388, 390, 391, 392, 396, 399, 401, 403, 404, 405], "summary": {"covered_lines": 18, "num_statements": 20, "percent_covered": 86.66666666666667, "percent_covered_display": "87", "missing_lines": 2, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 2}, "missing_lines": [380, 407], "excluded_lines": [362], "executed_branches": [[373, 374], [373, 379], [379, 382], [382, 383], [382, 386], [388, 390], [388, 396], [399, 401]], "missing_branches": [[379, 380], [399, 407]]}, "EreaderLayoutManager._is_at_beginning": {"executed_lines": [417, 419], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [410], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.jump_to_position": {"executed_lines": [433, 434, 435, 436], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [424], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.jump_to_chapter": {"executed_lines": [448, 449, 450, 451], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [439], "executed_branches": [[449, 450], [449, 451]], "missing_branches": []}, "EreaderLayoutManager.jump_to_chapter_index": {"executed_lines": [463, 464, 465, 466], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [454], "executed_branches": [[464, 465], [464, 466]], "missing_branches": []}, "EreaderLayoutManager._add_to_history": {"executed_lines": [477, 481, 484], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [485], "excluded_lines": [469], "executed_branches": [[477, -468], [477, 481], [484, -468]], "missing_branches": [[484, 485]]}, "EreaderLayoutManager._get_from_history": {"executed_lines": [505, 506, 509, 513, 522, 523, 525], "summary": {"covered_lines": 7, "num_statements": 8, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [510], "excluded_lines": [491], "executed_branches": [[505, 506], [505, 525], [509, 513], [513, 505], [513, 522]], "missing_branches": [[509, 510]]}, "EreaderLayoutManager._clear_history": {"executed_lines": [529], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [528], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.set_font_scale": {"executed_lines": [543, 544, 546, 549], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [532], "executed_branches": [[543, 544], [543, 549]], "missing_branches": []}, "EreaderLayoutManager.get_font_scale": {"executed_lines": [553], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [552], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.set_font_family": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [576, 579, 581], "excluded_lines": [556], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.get_font_family": {"executed_lines": [590], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [584], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.increase_line_spacing": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [604, 605, 606, 607, 608], "excluded_lines": [593], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.decrease_line_spacing": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [622, 623, 624, 625, 626], "excluded_lines": [611], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.increase_inter_block_spacing": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [640, 641, 642, 643, 644], "excluded_lines": [629], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.decrease_inter_block_spacing": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [658, 660, 661, 662, 663], "excluded_lines": [647], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.increase_word_spacing": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [677, 678, 679, 680, 681], "excluded_lines": [666], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.decrease_word_spacing": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [695, 696, 697, 698, 699], "excluded_lines": [684], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.get_table_of_contents": {"executed_lines": [709], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [703], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.get_current_chapter": {"executed_lines": [718], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [712], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.add_bookmark": {"executed_lines": [730, 731, 732], "summary": {"covered_lines": 3, "num_statements": 5, "percent_covered": 60.0, "percent_covered_display": "60", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [733, 734], "excluded_lines": [721], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.remove_bookmark": {"executed_lines": [746], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [737], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.jump_to_bookmark": {"executed_lines": [758, 759, 760, 761], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [749], "executed_branches": [[759, 760], [759, 761]], "missing_branches": []}, "EreaderLayoutManager.list_bookmarks": {"executed_lines": [770], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [764], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.get_reading_progress": {"executed_lines": [779, 780, 784, 785, 787], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [773], "executed_branches": [[779, 780], [779, 784]], "missing_branches": []}, "EreaderLayoutManager.has_cover": {"executed_lines": [796], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [790], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.is_on_cover": {"executed_lines": [805], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [799], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.jump_to_cover": {"executed_lines": [814, 817, 818, 819], "summary": {"covered_lines": 4, "num_statements": 5, "percent_covered": 71.42857142857143, "percent_covered_display": "71", "missing_lines": 1, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [815], "excluded_lines": [808], "executed_branches": [[814, 817]], "missing_branches": [[814, 815]]}, "EreaderLayoutManager.get_position_info": {"executed_lines": [828, 829, 831], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [822], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.get_cache_stats": {"executed_lines": [853], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [847], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.shutdown": {"executed_lines": [861, 864], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [856], "executed_branches": [], "missing_branches": []}, "EreaderLayoutManager.__del__": {"executed_lines": [868], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [867], "executed_branches": [], "missing_branches": []}, "create_ereader_manager": {"executed_lines": [888], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [876], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 24, 25, 29, 47, 61, 73, 84, 100, 112, 121, 134, 151, 152, 165, 222, 227, 232, 253, 295, 309, 326, 361, 409, 423, 438, 453, 468, 487, 527, 531, 551, 555, 583, 592, 610, 628, 646, 665, 683, 701, 711, 720, 736, 748, 763, 772, 789, 798, 807, 821, 846, 855, 866, 872], "summary": {"covered_lines": 64, "num_statements": 64, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 25, 152], "executed_branches": [], "missing_branches": []}}, "classes": {"BookmarkManager": {"executed_lines": [37, 38, 39, 41, 42, 44, 45, 49, 50, 51, 52, 53, 57, 58, 59, 63, 64, 68, 69, 81, 82, 94, 95, 96, 97, 98, 110, 119, 128, 129, 130, 141, 142, 143, 144, 145, 146, 147, 148], "summary": {"covered_lines": 39, "num_statements": 43, "percent_covered": 91.83673469387755, "percent_covered_display": "92", "missing_lines": 4, "excluded_lines": 9, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [70, 71, 131, 132], "excluded_lines": [30, 48, 62, 74, 85, 101, 113, 122, 135], "executed_branches": [[49, -47], [49, 50], [94, 95], [94, 98], [141, 142], [141, 148]], "missing_branches": []}, "EreaderLayoutManager": {"executed_lines": [183, 184, 185, 188, 189, 190, 193, 194, 195, 198, 199, 202, 203, 207, 208, 211, 212, 213, 214, 217, 219, 225, 230, 243, 244, 247, 248, 249, 251, 264, 266, 270, 274, 275, 276, 279, 286, 287, 293, 297, 298, 301, 303, 304, 307, 320, 321, 323, 324, 337, 338, 340, 341, 344, 345, 348, 350, 354, 355, 356, 357, 359, 373, 374, 375, 376, 379, 382, 383, 386, 388, 390, 391, 392, 396, 399, 401, 403, 404, 405, 417, 419, 433, 434, 435, 436, 448, 449, 450, 451, 463, 464, 465, 466, 477, 481, 484, 505, 506, 509, 513, 522, 523, 525, 529, 543, 544, 546, 549, 553, 590, 709, 718, 730, 731, 732, 746, 758, 759, 760, 761, 770, 779, 780, 784, 785, 787, 796, 805, 814, 817, 818, 819, 828, 829, 831, 853, 861, 864, 868], "summary": {"covered_lines": 140, "num_statements": 184, "percent_covered": 78.33333333333333, "percent_covered_display": "78", "missing_lines": 44, "excluded_lines": 40, "num_branches": 56, "num_partial_branches": 8, "covered_branches": 48, "missing_branches": 8}, "missing_lines": [268, 289, 291, 343, 380, 407, 485, 510, 576, 579, 581, 604, 605, 606, 607, 608, 622, 623, 624, 625, 626, 640, 641, 642, 643, 644, 658, 660, 661, 662, 663, 677, 678, 679, 680, 681, 695, 696, 697, 698, 699, 733, 734, 815], "excluded_lines": [172, 224, 229, 233, 254, 296, 310, 327, 362, 410, 424, 439, 454, 469, 491, 528, 532, 552, 556, 584, 593, 611, 629, 647, 666, 684, 703, 712, 721, 737, 749, 764, 773, 790, 799, 808, 822, 847, 856, 867], "executed_branches": [[188, 189], [188, 190], [212, 213], [212, 217], [243, 244], [243, 247], [248, 249], [248, 251], [266, 270], [286, 287], [297, 298], [297, 301], [303, 304], [303, 307], [320, 321], [320, 323], [337, 338], [337, 348], [340, 341], [354, 355], [354, 359], [373, 374], [373, 379], [379, 382], [382, 383], [382, 386], [388, 390], [388, 396], [399, 401], [449, 450], [449, 451], [464, 465], [464, 466], [477, -468], [477, 481], [484, -468], [505, 506], [505, 525], [509, 513], [513, 505], [513, 522], [543, 544], [543, 549], [759, 760], [759, 761], [779, 780], [779, 784], [814, 817]], "missing_branches": [[266, 268], [286, 293], [340, 343], [379, 380], [399, 407], [484, 485], [509, 510], [814, 815]]}, "": {"executed_lines": [1, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 24, 25, 29, 47, 61, 73, 84, 100, 112, 121, 134, 151, 152, 165, 222, 227, 232, 253, 295, 309, 326, 361, 409, 423, 438, 453, 468, 487, 527, 531, 551, 555, 583, 592, 610, 628, 646, 665, 683, 701, 711, 720, 736, 748, 763, 772, 789, 798, 807, 821, 846, 855, 866, 872, 888], "summary": {"covered_lines": 65, "num_statements": 65, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 25, 152, 876], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/layout/page_buffer.py": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 22, 57, 58, 63, 71, 72, 75, 76, 79, 81, 85, 86, 87, 90, 91, 92, 93, 95, 110, 111, 112, 113, 116, 117, 119, 130, 132, 133, 134, 137, 139, 140, 141, 143, 145, 160, 163, 166, 167, 168, 170, 173, 174, 176, 177, 179, 190, 193, 194, 195, 197, 198, 200, 202, 204, 206, 208, 209, 210, 211, 214, 221, 222, 226, 228, 230, 232, 234, 236, 237, 238, 239, 242, 249, 250, 254, 256, 258, 261, 263, 264, 265, 266, 267, 277, 278, 279, 282, 283, 285, 287, 289, 290, 291, 294, 295, 296, 297, 299, 306, 307, 308, 310, 321, 323, 333, 335, 337, 338, 339, 342, 343, 346, 348, 350, 353, 354, 358, 377, 379, 380, 381, 382, 383, 384, 386, 387, 388, 390, 403, 404, 405, 408, 409, 411, 415, 417, 419, 425, 428, 431, 434, 436, 438, 453, 458, 459, 475, 478, 481, 484, 486, 488, 510, 512, 514, 516, 518, 520], "summary": {"covered_lines": 168, "num_statements": 195, "percent_covered": 83.01158301158301, "percent_covered_display": "83", "missing_lines": 27, "excluded_lines": 25, "num_branches": 64, "num_partial_branches": 9, "covered_branches": 47, "missing_branches": 17}, "missing_lines": [39, 42, 44, 46, 47, 49, 52, 54, 191, 259, 270, 273, 275, 317, 318, 319, 454, 455, 461, 465, 467, 469, 495, 496, 499, 502, 503], "excluded_lines": [1, 30, 58, 64, 101, 120, 151, 183, 201, 229, 257, 286, 300, 311, 322, 334, 349, 354, 366, 392, 442, 489, 511, 515, 519], "executed_branches": [[116, 117], [130, 132], [130, 137], [137, 139], [137, 143], [166, 167], [167, 168], [167, 170], [173, -145], [173, 174], [190, 193], [194, 195], [197, -179], [197, 198], [204, -200], [204, 206], [206, 208], [206, 214], [209, 210], [209, 211], [232, 234], [234, 236], [234, 242], [237, 238], [237, 239], [258, 261], [264, 265], [264, 282], [265, 264], [265, 266], [282, -256], [282, 283], [289, 290], [289, 291], [306, 307], [335, 337], [335, 346], [338, 339], [338, 342], [403, 404], [403, 408], [409, 411], [409, 425], [415, 417], [415, 425], [453, 458], [459, 475]], "missing_branches": [[46, 47], [46, 49], [116, -95], [166, 173], [190, 191], [194, 197], [232, -228], [258, 259], [306, -299], [317, -310], [317, 318], [453, 454], [459, 461], [465, 467], [465, 475], [495, -488], [495, 496]], "functions": {"_render_page_worker": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 8, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 8, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [39, 42, 44, 46, 47, 49, 52, 54], "excluded_lines": [30], "executed_branches": [], "missing_branches": [[46, 47], [46, 49]]}, "PageBuffer.__init__": {"executed_lines": [71, 72, 75, 76, 79, 81, 85, 86, 87, 90, 91, 92, 93], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [64], "executed_branches": [], "missing_branches": []}, "PageBuffer.initialize": {"executed_lines": [110, 111, 112, 113, 116, 117], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [101], "executed_branches": [[116, 117]], "missing_branches": [[116, -95]]}, "PageBuffer.get_page": {"executed_lines": [130, 132, 133, 134, 137, 139, 140, 141, 143], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [120], "executed_branches": [[130, 132], [130, 137], [137, 139], [137, 143]], "missing_branches": []}, "PageBuffer.cache_page": {"executed_lines": [160, 163, 166, 167, 168, 170, 173, 174, 176, 177], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 93.75, "percent_covered_display": "94", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [151], "executed_branches": [[166, 167], [167, 168], [167, 170], [173, -145], [173, 174]], "missing_branches": [[166, 173]]}, "PageBuffer.start_background_rendering": {"executed_lines": [190, 193, 194, 195, 197, 198], "summary": {"covered_lines": 6, "num_statements": 7, "percent_covered": 76.92307692307692, "percent_covered_display": "77", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 4, "missing_branches": 2}, "missing_lines": [191], "excluded_lines": [183], "executed_branches": [[190, 193], [194, 195], [197, -179], [197, 198]], "missing_branches": [[190, 191], [194, 197]]}, "PageBuffer._queue_forward_renders": {"executed_lines": [202, 204, 206, 208, 209, 210, 211, 214, 221, 222, 226], "summary": {"covered_lines": 11, "num_statements": 11, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [201], "executed_branches": [[204, -200], [204, 206], [206, 208], [206, 214], [209, 210], [209, 211]], "missing_branches": []}, "PageBuffer._queue_backward_renders": {"executed_lines": [230, 232, 234, 236, 237, 238, 239, 242, 249, 250, 254], "summary": {"covered_lines": 11, "num_statements": 11, "percent_covered": 94.11764705882354, "percent_covered_display": "94", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [229], "executed_branches": [[232, 234], [234, 236], [234, 242], [237, 238], [237, 239]], "missing_branches": [[232, -228]]}, "PageBuffer.check_completed_renders": {"executed_lines": [258, 261, 263, 264, 265, 266, 267, 277, 278, 279, 282, 283], "summary": {"covered_lines": 12, "num_statements": 16, "percent_covered": 79.16666666666667, "percent_covered_display": "79", "missing_lines": 4, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [259, 270, 273, 275], "excluded_lines": [257], "executed_branches": [[258, 261], [264, 265], [264, 282], [265, 264], [265, 266], [282, -256], [282, 283]], "missing_branches": [[258, 259]]}, "PageBuffer.invalidate_all": {"executed_lines": [287, 289, 290, 291, 294, 295, 296, 297], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [286], "executed_branches": [[289, 290], [289, 291]], "missing_branches": []}, "PageBuffer.set_font_scale": {"executed_lines": [306, 307, 308], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [], "excluded_lines": [300], "executed_branches": [[306, 307]], "missing_branches": [[306, -299]]}, "PageBuffer.set_font_family": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 3, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 3, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [317, 318, 319], "excluded_lines": [311], "executed_branches": [], "missing_branches": [[317, -310], [317, 318]]}, "PageBuffer.get_cache_stats": {"executed_lines": [323], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [322], "executed_branches": [], "missing_branches": []}, "PageBuffer.shutdown": {"executed_lines": [335, 337, 338, 339, 342, 343, 346], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 4, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [334], "executed_branches": [[335, 337], [335, 346], [338, 339], [338, 342]], "missing_branches": []}, "PageBuffer.__del__": {"executed_lines": [350], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [349], "executed_branches": [], "missing_branches": []}, "BufferedPageRenderer.__init__": {"executed_lines": [377, 379, 380, 381, 382, 383, 384, 386, 387, 388], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [366], "executed_branches": [], "missing_branches": []}, "BufferedPageRenderer.render_page": {"executed_lines": [403, 404, 405, 408, 409, 411, 415, 417, 419, 425, 428, 431, 434, 436], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 0, "covered_branches": 6, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [392], "executed_branches": [[403, 404], [403, 408], [409, 411], [409, 425], [415, 417], [415, 425]], "missing_branches": []}, "BufferedPageRenderer.render_page_backward": {"executed_lines": [453, 458, 459, 475, 478, 481, 484, 486], "summary": {"covered_lines": 8, "num_statements": 14, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 6, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 4}, "missing_lines": [454, 455, 461, 465, 467, 469], "excluded_lines": [442], "executed_branches": [[453, 458], [459, 475]], "missing_branches": [[453, 454], [459, 461], [465, 467], [465, 475]]}, "BufferedPageRenderer.set_font_family": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [495, 496, 499, 502, 503], "excluded_lines": [489], "executed_branches": [], "missing_branches": [[495, -488], [495, 496]]}, "BufferedPageRenderer.get_font_family": {"executed_lines": [512], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [511], "executed_branches": [], "missing_branches": []}, "BufferedPageRenderer.get_cache_stats": {"executed_lines": [516], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [515], "executed_branches": [], "missing_branches": []}, "BufferedPageRenderer.shutdown": {"executed_lines": [520], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [519], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 22, 57, 58, 63, 95, 119, 145, 179, 200, 228, 256, 285, 299, 310, 321, 333, 348, 353, 354, 358, 390, 438, 488, 510, 514, 518], "summary": {"covered_lines": 35, "num_statements": 35, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 58, 354], "executed_branches": [], "missing_branches": []}}, "classes": {"PageBuffer": {"executed_lines": [71, 72, 75, 76, 79, 81, 85, 86, 87, 90, 91, 92, 93, 110, 111, 112, 113, 116, 117, 130, 132, 133, 134, 137, 139, 140, 141, 143, 160, 163, 166, 167, 168, 170, 173, 174, 176, 177, 190, 193, 194, 195, 197, 198, 202, 204, 206, 208, 209, 210, 211, 214, 221, 222, 226, 230, 232, 234, 236, 237, 238, 239, 242, 249, 250, 254, 258, 261, 263, 264, 265, 266, 267, 277, 278, 279, 282, 283, 287, 289, 290, 291, 294, 295, 296, 297, 306, 307, 308, 323, 335, 337, 338, 339, 342, 343, 346, 350], "summary": {"covered_lines": 98, "num_statements": 106, "percent_covered": 88.96103896103897, "percent_covered_display": "89", "missing_lines": 8, "excluded_lines": 14, "num_branches": 48, "num_partial_branches": 7, "covered_branches": 39, "missing_branches": 9}, "missing_lines": [191, 259, 270, 273, 275, 317, 318, 319], "excluded_lines": [64, 101, 120, 151, 183, 201, 229, 257, 286, 300, 311, 322, 334, 349], "executed_branches": [[116, 117], [130, 132], [130, 137], [137, 139], [137, 143], [166, 167], [167, 168], [167, 170], [173, -145], [173, 174], [190, 193], [194, 195], [197, -179], [197, 198], [204, -200], [204, 206], [206, 208], [206, 214], [209, 210], [209, 211], [232, 234], [234, 236], [234, 242], [237, 238], [237, 239], [258, 261], [264, 265], [264, 282], [265, 264], [265, 266], [282, -256], [282, 283], [289, 290], [289, 291], [306, 307], [335, 337], [335, 346], [338, 339], [338, 342]], "missing_branches": [[116, -95], [166, 173], [190, 191], [194, 197], [232, -228], [258, 259], [306, -299], [317, -310], [317, 318]]}, "BufferedPageRenderer": {"executed_lines": [377, 379, 380, 381, 382, 383, 384, 386, 387, 388, 403, 404, 405, 408, 409, 411, 415, 417, 419, 425, 428, 431, 434, 436, 453, 458, 459, 475, 478, 481, 484, 486, 512, 516, 520], "summary": {"covered_lines": 35, "num_statements": 46, "percent_covered": 71.66666666666667, "percent_covered_display": "72", "missing_lines": 11, "excluded_lines": 7, "num_branches": 14, "num_partial_branches": 2, "covered_branches": 8, "missing_branches": 6}, "missing_lines": [454, 455, 461, 465, 467, 469, 495, 496, 499, 502, 503], "excluded_lines": [366, 392, 442, 489, 511, 515, 519], "executed_branches": [[403, 404], [403, 408], [409, 411], [409, 425], [415, 417], [415, 425], [453, 458], [459, 475]], "missing_branches": [[453, 454], [459, 461], [465, 467], [465, 475], [495, -488], [495, 496]]}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 22, 57, 58, 63, 95, 119, 145, 179, 200, 228, 256, 285, 299, 310, 321, 333, 348, 353, 354, 358, 390, 438, 488, 510, 514, 518], "summary": {"covered_lines": 35, "num_statements": 43, "percent_covered": 77.77777777777777, "percent_covered_display": "78", "missing_lines": 8, "excluded_lines": 4, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 2}, "missing_lines": [39, 42, 44, 46, 47, 49, 52, 54], "excluded_lines": [1, 30, 58, 354], "executed_branches": [], "missing_branches": [[46, 47], [46, 49]]}}}, "pyWebLayout/layout/table_optimizer.py": {"executed_lines": [1, 8, 9, 12, 40, 42, 43, 44, 47, 48, 49, 52, 53, 54, 57, 58, 61, 62, 65, 67, 69, 75, 76, 79, 80, 81, 84, 88, 89, 90, 93, 98, 99, 102, 103, 105, 106, 109, 117, 128, 129, 130, 131, 132, 135, 136, 137, 141, 142, 145, 146, 148, 149, 151, 155, 165, 167, 170, 173, 177, 183, 186, 189, 199, 200, 201, 204, 205, 208, 219, 221, 222, 224, 226, 229, 244, 245, 248, 254, 255, 256, 257, 258, 259, 260, 262, 265, 275, 276, 278, 280, 283, 284, 287, 288, 289, 294, 295, 296, 297, 302, 318, 319, 320, 323, 324, 327, 329, 331, 334, 335, 338, 341, 342, 345, 347, 349, 351, 352, 353, 356, 357, 358, 360, 361, 364, 365, 367, 368, 369, 370, 371, 372, 376, 379, 391, 394, 396], "summary": {"covered_lines": 137, "num_statements": 151, "percent_covered": 87.67123287671232, "percent_covered_display": "88", "missing_lines": 14, "excluded_lines": 9, "num_branches": 68, "num_partial_branches": 9, "covered_branches": 55, "missing_branches": 13}, "missing_lines": [70, 71, 72, 73, 82, 152, 168, 249, 250, 251, 290, 291, 299, 374], "excluded_lines": [1, 16, 118, 190, 209, 230, 266, 306, 380], "executed_branches": [[43, 44], [43, 47], [47, 48], [47, 52], [67, 69], [67, 109], [69, 75], [79, 80], [79, 105], [81, 84], [145, -117], [145, 146], [146, 148], [151, 155], [165, 167], [165, 186], [167, 170], [200, 201], [200, 204], [221, 222], [221, 226], [248, 254], [255, 256], [258, 259], [258, 262], [259, 258], [259, 260], [275, 276], [275, 278], [278, 280], [283, 284], [283, 287], [287, 288], [287, 294], [319, 320], [319, 323], [329, 331], [329, 334], [341, 342], [341, 345], [345, 347], [345, 358], [349, 351], [349, 356], [351, 352], [351, 376], [356, 357], [356, 376], [358, 360], [358, 364], [360, 361], [360, 376], [367, 368], [367, 376], [368, 369]], "missing_branches": [[69, 70], [81, 82], [146, 145], [151, 152], [167, 168], [248, 249], [249, 250], [249, 254], [250, 249], [250, 251], [255, 262], [278, 299], [368, 374]], "functions": {"optimize_table_layout": {"executed_lines": [40, 42, 43, 44, 47, 48, 49, 52, 53, 54, 57, 58, 61, 62, 65, 67, 69, 75, 76, 79, 80, 81, 84, 88, 89, 90, 93, 98, 99, 102, 103, 105, 106, 109], "summary": {"covered_lines": 34, "num_statements": 39, "percent_covered": 86.27450980392157, "percent_covered_display": "86", "missing_lines": 5, "excluded_lines": 1, "num_branches": 12, "num_partial_branches": 2, "covered_branches": 10, "missing_branches": 2}, "missing_lines": [70, 71, 72, 73, 82], "excluded_lines": [16], "executed_branches": [[43, 44], [43, 47], [47, 48], [47, 52], [67, 69], [67, 109], [69, 75], [79, 80], [79, 105], [81, 84]], "missing_branches": [[69, 70], [81, 82]]}, "layout_cell_content": {"executed_lines": [128, 129, 130, 131, 132, 135, 136, 137, 141, 142, 145, 146, 148, 149, 151, 155, 165, 167, 170, 173, 177, 183, 186], "summary": {"covered_lines": 23, "num_statements": 25, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 2, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 3, "covered_branches": 7, "missing_branches": 3}, "missing_lines": [152, 168], "excluded_lines": [118], "executed_branches": [[145, -117], [145, 146], [146, 148], [151, 155], [165, 167], [165, 186], [167, 170]], "missing_branches": [[146, 145], [151, 152], [167, 168]]}, "get_column_count": {"executed_lines": [199, 200, 201, 204, 205], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [190], "executed_branches": [[200, 201], [200, 204]], "missing_branches": []}, "sample_table_rows": {"executed_lines": [219, 221, 222, 224, 226], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [209], "executed_branches": [[221, 222], [221, 226]], "missing_branches": []}, "extract_html_column_widths": {"executed_lines": [244, 245, 248, 254, 255, 256, 257, 258, 259, 260, 262], "summary": {"covered_lines": 11, "num_statements": 14, "percent_covered": 65.38461538461539, "percent_covered_display": "65", "missing_lines": 3, "excluded_lines": 1, "num_branches": 12, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 6}, "missing_lines": [249, 250, 251], "excluded_lines": [230], "executed_branches": [[248, 254], [255, 256], [258, 259], [258, 262], [259, 258], [259, 260]], "missing_branches": [[248, 249], [249, 250], [249, 254], [250, 249], [250, 251], [255, 262]]}, "parse_html_width": {"executed_lines": [275, 276, 278, 280, 283, 284, 287, 288, 289, 294, 295, 296, 297], "summary": {"covered_lines": 13, "num_statements": 16, "percent_covered": 83.33333333333333, "percent_covered_display": "83", "missing_lines": 3, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [290, 291, 299], "excluded_lines": [266], "executed_branches": [[275, 276], [275, 278], [278, 280], [283, 284], [283, 287], [287, 288], [287, 294]], "missing_branches": [[278, 299]]}, "distribute_column_widths": {"executed_lines": [318, 319, 320, 323, 324, 327, 329, 331, 334, 335, 338, 341, 342, 345, 347, 349, 351, 352, 353, 356, 357, 358, 360, 361, 364, 365, 367, 368, 369, 370, 371, 372, 376], "summary": {"covered_lines": 33, "num_statements": 34, "percent_covered": 96.42857142857143, "percent_covered_display": "96", "missing_lines": 1, "excluded_lines": 1, "num_branches": 22, "num_partial_branches": 1, "covered_branches": 21, "missing_branches": 1}, "missing_lines": [374], "excluded_lines": [306], "executed_branches": [[319, 320], [319, 323], [329, 331], [329, 334], [341, 342], [341, 345], [345, 347], [345, 358], [349, 351], [349, 356], [351, 352], [351, 376], [356, 357], [356, 376], [358, 360], [358, 364], [360, 361], [360, 376], [367, 368], [367, 376], [368, 369]], "missing_branches": [[368, 374]]}, "calculate_table_overhead": {"executed_lines": [391, 394, 396], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [380], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 8, 9, 12, 117, 189, 208, 229, 265, 302, 379], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1, 8, 9, 12, 40, 42, 43, 44, 47, 48, 49, 52, 53, 54, 57, 58, 61, 62, 65, 67, 69, 75, 76, 79, 80, 81, 84, 88, 89, 90, 93, 98, 99, 102, 103, 105, 106, 109, 117, 128, 129, 130, 131, 132, 135, 136, 137, 141, 142, 145, 146, 148, 149, 151, 155, 165, 167, 170, 173, 177, 183, 186, 189, 199, 200, 201, 204, 205, 208, 219, 221, 222, 224, 226, 229, 244, 245, 248, 254, 255, 256, 257, 258, 259, 260, 262, 265, 275, 276, 278, 280, 283, 284, 287, 288, 289, 294, 295, 296, 297, 302, 318, 319, 320, 323, 324, 327, 329, 331, 334, 335, 338, 341, 342, 345, 347, 349, 351, 352, 353, 356, 357, 358, 360, 361, 364, 365, 367, 368, 369, 370, 371, 372, 376, 379, 391, 394, 396], "summary": {"covered_lines": 137, "num_statements": 151, "percent_covered": 87.67123287671232, "percent_covered_display": "88", "missing_lines": 14, "excluded_lines": 9, "num_branches": 68, "num_partial_branches": 9, "covered_branches": 55, "missing_branches": 13}, "missing_lines": [70, 71, 72, 73, 82, 152, 168, 249, 250, 251, 290, 291, 299, 374], "excluded_lines": [1, 16, 118, 190, 209, 230, 266, 306, 380], "executed_branches": [[43, 44], [43, 47], [47, 48], [47, 52], [67, 69], [67, 109], [69, 75], [79, 80], [79, 105], [81, 84], [145, -117], [145, 146], [146, 148], [151, 155], [165, 167], [165, 186], [167, 170], [200, 201], [200, 204], [221, 222], [221, 226], [248, 254], [255, 256], [258, 259], [258, 262], [259, 258], [259, 260], [275, 276], [275, 278], [278, 280], [283, 284], [283, 287], [287, 288], [287, 294], [319, 320], [319, 323], [329, 331], [329, 334], [341, 342], [341, 345], [345, 347], [345, 358], [349, 351], [349, 356], [351, 352], [351, 376], [356, 357], [356, 376], [358, 360], [358, 364], [360, 361], [360, 376], [367, 368], [367, 376], [368, 369]], "missing_branches": [[69, 70], [81, 82], [146, 145], [151, 152], [167, 168], [248, 249], [249, 250], [249, 254], [250, 249], [250, 251], [255, 262], [278, 299], [368, 374]]}}}, "pyWebLayout/style/__init__.py": {"executed_lines": [1, 7, 11, 14, 15, 16, 18], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": [], "functions": {"": {"executed_lines": [1, 7, 11, 14, 15, 16, 18], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}, "classes": {"": {"executed_lines": [1, 7, 11, 14, 15, 16, 18], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/style/abstract_style.py": {"executed_lines": [1, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 25, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 56, 59, 60, 61, 73, 74, 75, 76, 77, 80, 81, 84, 85, 86, 87, 88, 89, 92, 95, 97, 100, 107, 116, 134, 136, 165, 175, 180, 181, 184, 185, 192, 194, 196, 197, 200, 202, 204, 205, 206, 207, 208, 210, 211, 213, 215, 217, 218, 219, 221, 231, 233, 248, 249, 252, 253, 255, 256, 257, 259, 275, 277, 278, 280, 283, 284, 285, 288, 289, 291, 293, 295, 307, 308, 312, 313, 315, 325, 326, 329, 330, 336, 340, 342], "summary": {"covered_lines": 103, "num_statements": 130, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 27, "excluded_lines": 22, "num_branches": 22, "num_partial_branches": 7, "covered_branches": 11, "missing_branches": 11}, "missing_lines": [39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 101, 102, 103, 105, 148, 153, 160, 161, 163, 250, 279, 309, 327, 333, 334, 338], "excluded_lines": [1, 17, 26, 38, 61, 98, 108, 137, 166, 185, 193, 203, 212, 216, 222, 237, 263, 292, 297, 316, 337, 341], "executed_branches": [[100, -97], [249, 252], [252, 253], [275, 277], [275, 283], [278, 280], [284, 285], [284, 288], [308, 312], [326, 329], [329, 330]], "missing_branches": [[39, 40], [39, 41], [41, 42], [41, 50], [100, 101], [249, 250], [252, 255], [278, 279], [308, 309], [326, 327], [329, 333]], "functions": {"FontSize.from_value": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 11, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 11, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50], "excluded_lines": [38], "executed_branches": [], "missing_branches": [[39, 40], [39, 41], [41, 42], [41, 50]]}, "AbstractStyle.__post_init__": {"executed_lines": [100], "summary": {"covered_lines": 1, "num_statements": 5, "percent_covered": 28.571428571428573, "percent_covered_display": "29", "missing_lines": 4, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [101, 102, 103, 105], "excluded_lines": [98], "executed_branches": [[100, -97]], "missing_branches": [[100, 101]]}, "AbstractStyle.__hash__": {"executed_lines": [116, 134], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [108], "executed_branches": [], "missing_branches": []}, "AbstractStyle.merge_with": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [148, 153, 160, 161, 163], "excluded_lines": [137], "executed_branches": [], "missing_branches": []}, "AbstractStyle.with_modifications": {"executed_lines": [175, 180, 181], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [166], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry.__init__": {"executed_lines": [194, 196, 197, 200], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [193], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry._create_default_style": {"executed_lines": [204, 205, 206, 207, 208], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [203], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry.default_style": {"executed_lines": [213], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [212], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry._generate_style_id": {"executed_lines": [217, 218, 219], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [216], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry.get_style_id": {"executed_lines": [231], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [222], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry.register_style": {"executed_lines": [248, 249, 252, 253, 255, 256, 257], "summary": {"covered_lines": 7, "num_statements": 8, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 1, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [250], "excluded_lines": [237], "executed_branches": [[249, 252], [252, 253]], "missing_branches": [[249, 250], [252, 255]]}, "AbstractStyleRegistry.get_or_create_style": {"executed_lines": [275, 277, 278, 280, 283, 284, 285, 288, 289], "summary": {"covered_lines": 9, "num_statements": 10, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 1, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [279], "excluded_lines": [263], "executed_branches": [[275, 277], [275, 283], [278, 280], [284, 285], [284, 288]], "missing_branches": [[278, 279]]}, "AbstractStyleRegistry.get_style_by_id": {"executed_lines": [293], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [292], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry.create_derived_style": {"executed_lines": [307, 308, 312, 313], "summary": {"covered_lines": 4, "num_statements": 5, "percent_covered": 71.42857142857143, "percent_covered_display": "71", "missing_lines": 1, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [309], "excluded_lines": [297], "executed_branches": [[308, 312]], "missing_branches": [[308, 309]]}, "AbstractStyleRegistry.resolve_effective_style": {"executed_lines": [325, 326, 329, 330], "summary": {"covered_lines": 4, "num_statements": 7, "percent_covered": 54.54545454545455, "percent_covered_display": "55", "missing_lines": 3, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 2, "covered_branches": 2, "missing_branches": 2}, "missing_lines": [327, 333, 334], "excluded_lines": [316], "executed_branches": [[326, 329], [329, 330]], "missing_branches": [[326, 327], [329, 333]]}, "AbstractStyleRegistry.get_all_styles": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [338], "excluded_lines": [337], "executed_branches": [], "missing_branches": []}, "AbstractStyleRegistry.get_style_count": {"executed_lines": [342], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [341], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 25, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 56, 59, 60, 61, 73, 74, 75, 76, 77, 80, 81, 84, 85, 86, 87, 88, 89, 92, 95, 97, 107, 136, 165, 184, 185, 192, 202, 210, 211, 215, 221, 233, 259, 291, 295, 315, 336, 340], "summary": {"covered_lines": 57, "num_statements": 57, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17, 26, 61, 185], "executed_branches": [], "missing_branches": []}}, "classes": {"FontFamily": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "FontSize": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 11, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 11, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50], "excluded_lines": [38], "executed_branches": [], "missing_branches": [[39, 40], [39, 41], [41, 42], [41, 50]]}, "AbstractStyle": {"executed_lines": [100, 116, 134, 175, 180, 181], "summary": {"covered_lines": 6, "num_statements": 15, "percent_covered": 41.1764705882353, "percent_covered_display": "41", "missing_lines": 9, "excluded_lines": 4, "num_branches": 2, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 1}, "missing_lines": [101, 102, 103, 105, 148, 153, 160, 161, 163], "excluded_lines": [98, 108, 137, 166], "executed_branches": [[100, -97]], "missing_branches": [[100, 101]]}, "AbstractStyleRegistry": {"executed_lines": [194, 196, 197, 200, 204, 205, 206, 207, 208, 213, 217, 218, 219, 231, 248, 249, 252, 253, 255, 256, 257, 275, 277, 278, 280, 283, 284, 285, 288, 289, 293, 307, 308, 312, 313, 325, 326, 329, 330, 342], "summary": {"covered_lines": 40, "num_statements": 47, "percent_covered": 79.36507936507937, "percent_covered_display": "79", "missing_lines": 7, "excluded_lines": 12, "num_branches": 16, "num_partial_branches": 6, "covered_branches": 10, "missing_branches": 6}, "missing_lines": [250, 279, 309, 327, 333, 334, 338], "excluded_lines": [193, 203, 212, 216, 222, 237, 263, 292, 297, 316, 337, 341], "executed_branches": [[249, 252], [252, 253], [275, 277], [275, 283], [278, 280], [284, 285], [284, 288], [308, 312], [326, 329], [329, 330]], "missing_branches": [[249, 250], [252, 255], [278, 279], [308, 309], [326, 327], [329, 333]]}, "": {"executed_lines": [1, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 25, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 56, 59, 60, 61, 73, 74, 75, 76, 77, 80, 81, 84, 85, 86, 87, 88, 89, 92, 95, 97, 107, 136, 165, 184, 185, 192, 202, 210, 211, 215, 221, 233, 259, 291, 295, 315, 336, 340], "summary": {"covered_lines": 57, "num_statements": 57, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17, 26, 61, 185], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/style/alignment.py": {"executed_lines": [1, 7, 10, 11, 13, 14, 15, 16, 19, 20, 21, 23], "summary": {"covered_lines": 10, "num_statements": 11, "percent_covered": 90.9090909090909, "percent_covered_display": "91", "missing_lines": 1, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [25], "excluded_lines": [1, 11, 24], "executed_branches": [], "missing_branches": [], "functions": {"Alignment.__str__": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [25], "excluded_lines": [24], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 7, 10, 11, 13, 14, 15, 16, 19, 20, 21, 23], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 11], "executed_branches": [], "missing_branches": []}}, "classes": {"Alignment": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [25], "excluded_lines": [24], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 7, 10, 11, 13, 14, 15, 16, 19, 20, 21, 23], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 11], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/style/concrete_style.py": {"executed_lines": [1, 8, 9, 10, 11, 12, 15, 16, 17, 23, 24, 25, 26, 27, 30, 31, 32, 35, 36, 37, 40, 43, 44, 45, 53, 54, 55, 56, 59, 60, 61, 64, 65, 66, 67, 68, 69, 72, 73, 76, 78, 80, 93, 94, 101, 108, 109, 112, 123, 144, 155, 156, 159, 160, 162, 163, 164, 166, 167, 169, 171, 173, 175, 178, 180, 181, 182, 185, 186, 187, 189, 190, 192, 195, 215, 216, 218, 220, 221, 230, 233, 235, 236, 237, 239, 249, 252, 253, 256, 258, 261, 262, 264, 266, 267, 268, 270, 271, 272, 275, 276, 277, 278, 279, 288, 297, 301, 311, 312, 330, 332, 333, 346, 349, 350, 370, 373, 374, 376, 377, 379, 380, 381, 382, 383, 394, 413, 417, 419, 422, 423, 430, 437, 438, 440, 450, 452, 462, 465, 466, 469, 470, 472, 474, 479, 481], "summary": {"covered_lines": 141, "num_statements": 207, "percent_covered": 63.44086021505376, "percent_covered_display": "63", "missing_lines": 66, "excluded_lines": 23, "num_branches": 72, "num_partial_branches": 12, "covered_branches": 36, "missing_branches": 36}, "missing_lines": [222, 223, 224, 225, 228, 242, 243, 244, 246, 274, 281, 282, 283, 285, 290, 291, 292, 293, 295, 299, 314, 315, 317, 318, 320, 321, 322, 325, 326, 328, 335, 336, 338, 339, 340, 341, 342, 344, 352, 353, 355, 356, 357, 358, 359, 360, 361, 363, 364, 365, 366, 368, 384, 385, 387, 388, 389, 390, 392, 402, 406, 408, 411, 415, 476, 477], "excluded_lines": [1, 17, 45, 79, 94, 102, 145, 219, 231, 260, 310, 331, 348, 372, 395, 414, 418, 423, 431, 441, 453, 475, 480], "executed_branches": [[155, 156], [155, 159], [178, 180], [178, 187], [180, 181], [180, 185], [187, 189], [187, 190], [190, 192], [190, 195], [220, 221], [233, 235], [233, 237], [237, 239], [252, 253], [252, 256], [261, 262], [261, 264], [264, 266], [266, 267], [266, 268], [268, 270], [272, 275], [275, 276], [288, 297], [311, 312], [332, 333], [349, 350], [373, 374], [373, 376], [376, 377], [376, 379], [379, 380], [380, 381], [465, 466], [465, 469]], "missing_branches": [[220, 222], [222, 223], [222, 224], [224, 225], [224, 228], [237, 242], [264, 299], [268, 285], [272, 274], [275, 281], [288, 290], [292, 293], [292, 295], [311, 314], [314, 315], [314, 320], [315, 317], [315, 318], [320, 321], [320, 328], [321, 322], [321, 325], [332, 335], [335, 336], [335, 338], [338, 339], [338, 344], [349, 352], [352, 353], [352, 355], [355, 356], [355, 368], [356, 357], [356, 363], [379, 392], [380, 387]], "functions": {"ConcreteStyle.create_font": {"executed_lines": [80], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [79], "executed_branches": [], "missing_branches": []}, "StyleResolver.__init__": {"executed_lines": [108, 109, 112, 123], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [102], "executed_branches": [], "missing_branches": []}, "StyleResolver.resolve_style": {"executed_lines": [155, 156, 159, 160, 162, 163, 164, 166, 167, 169, 171, 173, 175, 178, 180, 181, 182, 185, 186, 187, 189, 190, 192, 195, 215, 216], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 0, "covered_branches": 10, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [145], "executed_branches": [[155, 156], [155, 159], [178, 180], [178, 187], [180, 181], [180, 185], [187, 189], [187, 190], [190, 192], [190, 195]], "missing_branches": []}, "StyleResolver._resolve_font_path": {"executed_lines": [220, 221], "summary": {"covered_lines": 2, "num_statements": 7, "percent_covered": 23.076923076923077, "percent_covered_display": "23", "missing_lines": 5, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 5}, "missing_lines": [222, 223, 224, 225, 228], "excluded_lines": [219], "executed_branches": [[220, 221]], "missing_branches": [[220, 222], [222, 223], [222, 224], [224, 225], [224, 228]]}, "StyleResolver._resolve_font_size": {"executed_lines": [233, 235, 236, 237, 239, 249, 252, 253, 256], "summary": {"covered_lines": 9, "num_statements": 13, "percent_covered": 73.6842105263158, "percent_covered_display": "74", "missing_lines": 4, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 5, "missing_branches": 1}, "missing_lines": [242, 243, 244, 246], "excluded_lines": [231], "executed_branches": [[233, 235], [233, 237], [237, 239], [252, 253], [252, 256]], "missing_branches": [[237, 242]]}, "StyleResolver._resolve_color": {"executed_lines": [261, 262, 264, 266, 267, 268, 270, 271, 272, 275, 276, 277, 278, 279, 288, 297], "summary": {"covered_lines": 16, "num_statements": 27, "percent_covered": 58.13953488372093, "percent_covered_display": "58", "missing_lines": 11, "excluded_lines": 1, "num_branches": 16, "num_partial_branches": 5, "covered_branches": 9, "missing_branches": 7}, "missing_lines": [274, 281, 282, 283, 285, 290, 291, 292, 293, 295, 299], "excluded_lines": [260], "executed_branches": [[261, 262], [261, 264], [264, 266], [266, 267], [266, 268], [268, 270], [272, 275], [275, 276], [288, 297]], "missing_branches": [[264, 299], [268, 285], [272, 274], [275, 281], [288, 290], [292, 293], [292, 295]]}, "StyleResolver._resolve_background_color": {"executed_lines": [311, 312], "summary": {"covered_lines": 2, "num_statements": 12, "percent_covered": 13.636363636363637, "percent_covered_display": "14", "missing_lines": 10, "excluded_lines": 1, "num_branches": 10, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 9}, "missing_lines": [314, 315, 317, 318, 320, 321, 322, 325, 326, 328], "excluded_lines": [310], "executed_branches": [[311, 312]], "missing_branches": [[311, 314], [314, 315], [314, 320], [315, 317], [315, 318], [320, 321], [320, 328], [321, 322], [321, 325]]}, "StyleResolver._resolve_line_height": {"executed_lines": [332, 333], "summary": {"covered_lines": 2, "num_statements": 10, "percent_covered": 18.75, "percent_covered_display": "19", "missing_lines": 8, "excluded_lines": 1, "num_branches": 6, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 5}, "missing_lines": [335, 336, 338, 339, 340, 341, 342, 344], "excluded_lines": [331], "executed_branches": [[332, 333]], "missing_branches": [[332, 335], [335, 336], [335, 338], [338, 339], [338, 344]]}, "StyleResolver._resolve_letter_spacing": {"executed_lines": [349, 350], "summary": {"covered_lines": 2, "num_statements": 16, "percent_covered": 12.5, "percent_covered_display": "12", "missing_lines": 14, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 1, "missing_branches": 7}, "missing_lines": [352, 353, 355, 356, 357, 358, 359, 360, 361, 363, 364, 365, 366, 368], "excluded_lines": [348], "executed_branches": [[349, 350]], "missing_branches": [[349, 352], [352, 353], [352, 355], [355, 356], [355, 368], [356, 357], [356, 363]]}, "StyleResolver._resolve_word_spacing": {"executed_lines": [373, 374, 376, 377, 379, 380, 381, 382, 383], "summary": {"covered_lines": 9, "num_statements": 16, "percent_covered": 62.5, "percent_covered_display": "62", "missing_lines": 7, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 2, "covered_branches": 6, "missing_branches": 2}, "missing_lines": [384, 385, 387, 388, 389, 390, 392], "excluded_lines": [372], "executed_branches": [[373, 374], [373, 376], [376, 377], [376, 379], [379, 380], [380, 381]], "missing_branches": [[379, 392], [380, 387]]}, "StyleResolver.update_context": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [402, 406, 408, 411], "excluded_lines": [395], "executed_branches": [], "missing_branches": []}, "StyleResolver.clear_cache": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [415], "excluded_lines": [414], "executed_branches": [], "missing_branches": []}, "StyleResolver.get_cache_size": {"executed_lines": [419], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [418], "executed_branches": [], "missing_branches": []}, "ConcreteStyleRegistry.__init__": {"executed_lines": [437, 438], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [431], "executed_branches": [], "missing_branches": []}, "ConcreteStyleRegistry.get_concrete_style": {"executed_lines": [450], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [441], "executed_branches": [], "missing_branches": []}, "ConcreteStyleRegistry.get_font": {"executed_lines": [462, 465, 466, 469, 470, 472], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [453], "executed_branches": [[465, 466], [465, 469]], "missing_branches": []}, "ConcreteStyleRegistry.clear_caches": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [476, 477], "excluded_lines": [475], "executed_branches": [], "missing_branches": []}, "ConcreteStyleRegistry.get_cache_stats": {"executed_lines": [481], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [480], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 15, 16, 17, 23, 24, 25, 26, 27, 30, 31, 32, 35, 36, 37, 40, 43, 44, 45, 53, 54, 55, 56, 59, 60, 61, 64, 65, 66, 67, 68, 69, 72, 73, 76, 78, 93, 94, 101, 144, 218, 230, 258, 301, 330, 346, 370, 394, 413, 417, 422, 423, 430, 440, 452, 474, 479], "summary": {"covered_lines": 57, "num_statements": 57, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17, 45, 94, 423], "executed_branches": [], "missing_branches": []}}, "classes": {"RenderingContext": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "ConcreteStyle": {"executed_lines": [80], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [79], "executed_branches": [], "missing_branches": []}, "StyleResolver": {"executed_lines": [108, 109, 112, 123, 155, 156, 159, 160, 162, 163, 164, 166, 167, 169, 171, 173, 175, 178, 180, 181, 182, 185, 186, 187, 189, 190, 192, 195, 215, 216, 220, 221, 233, 235, 236, 237, 239, 249, 252, 253, 256, 261, 262, 264, 266, 267, 268, 270, 271, 272, 275, 276, 277, 278, 279, 288, 297, 311, 312, 332, 333, 349, 350, 373, 374, 376, 377, 379, 380, 381, 382, 383, 419], "summary": {"covered_lines": 73, "num_statements": 137, "percent_covered": 51.690821256038646, "percent_covered_display": "52", "missing_lines": 64, "excluded_lines": 12, "num_branches": 70, "num_partial_branches": 12, "covered_branches": 34, "missing_branches": 36}, "missing_lines": [222, 223, 224, 225, 228, 242, 243, 244, 246, 274, 281, 282, 283, 285, 290, 291, 292, 293, 295, 299, 314, 315, 317, 318, 320, 321, 322, 325, 326, 328, 335, 336, 338, 339, 340, 341, 342, 344, 352, 353, 355, 356, 357, 358, 359, 360, 361, 363, 364, 365, 366, 368, 384, 385, 387, 388, 389, 390, 392, 402, 406, 408, 411, 415], "excluded_lines": [102, 145, 219, 231, 260, 310, 331, 348, 372, 395, 414, 418], "executed_branches": [[155, 156], [155, 159], [178, 180], [178, 187], [180, 181], [180, 185], [187, 189], [187, 190], [190, 192], [190, 195], [220, 221], [233, 235], [233, 237], [237, 239], [252, 253], [252, 256], [261, 262], [261, 264], [264, 266], [266, 267], [266, 268], [268, 270], [272, 275], [275, 276], [288, 297], [311, 312], [332, 333], [349, 350], [373, 374], [373, 376], [376, 377], [376, 379], [379, 380], [380, 381]], "missing_branches": [[220, 222], [222, 223], [222, 224], [224, 225], [224, 228], [237, 242], [264, 299], [268, 285], [272, 274], [275, 281], [288, 290], [292, 293], [292, 295], [311, 314], [314, 315], [314, 320], [315, 317], [315, 318], [320, 321], [320, 328], [321, 322], [321, 325], [332, 335], [335, 336], [335, 338], [338, 339], [338, 344], [349, 352], [352, 353], [352, 355], [355, 356], [355, 368], [356, 357], [356, 363], [379, 392], [380, 387]]}, "ConcreteStyleRegistry": {"executed_lines": [437, 438, 450, 462, 465, 466, 469, 470, 472, 481], "summary": {"covered_lines": 10, "num_statements": 12, "percent_covered": 85.71428571428571, "percent_covered_display": "86", "missing_lines": 2, "excluded_lines": 5, "num_branches": 2, "num_partial_branches": 0, "covered_branches": 2, "missing_branches": 0}, "missing_lines": [476, 477], "excluded_lines": [431, 441, 453, 475, 480], "executed_branches": [[465, 466], [465, 469]], "missing_branches": []}, "": {"executed_lines": [1, 8, 9, 10, 11, 12, 15, 16, 17, 23, 24, 25, 26, 27, 30, 31, 32, 35, 36, 37, 40, 43, 44, 45, 53, 54, 55, 56, 59, 60, 61, 64, 65, 66, 67, 68, 69, 72, 73, 76, 78, 93, 94, 101, 144, 218, 230, 258, 301, 330, 346, 370, 394, 413, 417, 422, 423, 430, 440, 452, 474, 479], "summary": {"covered_lines": 57, "num_statements": 57, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 5, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [1, 17, 45, 94, 423], "executed_branches": [], "missing_branches": []}}}, "pyWebLayout/style/fonts.py": {"executed_lines": [3, 4, 5, 6, 7, 10, 14, 17, 20, 23, 24, 25, 28, 29, 30, 33, 34, 35, 36, 39, 40, 41, 42, 43, 46, 73, 138, 139, 144, 169, 170, 171, 172, 173, 174, 175, 176, 177, 179, 181, 182, 232, 237, 238, 242, 244, 245, 247, 248, 249, 250, 254, 255, 256, 257, 264, 267, 268, 269, 272, 275, 276, 277, 278, 281, 282, 284, 285, 289, 292, 294, 295, 296, 297, 308, 309, 311, 313, 314, 317, 318, 320, 322, 323, 325, 327, 328, 330, 332, 333, 335, 337, 338, 340, 342, 343, 345, 347, 348, 350, 352, 353, 355, 357, 358, 360, 362, 374, 385, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406], "summary": {"covered_lines": 118, "num_statements": 161, "percent_covered": 66.32124352331606, "percent_covered_display": "66", "missing_lines": 43, "excluded_lines": 23, "num_branches": 32, "num_partial_branches": 2, "covered_branches": 10, "missing_branches": 22}, "missing_lines": [56, 57, 60, 61, 63, 64, 65, 66, 68, 69, 70, 94, 95, 96, 99, 105, 108, 110, 112, 113, 114, 115, 117, 118, 119, 120, 122, 123, 125, 127, 128, 130, 131, 132, 134, 135, 219, 220, 259, 261, 262, 303, 305], "excluded_lines": [40, 47, 78, 139, 154, 192, 233, 265, 319, 324, 329, 334, 339, 344, 349, 354, 359, 363, 389, 393, 397, 401, 405], "executed_branches": [[237, 238], [237, 242], [254, 255], [268, 269], [268, 272], [275, 276], [275, 281], [282, 284], [282, 292], [294, 295]], "missing_branches": [[56, 57], [56, 60], [63, 64], [63, 68], [95, 96], [95, 99], [110, 112], [110, 118], [112, 113], [112, 114], [114, 115], [114, 117], [118, 119], [118, 120], [120, 122], [120, 127], [122, 123], [122, 125], [130, 131], [130, 134], [254, 259], [294, 303]], "functions": {"get_bundled_fonts_dir": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 11, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 11, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 4}, "missing_lines": [56, 57, 60, 61, 63, 64, 65, 66, 68, 69, 70], "excluded_lines": [47], "executed_branches": [], "missing_branches": [[56, 57], [56, 60], [63, 64], [63, 68]]}, "get_bundled_font_path": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 25, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 25, "excluded_lines": 1, "num_branches": 16, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 16}, "missing_lines": [94, 95, 96, 99, 105, 108, 110, 112, 113, 114, 115, 117, 118, 119, 120, 122, 123, 125, 127, 128, 130, 131, 132, 134, 135], "excluded_lines": [78], "executed_branches": [], "missing_branches": [[95, 96], [95, 99], [110, 112], [110, 118], [112, 113], [112, 114], [114, 115], [114, 117], [118, 119], [118, 120], [120, 122], [120, 127], [122, 123], [122, 125], [130, 131], [130, 134]]}, "Font.__init__": {"executed_lines": [169, 170, 171, 172, 173, 174, 175, 176, 177, 179], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [154], "executed_branches": [], "missing_branches": []}, "Font.from_family": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 2, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 2, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [219, 220], "excluded_lines": [192], "executed_branches": [], "missing_branches": []}, "Font._get_bundled_font_path": {"executed_lines": [237, 238, 242, 244, 245, 247, 248, 249, 250, 254, 255, 256, 257], "summary": {"covered_lines": 13, "num_statements": 16, "percent_covered": 80.0, "percent_covered_display": "80", "missing_lines": 3, "excluded_lines": 1, "num_branches": 4, "num_partial_branches": 1, "covered_branches": 3, "missing_branches": 1}, "missing_lines": [259, 261, 262], "excluded_lines": [233], "executed_branches": [[237, 238], [237, 242], [254, 255]], "missing_branches": [[254, 259]]}, "Font._load_font": {"executed_lines": [267, 268, 269, 272, 275, 276, 277, 278, 281, 282, 284, 285, 289, 292, 294, 295, 296, 297, 308, 309, 311, 313, 314], "summary": {"covered_lines": 23, "num_statements": 25, "percent_covered": 90.9090909090909, "percent_covered_display": "91", "missing_lines": 2, "excluded_lines": 1, "num_branches": 8, "num_partial_branches": 1, "covered_branches": 7, "missing_branches": 1}, "missing_lines": [303, 305], "excluded_lines": [265], "executed_branches": [[268, 269], [268, 272], [275, 276], [275, 281], [282, 284], [282, 292], [294, 295]], "missing_branches": [[294, 303]]}, "Font.font": {"executed_lines": [320], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [319], "executed_branches": [], "missing_branches": []}, "Font.font_size": {"executed_lines": [325], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [324], "executed_branches": [], "missing_branches": []}, "Font.colour": {"executed_lines": [330], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [329], "executed_branches": [], "missing_branches": []}, "Font.color": {"executed_lines": [335], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [334], "executed_branches": [], "missing_branches": []}, "Font.background": {"executed_lines": [340], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [339], "executed_branches": [], "missing_branches": []}, "Font.weight": {"executed_lines": [345], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [344], "executed_branches": [], "missing_branches": []}, "Font.style": {"executed_lines": [350], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [349], "executed_branches": [], "missing_branches": []}, "Font.decoration": {"executed_lines": [355], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [354], "executed_branches": [], "missing_branches": []}, "Font.min_hyphenation_width": {"executed_lines": [360], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [359], "executed_branches": [], "missing_branches": []}, "Font._with_modified": {"executed_lines": [374, 385, 386], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [363], "executed_branches": [], "missing_branches": []}, "Font.with_size": {"executed_lines": [390], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [389], "executed_branches": [], "missing_branches": []}, "Font.with_colour": {"executed_lines": [394], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [393], "executed_branches": [], "missing_branches": []}, "Font.with_weight": {"executed_lines": [398], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [397], "executed_branches": [], "missing_branches": []}, "Font.with_style": {"executed_lines": [402], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [401], "executed_branches": [], "missing_branches": []}, "Font.with_decoration": {"executed_lines": [406], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [405], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [3, 4, 5, 6, 7, 10, 14, 17, 20, 23, 24, 25, 28, 29, 30, 33, 34, 35, 36, 39, 40, 41, 42, 43, 46, 73, 138, 139, 144, 181, 182, 232, 264, 317, 318, 322, 323, 327, 328, 332, 333, 337, 338, 342, 343, 347, 348, 352, 353, 357, 358, 362, 388, 392, 396, 400, 404], "summary": {"covered_lines": 55, "num_statements": 55, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [40, 139], "executed_branches": [], "missing_branches": []}}, "classes": {"FontWeight": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "FontStyle": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "TextDecoration": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "BundledFont": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "Font": {"executed_lines": [169, 170, 171, 172, 173, 174, 175, 176, 177, 179, 237, 238, 242, 244, 245, 247, 248, 249, 250, 254, 255, 256, 257, 267, 268, 269, 272, 275, 276, 277, 278, 281, 282, 284, 285, 289, 292, 294, 295, 296, 297, 308, 309, 311, 313, 314, 320, 325, 330, 335, 340, 345, 350, 355, 360, 374, 385, 386, 390, 394, 398, 402, 406], "summary": {"covered_lines": 63, "num_statements": 70, "percent_covered": 89.02439024390245, "percent_covered_display": "89", "missing_lines": 7, "excluded_lines": 19, "num_branches": 12, "num_partial_branches": 2, "covered_branches": 10, "missing_branches": 2}, "missing_lines": [219, 220, 259, 261, 262, 303, 305], "excluded_lines": [154, 192, 233, 265, 319, 324, 329, 334, 339, 344, 349, 354, 359, 363, 389, 393, 397, 401, 405], "executed_branches": [[237, 238], [237, 242], [254, 255], [268, 269], [268, 272], [275, 276], [275, 281], [282, 284], [282, 292], [294, 295]], "missing_branches": [[254, 259], [294, 303]]}, "": {"executed_lines": [3, 4, 5, 6, 7, 10, 14, 17, 20, 23, 24, 25, 28, 29, 30, 33, 34, 35, 36, 39, 40, 41, 42, 43, 46, 73, 138, 139, 144, 181, 182, 232, 264, 317, 318, 322, 323, 327, 328, 332, 333, 337, 338, 342, 343, 347, 348, 352, 353, 357, 358, 362, 388, 392, 396, 400, 404], "summary": {"covered_lines": 55, "num_statements": 91, "percent_covered": 49.549549549549546, "percent_covered_display": "50", "missing_lines": 36, "excluded_lines": 4, "num_branches": 20, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 20}, "missing_lines": [56, 57, 60, 61, 63, 64, 65, 66, 68, 69, 70, 94, 95, 96, 99, 105, 108, 110, 112, 113, 114, 115, 117, 118, 119, 120, 122, 123, 125, 127, 128, 130, 131, 132, 134, 135], "excluded_lines": [40, 47, 78, 139], "executed_branches": [], "missing_branches": [[56, 57], [56, 60], [63, 64], [63, 68], [95, 96], [95, 99], [110, 112], [110, 118], [112, 113], [112, 114], [114, 115], [114, 117], [118, 119], [118, 120], [120, 122], [120, 127], [122, 123], [122, 125], [130, 131], [130, 134]]}}}, "pyWebLayout/style/page_style.py": {"executed_lines": [1, 2, 5, 6, 7, 12, 13, 16, 17, 18, 21, 24, 27, 29, 30, 31, 33, 34, 35, 37, 38, 39, 41, 42, 43, 45, 46, 48, 50, 51, 53, 55, 56, 58], "summary": {"covered_lines": 33, "num_statements": 33, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 4, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [7, 47, 52, 57], "executed_branches": [], "missing_branches": [], "functions": {"PageStyle.padding_top": {"executed_lines": [31], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "PageStyle.padding_right": {"executed_lines": [35], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "PageStyle.padding_bottom": {"executed_lines": [39], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "PageStyle.padding_left": {"executed_lines": [43], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [], "executed_branches": [], "missing_branches": []}, "PageStyle.total_horizontal_padding": {"executed_lines": [48], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [47], "executed_branches": [], "missing_branches": []}, "PageStyle.total_vertical_padding": {"executed_lines": [53], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [52], "executed_branches": [], "missing_branches": []}, "PageStyle.total_border_width": {"executed_lines": [58], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [57], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 5, 6, 7, 12, 13, 16, 17, 18, 21, 24, 27, 29, 30, 33, 34, 37, 38, 41, 42, 45, 46, 50, 51, 55, 56], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [7], "executed_branches": [], "missing_branches": []}}, "classes": {"PageStyle": {"executed_lines": [31, 35, 39, 43, 48, 53, 58], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 3, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [47, 52, 57], "executed_branches": [], "missing_branches": []}, "": {"executed_lines": [1, 2, 5, 6, 7, 12, 13, 16, 17, 18, 21, 24, 27, 29, 30, 33, 34, 37, 38, 41, 42, 45, 46, 50, 51, 55, 56], "summary": {"covered_lines": 26, "num_statements": 26, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 1, "num_branches": 0, "num_partial_branches": 0, "covered_branches": 0, "missing_branches": 0}, "missing_lines": [], "excluded_lines": [7], "executed_branches": [], "missing_branches": []}}}}, "totals": {"covered_lines": 4237, "num_statements": 5148, "percent_covered": 78.87010676156584, "percent_covered_display": "79", "missing_lines": 911, "excluded_lines": 746, "num_branches": 1596, "num_partial_branches": 234, "covered_branches": 1082, "missing_branches": 514}} \ No newline at end of file diff --git a/cov_info/coverage.svg b/cov_info/coverage.svg new file mode 100644 index 0000000..4033e89 --- /dev/null +++ b/cov_info/coverage.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 79% + 79% + + diff --git a/cov_info/coverage.xml b/cov_info/coverage.xml new file mode 100644 index 0000000..d7e763e --- /dev/null +++ b/cov_info/coverage.xml @@ -0,0 +1,5373 @@ + + + + + + /tmp/act_runner/0be8a4ff27b796db/hostexecutor/pyWebLayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cov_info/htmlcov/.gitignore b/cov_info/htmlcov/.gitignore new file mode 100644 index 0000000..ccccf14 --- /dev/null +++ b/cov_info/htmlcov/.gitignore @@ -0,0 +1,2 @@ +# Created by coverage.py +* diff --git a/cov_info/htmlcov/class_index.html b/cov_info/htmlcov/class_index.html new file mode 100644 index 0000000..5f6fdc1 --- /dev/null +++ b/cov_info/htmlcov/class_index.html @@ -0,0 +1,1453 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 79% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedbranchespartialcoverage
pyWebLayout/__init__.py(no class)10100100%
pyWebLayout/abstract/__init__.py(no class)50100100%
pyWebLayout/abstract/block.pyBlockType00000100%
pyWebLayout/abstract/block.pyBlock30200100%
pyWebLayout/abstract/block.pyParagraph1910104048%
pyWebLayout/abstract/block.pyHeadingLevel00000100%
pyWebLayout/abstract/block.pyHeading10540050%
pyWebLayout/abstract/block.pyQuote9740022%
pyWebLayout/abstract/block.pyCodeBlock14574061%
pyWebLayout/abstract/block.pyListStyle00000100%
pyWebLayout/abstract/block.pyHList198102062%
pyWebLayout/abstract/block.pyListItem12760042%
pyWebLayout/abstract/block.pyTableCell1810100044%
pyWebLayout/abstract/block.pyTableRow16882056%
pyWebLayout/abstract/block.pyTable3581316084%
pyWebLayout/abstract/block.pyImage92171730580%
pyWebLayout/abstract/block.pyLinkedImage19474178%
pyWebLayout/abstract/block.pyHorizontalRule6522012%
pyWebLayout/abstract/block.pyPageBreak6522012%
pyWebLayout/abstract/block.py(no class)21101700100%
pyWebLayout/abstract/document.pyMetadataType00000100%
pyWebLayout/abstract/document.pyDocument75232624366%
pyWebLayout/abstract/document.pyChapter2312104041%
pyWebLayout/abstract/document.pyBook18078196%
pyWebLayout/abstract/document.py(no class)780400100%
pyWebLayout/abstract/functional.pyLinkType00000100%
pyWebLayout/abstract/functional.pyLink14172094%
pyWebLayout/abstract/functional.pyButton14182094%
pyWebLayout/abstract/functional.pyForm16182094%
pyWebLayout/abstract/functional.pyFormFieldType00000100%
pyWebLayout/abstract/functional.pyFormField1601000100%
pyWebLayout/abstract/functional.py(no class)840600100%
pyWebLayout/abstract/inline.pyWord4909240100%
pyWebLayout/abstract/inline.pyFormattedSpan2306100100%
pyWebLayout/abstract/inline.pyLinkedWord17174190%
pyWebLayout/abstract/inline.pyLineBreak130360100%
pyWebLayout/abstract/inline.py(no class)550400100%
pyWebLayout/abstract/interactive_image.pyInteractiveImage234512374%
pyWebLayout/abstract/interactive_image.py(no class)110200100%
pyWebLayout/concrete/__init__.py(no class)70100100%
pyWebLayout/concrete/box.pyBox100020100%
pyWebLayout/concrete/box.py(no class)90100100%
pyWebLayout/concrete/dynamic_page.pySizeConstraints00000100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage1494214841964%
pyWebLayout/concrete/dynamic_page.py(no class)290300100%
pyWebLayout/concrete/functional.pyLinkText369618672%
pyWebLayout/concrete/functional.pyButtonText49898381%
pyWebLayout/concrete/functional.pyFormFieldText360760100%
pyWebLayout/concrete/functional.py(no class)440600100%
pyWebLayout/concrete/image.pyRenderableImage11381036492%
pyWebLayout/concrete/image.py(no class)210100100%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler363661400%
pyWebLayout/concrete/interaction_handler.pyInteractionStateManager444452600%
pyWebLayout/concrete/interaction_handler.py(no class)19193000%
pyWebLayout/concrete/page.pyPage156543074859%
pyWebLayout/concrete/page.py(no class)480100100%
pyWebLayout/concrete/table.pyTableStyle00000100%
pyWebLayout/concrete/table.pyTableCellRenderer131634561048%
pyWebLayout/concrete/table.pyTableRowRenderer24026197%
pyWebLayout/concrete/table.pyTableRenderer11113840985%
pyWebLayout/concrete/table.py(no class)370500100%
pyWebLayout/concrete/text.pyAlignmentHandler00100100%
pyWebLayout/concrete/text.pyLeftAlignmentHandler14118191%
pyWebLayout/concrete/text.pyCenterRightAlignmentHandler1901100100%
pyWebLayout/concrete/text.pyJustifyAlignmentHandler15316276%
pyWebLayout/concrete/text.pyText4771410281%
pyWebLayout/concrete/text.pyLine13211748691%
pyWebLayout/concrete/text.py(no class)560600100%
pyWebLayout/core/__init__.py(no class)20100100%
pyWebLayout/core/base.pyRenderable10100100%
pyWebLayout/core/base.pyInteractable4122167%
pyWebLayout/core/base.pyLayoutable00100100%
pyWebLayout/core/base.pyQueriable30100100%
pyWebLayout/core/base.pyHierarchical40200100%
pyWebLayout/core/base.pyGeometric8350062%
pyWebLayout/core/base.pyStyleable4120075%
pyWebLayout/core/base.pyFontRegistry2001100100%
pyWebLayout/core/base.pyMetadataContainer40200100%
pyWebLayout/core/base.pyBlockContainer201448125%
pyWebLayout/core/base.pyContainerAware992800%
pyWebLayout/core/base.py(no class)571102197%
pyWebLayout/core/callback_registry.pyCallbackRegistry5741818391%
pyWebLayout/core/callback_registry.py(no class)180300100%
pyWebLayout/core/highlight.pyHighlightColor00000100%
pyWebLayout/core/highlight.pyHighlight40320100%
pyWebLayout/core/highlight.pyHighlightManager4251010090%
pyWebLayout/core/highlight.py(no class)490520100%
pyWebLayout/core/query.pyQueryResult10100100%
pyWebLayout/core/query.pySelectionRange30300100%
pyWebLayout/core/query.py(no class)29132194%
pyWebLayout/io/__init__.py(no class)00100100%
pyWebLayout/io/readers/__init__.py(no class)20100100%
pyWebLayout/io/readers/epub_reader.pyEPUBReader25171141322769%
pyWebLayout/io/readers/epub_reader.py(no class)35542081%
pyWebLayout/io/readers/html_extraction.pyStyleContext60600100%
pyWebLayout/io/readers/html_extraction.py(no class)41830282543588%
pyWebLayout/layout/__init__.py(no class)00100100%
pyWebLayout/layout/document_layouter.pyDocumentLayouter3410724266%
pyWebLayout/layout/document_layouter.py(no class)182329721379%
pyWebLayout/layout/ereader_layout.pyRenderingPosition70520100%
pyWebLayout/layout/ereader_layout.pyChapterInfo40000100%
pyWebLayout/layout/ereader_layout.pyChapterNavigator351524295%
pyWebLayout/layout/ereader_layout.pyFontFamilyOverride772400%
pyWebLayout/layout/ereader_layout.pyFontScaler11126188%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter1452112721382%
pyWebLayout/layout/ereader_layout.py(no class)708104286%
pyWebLayout/layout/ereader_manager.pyBookmarkManager43496092%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager184444056878%
pyWebLayout/layout/ereader_manager.py(no class)650400100%
pyWebLayout/layout/page_buffer.pyPageBuffer10681448789%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer4611714272%
pyWebLayout/layout/page_buffer.py(no class)43842078%
pyWebLayout/layout/table_optimizer.py(no class)15114968988%
pyWebLayout/style/__init__.py(no class)60100100%
pyWebLayout/style/abstract_style.pyFontFamily00000100%
pyWebLayout/style/abstract_style.pyFontSize11111400%
pyWebLayout/style/abstract_style.pyAbstractStyle15942141%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry4771216679%
pyWebLayout/style/abstract_style.py(no class)570500100%
pyWebLayout/style/alignment.pyAlignment111000%
pyWebLayout/style/alignment.py(no class)100200100%
pyWebLayout/style/concrete_style.pyRenderingContext00000100%
pyWebLayout/style/concrete_style.pyConcreteStyle10100100%
pyWebLayout/style/concrete_style.pyStyleResolver1376412701252%
pyWebLayout/style/concrete_style.pyConcreteStyleRegistry12252086%
pyWebLayout/style/concrete_style.py(no class)570500100%
pyWebLayout/style/fonts.pyFontWeight00000100%
pyWebLayout/style/fonts.pyFontStyle00000100%
pyWebLayout/style/fonts.pyTextDecoration00000100%
pyWebLayout/style/fonts.pyBundledFont00000100%
pyWebLayout/style/fonts.pyFont7071912289%
pyWebLayout/style/fonts.py(no class)9136420050%
pyWebLayout/style/page_style.pyPageStyle70300100%
pyWebLayout/style/page_style.py(no class)260100100%
Total 5148911746159623479%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/cov_info/htmlcov/coverage_html_cb_6fb7b396.js b/cov_info/htmlcov/coverage_html_cb_6fb7b396.js new file mode 100644 index 0000000..1face13 --- /dev/null +++ b/cov_info/htmlcov/coverage_html_cb_6fb7b396.js @@ -0,0 +1,733 @@ +// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// General helpers +function debounce(callback, wait) { + let timeoutId = null; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + callback.apply(this, args); + }, wait); + }; +}; + +function checkVisible(element) { + const rect = element.getBoundingClientRect(); + const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight); + const viewTop = 30; + return !(rect.bottom < viewTop || rect.top >= viewBottom); +} + +function on_click(sel, fn) { + const elt = document.querySelector(sel); + if (elt) { + elt.addEventListener("click", fn); + } +} + +// Helpers for table sorting +function getCellValue(row, column = 0) { + const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.childElementCount == 1) { + var child = cell.firstElementChild; + if (child.tagName === "A") { + child = child.firstElementChild; + } + if (child instanceof HTMLDataElement && child.value) { + return child.value; + } + } + return cell.innerText || cell.textContent; +} + +function rowComparator(rowA, rowB, column = 0) { + let valueA = getCellValue(rowA, column); + let valueB = getCellValue(rowB, column); + if (!isNaN(valueA) && !isNaN(valueB)) { + return valueA - valueB; + } + return valueA.localeCompare(valueB, undefined, {numeric: true}); +} + +function sortColumn(th) { + // Get the current sorting direction of the selected header, + // clear state on other headers and then set the new sorting direction. + const currentSortOrder = th.getAttribute("aria-sort"); + [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + var direction; + if (currentSortOrder === "none") { + direction = th.dataset.defaultSortOrder || "ascending"; + } + else if (currentSortOrder === "ascending") { + direction = "descending"; + } + else { + direction = "ascending"; + } + th.setAttribute("aria-sort", direction); + + const column = [...th.parentElement.cells].indexOf(th) + + // Sort all rows and afterwards append them in order to move them in the DOM. + Array.from(th.closest("table").querySelectorAll("tbody tr")) + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr)); + + // Save the sort order for next time. + if (th.id !== "region") { + let th_id = "file"; // Sort by file if we don't have a column id + let current_direction = direction; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)) + } + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ + "th_id": th.id, + "direction": current_direction + })); + if (th.id !== th_id || document.getElementById("region")) { + // Sort column has changed, unset sorting by function or class. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": false, + "region_direction": current_direction + })); + } + } + else { + // Sort column has changed to by function or class, remember that. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": true, + "region_direction": direction + })); + } +} + +// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. +coverage.assign_shortkeys = function () { + document.querySelectorAll("[data-shortcut]").forEach(element => { + document.addEventListener("keypress", event => { + if (event.target.tagName.toLowerCase() === "input") { + return; // ignore keypress from search filter + } + if (event.key === element.dataset.shortcut) { + element.click(); + } + }); + }); +}; + +// Create the events for the filter box. +coverage.wire_up_filter = function () { + // Populate the filter and hide100 inputs if there are saved values for them. + const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE); + if (saved_filter_value) { + document.getElementById("filter").value = saved_filter_value; + } + const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE); + if (saved_hide100_value) { + document.getElementById("hide100").checked = JSON.parse(saved_hide100_value); + } + + // Cache elements. + const table = document.querySelector("table.index"); + const table_body_rows = table.querySelectorAll("tbody tr"); + const no_rows = document.getElementById("no_rows"); + + // Observe filter keyevents. + const filter_handler = (event => { + // Keep running total of each metric, first index contains number of shown rows + const totals = new Array(table.rows[0].cells.length).fill(0); + // Accumulate the percentage as fraction + totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection + + var text = document.getElementById("filter").value; + // Store filter value + localStorage.setItem(coverage.FILTER_STORAGE, text); + const casefold = (text === text.toLowerCase()); + const hide100 = document.getElementById("hide100").checked; + // Store hide value. + localStorage.setItem(coverage.HIDE100_STORAGE, JSON.stringify(hide100)); + + // Hide / show elements. + table_body_rows.forEach(row => { + var show = false; + // Check the text filter. + for (let column = 0; column < totals.length; column++) { + cell = row.cells[column]; + if (cell.classList.contains("name")) { + var celltext = cell.textContent; + if (casefold) { + celltext = celltext.toLowerCase(); + } + if (celltext.includes(text)) { + show = true; + } + } + } + + // Check the "hide covered" filter. + if (show && hide100) { + const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" "); + show = (numer !== denom); + } + + if (!show) { + // hide + row.classList.add("hidden"); + return; + } + + // show + row.classList.remove("hidden"); + totals[0]++; + + for (let column = 0; column < totals.length; column++) { + // Accumulate dynamic totals + cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } + if (column === totals.length - 1) { + // Last column contains percentage + const [numer, denom] = cell.dataset.ratio.split(" "); + totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection + totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection + } + else { + totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection + } + } + }); + + // Show placeholder if no rows will be displayed. + if (!totals[0]) { + // Show placeholder, hide table. + no_rows.style.display = "block"; + table.style.display = "none"; + return; + } + + // Hide placeholder, show table. + no_rows.style.display = null; + table.style.display = null; + + const footer = table.tFoot.rows[0]; + // Calculate new dynamic sum values based on visible rows. + for (let column = 0; column < totals.length; column++) { + // Get footer cell element. + const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } + + // Set value into dynamic footer cell element. + if (column === totals.length - 1) { + // Percentage column uses the numerator and denominator, + // and adapts to the number of decimal places. + const match = /\.([0-9]+)/.exec(cell.textContent); + const places = match ? match[1].length : 0; + const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection + cell.dataset.ratio = `${numer} ${denom}`; + // Check denom to prevent NaN if filtered files contain no statements + cell.textContent = denom + ? `${(numer * 100 / denom).toFixed(places)}%` + : `${(100).toFixed(places)}%`; + } + else { + cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection + } + } + }); + + document.getElementById("filter").addEventListener("input", debounce(filter_handler)); + document.getElementById("hide100").addEventListener("input", debounce(filter_handler)); + + // Trigger change event on setup, to force filter on page refresh + // (filter value may still be present). + document.getElementById("filter").dispatchEvent(new Event("input")); + document.getElementById("hide100").dispatchEvent(new Event("input")); +}; +coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE"; +coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE"; + +// Set up the click-to-sort columns. +coverage.wire_up_sorting = function () { + document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( + th => th.addEventListener("click", e => sortColumn(e.target)) + ); + + // Look for a localStorage item containing previous sort settings: + let th_id = "file", direction = "ascending"; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)); + } + let by_region = false, region_direction = "ascending"; + const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION); + if (sorted_by_region) { + ({ + by_region, + region_direction + } = JSON.parse(sorted_by_region)); + } + + const region_id = "region"; + if (by_region && document.getElementById(region_id)) { + direction = region_direction; + } + // If we are in a page that has a column with id of "region", sort on + // it if the last sort was by function or class. + let th; + if (document.getElementById(region_id)) { + th = document.getElementById(by_region ? region_id : th_id); + } + else { + th = document.getElementById(th_id); + } + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; +coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + coverage.wire_up_sorting(); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + + on_click(".button_show_hide_help", coverage.show_hide_help); +}; + +// -- pyfile stuff -- + +coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS"; + +coverage.pyfile_ready = function () { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === "t") { + document.querySelector(frag).closest(".n").classList.add("highlight"); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } + else { + coverage.set_sel(0); + } + + on_click(".button_toggle_run", coverage.toggle_lines); + on_click(".button_toggle_mis", coverage.toggle_lines); + on_click(".button_toggle_exc", coverage.toggle_lines); + on_click(".button_toggle_par", coverage.toggle_lines); + + on_click(".button_next_chunk", coverage.to_next_chunk_nicely); + on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely); + on_click(".button_top_of_page", coverage.to_top); + on_click(".button_first_chunk", coverage.to_first_chunk); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + on_click(".button_to_index", coverage.to_index); + + on_click(".button_show_hide_help", coverage.show_hide_help); + + coverage.filters = undefined; + try { + coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE); + } catch(err) {} + + if (coverage.filters) { + coverage.filters = JSON.parse(coverage.filters); + } + else { + coverage.filters = {run: false, exc: true, mis: true, par: true}; + } + + for (cls in coverage.filters) { + coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection + } + + coverage.assign_shortkeys(); + coverage.init_scroll_markers(); + coverage.wire_up_sticky_header(); + + document.querySelectorAll("[id^=ctxs]").forEach( + cbox => cbox.addEventListener("click", coverage.expand_contexts) + ); + + // Rebuild scroll markers when the window height changes. + window.addEventListener("resize", coverage.build_scroll_markers); +}; + +coverage.toggle_lines = function (event) { + const btn = event.target.closest("button"); + const category = btn.value + const show = !btn.classList.contains("show_" + category); + coverage.set_line_visibilty(category, show); + coverage.build_scroll_markers(); + coverage.filters[category] = show; + try { + localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters)); + } catch(err) {} +}; + +coverage.set_line_visibilty = function (category, should_show) { + const cls = "show_" + category; + const btn = document.querySelector(".button_toggle_" + category); + if (btn) { + if (should_show) { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls)); + btn.classList.add(cls); + } + else { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls)); + btn.classList.remove(cls); + } + } +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return document.getElementById("t" + n)?.closest("p"); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +coverage.to_prev_file = function () { + window.location = document.getElementById("prevFileLink").href; +} + +coverage.to_next_file = function () { + window.location = document.getElementById("nextFileLink").href; +} + +coverage.to_index = function () { + location.href = document.getElementById("indexLink").href; +} + +coverage.show_hide_help = function () { + const helpCheck = document.getElementById("help_panel_state") + helpCheck.checked = !helpCheck.checked; +} + +// Return a string indicating what kind of chunk this line belongs to, +// or null if not a chunk. +coverage.chunk_indicator = function (line_elt) { + const classes = line_elt?.className; + if (!classes) { + return null; + } + const match = classes.match(/\bshow_\w+\b/); + if (!match) { + return null; + } + return match[0]; +}; + +coverage.to_next_chunk = function () { + const c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + var chunk_indicator, probe_line; + while (true) { + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + if (chunk_indicator) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_indicator = chunk_indicator; + while (next_indicator === chunk_indicator) { + probe++; + probe_line = c.line_elt(probe); + next_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + const c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + var chunk_indicator = c.chunk_indicator(probe_line); + while (probe > 1 && !chunk_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + var prev_indicator = chunk_indicator; + while (prev_indicator === chunk_indicator) { + probe--; + if (probe <= 0) { + return; + } + probe_line = c.line_elt(probe); + prev_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(probe+1, end); + c.show_selection(); +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + const begin = coverage.line_elt(coverage.sel_begin); + const end = coverage.line_elt(coverage.sel_end-1); + + return ( + (checkVisible(begin) ? 1 : 0) + + (checkVisible(end) ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the top line on the screen as selection. + + // This will select the top-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(0, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(1); + } + else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the lowest line on the screen as selection. + + // This will select the bottom-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(coverage.lines_len); + } + else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (!probe_line) { + return; + } + var the_indicator = c.chunk_indicator(probe_line); + if (the_indicator) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var indicator = the_indicator; + while (probe > 0 && indicator === the_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + break; + } + indicator = c.chunk_indicator(probe_line); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + indicator = the_indicator; + while (indicator === the_indicator) { + probe++; + probe_line = c.line_elt(probe); + indicator = c.chunk_indicator(probe_line); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + // Highlight the lines in the chunk + document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight")); + for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) { + coverage.line_elt(probe).querySelector(".n").classList.add("highlight"); + } + + coverage.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + const element = coverage.line_elt(coverage.sel_begin); + coverage.scroll_window(element.offsetTop - 60); + } +}; + +coverage.scroll_window = function (to_pos) { + window.scroll({top: to_pos, behavior: "smooth"}); +}; + +coverage.init_scroll_markers = function () { + // Init some variables + coverage.lines_len = document.querySelectorAll("#source > p").length; + + // Build html + coverage.build_scroll_markers(); +}; + +coverage.build_scroll_markers = function () { + const temp_scroll_marker = document.getElementById("scroll_marker") + if (temp_scroll_marker) temp_scroll_marker.remove(); + // Don't build markers if the window has no scroll bar. + if (document.body.scrollHeight <= window.innerHeight) { + return; + } + + const marker_scale = window.innerHeight / document.body.scrollHeight; + const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10); + + let previous_line = -99, last_mark, last_top; + + const scroll_marker = document.createElement("div"); + scroll_marker.id = "scroll_marker"; + document.getElementById("source").querySelectorAll( + "p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par" + ).forEach(element => { + const line_top = Math.floor(element.offsetTop * marker_scale); + const line_number = parseInt(element.querySelector(".n a").id.substr(1)); + + if (line_number === previous_line + 1) { + // If this solid missed block just make previous mark higher. + last_mark.style.height = `${line_top + line_height - last_top}px`; + } + else { + // Add colored line in scroll_marker block. + last_mark = document.createElement("div"); + last_mark.id = `m${line_number}`; + last_mark.classList.add("marker"); + last_mark.style.height = `${line_height}px`; + last_mark.style.top = `${line_top}px`; + scroll_marker.append(last_mark); + last_top = line_top; + } + + previous_line = line_number; + }); + + // Append last to prevent layout calculation + document.body.append(scroll_marker); +}; + +coverage.wire_up_sticky_header = function () { + const header = document.querySelector("header"); + const header_bottom = ( + header.querySelector(".content h2").getBoundingClientRect().top - + header.getBoundingClientRect().top + ); + + function updateHeader() { + if (window.scrollY > header_bottom) { + header.classList.add("sticky"); + } + else { + header.classList.remove("sticky"); + } + } + + window.addEventListener("scroll", updateHeader); + updateHeader(); +}; + +coverage.expand_contexts = function (e) { + var ctxs = e.target.parentNode.querySelector(".ctxs"); + + if (!ctxs.classList.contains("expanded")) { + var ctxs_text = ctxs.textContent; + var width = Number(ctxs_text[0]); + ctxs.textContent = ""; + for (var i = 1; i < ctxs_text.length; i += width) { + key = ctxs_text.substring(i, i + width).trim(); + ctxs.appendChild(document.createTextNode(contexts[key])); + ctxs.appendChild(document.createElement("br")); + } + ctxs.classList.add("expanded"); + } +}; + +document.addEventListener("DOMContentLoaded", () => { + if (document.body.classList.contains("indexfile")) { + coverage.index_ready(); + } + else { + coverage.pyfile_ready(); + } +}); diff --git a/cov_info/htmlcov/favicon_32_cb_58284776.png b/cov_info/htmlcov/favicon_32_cb_58284776.png new file mode 100644 index 0000000000000000000000000000000000000000..8649f0475d8d20793b2ec431fe25a186a414cf10 GIT binary patch literal 1732 zcmV;#20QtQP)K2KOkBOVxIZChq#W-v7@TU%U6P(wycKT1hUJUToW3ke1U1ONa4 z000000000000000bb)GRa9mqwR9|UWHy;^RUrt?IT__Y0JUcxmBP0(51q1>E00030 z|NrOz)aw7%8sJzM<5^g%z7^qE`}_Ot|JUUG(NUkWzR|7K?Zo%@_v-8G-1N%N=D$;; zw;keH4dGY$`1t4M=HK_s*zm^0#KgqfwWhe3qO_HtvXYvtjgX>;-~C$L`&k>^R)9)7 zdPh2TL^pCnHC#0+_4D)M`p?qp!pq{jO_{8;$fbaflbx`Tn52n|n}8VFRTA1&ugOP< zPd{uvFjz7t*Vot1&d$l-xWCk}s;sQL&#O(Bskh6gqNJv>#iB=ypG1e3K!K4yc7!~M zfj4S*g^zZ7eP$+_Sl07Z646l;%urinP#D8a6TwRtnLIRcI!r4f@bK~9-`~;E(N?Lv zSEst7s;rcxsi~}{Nsytfz@MtUoR*iFc8!#vvx}Umhm4blk(_~MdVD-@dW&>!Nn~ro z_E~-ESVQAj6Wmn;(olz(O&_{U2*pZBc1aYjMh>Dq3z|6`jW`RDHV=t3I6yRKJ~LOX zz_z!!vbVXPqob#=pj3^VMT?x6t(irRmSKsMo1~LLkB&=#j!=M%NP35mfqim$drWb9 zYIb>no_LUwc!r^NkDzs4YHu@=ZHRzrafWDZd1EhEVq=tGX?tK$pIa)DTh#bkvh!J- z?^%@YS!U*0E8$q$_*aOTQ&)Ra64g>ep;BdcQgvlg8qQHrP*E$;P{-m=A*@axn@$bO zO-Y4JzS&EAi%YG}N?cn?YFS7ivPY=EMV6~YH;+Xxu|tefLS|Aza)Cg6us#)=JW!uH zQa?H>d^j+YHCtyjL^LulF*05|F$RG!AX_OHVI&MtA~_@=5_lU|0000rbW%=J06GH4 z^5LD8b8apw8vNh1ua1mF{{Hy)_U`NA;Nacc+sCpuHXa-V{r&yz?c(9#+}oX+NmiRW z+W-IqK1oDDR5;6GfCDCOP5}iL5fK(cB~ET81`MFgF2kGa9AjhSIk~-E-4&*tPPKdiilQJ11k_J082ZS z>@TvivP!5ZFG?t@{t+GpR3XR&@*hA_VE1|Lo8@L@)l*h(Z@=?c-NS$Fk&&61IzUU9 z*nPqBM=OBZ-6ka1SJgGAS-Us5EN)r#dUX%>wQZLa2ytPCtMKp)Ob z*xcu38Z&d5<-NBS)@jRD+*!W*cf-m_wmxDEqBf?czI%3U0J$Xik;lA`jg}VH?(S(V zE!M3;X2B8w0TnnW&6(8;_Uc)WD;Ms6PKP+s(sFgO!}B!^ES~GDt4qLPxwYB)^7)XA zZwo9zDy-B0B+jT6V=!=bo(zs_8{eBA78gT9GH$(DVhz;4VAYwz+bOIdZ-PNb|I&rl z^XG=vFLF)1{&nT2*0vMz#}7^9hXzzf&ZdKlEj{LihP;|;Ywqn35ajP?H?7t|i-Un% z&&kxee@9B{nwgv1+S-~0)E1{ob1^Wn`F2isurqThKK=3%&;`@{0{!D- z&CSj80t;uPu&FaJFtSXKH#ajgGj}=sEad7US6jP0|Db@0j)?(5@sf<7`~a9>s;wCa zm^)spe{uxGFmrJYI9cOh7s$>8Npkt-5EWB1UKc`{W{y5Ce$1+nM9Cr;);=Ju#N^62OSlJMn7omiUgP&ErsYzT~iGxcW aE(`!K@+CXylaC4j0000 + + + + Coverage report + + + + + +
+
+

Coverage report: + 79% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedbranchespartialcoverage
pyWebLayout/__init__.py(no function)10100100%
pyWebLayout/abstract/__init__.py(no function)50100100%
pyWebLayout/abstract/block.pyBlock.__init__20100100%
pyWebLayout/abstract/block.pyBlock.block_type10100100%
pyWebLayout/abstract/block.pyParagraph.__init__30100100%
pyWebLayout/abstract/block.pyParagraph.create_and_add_to551000%
pyWebLayout/abstract/block.pyParagraph.add_word10100100%
pyWebLayout/abstract/block.pyParagraph.create_word111000%
pyWebLayout/abstract/block.pyParagraph.add_span111000%
pyWebLayout/abstract/block.pyParagraph.create_span111000%
pyWebLayout/abstract/block.pyParagraph.words10100100%
pyWebLayout/abstract/block.pyParagraph.words_iter20120100%
pyWebLayout/abstract/block.pyParagraph.spans221200%
pyWebLayout/abstract/block.pyParagraph.word_count10100100%
pyWebLayout/abstract/block.pyParagraph.__len__10000100%
pyWebLayout/abstract/block.pyHeading.__init__30100100%
pyWebLayout/abstract/block.pyHeading.create_and_add_to551000%
pyWebLayout/abstract/block.pyHeading.level10100100%
pyWebLayout/abstract/block.pyHeading.level10100100%
pyWebLayout/abstract/block.pyQuote.__init__20100100%
pyWebLayout/abstract/block.pyQuote.create_and_add_to551000%
pyWebLayout/abstract/block.pyQuote.style111000%
pyWebLayout/abstract/block.pyQuote.style111000%
pyWebLayout/abstract/block.pyCodeBlock.__init__30100100%
pyWebLayout/abstract/block.pyCodeBlock.create_and_add_to551200%
pyWebLayout/abstract/block.pyCodeBlock.language10100100%
pyWebLayout/abstract/block.pyCodeBlock.language10100100%
pyWebLayout/abstract/block.pyCodeBlock.add_line10100100%
pyWebLayout/abstract/block.pyCodeBlock.lines20120100%
pyWebLayout/abstract/block.pyCodeBlock.line_count10100100%
pyWebLayout/abstract/block.pyHList.__init__40100100%
pyWebLayout/abstract/block.pyHList.create_and_add_to551000%
pyWebLayout/abstract/block.pyHList.style10100100%
pyWebLayout/abstract/block.pyHList.style10100100%
pyWebLayout/abstract/block.pyHList.default_style111000%
pyWebLayout/abstract/block.pyHList.default_style111000%
pyWebLayout/abstract/block.pyHList.add_item20100100%
pyWebLayout/abstract/block.pyHList.create_item111000%
pyWebLayout/abstract/block.pyHList.items20120100%
pyWebLayout/abstract/block.pyHList.item_count10100100%
pyWebLayout/abstract/block.pyListItem.__init__30100100%
pyWebLayout/abstract/block.pyListItem.create_and_add_to551000%
pyWebLayout/abstract/block.pyListItem.term10100100%
pyWebLayout/abstract/block.pyListItem.term10100100%
pyWebLayout/abstract/block.pyListItem.style111000%
pyWebLayout/abstract/block.pyListItem.style111000%
pyWebLayout/abstract/block.pyTableCell.__init__50100100%
pyWebLayout/abstract/block.pyTableCell.create_and_add_to551000%
pyWebLayout/abstract/block.pyTableCell.is_header10100100%
pyWebLayout/abstract/block.pyTableCell.is_header111000%
pyWebLayout/abstract/block.pyTableCell.colspan10100100%
pyWebLayout/abstract/block.pyTableCell.colspan111000%
pyWebLayout/abstract/block.pyTableCell.rowspan10100100%
pyWebLayout/abstract/block.pyTableCell.rowspan111000%
pyWebLayout/abstract/block.pyTableCell.style111000%
pyWebLayout/abstract/block.pyTableCell.style111000%
pyWebLayout/abstract/block.pyTableRow.__init__30100100%
pyWebLayout/abstract/block.pyTableRow.create_and_add_to551000%
pyWebLayout/abstract/block.pyTableRow.style111000%
pyWebLayout/abstract/block.pyTableRow.style111000%
pyWebLayout/abstract/block.pyTableRow.add_cell20100100%
pyWebLayout/abstract/block.pyTableRow.create_cell111000%
pyWebLayout/abstract/block.pyTableRow.cells20120100%
pyWebLayout/abstract/block.pyTableRow.cell_count10100100%
pyWebLayout/abstract/block.pyTable.__init__60100100%
pyWebLayout/abstract/block.pyTable.create_and_add_to551000%
pyWebLayout/abstract/block.pyTable.caption10100100%
pyWebLayout/abstract/block.pyTable.caption10100100%
pyWebLayout/abstract/block.pyTable.style111000%
pyWebLayout/abstract/block.pyTable.style111000%
pyWebLayout/abstract/block.pyTable.add_row60140100%
pyWebLayout/abstract/block.pyTable.create_row111000%
pyWebLayout/abstract/block.pyTable.header_rows20120100%
pyWebLayout/abstract/block.pyTable.body_rows20120100%
pyWebLayout/abstract/block.pyTable.footer_rows20120100%
pyWebLayout/abstract/block.pyTable.all_rows60160100%
pyWebLayout/abstract/block.pyTable.row_count10100100%
pyWebLayout/abstract/block.pyImage.__init__50100100%
pyWebLayout/abstract/block.pyImage.create_and_add_to551200%
pyWebLayout/abstract/block.pyImage.source10100100%
pyWebLayout/abstract/block.pyImage.source10100100%
pyWebLayout/abstract/block.pyImage.alt_text10100100%
pyWebLayout/abstract/block.pyImage.alt_text10100100%
pyWebLayout/abstract/block.pyImage.width10100100%
pyWebLayout/abstract/block.pyImage.width10100100%
pyWebLayout/abstract/block.pyImage.height10100100%
pyWebLayout/abstract/block.pyImage.height10100100%
pyWebLayout/abstract/block.pyImage.get_dimensions10100100%
pyWebLayout/abstract/block.pyImage.get_aspect_ratio30120100%
pyWebLayout/abstract/block.pyImage.calculate_scaled_dimensions10216181%
pyWebLayout/abstract/block.pyImage._is_url20100100%
pyWebLayout/abstract/block.pyImage._download_to_temp16410075%
pyWebLayout/abstract/block.pyImage.load_image_data21418183%
pyWebLayout/abstract/block.pyImage.get_image_info212112385%
pyWebLayout/abstract/block.pyLinkedImage.__init__70100100%
pyWebLayout/abstract/block.pyLinkedImage.location10100100%
pyWebLayout/abstract/block.pyLinkedImage.link_type10100100%
pyWebLayout/abstract/block.pyLinkedImage.link_callback111000%
pyWebLayout/abstract/block.pyLinkedImage.params111000%
pyWebLayout/abstract/block.pyLinkedImage.link_title111000%
pyWebLayout/abstract/block.pyLinkedImage.execute_link7114182%
pyWebLayout/abstract/block.pyHorizontalRule.__init__10100100%
pyWebLayout/abstract/block.pyHorizontalRule.create_and_add_to551200%
pyWebLayout/abstract/block.pyPageBreak.__init__10100100%
pyWebLayout/abstract/block.pyPageBreak.create_and_add_to551200%
pyWebLayout/abstract/block.py(no function)21101700100%
pyWebLayout/abstract/document.pyDocument.__init__19316176%
pyWebLayout/abstract/document.pyDocument.blocks10100100%
pyWebLayout/abstract/document.pyDocument.default_style111000%
pyWebLayout/abstract/document.pyDocument.default_style111000%
pyWebLayout/abstract/document.pyDocument.add_block10100100%
pyWebLayout/abstract/document.pyDocument.create_paragraph551200%
pyWebLayout/abstract/document.pyDocument.create_heading551200%
pyWebLayout/abstract/document.pyDocument.create_chapter331200%
pyWebLayout/abstract/document.pyDocument.add_anchor10100100%
pyWebLayout/abstract/document.pyDocument.get_anchor10100100%
pyWebLayout/abstract/document.pyDocument.add_resource10100100%
pyWebLayout/abstract/document.pyDocument.get_resource10100100%
pyWebLayout/abstract/document.pyDocument.add_stylesheet10100100%
pyWebLayout/abstract/document.pyDocument.add_script10100100%
pyWebLayout/abstract/document.pyDocument.get_title10100100%
pyWebLayout/abstract/document.pyDocument.set_title10100100%
pyWebLayout/abstract/document.pyDocument.title10100100%
pyWebLayout/abstract/document.pyDocument.title111000%
pyWebLayout/abstract/document.pyDocument.find_blocks_by_type40100100%
pyWebLayout/abstract/document.pyDocument.find_blocks_by_type._find_recursive7208273%
pyWebLayout/abstract/document.pyDocument.find_headings20100100%
pyWebLayout/abstract/document.pyDocument.generate_table_of_contents100140100%
pyWebLayout/abstract/document.pyDocument.get_or_create_style20100100%
pyWebLayout/abstract/document.pyDocument.get_font_for_style10100100%
pyWebLayout/abstract/document.pyDocument.update_rendering_context111000%
pyWebLayout/abstract/document.pyDocument.get_style_registry10100100%
pyWebLayout/abstract/document.pyDocument.get_concrete_style_registry111000%
pyWebLayout/abstract/document.pyChapter.__init__60100100%
pyWebLayout/abstract/document.pyChapter.title10100100%
pyWebLayout/abstract/document.pyChapter.title10100100%
pyWebLayout/abstract/document.pyChapter.level10100100%
pyWebLayout/abstract/document.pyChapter.blocks10100100%
pyWebLayout/abstract/document.pyChapter.style111000%
pyWebLayout/abstract/document.pyChapter.style111000%
pyWebLayout/abstract/document.pyChapter.add_block10100100%
pyWebLayout/abstract/document.pyChapter.create_paragraph551200%
pyWebLayout/abstract/document.pyChapter.create_heading551200%
pyWebLayout/abstract/document.pyBook.__init__40120100%
pyWebLayout/abstract/document.pyBook.chapters10100100%
pyWebLayout/abstract/document.pyBook.add_chapter10100100%
pyWebLayout/abstract/document.pyBook.create_chapter5012186%
pyWebLayout/abstract/document.pyBook.get_author10100100%
pyWebLayout/abstract/document.pyBook.set_author10100100%
pyWebLayout/abstract/document.pyBook.generate_table_of_contents50140100%
pyWebLayout/abstract/document.py(no function)780400100%
pyWebLayout/abstract/functional.pyLink.__init__60100100%
pyWebLayout/abstract/functional.pyLink.location10100100%
pyWebLayout/abstract/functional.pyLink.link_type10100100%
pyWebLayout/abstract/functional.pyLink.params10100100%
pyWebLayout/abstract/functional.pyLink.title10100100%
pyWebLayout/abstract/functional.pyLink.html_id111000%
pyWebLayout/abstract/functional.pyLink.execute30120100%
pyWebLayout/abstract/functional.pyButton.__init__50100100%
pyWebLayout/abstract/functional.pyButton.label10100100%
pyWebLayout/abstract/functional.pyButton.label10100100%
pyWebLayout/abstract/functional.pyButton.enabled10100100%
pyWebLayout/abstract/functional.pyButton.enabled10100100%
pyWebLayout/abstract/functional.pyButton.params10100100%
pyWebLayout/abstract/functional.pyButton.html_id111000%
pyWebLayout/abstract/functional.pyButton.execute30120100%
pyWebLayout/abstract/functional.pyForm.__init__50100100%
pyWebLayout/abstract/functional.pyForm.form_id10100100%
pyWebLayout/abstract/functional.pyForm.action10100100%
pyWebLayout/abstract/functional.pyForm.html_id111000%
pyWebLayout/abstract/functional.pyForm.add_field20100100%
pyWebLayout/abstract/functional.pyForm.get_field10100100%
pyWebLayout/abstract/functional.pyForm.get_values10100100%
pyWebLayout/abstract/functional.pyForm.execute40120100%
pyWebLayout/abstract/functional.pyFormField.__init__70100100%
pyWebLayout/abstract/functional.pyFormField.name10100100%
pyWebLayout/abstract/functional.pyFormField.field_type10100100%
pyWebLayout/abstract/functional.pyFormField.label10100100%
pyWebLayout/abstract/functional.pyFormField.value10100100%
pyWebLayout/abstract/functional.pyFormField.value10100100%
pyWebLayout/abstract/functional.pyFormField.required10100100%
pyWebLayout/abstract/functional.pyFormField.options10100100%
pyWebLayout/abstract/functional.pyFormField.form10100100%
pyWebLayout/abstract/functional.pyFormField.form10100100%
pyWebLayout/abstract/functional.py(no function)840600100%
pyWebLayout/abstract/inline.pyWord.__init__80120100%
pyWebLayout/abstract/inline.pyWord.create_and_add_to3201220100%
pyWebLayout/abstract/inline.pyWord.add_concete10000100%
pyWebLayout/abstract/inline.pyWord.text10100100%
pyWebLayout/abstract/inline.pyWord.style10100100%
pyWebLayout/abstract/inline.pyWord.background10100100%
pyWebLayout/abstract/inline.pyWord.previous10100100%
pyWebLayout/abstract/inline.pyWord.next10100100%
pyWebLayout/abstract/inline.pyWord.add_next10100100%
pyWebLayout/abstract/inline.pyWord.possible_hyphenation20100100%
pyWebLayout/abstract/inline.pyFormattedSpan.__init__30100100%
pyWebLayout/abstract/inline.pyFormattedSpan.create_and_add_to110180100%
pyWebLayout/abstract/inline.pyFormattedSpan.style10100100%
pyWebLayout/abstract/inline.pyFormattedSpan.background10100100%
pyWebLayout/abstract/inline.pyFormattedSpan.words10100100%
pyWebLayout/abstract/inline.pyFormattedSpan.add_word60120100%
pyWebLayout/abstract/inline.pyLinkedWord.__init__60100100%
pyWebLayout/abstract/inline.pyLinkedWord.location10100100%
pyWebLayout/abstract/inline.pyLinkedWord.link_type10100100%
pyWebLayout/abstract/inline.pyLinkedWord.link_callback10100100%
pyWebLayout/abstract/inline.pyLinkedWord.params10100100%
pyWebLayout/abstract/inline.pyLinkedWord.link_title10100100%
pyWebLayout/abstract/inline.pyLinkedWord.execute_link6114180%
pyWebLayout/abstract/inline.pyLineBreak.__init__30100100%
pyWebLayout/abstract/inline.pyLineBreak.block_type10100100%
pyWebLayout/abstract/inline.pyLineBreak.create_and_add_to90160100%
pyWebLayout/abstract/inline.py(no function)550400100%
pyWebLayout/abstract/interactive_image.pyInteractiveImage.__init__40100100%
pyWebLayout/abstract/interactive_image.pyInteractiveImage.interact40140100%
pyWebLayout/abstract/interactive_image.pyInteractiveImage.in_object30100100%
pyWebLayout/abstract/interactive_image.pyInteractiveImage.create_and_add_to10418350%
pyWebLayout/abstract/interactive_image.pyInteractiveImage.set_rendered_bounds20100100%
pyWebLayout/abstract/interactive_image.py(no function)110200100%
pyWebLayout/concrete/__init__.py(no function)70100100%
pyWebLayout/concrete/box.pyBox.__init__90020100%
pyWebLayout/concrete/box.pyBox.in_shape10000100%
pyWebLayout/concrete/box.py(no function)90100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.__init__90100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.constraints10100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.measure3010120468%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.get_min_width237118561%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.get_preferred_width298122665%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.measure_content_height13618148%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.layout70100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.render7116277%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.render_partial1510110124%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.has_more_content20100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.reset_pagination10100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.invalidate_caches60100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.add_child30100100%
pyWebLayout/concrete/dynamic_page.pyDynamicPage.clear_children30100100%
pyWebLayout/concrete/dynamic_page.py(no function)290300100%
pyWebLayout/concrete/functional.pyLinkText.__init__171110289%
pyWebLayout/concrete/functional.pyLinkText.link10100100%
pyWebLayout/concrete/functional.pyLinkText.set_hovered20100100%
pyWebLayout/concrete/functional.pyLinkText.set_pressed221000%
pyWebLayout/concrete/functional.pyLinkText._mark_page_dirty2112150%
pyWebLayout/concrete/functional.pyLinkText.render12516356%
pyWebLayout/concrete/functional.pyButtonText.__init__100100100%
pyWebLayout/concrete/functional.pyButtonText.button10100100%
pyWebLayout/concrete/functional.pyButtonText.size10100100%
pyWebLayout/concrete/functional.pyButtonText.set_pressed20100100%
pyWebLayout/concrete/functional.pyButtonText.set_hovered20100100%
pyWebLayout/concrete/functional.pyButtonText.set_page111000%
pyWebLayout/concrete/functional.pyButtonText._mark_page_dirty2112150%
pyWebLayout/concrete/functional.pyButtonText.render27616276%
pyWebLayout/concrete/functional.pyButtonText.in_object30100100%
pyWebLayout/concrete/functional.pyFormFieldText.__init__80100100%
pyWebLayout/concrete/functional.pyFormFieldText.field10100100%
pyWebLayout/concrete/functional.pyFormFieldText.size10100100%
pyWebLayout/concrete/functional.pyFormFieldText.set_focused10100100%
pyWebLayout/concrete/functional.pyFormFieldText.render170140100%
pyWebLayout/concrete/functional.pyFormFieldText.handle_click50120100%
pyWebLayout/concrete/functional.pyFormFieldText.in_object30100100%
pyWebLayout/concrete/functional.pycreate_link_text10100100%
pyWebLayout/concrete/functional.pycreate_button_text10100100%
pyWebLayout/concrete/functional.pycreate_form_field_text10100100%
pyWebLayout/concrete/functional.py(no function)410300100%
pyWebLayout/concrete/image.pyRenderableImage.__init__150140100%
pyWebLayout/concrete/image.pyRenderableImage.origin10100100%
pyWebLayout/concrete/image.pyRenderableImage.size10100100%
pyWebLayout/concrete/image.pyRenderableImage.width10100100%
pyWebLayout/concrete/image.pyRenderableImage.set_origin10100100%
pyWebLayout/concrete/image.pyRenderableImage._load_image23518181%
pyWebLayout/concrete/image.pyRenderableImage.render1801100100%
pyWebLayout/concrete/image.pyRenderableImage._resize_image15114189%
pyWebLayout/concrete/image.pyRenderableImage._draw_error_placeholder352110291%
pyWebLayout/concrete/image.pyRenderableImage.in_object30100100%
pyWebLayout/concrete/image.py(no function)210100100%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler.__init__221000%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler.set_pressed_state551400%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler.set_hovered_state551400%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler.render_current_state111000%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler.execute_with_feedback11111400%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler.execute_async_with_feedback771000%
pyWebLayout/concrete/interaction_handler.pyInteractionHandler.execute_async_with_feedback.execute_callback550200%
pyWebLayout/concrete/interaction_handler.pyInteractionStateManager.__init__331000%
pyWebLayout/concrete/interaction_handler.pyInteractionStateManager.update_hover171711200%
pyWebLayout/concrete/interaction_handler.pyInteractionStateManager.handle_mouse_down991400%
pyWebLayout/concrete/interaction_handler.pyInteractionStateManager.handle_mouse_up991600%
pyWebLayout/concrete/interaction_handler.pyInteractionStateManager.reset661400%
pyWebLayout/concrete/interaction_handler.py(no function)19193000%
pyWebLayout/concrete/page.pyPage.__init__90100100%
pyWebLayout/concrete/page.pyPage.free_space10100100%
pyWebLayout/concrete/page.pyPage.can_fit_line50120100%
pyWebLayout/concrete/page.pyPage.size10100100%
pyWebLayout/concrete/page.pyPage.canvas_size20100100%
pyWebLayout/concrete/page.pyPage.content_size20100100%
pyWebLayout/concrete/page.pyPage.border_size10100100%
pyWebLayout/concrete/page.pyPage.available_width10100100%
pyWebLayout/concrete/page.pyPage.style10100100%
pyWebLayout/concrete/page.pyPage.callbacks10100100%
pyWebLayout/concrete/page.pyPage.is_dirty111000%
pyWebLayout/concrete/page.pyPage.mark_dirty111000%
pyWebLayout/concrete/page.pyPage.mark_clean111000%
pyWebLayout/concrete/page.pyPage.draw40120100%
pyWebLayout/concrete/page.pyPage.add_child40100100%
pyWebLayout/concrete/page.pyPage.remove_child60100100%
pyWebLayout/concrete/page.pyPage.clear_children50100100%
pyWebLayout/concrete/page.pyPage.children10100100%
pyWebLayout/concrete/page.pyPage._get_child_property181812000%
pyWebLayout/concrete/page.pyPage._get_child_height771400%
pyWebLayout/concrete/page.pyPage.render_children7118280%
pyWebLayout/concrete/page.pyPage.render50100100%
pyWebLayout/concrete/page.pyPage._create_canvas70120100%
pyWebLayout/concrete/page.pyPage._get_child_position331000%
pyWebLayout/concrete/page.pyPage.query_point11118189%
pyWebLayout/concrete/page.pyPage._point_in_child10101400%
pyWebLayout/concrete/page.pyPage._get_child_size991400%
pyWebLayout/concrete/page.pyPage._make_query_result12116189%
pyWebLayout/concrete/page.pyPage.query_range191114485%
pyWebLayout/concrete/page.pyPage.in_object10100100%
pyWebLayout/concrete/page.py(no function)480100100%
pyWebLayout/concrete/table.pyTableCellRenderer.__init__70100100%
pyWebLayout/concrete/table.pyTableCellRenderer.render130120100%
pyWebLayout/concrete/table.pyTableCellRenderer._render_cell_content64161321071%
pyWebLayout/concrete/table.pyTableCellRenderer._render_image_in_cell474712200%
pyWebLayout/concrete/table.pyTableRowRenderer.__init__100100100%
pyWebLayout/concrete/table.pyTableRowRenderer.render14016195%
pyWebLayout/concrete/table.pyTableRenderer.__init__100100100%
pyWebLayout/concrete/table.pyTableRenderer._calculate_dimensions12114188%
pyWebLayout/concrete/table.pyTableRenderer._calculate_row_height_for_section365118581%
pyWebLayout/concrete/table.pyTableRenderer._estimate_wrapped_lines245110376%
pyWebLayout/concrete/table.pyTableRenderer.render170180100%
pyWebLayout/concrete/table.pyTableRenderer._render_caption10210080%
pyWebLayout/concrete/table.pyTableRenderer.height10100100%
pyWebLayout/concrete/table.pyTableRenderer.width10100100%
pyWebLayout/concrete/table.py(no function)370500100%
pyWebLayout/concrete/text.pyAlignmentHandler.calculate_spacing_and_position00100100%
pyWebLayout/concrete/text.pyLeftAlignmentHandler.calculate_spacing_and_position14118191%
pyWebLayout/concrete/text.pyCenterRightAlignmentHandler.__init__10000100%
pyWebLayout/concrete/text.pyCenterRightAlignmentHandler.calculate_spacing_and_position1801100100%
pyWebLayout/concrete/text.pyJustifyAlignmentHandler.__init__10000100%
pyWebLayout/concrete/text.pyJustifyAlignmentHandler.calculate_spacing_and_position14316275%
pyWebLayout/concrete/text.pyText.__init__80100100%
pyWebLayout/concrete/text.pyText._calculate_dimensions50100100%
pyWebLayout/concrete/text.pyText.from_word10000100%
pyWebLayout/concrete/text.pyText.text10100100%
pyWebLayout/concrete/text.pyText.style10100100%
pyWebLayout/concrete/text.pyText.origin10100100%
pyWebLayout/concrete/text.pyText.line10100100%
pyWebLayout/concrete/text.pyText.line10100100%
pyWebLayout/concrete/text.pyText.width10100100%
pyWebLayout/concrete/text.pyText.size30100100%
pyWebLayout/concrete/text.pyText.set_origin10100100%
pyWebLayout/concrete/text.pyText.add_line10100100%
pyWebLayout/concrete/text.pyText.in_object40100100%
pyWebLayout/concrete/text.pyText._apply_decoration14618159%
pyWebLayout/concrete/text.pyText.render4112167%
pyWebLayout/concrete/text.pyLine.__init__170100100%
pyWebLayout/concrete/text.pyLine._create_alignment_handler50140100%
pyWebLayout/concrete/text.pyLine.text_objects10100100%
pyWebLayout/concrete/text.pyLine.set_next111000%
pyWebLayout/concrete/text.pyLine.add_word759128586%
pyWebLayout/concrete/text.pyLine.render170180100%
pyWebLayout/concrete/text.pyLine.query_point16118192%
pyWebLayout/concrete/text.py(no function)560600100%
pyWebLayout/core/__init__.py(no function)20100100%
pyWebLayout/core/base.pyRenderable.render00100100%
pyWebLayout/core/base.pyRenderable.origin10000100%
pyWebLayout/core/base.pyInteractable.__init__10100100%
pyWebLayout/core/base.pyInteractable.interact3112160%
pyWebLayout/core/base.pyLayoutable.layout00100100%
pyWebLayout/core/base.pyQueriable.in_object30100100%
pyWebLayout/core/base.pyHierarchical.__init__20000100%
pyWebLayout/core/base.pyHierarchical.parent10100100%
pyWebLayout/core/base.pyHierarchical.parent10100100%
pyWebLayout/core/base.pyGeometric.__init__30000100%
pyWebLayout/core/base.pyGeometric.origin10100100%
pyWebLayout/core/base.pyGeometric.origin111000%
pyWebLayout/core/base.pyGeometric.size10100100%
pyWebLayout/core/base.pyGeometric.size111000%
pyWebLayout/core/base.pyGeometric.set_origin111000%
pyWebLayout/core/base.pyStyleable.__init__20000100%
pyWebLayout/core/base.pyStyleable.style10100100%
pyWebLayout/core/base.pyStyleable.style111000%
pyWebLayout/core/base.pyFontRegistry.__init__20000100%
pyWebLayout/core/base.pyFontRegistry.get_or_create_font1801100100%
pyWebLayout/core/base.pyMetadataContainer.__init__20000100%
pyWebLayout/core/base.pyMetadataContainer.set_metadata10100100%
pyWebLayout/core/base.pyMetadataContainer.get_metadata10100100%
pyWebLayout/core/base.pyBlockContainer.__init__20000100%
pyWebLayout/core/base.pyBlockContainer.blocks10100100%
pyWebLayout/core/base.pyBlockContainer.add_block3012180%
pyWebLayout/core/base.pyBlockContainer.create_paragraph661200%
pyWebLayout/core/base.pyBlockContainer.create_heading881400%
pyWebLayout/core/base.pyContainerAware._validate_container221200%
pyWebLayout/core/base.pyContainerAware._inherit_style771600%
pyWebLayout/core/base.py(no function)571102197%
pyWebLayout/core/callback_registry.pyCallbackRegistry.__init__40100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.register130140100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.get_by_id10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.get_by_type10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.get_all_ids10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.get_all_types10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.set_callback50120100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.set_callbacks_by_type40120100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.unregister11314267%
pyWebLayout/core/callback_registry.pyCallbackRegistry.clear40100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.count10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.count_by_type10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry._get_type_name8116186%
pyWebLayout/core/callback_registry.pyCallbackRegistry.__len__10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.__contains__10100100%
pyWebLayout/core/callback_registry.pyCallbackRegistry.__repr__00300100%
pyWebLayout/core/callback_registry.py(no function)180300100%
pyWebLayout/core/highlight.pyHighlight.__post_init__20120100%
pyWebLayout/core/highlight.pyHighlight.to_dict10100100%
pyWebLayout/core/highlight.pyHighlight.from_dict10100100%
pyWebLayout/core/highlight.pyHighlightManager.__init__50100100%
pyWebLayout/core/highlight.pyHighlightManager.add_highlight20100100%
pyWebLayout/core/highlight.pyHighlightManager.remove_highlight50120100%
pyWebLayout/core/highlight.pyHighlightManager.get_highlight10100100%
pyWebLayout/core/highlight.pyHighlightManager.list_highlights10100100%
pyWebLayout/core/highlight.pyHighlightManager.clear_all20100100%
pyWebLayout/core/highlight.pyHighlightManager.get_highlights_for_page80160100%
pyWebLayout/core/highlight.pyHighlightManager._get_filepath10100100%
pyWebLayout/core/highlight.pyHighlightManager._save_highlights7210071%
pyWebLayout/core/highlight.pyHighlightManager._load_highlights10312075%
pyWebLayout/core/highlight.pycreate_highlight_from_query_result80120100%
pyWebLayout/core/highlight.py(no function)410400100%
pyWebLayout/core/query.pyQueryResult.to_dict10100100%
pyWebLayout/core/query.pySelectionRange.text10100100%
pyWebLayout/core/query.pySelectionRange.bounds_list10100100%
pyWebLayout/core/query.pySelectionRange.to_dict10100100%
pyWebLayout/core/query.py(no function)29132194%
pyWebLayout/io/__init__.py(no function)00100100%
pyWebLayout/io/readers/__init__.py(no function)20100100%
pyWebLayout/io/readers/epub_reader.pydefault_eink_processor551200%
pyWebLayout/io/readers/epub_reader.pyEPUBReader.__init__100100100%
pyWebLayout/io/readers/epub_reader.pyEPUBReader.read120100100%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._extract_epub165110354%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._parse_package_document151110480%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._parse_metadata312128293%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._parse_manifest11116282%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._parse_spine10118378%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._parse_toc169114333%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._parse_nav_points13114188%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._create_book160116294%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._add_cover_chapter33311817%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._process_chapter_images238110264%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._process_content_images20120100%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._add_chapters3611110272%
pyWebLayout/io/readers/epub_reader.pyEPUBReader._add_chapters.add_to_toc_map7106277%
pyWebLayout/io/readers/epub_reader.pyread_epub20100100%
pyWebLayout/io/readers/epub_reader.py(no function)280200100%
pyWebLayout/io/readers/html_extraction.pyStyleContext.with_font10100100%
pyWebLayout/io/readers/html_extraction.pyStyleContext.with_background10100100%
pyWebLayout/io/readers/html_extraction.pyStyleContext.with_css_classes10100100%
pyWebLayout/io/readers/html_extraction.pyStyleContext.with_css_styles10100100%
pyWebLayout/io/readers/html_extraction.pyStyleContext.with_attributes10100100%
pyWebLayout/io/readers/html_extraction.pyStyleContext.push_element10100100%
pyWebLayout/io/readers/html_extraction.pycreate_base_context50140100%
pyWebLayout/io/readers/html_extraction.pyapply_element_styling180140100%
pyWebLayout/io/readers/html_extraction.pyparse_inline_styles60140100%
pyWebLayout/io/readers/html_extraction.pyapply_element_font_styles7513148782%
pyWebLayout/io/readers/html_extraction.pyapply_background_styles5114178%
pyWebLayout/io/readers/html_extraction.pyextract_text_content476140578%
pyWebLayout/io/readers/html_extraction.pyprocess_element30100100%
pyWebLayout/io/readers/html_extraction.pyparagraph_handler331124491%
pyWebLayout/io/readers/html_extraction.pydiv_handler100180100%
pyWebLayout/io/readers/html_extraction.pyheading_handler70120100%
pyWebLayout/io/readers/html_extraction.pyblockquote_handler112110271%
pyWebLayout/io/readers/html_extraction.pypreformatted_handler60120100%
pyWebLayout/io/readers/html_extraction.pycode_handler3112160%
pyWebLayout/io/readers/html_extraction.pyunordered_list_handler8016193%
pyWebLayout/io/readers/html_extraction.pyordered_list_handler8016193%
pyWebLayout/io/readers/html_extraction.pylist_item_handler202118382%
pyWebLayout/io/readers/html_extraction.pytable_handler211116486%
pyWebLayout/io/readers/html_extraction.pytable_row_handler8016193%
pyWebLayout/io/readers/html_extraction.pytable_cell_handler220118295%
pyWebLayout/io/readers/html_extraction.pytable_header_cell_handler221118390%
pyWebLayout/io/readers/html_extraction.pyhorizontal_rule_handler10100100%
pyWebLayout/io/readers/html_extraction.pyline_break_handler10100100%
pyWebLayout/io/readers/html_extraction.pyimage_handler16216091%
pyWebLayout/io/readers/html_extraction.pyignore_handler10100100%
pyWebLayout/io/readers/html_extraction.pygeneric_handler10100100%
pyWebLayout/io/readers/html_extraction.pyparse_html_string130180100%
pyWebLayout/io/readers/html_extraction.py(no function)470200100%
pyWebLayout/layout/__init__.py(no function)00100100%
pyWebLayout/layout/document_layouter.pyparagraph_layouter7311140781%
pyWebLayout/layout/document_layouter.pyparagraph_layouter.create_new_line11116188%
pyWebLayout/layout/document_layouter.pypagebreak_layouter111000%
pyWebLayout/layout/document_layouter.pyimage_layouter17118192%
pyWebLayout/layout/document_layouter.pytable_layouter140120100%
pyWebLayout/layout/document_layouter.pybutton_layouter14141400%
pyWebLayout/layout/document_layouter.pyform_field_layouter14214278%
pyWebLayout/layout/document_layouter.pyform_layouter11218279%
pyWebLayout/layout/document_layouter.pyDocumentLayouter.__init__70120100%
pyWebLayout/layout/document_layouter.pyDocumentLayouter.layout_paragraph10100100%
pyWebLayout/layout/document_layouter.pyDocumentLayouter.layout_image10100100%
pyWebLayout/layout/document_layouter.pyDocumentLayouter.layout_table10100100%
pyWebLayout/layout/document_layouter.pyDocumentLayouter.layout_button111000%
pyWebLayout/layout/document_layouter.pyDocumentLayouter.layout_form10100100%
pyWebLayout/layout/document_layouter.pyDocumentLayouter.layout_document229122257%
pyWebLayout/layout/document_layouter.py(no function)270100100%
pyWebLayout/layout/ereader_layout.pyRenderingPosition.to_dict10100100%
pyWebLayout/layout/ereader_layout.pyRenderingPosition.from_dict10100100%
pyWebLayout/layout/ereader_layout.pyRenderingPosition.copy10100100%
pyWebLayout/layout/ereader_layout.pyRenderingPosition.__eq__30120100%
pyWebLayout/layout/ereader_layout.pyRenderingPosition.__hash__10100100%
pyWebLayout/layout/ereader_layout.pyChapterInfo.__init__40000100%
pyWebLayout/layout/ereader_layout.pyChapterNavigator.__init__30000100%
pyWebLayout/layout/ereader_layout.pyChapterNavigator._build_chapter_map130180100%
pyWebLayout/layout/ereader_layout.pyChapterNavigator._extract_heading_text5014189%
pyWebLayout/layout/ereader_layout.pyChapterNavigator.get_table_of_contents10100100%
pyWebLayout/layout/ereader_layout.pyChapterNavigator.get_chapter_position40140100%
pyWebLayout/layout/ereader_layout.pyChapterNavigator.get_current_chapter9118188%
pyWebLayout/layout/ereader_layout.pyFontFamilyOverride.__init__111000%
pyWebLayout/layout/ereader_layout.pyFontFamilyOverride.override_font661400%
pyWebLayout/layout/ereader_layout.pyFontScaler.scale_font7114182%
pyWebLayout/layout/ereader_layout.pyFontScaler.scale_word_spacing40120100%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter.__init__60000100%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter.render_page_forward171110193%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter.render_page_backward3613120459%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._scale_block_fonts131110287%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._layout_block_on_page133110374%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._layout_paragraph_on_page17218284%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._layout_heading_on_page10100100%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._layout_table_on_page50100100%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._layout_list_on_page40100100%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._layout_image_on_page70120100%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._estimate_page_start50100100%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._adjust_start_estimate14116190%
pyWebLayout/layout/ereader_layout.pyBidirectionalLayouter._position_compare70160100%
pyWebLayout/layout/ereader_layout.py_add_page_methods7514236%
pyWebLayout/layout/ereader_layout.py_add_page_methods.can_fit_line221000%
pyWebLayout/layout/ereader_layout.py_add_page_methods.available_width111000%
pyWebLayout/layout/ereader_layout.py(no function)600700100%
pyWebLayout/layout/ereader_manager.pyBookmarkManager.__init__70100100%
pyWebLayout/layout/ereader_manager.pyBookmarkManager._load_bookmarks80120100%
pyWebLayout/layout/ereader_manager.pyBookmarkManager._save_bookmarks6210067%
pyWebLayout/layout/ereader_manager.pyBookmarkManager.add_bookmark20100100%
pyWebLayout/layout/ereader_manager.pyBookmarkManager.remove_bookmark50120100%
pyWebLayout/layout/ereader_manager.pyBookmarkManager.get_bookmark10100100%
pyWebLayout/layout/ereader_manager.pyBookmarkManager.list_bookmarks10100100%
pyWebLayout/layout/ereader_manager.pyBookmarkManager.save_reading_position5210060%
pyWebLayout/layout/ereader_manager.pyBookmarkManager.load_reading_position80120100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.__init__210140100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.set_position_changed_callback10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.set_chapter_changed_callback10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager._detect_cover60140100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager._render_cover_page13314271%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager._notify_position_changed60140100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_current_page40120100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.next_page14116190%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.previous_page202110287%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager._is_at_beginning20100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.jump_to_position40100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.jump_to_chapter40120100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.jump_to_chapter_index40120100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager._add_to_history4114175%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager._get_from_history8116186%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager._clear_history10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.set_font_scale40120100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_font_scale10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.set_font_family331000%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_font_family10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.increase_line_spacing551000%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.decrease_line_spacing551000%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.increase_inter_block_spacing551000%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.decrease_inter_block_spacing551000%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.increase_word_spacing551000%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.decrease_word_spacing551000%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_table_of_contents10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_current_chapter10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.add_bookmark5210060%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.remove_bookmark10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.jump_to_bookmark40120100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.list_bookmarks10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_reading_progress50120100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.has_cover10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.is_on_cover10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.jump_to_cover5112171%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_position_info30100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.get_cache_stats10100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.shutdown20100100%
pyWebLayout/layout/ereader_manager.pyEreaderLayoutManager.__del__10100100%
pyWebLayout/layout/ereader_manager.pycreate_ereader_manager10100100%
pyWebLayout/layout/ereader_manager.py(no function)640300100%
pyWebLayout/layout/page_buffer.py_render_page_worker881200%
pyWebLayout/layout/page_buffer.pyPageBuffer.__init__130100100%
pyWebLayout/layout/page_buffer.pyPageBuffer.initialize6012188%
pyWebLayout/layout/page_buffer.pyPageBuffer.get_page90140100%
pyWebLayout/layout/page_buffer.pyPageBuffer.cache_page10016194%
pyWebLayout/layout/page_buffer.pyPageBuffer.start_background_rendering7116277%
pyWebLayout/layout/page_buffer.pyPageBuffer._queue_forward_renders110160100%
pyWebLayout/layout/page_buffer.pyPageBuffer._queue_backward_renders11016194%
pyWebLayout/layout/page_buffer.pyPageBuffer.check_completed_renders16418179%
pyWebLayout/layout/page_buffer.pyPageBuffer.invalidate_all80120100%
pyWebLayout/layout/page_buffer.pyPageBuffer.set_font_scale3012180%
pyWebLayout/layout/page_buffer.pyPageBuffer.set_font_family331200%
pyWebLayout/layout/page_buffer.pyPageBuffer.get_cache_stats10100100%
pyWebLayout/layout/page_buffer.pyPageBuffer.shutdown70140100%
pyWebLayout/layout/page_buffer.pyPageBuffer.__del__10100100%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer.__init__100100100%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer.render_page140160100%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer.render_page_backward14616250%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer.set_font_family551200%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer.get_font_family10100100%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer.get_cache_stats10100100%
pyWebLayout/layout/page_buffer.pyBufferedPageRenderer.shutdown10100100%
pyWebLayout/layout/page_buffer.py(no function)350300100%
pyWebLayout/layout/table_optimizer.pyoptimize_table_layout395112286%
pyWebLayout/layout/table_optimizer.pylayout_cell_content252110386%
pyWebLayout/layout/table_optimizer.pyget_column_count50120100%
pyWebLayout/layout/table_optimizer.pysample_table_rows50120100%
pyWebLayout/layout/table_optimizer.pyextract_html_column_widths143112265%
pyWebLayout/layout/table_optimizer.pyparse_html_width16318183%
pyWebLayout/layout/table_optimizer.pydistribute_column_widths341122196%
pyWebLayout/layout/table_optimizer.pycalculate_table_overhead30100100%
pyWebLayout/layout/table_optimizer.py(no function)100100100%
pyWebLayout/style/__init__.py(no function)60100100%
pyWebLayout/style/abstract_style.pyFontSize.from_value11111400%
pyWebLayout/style/abstract_style.pyAbstractStyle.__post_init__5412129%
pyWebLayout/style/abstract_style.pyAbstractStyle.__hash__20100100%
pyWebLayout/style/abstract_style.pyAbstractStyle.merge_with551000%
pyWebLayout/style/abstract_style.pyAbstractStyle.with_modifications30100100%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.__init__40100100%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry._create_default_style50100100%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.default_style10100100%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry._generate_style_id30100100%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.get_style_id10100100%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.register_style8114275%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.get_or_create_style10116188%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.get_style_by_id10100100%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.create_derived_style5112171%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.resolve_effective_style7314255%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.get_all_styles111000%
pyWebLayout/style/abstract_style.pyAbstractStyleRegistry.get_style_count10100100%
pyWebLayout/style/abstract_style.py(no function)570500100%
pyWebLayout/style/alignment.pyAlignment.__str__111000%
pyWebLayout/style/alignment.py(no function)100200100%
pyWebLayout/style/concrete_style.pyConcreteStyle.create_font10100100%
pyWebLayout/style/concrete_style.pyStyleResolver.__init__40100100%
pyWebLayout/style/concrete_style.pyStyleResolver.resolve_style2601100100%
pyWebLayout/style/concrete_style.pyStyleResolver._resolve_font_path7516123%
pyWebLayout/style/concrete_style.pyStyleResolver._resolve_font_size13416174%
pyWebLayout/style/concrete_style.pyStyleResolver._resolve_color2711116558%
pyWebLayout/style/concrete_style.pyStyleResolver._resolve_background_color1210110114%
pyWebLayout/style/concrete_style.pyStyleResolver._resolve_line_height10816119%
pyWebLayout/style/concrete_style.pyStyleResolver._resolve_letter_spacing161418112%
pyWebLayout/style/concrete_style.pyStyleResolver._resolve_word_spacing16718262%
pyWebLayout/style/concrete_style.pyStyleResolver.update_context441000%
pyWebLayout/style/concrete_style.pyStyleResolver.clear_cache111000%
pyWebLayout/style/concrete_style.pyStyleResolver.get_cache_size10100100%
pyWebLayout/style/concrete_style.pyConcreteStyleRegistry.__init__20100100%
pyWebLayout/style/concrete_style.pyConcreteStyleRegistry.get_concrete_style10100100%
pyWebLayout/style/concrete_style.pyConcreteStyleRegistry.get_font60120100%
pyWebLayout/style/concrete_style.pyConcreteStyleRegistry.clear_caches221000%
pyWebLayout/style/concrete_style.pyConcreteStyleRegistry.get_cache_stats10100100%
pyWebLayout/style/concrete_style.py(no function)570500100%
pyWebLayout/style/fonts.pyget_bundled_fonts_dir11111400%
pyWebLayout/style/fonts.pyget_bundled_font_path252511600%
pyWebLayout/style/fonts.pyFont.__init__100100100%
pyWebLayout/style/fonts.pyFont.from_family221000%
pyWebLayout/style/fonts.pyFont._get_bundled_font_path16314180%
pyWebLayout/style/fonts.pyFont._load_font25218191%
pyWebLayout/style/fonts.pyFont.font10100100%
pyWebLayout/style/fonts.pyFont.font_size10100100%
pyWebLayout/style/fonts.pyFont.colour10100100%
pyWebLayout/style/fonts.pyFont.color10100100%
pyWebLayout/style/fonts.pyFont.background10100100%
pyWebLayout/style/fonts.pyFont.weight10100100%
pyWebLayout/style/fonts.pyFont.style10100100%
pyWebLayout/style/fonts.pyFont.decoration10100100%
pyWebLayout/style/fonts.pyFont.min_hyphenation_width10100100%
pyWebLayout/style/fonts.pyFont._with_modified30100100%
pyWebLayout/style/fonts.pyFont.with_size10100100%
pyWebLayout/style/fonts.pyFont.with_colour10100100%
pyWebLayout/style/fonts.pyFont.with_weight10100100%
pyWebLayout/style/fonts.pyFont.with_style10100100%
pyWebLayout/style/fonts.pyFont.with_decoration10100100%
pyWebLayout/style/fonts.py(no function)550200100%
pyWebLayout/style/page_style.pyPageStyle.padding_top10000100%
pyWebLayout/style/page_style.pyPageStyle.padding_right10000100%
pyWebLayout/style/page_style.pyPageStyle.padding_bottom10000100%
pyWebLayout/style/page_style.pyPageStyle.padding_left10000100%
pyWebLayout/style/page_style.pyPageStyle.total_horizontal_padding10100100%
pyWebLayout/style/page_style.pyPageStyle.total_vertical_padding10100100%
pyWebLayout/style/page_style.pyPageStyle.total_border_width10100100%
pyWebLayout/style/page_style.py(no function)260100100%
Total 5148911746159623479%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/cov_info/htmlcov/index.html b/cov_info/htmlcov/index.html new file mode 100644 index 0000000..c968251 --- /dev/null +++ b/cov_info/htmlcov/index.html @@ -0,0 +1,443 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 79% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filestatementsmissingexcludedbranchespartialcoverage
pyWebLayout/__init__.py10100100%
pyWebLayout/abstract/__init__.py50100100%
pyWebLayout/abstract/block.py4899911966679%
pyWebLayout/abstract/document.py194354736478%
pyWebLayout/abstract/functional.py1443396098%
pyWebLayout/abstract/inline.py15712944199%
pyWebLayout/abstract/interactive_image.py344712380%
pyWebLayout/concrete/__init__.py70100100%
pyWebLayout/concrete/box.py190120100%
pyWebLayout/concrete/dynamic_page.py1784217841968%
pyWebLayout/concrete/functional.py165172832987%
pyWebLayout/concrete/image.py13481136493%
pyWebLayout/concrete/interaction_handler.py9999144000%
pyWebLayout/concrete/page.py204543174866%
pyWebLayout/concrete/table.py30376191022070%
pyWebLayout/concrete/text.py2832231821190%
pyWebLayout/core/__init__.py20100100%
pyWebLayout/core/base.py134293330372%
pyWebLayout/core/callback_registry.py7542118392%
pyWebLayout/core/highlight.py9551814095%
pyWebLayout/core/query.py33172194%
pyWebLayout/io/__init__.py00100100%
pyWebLayout/io/readers/__init__.py20100100%
pyWebLayout/io/readers/epub_reader.py28676181342770%
pyWebLayout/io/readers/html_extraction.py42430342543588%
pyWebLayout/layout/__init__.py00100100%
pyWebLayout/layout/document_layouter.py2164216961577%
pyWebLayout/layout/ereader_layout.py27938361121883%
pyWebLayout/layout/ereader_manager.py292485362884%
pyWebLayout/layout/page_buffer.py195272564983%
pyWebLayout/layout/table_optimizer.py15114968988%
pyWebLayout/style/__init__.py60100100%
pyWebLayout/style/abstract_style.py130272222775%
pyWebLayout/style/alignment.py11130091%
pyWebLayout/style/concrete_style.py2076623721263%
pyWebLayout/style/fonts.py161432332266%
pyWebLayout/style/page_style.py330400100%
Total5148911746159623479%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/cov_info/htmlcov/keybd_closed_cb_ce680311.png b/cov_info/htmlcov/keybd_closed_cb_ce680311.png new file mode 100644 index 0000000000000000000000000000000000000000..ba119c47df81ed2bbd27a06988abf700139c4f99 GIT binary patch literal 9004 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 literal 0 HcmV?d00001 diff --git a/cov_info/htmlcov/status.json b/cov_info/htmlcov/status.json new file mode 100644 index 0000000..08410fa --- /dev/null +++ b/cov_info/htmlcov/status.json @@ -0,0 +1 @@ +{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.11.2","globals":"2fca1a77c6917c5c6b114cea922ac216","files":{"z_20e398e67121d457___init___py":{"hash":"1f3e5f7b483f69f3fe1264707a2aec3b","index":{"url":"z_20e398e67121d457___init___py.html","file":"pyWebLayout/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":1,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_af715639580e2d86___init___py":{"hash":"52103261f44cfd07a61fcb51dd90a4d4","index":{"url":"z_af715639580e2d86___init___py.html","file":"pyWebLayout/abstract/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":5,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_af715639580e2d86_block_py":{"hash":"b2965902cafe50cb5f0362958505be1b","index":{"url":"z_af715639580e2d86_block_py.html","file":"pyWebLayout/abstract/block.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":489,"n_excluded":119,"n_missing":99,"n_branches":66,"n_partial_branches":6,"n_missing_branches":16}}},"z_af715639580e2d86_document_py":{"hash":"feaa44c187c3f9048e4354f2e0991814","index":{"url":"z_af715639580e2d86_document_py.html","file":"pyWebLayout/abstract/document.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":194,"n_excluded":47,"n_missing":35,"n_branches":36,"n_partial_branches":4,"n_missing_branches":16}}},"z_af715639580e2d86_functional_py":{"hash":"b72bd490e667f2ea8a4b868762fd9187","index":{"url":"z_af715639580e2d86_functional_py.html","file":"pyWebLayout/abstract/functional.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":144,"n_excluded":39,"n_missing":3,"n_branches":6,"n_partial_branches":0,"n_missing_branches":0}}},"z_af715639580e2d86_inline_py":{"hash":"b6fba690864307138730ca1087ceb761","index":{"url":"z_af715639580e2d86_inline_py.html","file":"pyWebLayout/abstract/inline.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":157,"n_excluded":29,"n_missing":1,"n_branches":44,"n_partial_branches":1,"n_missing_branches":1}}},"z_af715639580e2d86_interactive_image_py":{"hash":"1446344d88dd9077851b241e83ca25e8","index":{"url":"z_af715639580e2d86_interactive_image_py.html","file":"pyWebLayout/abstract/interactive_image.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":34,"n_excluded":7,"n_missing":4,"n_branches":12,"n_partial_branches":3,"n_missing_branches":5}}},"z_7d48e1f4c6486fa2___init___py":{"hash":"4af3e16803dedd3de5b535f3969d5892","index":{"url":"z_7d48e1f4c6486fa2___init___py.html","file":"pyWebLayout/concrete/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":7,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_7d48e1f4c6486fa2_box_py":{"hash":"dbd77aa53857bf20b4a9220a9d885aca","index":{"url":"z_7d48e1f4c6486fa2_box_py.html","file":"pyWebLayout/concrete/box.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":19,"n_excluded":1,"n_missing":0,"n_branches":2,"n_partial_branches":0,"n_missing_branches":0}}},"z_7d48e1f4c6486fa2_dynamic_page_py":{"hash":"acc967dd6ea1089847cdb30e3d9539ba","index":{"url":"z_7d48e1f4c6486fa2_dynamic_page_py.html","file":"pyWebLayout/concrete/dynamic_page.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":178,"n_excluded":17,"n_missing":42,"n_branches":84,"n_partial_branches":19,"n_missing_branches":41}}},"z_7d48e1f4c6486fa2_functional_py":{"hash":"e01086252b115d2962f3ced29fdfcfec","index":{"url":"z_7d48e1f4c6486fa2_functional_py.html","file":"pyWebLayout/concrete/functional.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":165,"n_excluded":28,"n_missing":17,"n_branches":32,"n_partial_branches":9,"n_missing_branches":9}}},"z_7d48e1f4c6486fa2_image_py":{"hash":"94f8c20e5f595848348828a485833843","index":{"url":"z_7d48e1f4c6486fa2_image_py.html","file":"pyWebLayout/concrete/image.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":134,"n_excluded":11,"n_missing":8,"n_branches":36,"n_partial_branches":4,"n_missing_branches":4}}},"z_7d48e1f4c6486fa2_interaction_handler_py":{"hash":"c37398795b1409a3dd4cbac354fba389","index":{"url":"z_7d48e1f4c6486fa2_interaction_handler_py.html","file":"pyWebLayout/concrete/interaction_handler.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":99,"n_excluded":14,"n_missing":99,"n_branches":40,"n_partial_branches":0,"n_missing_branches":40}}},"z_7d48e1f4c6486fa2_page_py":{"hash":"b2e2fed37c7ad0181af8e1ad84d87b72","index":{"url":"z_7d48e1f4c6486fa2_page_py.html","file":"pyWebLayout/concrete/page.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":204,"n_excluded":31,"n_missing":54,"n_branches":74,"n_partial_branches":8,"n_missing_branches":40}}},"z_7d48e1f4c6486fa2_table_py":{"hash":"2cca979c217c62b82e0daa571191b884","index":{"url":"z_7d48e1f4c6486fa2_table_py.html","file":"pyWebLayout/concrete/table.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":303,"n_excluded":19,"n_missing":76,"n_branches":102,"n_partial_branches":20,"n_missing_branches":44}}},"z_7d48e1f4c6486fa2_text_py":{"hash":"ddadffe016a9c5c9be5aa8c799cc753f","index":{"url":"z_7d48e1f4c6486fa2_text_py.html","file":"pyWebLayout/concrete/text.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":283,"n_excluded":31,"n_missing":22,"n_branches":82,"n_partial_branches":11,"n_missing_branches":13}}},"z_40407af872b0cf37___init___py":{"hash":"d0fbc113a028cfad632669a416f0886a","index":{"url":"z_40407af872b0cf37___init___py.html","file":"pyWebLayout/core/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":2,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_40407af872b0cf37_base_py":{"hash":"d96828163c9b07e01d28d2a3a6eea907","index":{"url":"z_40407af872b0cf37_base_py.html","file":"pyWebLayout/core/base.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":134,"n_excluded":33,"n_missing":29,"n_branches":30,"n_partial_branches":3,"n_missing_branches":17}}},"z_40407af872b0cf37_callback_registry_py":{"hash":"fdfef947e21b351a960055c7f6224324","index":{"url":"z_40407af872b0cf37_callback_registry_py.html","file":"pyWebLayout/core/callback_registry.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":75,"n_excluded":21,"n_missing":4,"n_branches":18,"n_partial_branches":3,"n_missing_branches":3}}},"z_40407af872b0cf37_highlight_py":{"hash":"a9cce9d2e32ac235d1d99cbddc314696","index":{"url":"z_40407af872b0cf37_highlight_py.html","file":"pyWebLayout/core/highlight.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":95,"n_excluded":18,"n_missing":5,"n_branches":14,"n_partial_branches":0,"n_missing_branches":0}}},"z_40407af872b0cf37_query_py":{"hash":"bbe6ed1474b9cba6cf7ca573ca2a76b9","index":{"url":"z_40407af872b0cf37_query_py.html","file":"pyWebLayout/core/query.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":33,"n_excluded":7,"n_missing":1,"n_branches":2,"n_partial_branches":1,"n_missing_branches":1}}},"z_fc521de9aff00981___init___py":{"hash":"eda5ff74964fcfcd2751d31f62011f00","index":{"url":"z_fc521de9aff00981___init___py.html","file":"pyWebLayout/io/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_263f2e628cef8c50___init___py":{"hash":"73822281f46c34d862edb425ce28d2e0","index":{"url":"z_263f2e628cef8c50___init___py.html","file":"pyWebLayout/io/readers/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":2,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_263f2e628cef8c50_epub_reader_py":{"hash":"7f05b0e3780973fe50dcf64939435f6e","index":{"url":"z_263f2e628cef8c50_epub_reader_py.html","file":"pyWebLayout/io/readers/epub_reader.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":286,"n_excluded":18,"n_missing":76,"n_branches":134,"n_partial_branches":27,"n_missing_branches":49}}},"z_263f2e628cef8c50_html_extraction_py":{"hash":"565155d84110a27d9c240c4ed3c8f7a3","index":{"url":"z_263f2e628cef8c50_html_extraction_py.html","file":"pyWebLayout/io/readers/html_extraction.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":424,"n_excluded":34,"n_missing":30,"n_branches":254,"n_partial_branches":35,"n_missing_branches":49}}},"z_427cc3035faf7633___init___py":{"hash":"ae5755913cc3724d93ff8d50a62ff8a1","index":{"url":"z_427cc3035faf7633___init___py.html","file":"pyWebLayout/layout/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_427cc3035faf7633_document_layouter_py":{"hash":"200174133996d3f50731797f4df7a613","index":{"url":"z_427cc3035faf7633_document_layouter_py.html","file":"pyWebLayout/layout/document_layouter.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":216,"n_excluded":16,"n_missing":42,"n_branches":96,"n_partial_branches":15,"n_missing_branches":31}}},"z_427cc3035faf7633_ereader_layout_py":{"hash":"aa6c435c78715ff49bede760176bd548","index":{"url":"z_427cc3035faf7633_ereader_layout_py.html","file":"pyWebLayout/layout/ereader_layout.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":279,"n_excluded":36,"n_missing":38,"n_branches":112,"n_partial_branches":18,"n_missing_branches":28}}},"z_427cc3035faf7633_ereader_manager_py":{"hash":"9b8b6c9165cb9910ee6fc9d33c166cef","index":{"url":"z_427cc3035faf7633_ereader_manager_py.html","file":"pyWebLayout/layout/ereader_manager.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":292,"n_excluded":53,"n_missing":48,"n_branches":62,"n_partial_branches":8,"n_missing_branches":8}}},"z_427cc3035faf7633_page_buffer_py":{"hash":"775c3510a13f3cede9a451b8be15ad68","index":{"url":"z_427cc3035faf7633_page_buffer_py.html","file":"pyWebLayout/layout/page_buffer.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":195,"n_excluded":25,"n_missing":27,"n_branches":64,"n_partial_branches":9,"n_missing_branches":17}}},"z_427cc3035faf7633_table_optimizer_py":{"hash":"21132d0c11aff3dff43d95eb76202127","index":{"url":"z_427cc3035faf7633_table_optimizer_py.html","file":"pyWebLayout/layout/table_optimizer.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":151,"n_excluded":9,"n_missing":14,"n_branches":68,"n_partial_branches":9,"n_missing_branches":13}}},"z_ba7f6bdeb0188088___init___py":{"hash":"41a906a9dccfb065e208f250579afe22","index":{"url":"z_ba7f6bdeb0188088___init___py.html","file":"pyWebLayout/style/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":6,"n_excluded":1,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_ba7f6bdeb0188088_abstract_style_py":{"hash":"4f887588099a9d651eceacca8eb2713f","index":{"url":"z_ba7f6bdeb0188088_abstract_style_py.html","file":"pyWebLayout/style/abstract_style.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":130,"n_excluded":22,"n_missing":27,"n_branches":22,"n_partial_branches":7,"n_missing_branches":11}}},"z_ba7f6bdeb0188088_alignment_py":{"hash":"3b46f65c1e1e30bea4c2d96df7eb3d85","index":{"url":"z_ba7f6bdeb0188088_alignment_py.html","file":"pyWebLayout/style/alignment.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":11,"n_excluded":3,"n_missing":1,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_ba7f6bdeb0188088_concrete_style_py":{"hash":"cf38887b3e55892ba8e4ae35ccc321ee","index":{"url":"z_ba7f6bdeb0188088_concrete_style_py.html","file":"pyWebLayout/style/concrete_style.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":207,"n_excluded":23,"n_missing":66,"n_branches":72,"n_partial_branches":12,"n_missing_branches":36}}},"z_ba7f6bdeb0188088_fonts_py":{"hash":"4f99ff5cbb7dce90b7f85f2c21b2e9e1","index":{"url":"z_ba7f6bdeb0188088_fonts_py.html","file":"pyWebLayout/style/fonts.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":161,"n_excluded":23,"n_missing":43,"n_branches":32,"n_partial_branches":2,"n_missing_branches":22}}},"z_ba7f6bdeb0188088_page_style_py":{"hash":"e418813daed32f9d32f0149ae752a369","index":{"url":"z_ba7f6bdeb0188088_page_style_py.html","file":"pyWebLayout/style/page_style.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":33,"n_excluded":4,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}}}} \ No newline at end of file diff --git a/cov_info/htmlcov/style_cb_6b508a39.css b/cov_info/htmlcov/style_cb_6b508a39.css new file mode 100644 index 0000000..4735f27 --- /dev/null +++ b/cov_info/htmlcov/style_cb_6b508a39.css @@ -0,0 +1,377 @@ +@charset "UTF-8"; +/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ +/* Don't edit this .css file. Edit the .scss file instead! */ +html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } + +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { body { color: #eee; } } + +html > body { font-size: 16px; } + +a:active, a:focus { outline: 2px dashed #007acc; } + +p { font-size: .875em; line-height: 1.4em; } + +table { border-collapse: collapse; } + +td { vertical-align: top; } + +table tr.hidden { display: none !important; } + +p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +a.nav { text-decoration: none; color: inherit; } + +a.nav:hover { text-decoration: underline; color: inherit; } + +.hidden { display: none; } + +header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; } + +@media (prefers-color-scheme: dark) { header { background: black; } } + +@media (prefers-color-scheme: dark) { header { border-color: #333; } } + +header .content { padding: 1rem 3.5rem; } + +header h2 { margin-top: .5em; font-size: 1em; } + +header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } } + +@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } } + +header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } } + +header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } + +header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; } + +header.sticky .text { display: none; } + +header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; } + +header.sticky .content { padding: 0.5rem 3.5rem; } + +header.sticky .content p { font-size: 1em; } + +header.sticky ~ #source { padding-top: 6.5em; } + +main { position: relative; z-index: 1; } + +footer { margin: 1rem 3.5rem; } + +footer .content { padding: 0; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } } + +#index { margin: 1rem 0 0 3.5rem; } + +h1 { font-size: 1.25em; display: inline-block; } + +#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; } + +#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } } + +@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } } + +#filter_container #filter:focus { border-color: #007acc; } + +#filter_container :disabled ~ label { color: #ccc; } + +@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } } + +#filter_container label { font-size: .875em; color: #666; } + +@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { background: #333; } } + +@media (prefers-color-scheme: dark) { header button { border-color: #444; } } + +header button:active, header button:focus { outline: 2px dashed #007acc; } + +header button.run { background: #eeffee; } + +@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } } + +header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } } + +header button.mis { background: #ffeeee; } + +@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } } + +header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } } + +header button.exc { background: #f7f7f7; } + +@media (prefers-color-scheme: dark) { header button.exc { background: #333; } } + +header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } } + +header button.par { background: #ffffd5; } + +@media (prefers-color-scheme: dark) { header button.par { background: #650; } } + +header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } } + +#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; } + +#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; } + +#help_panel_wrapper { float: right; position: relative; } + +#keyboard_icon { margin: 5px; } + +#help_panel_state { display: none; } + +#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; } + +#help_panel .keyhelp p { margin-top: .75em; } + +#help_panel .legend { font-style: italic; margin-bottom: 1em; } + +.indexfile #help_panel { width: 25em; } + +.pyfile #help_panel { width: 18em; } + +#help_panel_state:checked ~ #help_panel { display: block; } + +kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; } + +#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } + +#source p { position: relative; white-space: pre; } + +#source p * { box-sizing: border-box; } + +#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; user-select: none; } + +@media (prefers-color-scheme: dark) { #source p .n { color: #777; } } + +#source p .n.highlight { background: #ffdd00; } + +#source p .n a { scroll-margin-top: 6em; text-decoration: none; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } + +#source p .n a:hover { text-decoration: underline; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } } + +#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; } + +@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } } + +#source p .t:hover { background: #f2f2f2; } + +@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } } + +#source p .t:hover ~ .r .annotate.long { display: block; } + +#source p .t .com { color: #008000; font-style: italic; line-height: 1px; } + +@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } } + +#source p .t .key { font-weight: bold; line-height: 1px; } + +#source p .t .str, #source p .t .fst { color: #0451a5; } + +@media (prefers-color-scheme: dark) { #source p .t .str, #source p .t .fst { color: #9cdcfe; } } + +#source p.mis .t { border-left: 0.2em solid #ff0000; } + +#source p.mis.show_mis .t { background: #fdd; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } } + +#source p.mis.show_mis .t:hover { background: #f2d2d2; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } } + +#source p.mis.mis2 .t { border-left: 0.2em dotted #ff0000; } + +#source p.mis.mis2.show_mis .t { background: #ffeeee; } + +@media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t { background: #351b1b; } } + +#source p.mis.mis2.show_mis .t:hover { background: #f2d2d2; } + +@media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t:hover { background: #532323; } } + +#source p.run .t { border-left: 0.2em solid #00dd00; } + +#source p.run.show_run .t { background: #dfd; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } } + +#source p.run.show_run .t:hover { background: #d2f2d2; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } } + +#source p.run.run2 .t { border-left: 0.2em dotted #00dd00; } + +#source p.run.run2.show_run .t { background: #eeffee; } + +@media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t { background: #2b2e24; } } + +#source p.run.run2.show_run .t:hover { background: #d2f2d2; } + +@media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t:hover { background: #404633; } } + +#source p.exc .t { border-left: 0.2em solid #808080; } + +#source p.exc.show_exc .t { background: #eee; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } } + +#source p.exc.show_exc .t:hover { background: #e2e2e2; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } } + +#source p.exc.exc2 .t { border-left: 0.2em dotted #808080; } + +#source p.exc.exc2.show_exc .t { background: #f7f7f7; } + +@media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t { background: #292929; } } + +#source p.exc.exc2.show_exc .t:hover { background: #e2e2e2; } + +@media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t:hover { background: #3c3c3c; } } + +#source p.par .t { border-left: 0.2em solid #bbbb00; } + +#source p.par.show_par .t { background: #ffa; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } } + +#source p.par.show_par .t:hover { background: #f2f2a2; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } } + +#source p.par.par2 .t { border-left: 0.2em dotted #bbbb00; } + +#source p.par.par2.show_par .t { background: #ffffd5; } + +@media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t { background: #423a0f; } } + +#source p.par.par2.show_par .t:hover { background: #f2f2a2; } + +@media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t:hover { background: #6d5d0c; } } + +#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; } + +@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } } + +#source p .annotate.short:hover ~ .long { display: block; } + +#source p .annotate.long { width: 30em; right: 2.5em; } + +#source p input { display: none; } + +#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; } + +#source p input ~ .r label.ctx::before { content: "▶ "; } + +#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } } + +#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } } + +#source p input:checked ~ .r label.ctx::before { content: "▼ "; } + +#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; } + +#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; } + +@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } } + +#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; } + +@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } } + +#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; } + +#index table.index { margin-left: -.5em; } + +#index td, #index th { text-align: right; padding: .25em .5em; border-bottom: 1px solid #eee; } + +@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } + +#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; } + +#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; cursor: pointer; } + +@media (prefers-color-scheme: dark) { #index th { color: #ddd; } } + +#index th:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } + +#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; } + +#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } + +@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } + +#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; } + +#index th[aria-sort="descending"] .arrows::after { content: " ▼"; } + +#index td.name { font-size: 1.15em; } + +#index td.name a { text-decoration: none; color: inherit; } + +#index td.name .no-noun { font-style: italic; } + +#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } + +#index tr.region:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } } + +#index tr.region:hover td.name { text-decoration: underline; color: inherit; } + +#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } + +@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } } + +#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; } + +@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } } diff --git a/cov_info/htmlcov/z_20e398e67121d457___init___py.html b/cov_info/htmlcov/z_20e398e67121d457___init___py.html new file mode 100644 index 0000000..de35b38 --- /dev/null +++ b/cov_info/htmlcov/z_20e398e67121d457___init___py.html @@ -0,0 +1,121 @@ + + + + + Coverage for pyWebLayout/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/__init__.py: + 100% +

+ +

+ 1 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2PyWebLayout - A Python library for HTML-like layout and rendering. 

+

3 

+

4This library provides classes for rendering HTML-like content to images 

+

5using a box-based layout system. It includes support for text, tables, 

+

6and containers, as well as parsers for HTML and EPUB content. It also 

+

7supports pagination for ebook-like content with the ability to pause, 

+

8save state, and resume rendering. 

+

9""" 

+

10 

+

11__version__ = '0.1.1' 

+

12 

+

13# Core abstractions 

+

14 

+

15# Style components 

+

16 

+

17 

+

18# Abstract document model 

+

19 

+

20# Concrete implementations 

+

21 

+

22# Abstract components 

+
+ + + diff --git a/cov_info/htmlcov/z_263f2e628cef8c50___init___py.html b/cov_info/htmlcov/z_263f2e628cef8c50___init___py.html new file mode 100644 index 0000000..689b2b1 --- /dev/null +++ b/cov_info/htmlcov/z_263f2e628cef8c50___init___py.html @@ -0,0 +1,116 @@ + + + + + Coverage for pyWebLayout/io/readers/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/io/readers/__init__.py: + 100% +

+ +

+ 2 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Readers module for pyWebLayout. 

+

3 

+

4This module provides specialized readers for different document formats. 

+

5""" 

+

6 

+

7# EPUB readers 

+

8from .epub_reader import read_epub # Legacy 

+

9 

+

10 

+

11__all__ = [ 

+

12 # HTML readers 

+

13 'read_html', 'read_html_file', 'parse_html_string', 

+

14 

+

15 # EPUB readers 

+

16 'read_epub', 

+

17] 

+
+ + + diff --git a/cov_info/htmlcov/z_263f2e628cef8c50_epub_reader_py.html b/cov_info/htmlcov/z_263f2e628cef8c50_epub_reader_py.html new file mode 100644 index 0000000..a6b9e66 --- /dev/null +++ b/cov_info/htmlcov/z_263f2e628cef8c50_epub_reader_py.html @@ -0,0 +1,702 @@ + + + + + Coverage for pyWebLayout/io/readers/epub_reader.py: 70% + + + + + +
+
+

+ Coverage for pyWebLayout/io/readers/epub_reader.py: + 70% +

+ +

+ 286 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2EPUB reader for pyWebLayout. 

+

3 

+

4This module provides functionality for reading EPUB documents and converting them 

+

5to pyWebLayout's abstract document model. 

+

6""" 

+

7 

+

8import os 

+

9import zipfile 

+

10import tempfile 

+

11from typing import Dict, List, Optional, Any, Callable 

+

12import xml.etree.ElementTree as ET 

+

13import urllib.parse 

+

14from PIL import Image as PILImage, ImageOps 

+

15 

+

16from pyWebLayout.abstract.document import Book, Chapter, MetadataType 

+

17from pyWebLayout.abstract.block import PageBreak 

+

18from pyWebLayout.io.readers.html_extraction import parse_html_string 

+

19 

+

20 

+

21# XML namespaces used in EPUB files 

+

22NAMESPACES = { 

+

23 'opf': 'http://www.idpf.org/2007/opf', 

+

24 'dc': 'http://purl.org/dc/elements/1.1/', 

+

25 'dcterms': 'http://purl.org/dc/terms/', 

+

26 'xhtml': 'http://www.w3.org/1999/xhtml', 

+

27 'ncx': 'http://www.daisy.org/z3986/2005/ncx/', 

+

28} 

+

29 

+

30 

+

31def default_eink_processor(img: PILImage.Image) -> PILImage.Image: 

+

32 """ 

+

33 Process image for 4-bit e-ink display using PIL only. 

+

34 Applies histogram equalization and 4-bit quantization. 

+

35 

+

36 Args: 

+

37 img: PIL Image to process 

+

38 

+

39 Returns: 

+

40 Processed PIL Image in L mode (grayscale) with 4-bit quantization 

+

41 """ 

+

42 # Convert to grayscale if needed 

+

43 if img.mode != 'L': 

+

44 img = img.convert('L') 

+

45 

+

46 # Apply histogram equalization for contrast enhancement 

+

47 img = ImageOps.equalize(img) 

+

48 

+

49 # Quantize to 4-bit (16 grayscale levels: 0, 17, 34, ..., 255) 

+

50 img = img.point(lambda x: (x // 16) * 17) 

+

51 

+

52 return img 

+

53 

+

54 

+

55class EPUBReader: 

+

56 """ 

+

57 Reader for EPUB documents. 

+

58 

+

59 This class extracts content from EPUB files and converts it to 

+

60 pyWebLayout's abstract document model. 

+

61 """ 

+

62 

+

63 def __init__(self, epub_path: str, image_processor: Optional[Callable[[ 

+

64 PILImage.Image], PILImage.Image]] = default_eink_processor): 

+

65 """ 

+

66 Initialize an EPUB reader. 

+

67 

+

68 Args: 

+

69 epub_path: Path to the EPUB file 

+

70 image_processor: Optional function to process images for display optimization. 

+

71 Defaults to default_eink_processor for 4-bit e-ink displays. 

+

72 Set to None to disable image processing. 

+

73 Custom processor should accept and return a PIL Image. 

+

74 """ 

+

75 self.epub_path = epub_path 

+

76 self.image_processor = image_processor 

+

77 self.book = Book() 

+

78 self.temp_dir = None 

+

79 self.content_dir = None 

+

80 self.metadata = {} 

+

81 self.toc = [] 

+

82 self.spine = [] 

+

83 self.manifest = {} 

+

84 self.cover_id = None # ID of the cover image in manifest 

+

85 

+

86 def read(self) -> Book: 

+

87 """ 

+

88 Read the EPUB file and convert it to a Book. 

+

89 

+

90 Returns: 

+

91 Book: The parsed book 

+

92 """ 

+

93 try: 

+

94 # Extract the EPUB file 

+

95 self.temp_dir = tempfile.mkdtemp() 

+

96 self._extract_epub() 

+

97 self._parse_package_document() 

+

98 self._parse_toc() 

+

99 self._create_book() 

+

100 

+

101 # Add chapters to the book 

+

102 self._add_chapters() 

+

103 

+

104 # Process images for e-ink display optimization 

+

105 self._process_content_images() 

+

106 

+

107 return self.book 

+

108 

+

109 finally: 

+

110 # Clean up temporary files 

+

111 if self.temp_dir: 

+

112 import shutil 

+

113 shutil.rmtree(self.temp_dir, ignore_errors=True) 

+

114 

+

115 def _extract_epub(self): 

+

116 """Extract the EPUB file to a temporary directory.""" 

+

117 with zipfile.ZipFile(self.epub_path, 'r') as zip_ref: 

+

118 zip_ref.extractall(self.temp_dir) 

+

119 

+

120 # Find the content directory (typically OEBPS or OPS) 

+

121 container_path = os.path.join(self.temp_dir, 'META-INF', 'container.xml') 

+

122 if os.path.exists(container_path): 122 ↛ 136line 122 didn't jump to line 136 because the condition on line 122 was always true

+

123 tree = ET.parse(container_path) 

+

124 root = tree.getroot() 

+

125 

+

126 # Get the path to the package document (content.opf) 

+

127 for rootfile in root.findall( 127 ↛ 136line 127 didn't jump to line 136 because the loop on line 127 didn't complete

+

128 './/{urn:oasis:names:tc:opendocument:xmlns:container}rootfile'): 

+

129 full_path = rootfile.get('full-path') 

+

130 if full_path: 130 ↛ 127line 130 didn't jump to line 127 because the condition on line 130 was always true

+

131 self.content_dir = os.path.dirname( 

+

132 os.path.join(self.temp_dir, full_path)) 

+

133 return 

+

134 

+

135 # Fallback: look for common content directories 

+

136 for content_dir in ['OEBPS', 'OPS', 'Content']: 

+

137 if os.path.exists(os.path.join(self.temp_dir, content_dir)): 

+

138 self.content_dir = os.path.join(self.temp_dir, content_dir) 

+

139 return 

+

140 

+

141 # If no content directory found, use the root 

+

142 self.content_dir = self.temp_dir 

+

143 

+

144 def _parse_package_document(self): 

+

145 """Parse the package document (content.opf).""" 

+

146 # Find the package document 

+

147 opf_path = None 

+

148 for root, dirs, files in os.walk(self.content_dir): 148 ↛ 156line 148 didn't jump to line 156 because the loop on line 148 didn't complete

+

149 for file in files: 149 ↛ 153line 149 didn't jump to line 153 because the loop on line 149 didn't complete

+

150 if file.endswith('.opf'): 

+

151 opf_path = os.path.join(root, file) 

+

152 break 

+

153 if opf_path: 153 ↛ 148line 153 didn't jump to line 148 because the condition on line 153 was always true

+

154 break 

+

155 

+

156 if not opf_path: 156 ↛ 157line 156 didn't jump to line 157 because the condition on line 156 was never true

+

157 raise ValueError("No package document (.opf) found in EPUB") 

+

158 

+

159 # Parse the package document 

+

160 tree = ET.parse(opf_path) 

+

161 root = tree.getroot() 

+

162 

+

163 # Parse metadata 

+

164 self._parse_metadata(root) 

+

165 

+

166 # Parse manifest 

+

167 self._parse_manifest(root) 

+

168 

+

169 # Parse spine 

+

170 self._parse_spine(root) 

+

171 

+

172 def _parse_metadata(self, root: ET.Element): 

+

173 """ 

+

174 Parse metadata from the package document. 

+

175 

+

176 Args: 

+

177 root: Root element of the package document 

+

178 """ 

+

179 # Find the metadata element 

+

180 metadata_elem = root.find('.//{{{0}}}metadata'.format(NAMESPACES['opf'])) 

+

181 if metadata_elem is None: 181 ↛ 182line 181 didn't jump to line 182 because the condition on line 181 was never true

+

182 return 

+

183 

+

184 # Parse DC metadata 

+

185 for elem in metadata_elem: 

+

186 if elem.tag.startswith('{{{0}}}'.format(NAMESPACES['dc'])): 

+

187 # Get the local name (without namespace) 

+

188 name = elem.tag.split('}', 1)[1] 

+

189 value = elem.text 

+

190 

+

191 if name == 'title': 

+

192 self.metadata['title'] = value 

+

193 elif name == 'creator': 

+

194 self.metadata['creator'] = value 

+

195 elif name == 'language': 

+

196 self.metadata['language'] = value 

+

197 elif name == 'description': 

+

198 self.metadata['description'] = value 

+

199 elif name == 'subject': 

+

200 if 'subjects' not in self.metadata: 

+

201 self.metadata['subjects'] = [] 

+

202 self.metadata['subjects'].append(value) 

+

203 elif name == 'date': 

+

204 self.metadata['date'] = value 

+

205 elif name == 'identifier': 

+

206 self.metadata['identifier'] = value 

+

207 elif name == 'publisher': 

+

208 self.metadata['publisher'] = value 

+

209 else: 

+

210 # Store other metadata 

+

211 self.metadata[name] = value 

+

212 

+

213 # Parse meta elements for cover reference 

+

214 for meta in metadata_elem.findall('.//{{{0}}}meta'.format(NAMESPACES['opf'])): 

+

215 name = meta.get('name') 

+

216 content = meta.get('content') 

+

217 

+

218 if name == 'cover' and content: 218 ↛ 220line 218 didn't jump to line 220 because the condition on line 218 was never true

+

219 # This is a reference to the cover image in the manifest 

+

220 self.cover_id = content 

+

221 

+

222 def _parse_manifest(self, root: ET.Element): 

+

223 """ 

+

224 Parse manifest from the package document. 

+

225 

+

226 Args: 

+

227 root: Root element of the package document 

+

228 """ 

+

229 # Find the manifest element 

+

230 manifest_elem = root.find('.//{{{0}}}manifest'.format(NAMESPACES['opf'])) 

+

231 if manifest_elem is None: 231 ↛ 232line 231 didn't jump to line 232 because the condition on line 231 was never true

+

232 return 

+

233 

+

234 # Parse items 

+

235 for item in manifest_elem.findall('.//{{{0}}}item'.format(NAMESPACES['opf'])): 

+

236 id = item.get('id') 

+

237 href = item.get('href') 

+

238 media_type = item.get('media-type') 

+

239 

+

240 if id and href: 240 ↛ 235line 240 didn't jump to line 235 because the condition on line 240 was always true

+

241 # Resolve relative path 

+

242 href = urllib.parse.unquote(href) 

+

243 path = os.path.normpath(os.path.join(self.content_dir, href)) 

+

244 

+

245 self.manifest[id] = { 

+

246 'href': href, 

+

247 'path': path, 

+

248 'media_type': media_type 

+

249 } 

+

250 

+

251 def _parse_spine(self, root: ET.Element): 

+

252 """ 

+

253 Parse spine from the package document. 

+

254 

+

255 Args: 

+

256 root: Root element of the package document 

+

257 """ 

+

258 # Find the spine element 

+

259 spine_elem = root.find('.//{{{0}}}spine'.format(NAMESPACES['opf'])) 

+

260 if spine_elem is None: 260 ↛ 261line 260 didn't jump to line 261 because the condition on line 260 was never true

+

261 return 

+

262 

+

263 # Get the toc attribute (NCX file ID) 

+

264 toc_id = spine_elem.get('toc') 

+

265 if toc_id and toc_id in self.manifest: 265 ↛ 269line 265 didn't jump to line 269 because the condition on line 265 was always true

+

266 self.toc_path = self.manifest[toc_id]['path'] 

+

267 

+

268 # Parse itemrefs 

+

269 for itemref in spine_elem.findall( 

+

270 './/{{{0}}}itemref'.format(NAMESPACES['opf'])): 

+

271 idref = itemref.get('idref') 

+

272 if idref and idref in self.manifest: 272 ↛ 269line 272 didn't jump to line 269 because the condition on line 272 was always true

+

273 self.spine.append(idref) 

+

274 

+

275 def _parse_toc(self): 

+

276 """Parse the table of contents.""" 

+

277 if not hasattr( 277 ↛ 282line 277 didn't jump to line 282 because the condition on line 277 was never true

+

278 self, 

+

279 'toc_path') or not self.toc_path or not os.path.exists( 

+

280 self.toc_path): 

+

281 # Try to find the toc.ncx file 

+

282 for root, dirs, files in os.walk(self.content_dir): 

+

283 for file in files: 

+

284 if file.endswith('.ncx'): 

+

285 self.toc_path = os.path.join(root, file) 

+

286 break 

+

287 if hasattr(self, 'toc_path') and self.toc_path: 

+

288 break 

+

289 

+

290 if not hasattr( 290 ↛ 295line 290 didn't jump to line 295 because the condition on line 290 was never true

+

291 self, 

+

292 'toc_path') or not self.toc_path or not os.path.exists( 

+

293 self.toc_path): 

+

294 # No TOC found 

+

295 return 

+

296 

+

297 # Parse the NCX file 

+

298 tree = ET.parse(self.toc_path) 

+

299 root = tree.getroot() 

+

300 

+

301 # Parse navMap 

+

302 nav_map = root.find('.//{{{0}}}navMap'.format(NAMESPACES['ncx'])) 

+

303 if nav_map is None: 303 ↛ 304line 303 didn't jump to line 304 because the condition on line 303 was never true

+

304 return 

+

305 

+

306 # Parse navPoints 

+

307 self._parse_nav_points(nav_map, []) 

+

308 

+

309 def _parse_nav_points(self, parent: ET.Element, path: List[Dict[str, Any]]): 

+

310 """ 

+

311 Recursively parse navPoints from the NCX file. 

+

312 

+

313 Args: 

+

314 parent: Parent element containing navPoints 

+

315 path: Current path in the TOC hierarchy 

+

316 """ 

+

317 for nav_point in parent.findall('.//{{{0}}}navPoint'.format(NAMESPACES['ncx'])): 

+

318 # Get navPoint attributes 

+

319 id = nav_point.get('id') 

+

320 play_order = nav_point.get('playOrder') 

+

321 

+

322 # Get navLabel 

+

323 nav_label = nav_point.find('.//{{{0}}}navLabel'.format(NAMESPACES['ncx'])) 

+

324 text_elem = nav_label.find( 

+

325 './/{{{0}}}text'.format(NAMESPACES['ncx'])) if nav_label else None 

+

326 label = text_elem.text if text_elem is not None else "" 

+

327 

+

328 # Get content 

+

329 content = nav_point.find('.//{{{0}}}content'.format(NAMESPACES['ncx'])) 

+

330 src = content.get('src') if content is not None else "" 

+

331 

+

332 # Create a TOC entry 

+

333 entry = { 

+

334 'id': id, 

+

335 'label': label, 

+

336 'src': src, 

+

337 'play_order': play_order, 

+

338 'children': [] 

+

339 } 

+

340 

+

341 # Add to TOC 

+

342 if path: 342 ↛ 343line 342 didn't jump to line 343 because the condition on line 342 was never true

+

343 path[-1]['children'].append(entry) 

+

344 else: 

+

345 self.toc.append(entry) 

+

346 

+

347 # Parse child navPoints 

+

348 self._parse_nav_points(nav_point, path + [entry]) 

+

349 

+

350 def _create_book(self): 

+

351 """Create a Book object from the parsed metadata.""" 

+

352 # Set book metadata 

+

353 if 'title' in self.metadata: 353 ↛ 356line 353 didn't jump to line 356 because the condition on line 353 was always true

+

354 self.book.set_title(self.metadata['title']) 

+

355 

+

356 if 'creator' in self.metadata: 

+

357 self.book.set_metadata(MetadataType.AUTHOR, self.metadata['creator']) 

+

358 

+

359 if 'language' in self.metadata: 

+

360 self.book.set_metadata(MetadataType.LANGUAGE, self.metadata['language']) 

+

361 

+

362 if 'description' in self.metadata: 

+

363 self.book.set_metadata( 

+

364 MetadataType.DESCRIPTION, 

+

365 self.metadata['description']) 

+

366 

+

367 if 'subjects' in self.metadata: 

+

368 self.book.set_metadata( 

+

369 MetadataType.KEYWORDS, ', '.join( 

+

370 self.metadata['subjects'])) 

+

371 

+

372 if 'date' in self.metadata: 

+

373 self.book.set_metadata(MetadataType.PUBLICATION_DATE, self.metadata['date']) 

+

374 

+

375 if 'identifier' in self.metadata: 375 ↛ 378line 375 didn't jump to line 378 because the condition on line 375 was always true

+

376 self.book.set_metadata(MetadataType.IDENTIFIER, self.metadata['identifier']) 

+

377 

+

378 if 'publisher' in self.metadata: 

+

379 self.book.set_metadata(MetadataType.PUBLISHER, self.metadata['publisher']) 

+

380 

+

381 def _add_cover_chapter(self): 

+

382 """Add a cover chapter if a cover image is available.""" 

+

383 if not self.cover_id or self.cover_id not in self.manifest: 383 ↛ 387line 383 didn't jump to line 387 because the condition on line 383 was always true

+

384 return 

+

385 

+

386 # Get the cover image path from the manifest 

+

387 cover_item = self.manifest[self.cover_id] 

+

388 cover_path = cover_item['path'] 

+

389 

+

390 # Check if the file exists 

+

391 if not os.path.exists(cover_path): 

+

392 print(f"Warning: Cover image file not found: {cover_path}") 

+

393 return 

+

394 

+

395 # Create a cover chapter 

+

396 cover_chapter = self.book.create_chapter("Cover", 0) 

+

397 

+

398 try: 

+

399 # Create an Image block for the cover 

+

400 from pyWebLayout.abstract.block import Image as AbstractImage 

+

401 from PIL import Image as PILImage 

+

402 import io 

+

403 

+

404 # Load the image into memory before the temp directory is cleaned up 

+

405 # We need to fully copy the image data to ensure it persists after temp 

+

406 # cleanup 

+

407 with open(cover_path, 'rb') as f: 

+

408 image_bytes = f.read() 

+

409 

+

410 # Create PIL image from bytes in memory 

+

411 pil_image = PILImage.open(io.BytesIO(image_bytes)) 

+

412 pil_image.load() # Force loading into memory 

+

413 

+

414 # Create a copy to ensure all data is in memory 

+

415 pil_image = pil_image.copy() 

+

416 

+

417 # Apply image processing if enabled 

+

418 if self.image_processor: 

+

419 try: 

+

420 pil_image = self.image_processor(pil_image) 

+

421 except Exception as e: 

+

422 print(f"Warning: Image processing failed for cover: {str(e)}") 

+

423 # Continue with unprocessed image 

+

424 

+

425 # Create an AbstractImage block with the cover image path 

+

426 cover_image = AbstractImage(source=cover_path, alt_text="Cover Image") 

+

427 

+

428 # Set dimensions from the loaded image 

+

429 cover_image._width = pil_image.width 

+

430 cover_image._height = pil_image.height 

+

431 

+

432 # Store the loaded PIL image in the abstract image so it persists after 

+

433 # temp cleanup 

+

434 cover_image._loaded_image = pil_image 

+

435 

+

436 # Add the image to the cover chapter 

+

437 cover_chapter.add_block(cover_image) 

+

438 

+

439 except Exception as e: 

+

440 print(f"Error creating cover chapter: {str(e)}") 

+

441 import traceback 

+

442 traceback.print_exc() 

+

443 # If we can't create the cover image, remove the chapter 

+

444 if hasattr(self.book, 'chapters') and cover_chapter in self.book.chapters: 

+

445 self.book.chapters.remove(cover_chapter) 

+

446 

+

447 def _process_chapter_images(self, chapter: Chapter): 

+

448 """ 

+

449 Load and process images in a single chapter. 

+

450 

+

451 This method loads images from disk into memory and applies image processing. 

+

452 Images must be loaded before the temporary EPUB directory is cleaned up. 

+

453 

+

454 Args: 

+

455 chapter: The chapter containing images to process 

+

456 """ 

+

457 from pyWebLayout.abstract.block import Image as AbstractImage 

+

458 from PIL import Image as PILImage 

+

459 import io 

+

460 

+

461 for block in chapter.blocks: 

+

462 if isinstance(block, AbstractImage): 

+

463 # Load image into memory if not already loaded 

+

464 if not hasattr(block, '_loaded_image') or not block._loaded_image: 464 ↛ 485line 464 didn't jump to line 485 because the condition on line 464 was always true

+

465 try: 

+

466 # Load the image from the source path 

+

467 if os.path.isfile(block.source): 467 ↛ 485line 467 didn't jump to line 485 because the condition on line 467 was always true

+

468 with open(block.source, 'rb') as f: 

+

469 image_bytes = f.read() 

+

470 # Create PIL image from bytes in memory 

+

471 pil_image = PILImage.open(io.BytesIO(image_bytes)) 

+

472 pil_image.load() # Force loading into memory 

+

473 block._loaded_image = pil_image.copy() # Create a copy to ensure it persists 

+

474 

+

475 # Set width and height on the block from the loaded image 

+

476 # This is required for layout calculations 

+

477 block._width = pil_image.width 

+

478 block._height = pil_image.height 

+

479 except Exception as e: 

+

480 print(f"Warning: Failed to load image '{block.source}': {str(e)}") 

+

481 # Continue without the image 

+

482 continue 

+

483 

+

484 # Apply image processing if enabled and image is loaded 

+

485 if self.image_processor and hasattr(block, '_loaded_image') and block._loaded_image: 

+

486 try: 

+

487 block._loaded_image = self.image_processor(block._loaded_image) 

+

488 except Exception as e: 

+

489 print( 

+

490 f"Warning: Image processing failed for image '{block.alt_text}': {str(e)}" 

+

491 ) 

+

492 # Continue with unprocessed image 

+

493 

+

494 def _process_content_images(self): 

+

495 """ 

+

496 Load all images into memory and apply image processing. 

+

497 

+

498 This must be called before the temporary EPUB directory is cleaned up, 

+

499 to ensure images are loaded from disk into memory. 

+

500 """ 

+

501 for chapter in self.book.chapters: 

+

502 self._process_chapter_images(chapter) 

+

503 

+

504 def _add_chapters(self): 

+

505 """Add chapters to the book based on the spine and TOC.""" 

+

506 # Add cover chapter first if available 

+

507 self._add_cover_chapter() 

+

508 

+

509 # Create a mapping from src to TOC entry 

+

510 toc_map = {} 

+

511 

+

512 def add_to_toc_map(entries): 

+

513 for entry in entries: 

+

514 if entry['src']: 514 ↛ 521line 514 didn't jump to line 521 because the condition on line 514 was always true

+

515 # Extract the path part of the src (remove fragment) 

+

516 src_parts = entry['src'].split('#', 1) 

+

517 path = src_parts[0] 

+

518 toc_map[path] = entry 

+

519 

+

520 # Process children 

+

521 if entry['children']: 521 ↛ 522line 521 didn't jump to line 522 because the condition on line 521 was never true

+

522 add_to_toc_map(entry['children']) 

+

523 

+

524 add_to_toc_map(self.toc) 

+

525 

+

526 # Process spine items 

+

527 # Start from chapter_index = 1 if cover was added, otherwise 0 

+

528 chapter_index = 1 if (self.cover_id and self.cover_id in self.manifest) else 0 

+

529 for i, idref in enumerate(self.spine): 

+

530 if idref not in self.manifest: 530 ↛ 531line 530 didn't jump to line 531 because the condition on line 530 was never true

+

531 continue 

+

532 

+

533 item = self.manifest[idref] 

+

534 path = item['path'] 

+

535 href = item['href'] 

+

536 

+

537 # Skip navigation files 

+

538 if (idref == 'nav' or 

+

539 item.get('media_type') == 'application/xhtml+xml' and 

+

540 ('nav' in href.lower() or 'toc' in href.lower())): 

+

541 continue 

+

542 

+

543 # Check if this item is in the TOC 

+

544 chapter_title = None 

+

545 if href in toc_map: 545 ↛ 549line 545 didn't jump to line 549 because the condition on line 545 was always true

+

546 chapter_title = toc_map[href]['label'] 

+

547 

+

548 # Create a chapter 

+

549 chapter_index += 1 

+

550 chapter = self.book.create_chapter(chapter_title, chapter_index) 

+

551 

+

552 # Parse the HTML content 

+

553 try: 

+

554 # Read the HTML file 

+

555 with open(path, 'r', encoding='utf-8') as f: 

+

556 html = f.read() 

+

557 

+

558 # Get the directory of the HTML file for resolving relative paths 

+

559 html_dir = os.path.dirname(path) 

+

560 

+

561 # Parse HTML and add blocks to chapter, passing base_path for image resolution 

+

562 blocks = parse_html_string(html, document=self.book, base_path=html_dir) 

+

563 

+

564 # Copy blocks to the chapter 

+

565 for block in blocks: 

+

566 chapter.add_block(block) 

+

567 

+

568 # Add a PageBreak after the chapter to ensure next chapter starts on new page 

+

569 # This helps maintain chapter boundaries during pagination 

+

570 chapter.add_block(PageBreak()) 

+

571 

+

572 except Exception as e: 

+

573 print(f"Error parsing chapter {i + 1}: {str(e)}") 

+

574 # Add an error message block 

+

575 from pyWebLayout.abstract.block import Paragraph 

+

576 from pyWebLayout.abstract.inline import Word 

+

577 from pyWebLayout.style import Font 

+

578 error_para = Paragraph() 

+

579 # Create a default font style for the error message 

+

580 default_font = Font() 

+

581 error_para.add_word( 

+

582 Word( 

+

583 f"Error loading chapter: {str(e)}", 

+

584 default_font 

+

585 ) 

+

586 ) 

+

587 chapter.add_block(error_para) 

+

588 # Still add PageBreak even after error 

+

589 chapter.add_block(PageBreak()) 

+

590 

+

591 

+

592def read_epub(epub_path: str) -> Book: 

+

593 """ 

+

594 Read an EPUB file and convert it to a Book. 

+

595 

+

596 Args: 

+

597 epub_path: Path to the EPUB file 

+

598 

+

599 Returns: 

+

600 Book: The parsed book 

+

601 """ 

+

602 reader = EPUBReader(epub_path) 

+

603 return reader.read() 

+
+ + + diff --git a/cov_info/htmlcov/z_263f2e628cef8c50_html_extraction_py.html b/cov_info/htmlcov/z_263f2e628cef8c50_html_extraction_py.html new file mode 100644 index 0000000..df8ce4a --- /dev/null +++ b/cov_info/htmlcov/z_263f2e628cef8c50_html_extraction_py.html @@ -0,0 +1,1031 @@ + + + + + Coverage for pyWebLayout/io/readers/html_extraction.py: 88% + + + + + +
+
+

+ Coverage for pyWebLayout/io/readers/html_extraction.py: + 88% +

+ +

+ 424 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2HTML extraction module for converting HTML elements to pyWebLayout abstract elements. 

+

3 

+

4This module provides handler functions for converting HTML elements into the abstract document structure 

+

5used by pyWebLayout, including paragraphs, headings, lists, tables, and inline formatting. 

+

6Each handler function has a robust signature that handles style hints, CSS classes, and attributes. 

+

7""" 

+

8 

+

9from typing import List, Dict, Any, Optional, Union, Callable, Tuple, NamedTuple 

+

10from bs4 import BeautifulSoup, Tag, NavigableString 

+

11from pyWebLayout.abstract.inline import Word 

+

12from pyWebLayout.abstract.block import ( 

+

13 Block, 

+

14 Paragraph, 

+

15 Heading, 

+

16 HeadingLevel, 

+

17 Quote, 

+

18 CodeBlock, 

+

19 HList, 

+

20 ListItem, 

+

21 ListStyle, 

+

22 Table, 

+

23 TableRow, 

+

24 TableCell, 

+

25 HorizontalRule, 

+

26 Image, 

+

27) 

+

28from pyWebLayout.style import Font, FontWeight, FontStyle, TextDecoration 

+

29 

+

30 

+

31class StyleContext(NamedTuple): 

+

32 """ 

+

33 Immutable style context passed to handler functions. 

+

34 Contains all styling information including inherited styles, CSS hints, and element attributes. 

+

35 """ 

+

36 

+

37 font: Font 

+

38 background: Optional[Tuple[int, int, int, int]] 

+

39 css_classes: set 

+

40 css_styles: Dict[str, str] 

+

41 element_attributes: Dict[str, Any] 

+

42 parent_elements: List[str] # Stack of parent element names 

+

43 document: Optional[Any] # Reference to document for font registry 

+

44 base_path: Optional[str] = None # Base path for resolving relative URLs 

+

45 

+

46 def with_font(self, font: Font) -> "StyleContext": 

+

47 """Create new context with modified font.""" 

+

48 return self._replace(font=font) 

+

49 

+

50 def with_background( 

+

51 self, background: Optional[Tuple[int, int, int, int]] 

+

52 ) -> "StyleContext": 

+

53 """Create new context with modified background.""" 

+

54 return self._replace(background=background) 

+

55 

+

56 def with_css_classes(self, css_classes: set) -> "StyleContext": 

+

57 """Create new context with modified CSS classes.""" 

+

58 return self._replace(css_classes=css_classes) 

+

59 

+

60 def with_css_styles(self, css_styles: Dict[str, str]) -> "StyleContext": 

+

61 """Create new context with modified CSS styles.""" 

+

62 return self._replace(css_styles=css_styles) 

+

63 

+

64 def with_attributes(self, attributes: Dict[str, Any]) -> "StyleContext": 

+

65 """Create new context with modified element attributes.""" 

+

66 return self._replace(element_attributes=attributes) 

+

67 

+

68 def push_element(self, element_name: str) -> "StyleContext": 

+

69 """Create new context with element pushed onto parent stack.""" 

+

70 return self._replace(parent_elements=self.parent_elements + [element_name]) 

+

71 

+

72 

+

73def create_base_context( 

+

74 base_font: Optional[Font] = None, 

+

75 document=None, 

+

76 base_path: Optional[str] = None) -> StyleContext: 

+

77 """ 

+

78 Create a base style context with default values. 

+

79 

+

80 Args: 

+

81 base_font: Base font to use, defaults to system default 

+

82 document: Document instance for font registry 

+

83 base_path: Base directory path for resolving relative URLs 

+

84 

+

85 Returns: 

+

86 StyleContext with default values 

+

87 """ 

+

88 # Use document's font registry if available, otherwise create default font 

+

89 if base_font is None: 

+

90 if document and hasattr(document, 'get_or_create_font'): 

+

91 base_font = document.get_or_create_font() 

+

92 else: 

+

93 base_font = Font() 

+

94 

+

95 return StyleContext( 

+

96 font=base_font, 

+

97 background=None, 

+

98 css_classes=set(), 

+

99 css_styles={}, 

+

100 element_attributes={}, 

+

101 parent_elements=[], 

+

102 document=document, 

+

103 base_path=base_path, 

+

104 ) 

+

105 

+

106 

+

107def apply_element_styling(context: StyleContext, element: Tag) -> StyleContext: 

+

108 """ 

+

109 Apply element-specific styling to context based on HTML element and attributes. 

+

110 

+

111 Args: 

+

112 context: Current style context 

+

113 element: BeautifulSoup Tag object 

+

114 

+

115 Returns: 

+

116 New StyleContext with applied styling 

+

117 """ 

+

118 tag_name = element.name.lower() 

+

119 attributes = dict(element.attrs) if element.attrs else {} 

+

120 

+

121 # Start with current context 

+

122 new_context = context.with_attributes(attributes).push_element(tag_name) 

+

123 

+

124 # Apply CSS classes 

+

125 css_classes = new_context.css_classes.copy() 

+

126 if "class" in attributes: 

+

127 classes = ( 

+

128 attributes["class"].split() 

+

129 if isinstance(attributes["class"], str) 

+

130 else attributes["class"] 

+

131 ) 

+

132 css_classes.update(classes) 

+

133 new_context = new_context.with_css_classes(css_classes) 

+

134 

+

135 # Apply inline styles 

+

136 css_styles = new_context.css_styles.copy() 

+

137 if "style" in attributes: 

+

138 inline_styles = parse_inline_styles(attributes["style"]) 

+

139 css_styles.update(inline_styles) 

+

140 new_context = new_context.with_css_styles(css_styles) 

+

141 

+

142 # Apply element-specific default styles 

+

143 font = apply_element_font_styles( 

+

144 new_context.font, tag_name, css_styles, new_context) 

+

145 new_context = new_context.with_font(font) 

+

146 

+

147 # Apply background from styles 

+

148 background = apply_background_styles(new_context.background, css_styles) 

+

149 new_context = new_context.with_background(background) 

+

150 

+

151 return new_context 

+

152 

+

153 

+

154def parse_inline_styles(style_text: str) -> Dict[str, str]: 

+

155 """ 

+

156 Parse CSS inline styles into dictionary. 

+

157 

+

158 Args: 

+

159 style_text: CSS style text (e.g., "color: red; font-weight: bold;") 

+

160 

+

161 Returns: 

+

162 Dictionary of CSS property-value pairs 

+

163 """ 

+

164 styles = {} 

+

165 for declaration in style_text.split(";"): 

+

166 if ":" in declaration: 

+

167 prop, value = declaration.split(":", 1) 

+

168 styles[prop.strip().lower()] = value.strip() 

+

169 return styles 

+

170 

+

171 

+

172def apply_element_font_styles(font: Font, 

+

173 tag_name: str, 

+

174 css_styles: Dict[str, 

+

175 str], 

+

176 context: Optional[StyleContext] = None) -> Font: 

+

177 """ 

+

178 Apply font styling based on HTML element and CSS styles. 

+

179 Uses document's font registry when available to avoid creating duplicate fonts. 

+

180 

+

181 Args: 

+

182 font: Current font 

+

183 tag_name: HTML tag name 

+

184 css_styles: CSS styles dictionary 

+

185 context: Style context with document reference for font registry 

+

186 

+

187 Returns: 

+

188 Font object with applied styling (either existing or newly created) 

+

189 """ 

+

190 # Default element styles 

+

191 element_font_styles = { 

+

192 "b": {"weight": FontWeight.BOLD}, 

+

193 "strong": {"weight": FontWeight.BOLD}, 

+

194 "i": {"style": FontStyle.ITALIC}, 

+

195 "em": {"style": FontStyle.ITALIC}, 

+

196 "u": {"decoration": TextDecoration.UNDERLINE}, 

+

197 "s": {"decoration": TextDecoration.STRIKETHROUGH}, 

+

198 "del": {"decoration": TextDecoration.STRIKETHROUGH}, 

+

199 "h1": {"size": 24, "weight": FontWeight.BOLD}, 

+

200 "h2": {"size": 20, "weight": FontWeight.BOLD}, 

+

201 "h3": {"size": 18, "weight": FontWeight.BOLD}, 

+

202 "h4": {"size": 16, "weight": FontWeight.BOLD}, 

+

203 "h5": {"size": 14, "weight": FontWeight.BOLD}, 

+

204 "h6": {"size": 12, "weight": FontWeight.BOLD}, 

+

205 } 

+

206 

+

207 # Start with current font properties 

+

208 font_size = font.font_size 

+

209 colour = font.colour 

+

210 weight = font.weight 

+

211 style = font.style 

+

212 decoration = font.decoration 

+

213 background = font.background 

+

214 language = font.language 

+

215 font_path = font._font_path 

+

216 

+

217 # Apply element default styles 

+

218 if tag_name in element_font_styles: 

+

219 elem_styles = element_font_styles[tag_name] 

+

220 if "size" in elem_styles: 

+

221 font_size = elem_styles["size"] 

+

222 if "weight" in elem_styles: 

+

223 weight = elem_styles["weight"] 

+

224 if "style" in elem_styles: 

+

225 style = elem_styles["style"] 

+

226 if "decoration" in elem_styles: 

+

227 decoration = elem_styles["decoration"] 

+

228 

+

229 # Apply CSS styles (override element defaults) 

+

230 if "font-size" in css_styles: 

+

231 # Parse font-size (simplified - could be enhanced) 

+

232 size_value = css_styles["font-size"].lower() 

+

233 if size_value.endswith("px"): 

+

234 try: 

+

235 font_size = int(float(size_value[:-2])) 

+

236 except ValueError: 

+

237 pass 

+

238 elif size_value.endswith("pt"): 238 ↛ 239line 238 didn't jump to line 239 because the condition on line 238 was never true

+

239 try: 

+

240 font_size = int(float(size_value[:-2])) 

+

241 except ValueError: 

+

242 pass 

+

243 

+

244 if "font-weight" in css_styles: 

+

245 weight_value = css_styles["font-weight"].lower() 

+

246 if weight_value in ["bold", "700", "800", "900"]: 246 ↛ 248line 246 didn't jump to line 248 because the condition on line 246 was always true

+

247 weight = FontWeight.BOLD 

+

248 elif weight_value in ["normal", "400"]: 

+

249 weight = FontWeight.NORMAL 

+

250 

+

251 if "font-style" in css_styles: 

+

252 style_value = css_styles["font-style"].lower() 

+

253 if style_value == "italic": 

+

254 style = FontStyle.ITALIC 

+

255 elif style_value == "normal": 255 ↛ 258line 255 didn't jump to line 258 because the condition on line 255 was always true

+

256 style = FontStyle.NORMAL 

+

257 

+

258 if "text-decoration" in css_styles: 

+

259 decoration_value = css_styles["text-decoration"].lower() 

+

260 if "underline" in decoration_value: 

+

261 decoration = TextDecoration.UNDERLINE 

+

262 elif "line-through" in decoration_value: 262 ↛ 263line 262 didn't jump to line 263 because the condition on line 262 was never true

+

263 decoration = TextDecoration.STRIKETHROUGH 

+

264 elif "none" in decoration_value: 264 ↛ 267line 264 didn't jump to line 267 because the condition on line 264 was always true

+

265 decoration = TextDecoration.NONE 

+

266 

+

267 if "color" in css_styles: 

+

268 # Parse color (simplified - could be enhanced for hex, rgb, etc.) 

+

269 color_value = css_styles["color"].lower() 

+

270 color_map = { 

+

271 "black": (0, 0, 0), 

+

272 "white": (255, 255, 255), 

+

273 "red": (255, 0, 0), 

+

274 "green": (0, 255, 0), 

+

275 "blue": (0, 0, 255), 

+

276 } 

+

277 if color_value in color_map: 

+

278 colour = color_map[color_value] 

+

279 elif color_value.startswith("#") and len(color_value) == 7: 

+

280 try: 

+

281 r = int(color_value[1:3], 16) 

+

282 g = int(color_value[3:5], 16) 

+

283 b = int(color_value[5:7], 16) 

+

284 colour = (r, g, b) 

+

285 except ValueError: 

+

286 pass 

+

287 

+

288 # Use document's style registry if available to avoid creating duplicate styles 

+

289 if context and context.document and hasattr( 

+

290 context.document, 'get_or_create_style'): 

+

291 # Create an abstract style first 

+

292 from pyWebLayout.style.abstract_style import FontFamily, FontSize 

+

293 

+

294 # Map font properties to abstract style properties 

+

295 font_family = FontFamily.SERIF # Default - could be enhanced to detect from font_path 

+

296 if font_size: 296 ↛ 300line 296 didn't jump to line 300 because the condition on line 296 was always true

+

297 font_size_value = font_size if isinstance( 

+

298 font_size, int) else FontSize.MEDIUM 

+

299 else: 

+

300 font_size_value = FontSize.MEDIUM 

+

301 

+

302 # Create abstract style and register it 

+

303 style_id, abstract_style = context.document.get_or_create_style( 

+

304 font_family=font_family, 

+

305 font_size=font_size_value, 

+

306 font_weight=weight, 

+

307 font_style=style, 

+

308 text_decoration=decoration, 

+

309 color=colour, 

+

310 language=language 

+

311 ) 

+

312 

+

313 # Get the concrete font for this style 

+

314 return context.document.get_font_for_style(abstract_style) 

+

315 elif context and context.document and hasattr(context.document, 'get_or_create_font'): 315 ↛ 317line 315 didn't jump to line 317 because the condition on line 315 was never true

+

316 # Fallback to old font registry system 

+

317 return context.document.get_or_create_font( 

+

318 font_path=font_path, 

+

319 font_size=font_size, 

+

320 colour=colour, 

+

321 weight=weight, 

+

322 style=style, 

+

323 decoration=decoration, 

+

324 background=background, 

+

325 language=language, 

+

326 min_hyphenation_width=font.min_hyphenation_width 

+

327 ) 

+

328 else: 

+

329 # Fallback to creating new font if no document context 

+

330 return Font( 

+

331 font_path=font_path, 

+

332 font_size=font_size, 

+

333 colour=colour, 

+

334 weight=weight, 

+

335 style=style, 

+

336 decoration=decoration, 

+

337 background=background, 

+

338 language=language, 

+

339 ) 

+

340 

+

341 

+

342def apply_background_styles( 

+

343 current_background: Optional[Tuple[int, int, int, int]], css_styles: Dict[str, str] 

+

344) -> Optional[Tuple[int, int, int, int]]: 

+

345 """ 

+

346 Apply background styling from CSS. 

+

347 

+

348 Args: 

+

349 current_background: Current background color (RGBA) 

+

350 css_styles: CSS styles dictionary 

+

351 

+

352 Returns: 

+

353 New background color or None 

+

354 """ 

+

355 if "background-color" in css_styles: 

+

356 bg_value = css_styles["background-color"].lower() 

+

357 if bg_value == "transparent": 357 ↛ 358line 357 didn't jump to line 358 because the condition on line 357 was never true

+

358 return None 

+

359 # Add color parsing logic here if needed 

+

360 

+

361 return current_background 

+

362 

+

363 

+

364def extract_text_content(element: Tag, context: StyleContext) -> List[Word]: 

+

365 """ 

+

366 Extract text content from an element, handling inline formatting and links. 

+

367 

+

368 Args: 

+

369 element: BeautifulSoup Tag object 

+

370 context: Current style context 

+

371 

+

372 Returns: 

+

373 List of Word objects (including LinkedWord for hyperlinks) 

+

374 """ 

+

375 from pyWebLayout.abstract.inline import LinkedWord 

+

376 from pyWebLayout.abstract.functional import LinkType 

+

377 

+

378 words = [] 

+

379 

+

380 for child in element.children: 

+

381 if isinstance(child, NavigableString): 

+

382 # Plain text - split into words 

+

383 text = str(child).strip() 

+

384 if text: 

+

385 word_texts = text.split() 

+

386 for word_text in word_texts: 

+

387 if word_text: 387 ↛ 386line 387 didn't jump to line 386 because the condition on line 387 was always true

+

388 words.append(Word(word_text, context.font, context.background)) 

+

389 elif isinstance(child, Tag): 389 ↛ 380line 389 didn't jump to line 380 because the condition on line 389 was always true

+

390 # Special handling for <a> tags (hyperlinks) 

+

391 if child.name.lower() == "a": 

+

392 href = child.get('href', '') 

+

393 if href: 

+

394 # Determine link type based on href 

+

395 if href.startswith(('http://', 'https://')): 

+

396 link_type = LinkType.EXTERNAL 

+

397 elif href.startswith('#'): 

+

398 link_type = LinkType.INTERNAL 

+

399 elif href.startswith('javascript:') or href.startswith('api:'): 

+

400 link_type = LinkType.API 

+

401 else: 

+

402 link_type = LinkType.INTERNAL 

+

403 

+

404 # Apply link styling 

+

405 child_context = apply_element_styling(context, child) 

+

406 

+

407 # Extract text and create LinkedWord for each word 

+

408 link_text = child.get_text(strip=True) 

+

409 title = child.get('title', '') 

+

410 

+

411 for word_text in link_text.split(): 

+

412 if word_text: 412 ↛ 411line 412 didn't jump to line 411 because the condition on line 412 was always true

+

413 linked_word = LinkedWord( 

+

414 text=word_text, 

+

415 style=child_context.font, 

+

416 location=href, 

+

417 link_type=link_type, 

+

418 background=child_context.background, 

+

419 title=title if title else None 

+

420 ) 

+

421 words.append(linked_word) 

+

422 else: 

+

423 # <a> without href - treat as normal text 

+

424 child_context = apply_element_styling(context, child) 

+

425 child_words = extract_text_content(child, child_context) 

+

426 words.extend(child_words) 

+

427 

+

428 # Process other inline elements 

+

429 elif child.name.lower() in [ 

+

430 "span", 

+

431 "strong", 

+

432 "b", 

+

433 "em", 

+

434 "i", 

+

435 "u", 

+

436 "s", 

+

437 "del", 

+

438 "ins", 

+

439 "mark", 

+

440 "small", 

+

441 "sub", 

+

442 "sup", 

+

443 "code", 

+

444 "q", 

+

445 "cite", 

+

446 "abbr", 

+

447 "time", 

+

448 ]: 

+

449 child_context = apply_element_styling(context, child) 

+

450 child_words = extract_text_content(child, child_context) 

+

451 words.extend(child_words) 

+

452 else: 

+

453 # Block element - shouldn't happen in well-formed HTML but handle 

+

454 # gracefully 

+

455 child_context = apply_element_styling(context, child) 

+

456 child_result = process_element(child, child_context) 

+

457 if isinstance(child_result, list): 457 ↛ 458line 457 didn't jump to line 458 because the condition on line 457 was never true

+

458 for block in child_result: 

+

459 if isinstance(block, Paragraph): 

+

460 for _, word in block.words_iter(): 

+

461 words.append(word) 

+

462 elif isinstance(child_result, Paragraph): 462 ↛ 463line 462 didn't jump to line 463 because the condition on line 462 was never true

+

463 for _, word in child_result.words_iter(): 

+

464 words.append(word) 

+

465 

+

466 return words 

+

467 

+

468 

+

469def process_element( 

+

470 element: Tag, context: StyleContext 

+

471) -> Union[Block, List[Block], None]: 

+

472 """ 

+

473 Process a single HTML element using appropriate handler. 

+

474 

+

475 Args: 

+

476 element: BeautifulSoup Tag object 

+

477 context: Current style context 

+

478 

+

479 Returns: 

+

480 Block object(s) or None if element should be ignored 

+

481 """ 

+

482 tag_name = element.name.lower() 

+

483 handler = HANDLERS.get(tag_name, generic_handler) 

+

484 return handler(element, context) 

+

485 

+

486 

+

487# Handler function signatures: 

+

488# All handlers receive (element: Tag, context: StyleContext) -> 

+

489# Union[Block, List[Block], None] 

+

490 

+

491 

+

492def paragraph_handler(element: Tag, context: StyleContext) -> Union[Paragraph, List[Block], Image]: 

+

493 """ 

+

494 Handle <p> elements. 

+

495 

+

496 Special handling for paragraphs containing images: 

+

497 - If the paragraph contains only an image (common in EPUBs), return the image block 

+

498 - If the paragraph contains images mixed with text, split into separate blocks 

+

499 - Otherwise, return a normal paragraph with text content 

+

500 """ 

+

501 # Check if paragraph contains any img tags (including nested ones) 

+

502 img_tags = element.find_all('img') 

+

503 

+

504 if img_tags: 

+

505 # Paragraph contains images - need special handling 

+

506 blocks = [] 

+

507 

+

508 # Check if this is an image-only paragraph (very common in EPUBs) 

+

509 # Get text content without the img tags 

+

510 text_content = element.get_text(strip=True) 

+

511 

+

512 if not text_content or len(text_content.strip()) == 0: 

+

513 # Image-only paragraph - return just the image(s) 

+

514 for img_tag in img_tags: 

+

515 child_context = apply_element_styling(context, img_tag) 

+

516 img_block = image_handler(img_tag, child_context) 

+

517 if img_block: 517 ↛ 514line 517 didn't jump to line 514 because the condition on line 517 was always true

+

518 blocks.append(img_block) 

+

519 

+

520 # Return single image or list of images 

+

521 if len(blocks) == 1: 

+

522 return blocks[0] 

+

523 return blocks if blocks else Paragraph(context.font) 

+

524 

+

525 # Mixed content - paragraph has both text and images 

+

526 # Process children in order to preserve structure 

+

527 for child in element.children: 

+

528 if isinstance(child, Tag): 

+

529 if child.name == 'img': 529 ↛ 538line 529 didn't jump to line 538 because the condition on line 529 was always true

+

530 # Add the image as a separate block 

+

531 child_context = apply_element_styling(context, child) 

+

532 img_block = image_handler(child, child_context) 

+

533 if img_block: 533 ↛ 527line 533 didn't jump to line 527 because the condition on line 533 was always true

+

534 blocks.append(img_block) 

+

535 else: 

+

536 # Process other inline elements as part of text 

+

537 # This will be handled by extract_text_content below 

+

538 pass 

+

539 

+

540 # Also add a paragraph with the text content 

+

541 paragraph = Paragraph(context.font) 

+

542 words = extract_text_content(element, context) 

+

543 if words: 543 ↛ 548line 543 didn't jump to line 548 because the condition on line 543 was always true

+

544 for word in words: 

+

545 paragraph.add_word(word) 

+

546 blocks.insert(0, paragraph) # Text comes before images 

+

547 

+

548 return blocks if blocks else Paragraph(context.font) 

+

549 

+

550 # No images - normal paragraph handling 

+

551 paragraph = Paragraph(context.font) 

+

552 words = extract_text_content(element, context) 

+

553 for word in words: 

+

554 paragraph.add_word(word) 

+

555 return paragraph 

+

556 

+

557 

+

558def div_handler(element: Tag, context: StyleContext) -> List[Block]: 

+

559 """Handle <div> elements - treat as generic container.""" 

+

560 blocks = [] 

+

561 for child in element.children: 

+

562 if isinstance(child, Tag): 

+

563 child_context = apply_element_styling(context, child) 

+

564 result = process_element(child, child_context) 

+

565 if result: 

+

566 if isinstance(result, list): 

+

567 blocks.extend(result) 

+

568 else: 

+

569 blocks.append(result) 

+

570 return blocks 

+

571 

+

572 

+

573def heading_handler(element: Tag, context: StyleContext) -> Heading: 

+

574 """Handle <h1>-<h6> elements.""" 

+

575 level_map = { 

+

576 "h1": HeadingLevel.H1, 

+

577 "h2": HeadingLevel.H2, 

+

578 "h3": HeadingLevel.H3, 

+

579 "h4": HeadingLevel.H4, 

+

580 "h5": HeadingLevel.H5, 

+

581 "h6": HeadingLevel.H6, 

+

582 } 

+

583 

+

584 level = level_map.get(element.name.lower(), HeadingLevel.H1) 

+

585 heading = Heading(level, context.font) 

+

586 words = extract_text_content(element, context) 

+

587 for word in words: 

+

588 heading.add_word(word) 

+

589 return heading 

+

590 

+

591 

+

592def blockquote_handler(element: Tag, context: StyleContext) -> Quote: 

+

593 """Handle <blockquote> elements.""" 

+

594 quote = Quote(context.font) 

+

595 for child in element.children: 

+

596 if isinstance(child, Tag): 

+

597 child_context = apply_element_styling(context, child) 

+

598 result = process_element(child, child_context) 

+

599 if result: 599 ↛ 595line 599 didn't jump to line 595 because the condition on line 599 was always true

+

600 if isinstance(result, list): 600 ↛ 601line 600 didn't jump to line 601 because the condition on line 600 was never true

+

601 for block in result: 

+

602 quote.add_block(block) 

+

603 else: 

+

604 quote.add_block(result) 

+

605 return quote 

+

606 

+

607 

+

608def preformatted_handler(element: Tag, context: StyleContext) -> CodeBlock: 

+

609 """Handle <pre> elements.""" 

+

610 language = context.element_attributes.get("data-language", "") 

+

611 code_block = CodeBlock(language) 

+

612 

+

613 # Preserve whitespace and line breaks in preformatted text 

+

614 text = element.get_text(separator="\n", strip=False) 

+

615 for line in text.split("\n"): 

+

616 code_block.add_line(line) 

+

617 

+

618 return code_block 

+

619 

+

620 

+

621def code_handler(element: Tag, context: StyleContext) -> Union[CodeBlock, None]: 

+

622 """Handle <code> elements.""" 

+

623 # If parent is <pre>, this is handled by preformatted_handler 

+

624 if context.parent_elements and context.parent_elements[-1] == "pre": 624 ↛ 625line 624 didn't jump to line 625 because the condition on line 624 was never true

+

625 return None # Will be handled by parent 

+

626 

+

627 # Inline code - handled during text extraction 

+

628 return None 

+

629 

+

630 

+

631def unordered_list_handler(element: Tag, context: StyleContext) -> HList: 

+

632 """Handle <ul> elements.""" 

+

633 hlist = HList(ListStyle.UNORDERED, context.font) 

+

634 for child in element.children: 

+

635 if isinstance(child, Tag) and child.name.lower() == "li": 

+

636 child_context = apply_element_styling(context, child) 

+

637 item = process_element(child, child_context) 

+

638 if item: 638 ↛ 634line 638 didn't jump to line 634 because the condition on line 638 was always true

+

639 hlist.add_item(item) 

+

640 return hlist 

+

641 

+

642 

+

643def ordered_list_handler(element: Tag, context: StyleContext) -> HList: 

+

644 """Handle <ol> elements.""" 

+

645 hlist = HList(ListStyle.ORDERED, context.font) 

+

646 for child in element.children: 

+

647 if isinstance(child, Tag) and child.name.lower() == "li": 

+

648 child_context = apply_element_styling(context, child) 

+

649 item = process_element(child, child_context) 

+

650 if item: 650 ↛ 646line 650 didn't jump to line 646 because the condition on line 650 was always true

+

651 hlist.add_item(item) 

+

652 return hlist 

+

653 

+

654 

+

655def list_item_handler(element: Tag, context: StyleContext) -> ListItem: 

+

656 """Handle <li> elements.""" 

+

657 list_item = ListItem(None, context.font) 

+

658 

+

659 for child in element.children: 

+

660 if isinstance(child, Tag): 

+

661 child_context = apply_element_styling(context, child) 

+

662 result = process_element(child, child_context) 

+

663 if result: 

+

664 if isinstance(result, list): 664 ↛ 665line 664 didn't jump to line 665 because the condition on line 664 was never true

+

665 for block in result: 

+

666 list_item.add_block(block) 

+

667 else: 

+

668 list_item.add_block(result) 

+

669 elif isinstance(child, NavigableString): 669 ↛ 659line 669 didn't jump to line 659 because the condition on line 669 was always true

+

670 # Direct text in list item - create paragraph 

+

671 text = str(child).strip() 

+

672 if text: 

+

673 paragraph = Paragraph(context.font) 

+

674 words = text.split() 

+

675 for word_text in words: 

+

676 if word_text: 676 ↛ 675line 676 didn't jump to line 675 because the condition on line 676 was always true

+

677 paragraph.add_word(Word(word_text, context.font)) 

+

678 list_item.add_block(paragraph) 

+

679 

+

680 return list_item 

+

681 

+

682 

+

683def table_handler(element: Tag, context: StyleContext) -> Table: 

+

684 """Handle <table> elements.""" 

+

685 caption = None 

+

686 caption_elem = element.find("caption") 

+

687 if caption_elem: 687 ↛ 688line 687 didn't jump to line 688 because the condition on line 687 was never true

+

688 caption = caption_elem.get_text(strip=True) 

+

689 

+

690 table = Table(caption, context.font) 

+

691 

+

692 # Process table rows 

+

693 for child in element.children: 

+

694 if isinstance(child, Tag): 

+

695 if child.name.lower() == "tr": 

+

696 child_context = apply_element_styling(context, child) 

+

697 row = process_element(child, child_context) 

+

698 if row: 698 ↛ 693line 698 didn't jump to line 693 because the condition on line 698 was always true

+

699 table.add_row(row) 

+

700 elif child.name.lower() in ["thead", "tbody", "tfoot"]: 700 ↛ 693line 700 didn't jump to line 693 because the condition on line 700 was always true

+

701 section = "header" if child.name.lower() == "thead" else "body" 

+

702 section = "footer" if child.name.lower() == "tfoot" else section 

+

703 

+

704 for row_elem in child.find_all("tr"): 

+

705 child_context = apply_element_styling(context, row_elem) 

+

706 row = process_element(row_elem, child_context) 

+

707 if row: 707 ↛ 704line 707 didn't jump to line 704 because the condition on line 707 was always true

+

708 table.add_row(row, section) 

+

709 

+

710 return table 

+

711 

+

712 

+

713def table_row_handler(element: Tag, context: StyleContext) -> TableRow: 

+

714 """Handle <tr> elements.""" 

+

715 row = TableRow(context.font) 

+

716 for child in element.children: 

+

717 if isinstance(child, Tag) and child.name.lower() in ["td", "th"]: 

+

718 child_context = apply_element_styling(context, child) 

+

719 cell = process_element(child, child_context) 

+

720 if cell: 720 ↛ 716line 720 didn't jump to line 716 because the condition on line 720 was always true

+

721 row.add_cell(cell) 

+

722 return row 

+

723 

+

724 

+

725def table_cell_handler(element: Tag, context: StyleContext) -> TableCell: 

+

726 """Handle <td> elements.""" 

+

727 colspan = int(context.element_attributes.get("colspan", 1)) 

+

728 rowspan = int(context.element_attributes.get("rowspan", 1)) 

+

729 cell = TableCell(False, colspan, rowspan, context.font) 

+

730 

+

731 # Process cell content 

+

732 for child in element.children: 

+

733 if isinstance(child, Tag): 

+

734 child_context = apply_element_styling(context, child) 

+

735 result = process_element(child, child_context) 

+

736 if result: 

+

737 if isinstance(result, list): 

+

738 for block in result: 

+

739 cell.add_block(block) 

+

740 else: 

+

741 cell.add_block(result) 

+

742 elif isinstance(child, NavigableString): 742 ↛ 732line 742 didn't jump to line 732 because the condition on line 742 was always true

+

743 # Direct text in cell - create paragraph 

+

744 text = str(child).strip() 

+

745 if text: 

+

746 paragraph = Paragraph(context.font) 

+

747 words = text.split() 

+

748 for word_text in words: 

+

749 if word_text: 749 ↛ 748line 749 didn't jump to line 748 because the condition on line 749 was always true

+

750 paragraph.add_word(Word(word_text, context.font)) 

+

751 cell.add_block(paragraph) 

+

752 

+

753 return cell 

+

754 

+

755 

+

756def table_header_cell_handler(element: Tag, context: StyleContext) -> TableCell: 

+

757 """Handle <th> elements.""" 

+

758 colspan = int(context.element_attributes.get("colspan", 1)) 

+

759 rowspan = int(context.element_attributes.get("rowspan", 1)) 

+

760 cell = TableCell(True, colspan, rowspan, context.font) 

+

761 

+

762 # Process cell content (same as td) 

+

763 for child in element.children: 

+

764 if isinstance(child, Tag): 

+

765 child_context = apply_element_styling(context, child) 

+

766 result = process_element(child, child_context) 

+

767 if result: 

+

768 if isinstance(result, list): 768 ↛ 772line 768 didn't jump to line 772 because the condition on line 768 was always true

+

769 for block in result: 

+

770 cell.add_block(block) 

+

771 else: 

+

772 cell.add_block(result) 

+

773 elif isinstance(child, NavigableString): 773 ↛ 763line 773 didn't jump to line 763 because the condition on line 773 was always true

+

774 text = str(child).strip() 

+

775 if text: 

+

776 paragraph = Paragraph(context.font) 

+

777 words = text.split() 

+

778 for word_text in words: 

+

779 if word_text: 779 ↛ 778line 779 didn't jump to line 778 because the condition on line 779 was always true

+

780 paragraph.add_word(Word(word_text, context.font)) 

+

781 cell.add_block(paragraph) 

+

782 

+

783 return cell 

+

784 

+

785 

+

786def horizontal_rule_handler(element: Tag, context: StyleContext) -> HorizontalRule: 

+

787 """Handle <hr> elements.""" 

+

788 return HorizontalRule() 

+

789 

+

790 

+

791def line_break_handler(element: Tag, context: StyleContext) -> None: 

+

792 """Handle <br> elements.""" 

+

793 # Line breaks are typically handled at the paragraph level 

+

794 return None 

+

795 

+

796 

+

797def image_handler(element: Tag, context: StyleContext) -> Image: 

+

798 """Handle <img> elements.""" 

+

799 import os 

+

800 import urllib.parse 

+

801 

+

802 src = context.element_attributes.get("src", "") 

+

803 alt_text = context.element_attributes.get("alt", "") 

+

804 

+

805 # Resolve relative paths if base_path is provided 

+

806 if context.base_path and src and not src.startswith(('http://', 'https://', '/')): 

+

807 # Parse the src to handle URL-encoded characters 

+

808 src_decoded = urllib.parse.unquote(src) 

+

809 # Resolve relative path to absolute path 

+

810 src = os.path.normpath(os.path.join(context.base_path, src_decoded)) 

+

811 

+

812 # Parse dimensions if provided 

+

813 width = height = None 

+

814 try: 

+

815 if "width" in context.element_attributes: 

+

816 width = int(context.element_attributes["width"]) 

+

817 if "height" in context.element_attributes: 

+

818 height = int(context.element_attributes["height"]) 

+

819 except ValueError: 

+

820 pass 

+

821 

+

822 return Image(source=src, alt_text=alt_text, width=width, height=height) 

+

823 

+

824 

+

825def ignore_handler(element: Tag, context: StyleContext) -> None: 

+

826 """Handle elements that should be ignored.""" 

+

827 return None 

+

828 

+

829 

+

830def generic_handler(element: Tag, context: StyleContext) -> List[Block]: 

+

831 """Handle unknown elements as generic containers.""" 

+

832 return div_handler(element, context) 

+

833 

+

834 

+

835# Handler registry - maps HTML tag names to handler functions 

+

836HANDLERS: Dict[str, Callable[[Tag, StyleContext], Union[Block, List[Block], None]]] = { 

+

837 # Block elements 

+

838 "p": paragraph_handler, 

+

839 "div": div_handler, 

+

840 "h1": heading_handler, 

+

841 "h2": heading_handler, 

+

842 "h3": heading_handler, 

+

843 "h4": heading_handler, 

+

844 "h5": heading_handler, 

+

845 "h6": heading_handler, 

+

846 "blockquote": blockquote_handler, 

+

847 "pre": preformatted_handler, 

+

848 "code": code_handler, 

+

849 "ul": unordered_list_handler, 

+

850 "ol": ordered_list_handler, 

+

851 "li": list_item_handler, 

+

852 "table": table_handler, 

+

853 "tr": table_row_handler, 

+

854 "td": table_cell_handler, 

+

855 "th": table_header_cell_handler, 

+

856 "hr": horizontal_rule_handler, 

+

857 "br": line_break_handler, 

+

858 # Semantic elements (treated as containers) 

+

859 "section": div_handler, 

+

860 "article": div_handler, 

+

861 "aside": div_handler, 

+

862 "nav": div_handler, 

+

863 "header": div_handler, 

+

864 "footer": div_handler, 

+

865 "main": div_handler, 

+

866 "figure": div_handler, 

+

867 "figcaption": paragraph_handler, 

+

868 # Media elements 

+

869 "img": image_handler, 

+

870 # Inline elements (handled during text extraction) 

+

871 "span": ignore_handler, 

+

872 "a": ignore_handler, 

+

873 "strong": ignore_handler, 

+

874 "b": ignore_handler, 

+

875 "em": ignore_handler, 

+

876 "i": ignore_handler, 

+

877 "u": ignore_handler, 

+

878 "s": ignore_handler, 

+

879 "del": ignore_handler, 

+

880 "ins": ignore_handler, 

+

881 "mark": ignore_handler, 

+

882 "small": ignore_handler, 

+

883 "sub": ignore_handler, 

+

884 "sup": ignore_handler, 

+

885 "q": ignore_handler, 

+

886 "cite": ignore_handler, 

+

887 "abbr": ignore_handler, 

+

888 "time": ignore_handler, 

+

889 # Ignored elements 

+

890 "script": ignore_handler, 

+

891 "style": ignore_handler, 

+

892 "meta": ignore_handler, 

+

893 "link": ignore_handler, 

+

894 "head": ignore_handler, 

+

895 "title": ignore_handler, 

+

896} 

+

897 

+

898 

+

899def parse_html_string( 

+

900 html_string: str, base_font: Optional[Font] = None, document=None, base_path: Optional[str] = None 

+

901) -> List[Block]: 

+

902 """ 

+

903 Parse HTML string and return list of Block objects. 

+

904 

+

905 Args: 

+

906 html_string: HTML content to parse 

+

907 base_font: Base font for styling, defaults to system default 

+

908 document: Document instance for font registry to avoid duplicate fonts 

+

909 base_path: Base directory path for resolving relative URLs (e.g., image sources) 

+

910 

+

911 Returns: 

+

912 List of Block objects representing the document structure 

+

913 """ 

+

914 soup = BeautifulSoup(html_string, "html.parser") 

+

915 context = create_base_context(base_font, document, base_path) 

+

916 

+

917 blocks = [] 

+

918 

+

919 # Process the body if it exists, otherwise process all top-level elements 

+

920 root_element = soup.find("body") or soup 

+

921 

+

922 for element in root_element.children: 

+

923 if isinstance(element, Tag): 

+

924 element_context = apply_element_styling(context, element) 

+

925 result = process_element(element, element_context) 

+

926 if result: 

+

927 if isinstance(result, list): 

+

928 blocks.extend(result) 

+

929 else: 

+

930 blocks.append(result) 

+

931 

+

932 return blocks 

+
+ + + diff --git a/cov_info/htmlcov/z_40407af872b0cf37___init___py.html b/cov_info/htmlcov/z_40407af872b0cf37___init___py.html new file mode 100644 index 0000000..b04dc8d --- /dev/null +++ b/cov_info/htmlcov/z_40407af872b0cf37___init___py.html @@ -0,0 +1,133 @@ + + + + + Coverage for pyWebLayout/core/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/core/__init__.py: + 100% +

+ +

+ 2 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Core functionality for the pyWebLayout library. 

+

3 

+

4This package contains the core abstractions and base classes that form the foundation 

+

5of the pyWebLayout rendering system. 

+

6""" 

+

7 

+

8from .base import ( 

+

9 Renderable, 

+

10 Interactable, 

+

11 Layoutable, 

+

12 Queriable, 

+

13 Hierarchical, 

+

14 Geometric, 

+

15 Styleable, 

+

16 FontRegistry, 

+

17 MetadataContainer, 

+

18 BlockContainer, 

+

19 ContainerAware, 

+

20) 

+

21 

+

22__all__ = [ 

+

23 'Renderable', 

+

24 'Interactable', 

+

25 'Layoutable', 

+

26 'Queriable', 

+

27 'Hierarchical', 

+

28 'Geometric', 

+

29 'Styleable', 

+

30 'FontRegistry', 

+

31 'MetadataContainer', 

+

32 'BlockContainer', 

+

33 'ContainerAware', 

+

34] 

+
+ + + diff --git a/cov_info/htmlcov/z_40407af872b0cf37_base_py.html b/cov_info/htmlcov/z_40407af872b0cf37_base_py.html new file mode 100644 index 0000000..5e8b067 --- /dev/null +++ b/cov_info/htmlcov/z_40407af872b0cf37_base_py.html @@ -0,0 +1,547 @@ + + + + + Coverage for pyWebLayout/core/base.py: 72% + + + + + +
+
+

+ Coverage for pyWebLayout/core/base.py: + 72% +

+ +

+ 134 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from abc import ABC 

+

2from typing import Optional, Tuple, TYPE_CHECKING, Any, Dict 

+

3import numpy as np 

+

4 

+

5 

+

6if TYPE_CHECKING: 6 ↛ 7line 6 didn't jump to line 7 because the condition on line 6 was never true

+

7 from pyWebLayout.style import Font, FontWeight, FontStyle, TextDecoration 

+

8 

+

9 

+

10class Renderable(ABC): 

+

11 """ 

+

12 Abstract base class for any object that can be rendered to an image. 

+

13 All renderable objects must implement the render method. 

+

14 """ 

+

15 

+

16 def render(self): 

+

17 """ 

+

18 Render the object to an image. 

+

19 

+

20 Returns: 

+

21 PIL.Image: The rendered image 

+

22 """ 

+

23 

+

24 @property 

+

25 def origin(self): 

+

26 return self._origin 

+

27 

+

28 

+

29class Interactable(ABC): 

+

30 """ 

+

31 Abstract base class for any object that can be interacted with. 

+

32 Interactable objects must have a callback that is executed when interacted with. 

+

33 """ 

+

34 

+

35 def __init__(self, callback=None): 

+

36 """ 

+

37 Initialize an interactable object. 

+

38 

+

39 Args: 

+

40 callback: The function to call when this object is interacted with 

+

41 """ 

+

42 self._callback = callback 

+

43 

+

44 def interact(self, point: np.generic): 

+

45 """ 

+

46 Handle interaction at the given point. 

+

47 

+

48 Args: 

+

49 point: The coordinates of the interaction 

+

50 

+

51 Returns: 

+

52 The result of calling the callback function with the point 

+

53 """ 

+

54 if self._callback is None: 54 ↛ 55line 54 didn't jump to line 55 because the condition on line 54 was never true

+

55 return None 

+

56 return self._callback(point) 

+

57 

+

58 

+

59class Layoutable(ABC): 

+

60 """ 

+

61 Abstract base class for any object that can be laid out. 

+

62 Layoutable objects must implement the layout method which arranges their contents. 

+

63 """ 

+

64 

+

65 def layout(self): 

+

66 """ 

+

67 Layout the object's contents. 

+

68 This method should be called before rendering to properly arrange the object's contents. 

+

69 """ 

+

70 

+

71 

+

72class Queriable(ABC): 

+

73 

+

74 def in_object(self, point: np.generic): 

+

75 """ 

+

76 check if a point is in the object 

+

77 """ 

+

78 point_array = np.array(point) 

+

79 relative_point = point_array - self._origin 

+

80 return np.all((0 <= relative_point) & (relative_point < self.size)) 

+

81 

+

82 

+

83# ============================================================================== 

+

84# Mixins - Reusable components for common patterns 

+

85# ============================================================================== 

+

86 

+

87 

+

88class Hierarchical: 

+

89 """ 

+

90 Mixin providing parent-child relationship management. 

+

91 

+

92 Classes using this mixin can track their parent in a document hierarchy. 

+

93 """ 

+

94 

+

95 def __init__(self, *args, **kwargs): 

+

96 super().__init__(*args, **kwargs) 

+

97 self._parent: Optional[Any] = None 

+

98 

+

99 @property 

+

100 def parent(self) -> Optional[Any]: 

+

101 """Get the parent object containing this object, if any""" 

+

102 return self._parent 

+

103 

+

104 @parent.setter 

+

105 def parent(self, parent: Any): 

+

106 """Set the parent object""" 

+

107 self._parent = parent 

+

108 

+

109 

+

110class Geometric: 

+

111 """ 

+

112 Mixin providing origin and size properties for positioned elements. 

+

113 

+

114 Provides standard geometric properties for elements that have a position 

+

115 and size in 2D space. Uses numpy arrays for efficient calculations. 

+

116 """ 

+

117 

+

118 def __init__(self, *args, origin=None, size=None, **kwargs): 

+

119 super().__init__(*args, **kwargs) 

+

120 self._origin = np.array(origin) if origin is not None else np.array([0, 0]) 

+

121 self._size = np.array(size) if size is not None else np.array([0, 0]) 

+

122 

+

123 @property 

+

124 def origin(self) -> np.ndarray: 

+

125 """Get the origin (top-left corner) of the element""" 

+

126 return self._origin 

+

127 

+

128 @origin.setter 

+

129 def origin(self, origin: np.ndarray): 

+

130 """Set the origin of the element""" 

+

131 self._origin = np.array(origin) 

+

132 

+

133 @property 

+

134 def size(self) -> np.ndarray: 

+

135 """Get the size (width, height) of the element""" 

+

136 return self._size 

+

137 

+

138 @size.setter 

+

139 def size(self, size: np.ndarray): 

+

140 """Set the size of the element""" 

+

141 self._size = np.array(size) 

+

142 

+

143 def set_origin(self, origin: np.ndarray): 

+

144 """Set the origin of this element (alternative setter method)""" 

+

145 self._origin = np.array(origin) 

+

146 

+

147 

+

148class Styleable: 

+

149 """ 

+

150 Mixin providing style property management. 

+

151 

+

152 Classes using this mixin can have a style property that can be 

+

153 inherited from parents or set explicitly. 

+

154 """ 

+

155 

+

156 def __init__(self, *args, style=None, **kwargs): 

+

157 super().__init__(*args, **kwargs) 

+

158 self._style = style 

+

159 

+

160 @property 

+

161 def style(self) -> Optional[Any]: 

+

162 """Get the style for this element""" 

+

163 return self._style 

+

164 

+

165 @style.setter 

+

166 def style(self, style: Any): 

+

167 """Set the style for this element""" 

+

168 self._style = style 

+

169 

+

170 

+

171class FontRegistry: 

+

172 """ 

+

173 Mixin providing font caching and creation with parent delegation. 

+

174 

+

175 This mixin allows classes to maintain a local font registry and create/reuse 

+

176 Font objects efficiently. It supports parent delegation, where font requests 

+

177 can cascade up to a parent container if one exists. 

+

178 

+

179 Classes using this mixin should also use Hierarchical to support parent delegation. 

+

180 """ 

+

181 

+

182 def __init__(self, *args, **kwargs): 

+

183 super().__init__(*args, **kwargs) 

+

184 self._fonts: Dict[str, 'Font'] = {} 

+

185 

+

186 def get_or_create_font(self, 

+

187 font_path: Optional[str] = None, 

+

188 font_size: int = 16, 

+

189 colour: Tuple[int, int, int] = (0, 0, 0), 

+

190 weight: 'FontWeight' = None, 

+

191 style: 'FontStyle' = None, 

+

192 decoration: 'TextDecoration' = None, 

+

193 background: Optional[Tuple[int, int, int, int]] = None, 

+

194 language: str = "en_EN", 

+

195 min_hyphenation_width: Optional[int] = None) -> 'Font': 

+

196 """ 

+

197 Get or create a font with the specified properties. 

+

198 

+

199 This method will first check if a parent object has a get_or_create_font 

+

200 method and delegate to it. Otherwise, it will manage fonts locally. 

+

201 

+

202 Args: 

+

203 font_path: Path to the font file (.ttf, .otf). If None, uses default font. 

+

204 font_size: Size of the font in points. 

+

205 colour: RGB color tuple for the text. 

+

206 weight: Font weight (normal or bold). 

+

207 style: Font style (normal or italic). 

+

208 decoration: Text decoration (none, underline, or strikethrough). 

+

209 background: RGBA background color for the text. If None, transparent background. 

+

210 language: Language code for hyphenation and text processing. 

+

211 min_hyphenation_width: Minimum width in pixels required for hyphenation. 

+

212 

+

213 Returns: 

+

214 Font object (either existing or newly created) 

+

215 """ 

+

216 # Import here to avoid circular imports 

+

217 from pyWebLayout.style import Font, FontWeight, FontStyle, TextDecoration 

+

218 

+

219 # Set defaults for enum types 

+

220 if weight is None: 

+

221 weight = FontWeight.NORMAL 

+

222 if style is None: 

+

223 style = FontStyle.NORMAL 

+

224 if decoration is None: 

+

225 decoration = TextDecoration.NONE 

+

226 

+

227 # If we have a parent with font management, delegate to parent 

+

228 if hasattr( 

+

229 self, 

+

230 '_parent') and self._parent and hasattr( 

+

231 self._parent, 

+

232 'get_or_create_font'): 

+

233 return self._parent.get_or_create_font( 

+

234 font_path=font_path, 

+

235 font_size=font_size, 

+

236 colour=colour, 

+

237 weight=weight, 

+

238 style=style, 

+

239 decoration=decoration, 

+

240 background=background, 

+

241 language=language, 

+

242 min_hyphenation_width=min_hyphenation_width 

+

243 ) 

+

244 

+

245 # Otherwise manage our own fonts 

+

246 # Create a unique key for this font configuration 

+

247 bg_tuple = background if background else (255, 255, 255, 0) 

+

248 min_hyph_width = min_hyphenation_width if min_hyphenation_width is not None else font_size * 4 

+

249 

+

250 font_key = ( 

+

251 font_path, 

+

252 font_size, 

+

253 colour, 

+

254 weight.value if hasattr(weight, 'value') else weight, 

+

255 style.value if hasattr(style, 'value') else style, 

+

256 decoration.value if hasattr(decoration, 'value') else decoration, 

+

257 bg_tuple, 

+

258 language, 

+

259 min_hyph_width 

+

260 ) 

+

261 

+

262 # Convert tuple to string for dictionary key 

+

263 key_str = str(font_key) 

+

264 

+

265 # Check if we already have this font 

+

266 if key_str in self._fonts: 

+

267 return self._fonts[key_str] 

+

268 

+

269 # Create new font and store it 

+

270 new_font = Font( 

+

271 font_path=font_path, 

+

272 font_size=font_size, 

+

273 colour=colour, 

+

274 weight=weight, 

+

275 style=style, 

+

276 decoration=decoration, 

+

277 background=background, 

+

278 language=language, 

+

279 min_hyphenation_width=min_hyphenation_width 

+

280 ) 

+

281 

+

282 self._fonts[key_str] = new_font 

+

283 return new_font 

+

284 

+

285 

+

286class MetadataContainer: 

+

287 """ 

+

288 Mixin providing metadata dictionary management. 

+

289 

+

290 Allows classes to store and retrieve arbitrary metadata as key-value pairs. 

+

291 """ 

+

292 

+

293 def __init__(self, *args, **kwargs): 

+

294 super().__init__(*args, **kwargs) 

+

295 self._metadata: Dict[Any, Any] = {} 

+

296 

+

297 def set_metadata(self, key: Any, value: Any): 

+

298 """ 

+

299 Set a metadata value. 

+

300 

+

301 Args: 

+

302 key: The metadata key 

+

303 value: The metadata value 

+

304 """ 

+

305 self._metadata[key] = value 

+

306 

+

307 def get_metadata(self, key: Any) -> Optional[Any]: 

+

308 """ 

+

309 Get a metadata value. 

+

310 

+

311 Args: 

+

312 key: The metadata key 

+

313 

+

314 Returns: 

+

315 The metadata value, or None if not set 

+

316 """ 

+

317 return self._metadata.get(key) 

+

318 

+

319 

+

320class BlockContainer: 

+

321 """ 

+

322 Mixin providing block management methods. 

+

323 

+

324 Provides standard methods for managing block-level children including 

+

325 adding blocks and creating common block types. 

+

326 

+

327 Classes using this mixin should also use Styleable to support style inheritance. 

+

328 """ 

+

329 

+

330 def __init__(self, *args, **kwargs): 

+

331 super().__init__(*args, **kwargs) 

+

332 self._blocks = [] 

+

333 

+

334 def blocks(self): 

+

335 """ 

+

336 Get an iterator over the blocks in this container. 

+

337 

+

338 Can be used as blocks() for iteration or accessing the _blocks list directly. 

+

339 

+

340 Returns: 

+

341 Iterator over blocks 

+

342 """ 

+

343 return iter(self._blocks) 

+

344 

+

345 def add_block(self, block): 

+

346 """ 

+

347 Add a block to this container. 

+

348 

+

349 Args: 

+

350 block: The block to add 

+

351 """ 

+

352 self._blocks.append(block) 

+

353 if hasattr(block, 'parent'): 353 ↛ exitline 353 didn't return from function 'add_block' because the condition on line 353 was always true

+

354 block.parent = self 

+

355 

+

356 def create_paragraph(self, style=None): 

+

357 """ 

+

358 Create a new paragraph and add it to this container. 

+

359 

+

360 Args: 

+

361 style: Optional style override. If None, inherits from container 

+

362 

+

363 Returns: 

+

364 The newly created Paragraph object 

+

365 """ 

+

366 from pyWebLayout.abstract.block import Paragraph 

+

367 

+

368 if style is None and hasattr(self, '_style'): 

+

369 style = self._style 

+

370 

+

371 paragraph = Paragraph(style) 

+

372 self.add_block(paragraph) 

+

373 return paragraph 

+

374 

+

375 def create_heading(self, level=None, style=None): 

+

376 """ 

+

377 Create a new heading and add it to this container. 

+

378 

+

379 Args: 

+

380 level: The heading level (h1-h6) 

+

381 style: Optional style override. If None, inherits from container 

+

382 

+

383 Returns: 

+

384 The newly created Heading object 

+

385 """ 

+

386 from pyWebLayout.abstract.block import Heading, HeadingLevel 

+

387 

+

388 if level is None: 

+

389 level = HeadingLevel.H1 

+

390 

+

391 if style is None and hasattr(self, '_style'): 

+

392 style = self._style 

+

393 

+

394 heading = Heading(level, style) 

+

395 self.add_block(heading) 

+

396 return heading 

+

397 

+

398 

+

399class ContainerAware: 

+

400 """ 

+

401 Mixin providing support for the create_and_add_to factory pattern. 

+

402 

+

403 This is a base that can be extended to provide the create_and_add_to 

+

404 class method pattern used throughout the abstract module. 

+

405 

+

406 Note: This is a framework for future refactoring. Currently, each class 

+

407 has its own create_and_add_to implementation due to varying constructor 

+

408 signatures. This mixin provides a foundation for standardizing that pattern. 

+

409 """ 

+

410 

+

411 @classmethod 

+

412 def _validate_container(cls, container, required_method='add_block'): 

+

413 """ 

+

414 Validate that a container has the required method. 

+

415 

+

416 Args: 

+

417 container: The container to validate 

+

418 required_method: The method name to check for 

+

419 

+

420 Raises: 

+

421 AttributeError: If the container doesn't have the required method 

+

422 """ 

+

423 if not hasattr(container, required_method): 

+

424 raise AttributeError( 

+

425 f"Container {type(container).__name__} must have a '{required_method}' method" 

+

426 ) 

+

427 

+

428 @classmethod 

+

429 def _inherit_style(cls, container, style=None): 

+

430 """ 

+

431 Inherit style from container if not explicitly provided. 

+

432 

+

433 Args: 

+

434 container: The container to inherit from 

+

435 style: Optional explicit style 

+

436 

+

437 Returns: 

+

438 The style to use (explicit or inherited) 

+

439 """ 

+

440 if style is not None: 

+

441 return style 

+

442 

+

443 if hasattr(container, 'style'): 

+

444 return container.style 

+

445 elif hasattr(container, 'default_style'): 

+

446 return container.default_style 

+

447 

+

448 return None 

+
+ + + diff --git a/cov_info/htmlcov/z_40407af872b0cf37_callback_registry_py.html b/cov_info/htmlcov/z_40407af872b0cf37_callback_registry_py.html new file mode 100644 index 0000000..f6a60da --- /dev/null +++ b/cov_info/htmlcov/z_40407af872b0cf37_callback_registry_py.html @@ -0,0 +1,377 @@ + + + + + Coverage for pyWebLayout/core/callback_registry.py: 92% + + + + + +
+
+

+ Coverage for pyWebLayout/core/callback_registry.py: + 92% +

+ +

+ 75 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Callback Registry for managing interactable elements and their callbacks. 

+

3 

+

4This module provides a registry system for tracking interactive elements (links, buttons, forms) 

+

5and managing their callbacks. Supports multiple binding strategies: 

+

6- HTML id attributes for HTML-generated content 

+

7- Auto-generated ids for programmatic construction 

+

8- Type-based batch operations 

+

9""" 

+

10 

+

11from typing import Dict, List, Optional, Callable 

+

12from pyWebLayout.core.base import Interactable 

+

13 

+

14 

+

15class CallbackRegistry: 

+

16 """ 

+

17 Registry for managing interactable callbacks with multiple binding strategies. 

+

18 

+

19 Supports: 

+

20 - Direct references by object id 

+

21 - HTML id attributes (from parsed HTML) 

+

22 - Type-based queries (all buttons, all links, etc.) 

+

23 - Auto-generated ids for programmatic construction 

+

24 

+

25 This enables flexible callback binding for both HTML-generated content 

+

26 and manually constructed UIs. 

+

27 """ 

+

28 

+

29 def __init__(self): 

+

30 """Initialize an empty callback registry.""" 

+

31 self._by_reference: Dict[int, Interactable] = {} # id(obj) -> obj 

+

32 self._by_id: Dict[str, Interactable] = {} # HTML id or auto id -> obj 

+

33 self._by_type: Dict[str, List[Interactable]] = {} # type name -> [objs] 

+

34 self._auto_counter: int = 0 

+

35 

+

36 def register(self, obj: Interactable, html_id: Optional[str] = None) -> str: 

+

37 """ 

+

38 Register an interactable object with optional HTML id. 

+

39 

+

40 The object is always registered by reference (using Python's id()). 

+

41 If an html_id is provided, it's also registered by that id. 

+

42 If no html_id is provided, an auto-generated id is created. 

+

43 

+

44 Args: 

+

45 obj: The interactable object to register 

+

46 html_id: Optional HTML id attribute value (e.g., from <button id="save-btn">) 

+

47 

+

48 Returns: 

+

49 The id used for registration (either html_id or auto-generated) 

+

50 

+

51 Example: 

+

52 >>> button = ButtonText(...) 

+

53 >>> registry.register(button, html_id="save-btn") 

+

54 'save-btn' 

+

55 >>> registry.register(other_button) # No html_id 

+

56 'auto_button_0' 

+

57 """ 

+

58 # Always register by Python object id for direct lookups 

+

59 obj_id = id(obj) 

+

60 self._by_reference[obj_id] = obj 

+

61 

+

62 # Determine type name and register by type 

+

63 type_name = self._get_type_name(obj) 

+

64 if type_name not in self._by_type: 

+

65 self._by_type[type_name] = [] 

+

66 self._by_type[type_name].append(obj) 

+

67 

+

68 # Register by HTML id or generate auto id 

+

69 if html_id: 

+

70 # Use provided HTML id 

+

71 self._by_id[html_id] = obj 

+

72 return html_id 

+

73 else: 

+

74 # Generate automatic id 

+

75 auto_id = f"auto_{type_name}_{self._auto_counter}" 

+

76 self._auto_counter += 1 

+

77 self._by_id[auto_id] = obj 

+

78 return auto_id 

+

79 

+

80 def get_by_id(self, identifier: str) -> Optional[Interactable]: 

+

81 """ 

+

82 Get an interactable by its id (HTML id or auto-generated id). 

+

83 

+

84 Args: 

+

85 identifier: The id to lookup (e.g., "save-btn" or "auto_button_0") 

+

86 

+

87 Returns: 

+

88 The interactable object, or None if not found 

+

89 

+

90 Example: 

+

91 >>> button = registry.get_by_id("save-btn") 

+

92 >>> if button: 

+

93 ... button._callback = my_save_function 

+

94 """ 

+

95 return self._by_id.get(identifier) 

+

96 

+

97 def get_by_type(self, type_name: str) -> List[Interactable]: 

+

98 """ 

+

99 Get all interactables of a specific type. 

+

100 

+

101 Args: 

+

102 type_name: The type name (e.g., "link", "button", "form_field") 

+

103 

+

104 Returns: 

+

105 List of interactable objects of that type (may be empty) 

+

106 

+

107 Example: 

+

108 >>> all_buttons = registry.get_by_type("button") 

+

109 >>> for button in all_buttons: 

+

110 ... print(button.text) 

+

111 """ 

+

112 return self._by_type.get(type_name, []).copy() 

+

113 

+

114 def get_all_ids(self) -> List[str]: 

+

115 """ 

+

116 Get all registered ids (both HTML ids and auto-generated ids). 

+

117 

+

118 Returns: 

+

119 List of all ids in the registry 

+

120 

+

121 Example: 

+

122 >>> ids = registry.get_all_ids() 

+

123 >>> print(ids) 

+

124 ['save-btn', 'cancel-btn', 'auto_link_0', 'auto_button_1'] 

+

125 """ 

+

126 return list(self._by_id.keys()) 

+

127 

+

128 def get_all_types(self) -> List[str]: 

+

129 """ 

+

130 Get all registered type names. 

+

131 

+

132 Returns: 

+

133 List of type names that have registered objects 

+

134 

+

135 Example: 

+

136 >>> types = registry.get_all_types() 

+

137 >>> print(types) 

+

138 ['link', 'button', 'form_field'] 

+

139 """ 

+

140 return list(self._by_type.keys()) 

+

141 

+

142 def set_callback(self, identifier: str, callback: Callable) -> bool: 

+

143 """ 

+

144 Set the callback for an interactable by its id. 

+

145 

+

146 Args: 

+

147 identifier: The id of the interactable 

+

148 callback: The callback function to set 

+

149 

+

150 Returns: 

+

151 True if the interactable was found and callback set, False otherwise 

+

152 

+

153 Example: 

+

154 >>> def on_save(point): 

+

155 ... print("Save clicked!") 

+

156 >>> registry.set_callback("save-btn", on_save) 

+

157 True 

+

158 """ 

+

159 obj = self.get_by_id(identifier) 

+

160 if obj: 

+

161 obj._callback = callback 

+

162 return True 

+

163 return False 

+

164 

+

165 def set_callbacks_by_type(self, type_name: str, callback: Callable) -> int: 

+

166 """ 

+

167 Set the callback for all interactables of a specific type. 

+

168 

+

169 Useful for batch operations like setting a default click sound 

+

170 for all buttons, or a default link handler for all links. 

+

171 

+

172 Args: 

+

173 type_name: The type name (e.g., "button", "link") 

+

174 callback: The callback function to set 

+

175 

+

176 Returns: 

+

177 Number of objects that had their callback set 

+

178 

+

179 Example: 

+

180 >>> def play_click_sound(point): 

+

181 ... audio.play("click.wav") 

+

182 >>> count = registry.set_callbacks_by_type("button", play_click_sound) 

+

183 >>> print(f"Set callback for {count} buttons") 

+

184 """ 

+

185 objects = self.get_by_type(type_name) 

+

186 for obj in objects: 

+

187 obj._callback = callback 

+

188 return len(objects) 

+

189 

+

190 def unregister(self, identifier: str) -> bool: 

+

191 """ 

+

192 Unregister an interactable by its id. 

+

193 

+

194 Args: 

+

195 identifier: The id of the interactable to unregister 

+

196 

+

197 Returns: 

+

198 True if the interactable was found and unregistered, False otherwise 

+

199 """ 

+

200 obj = self._by_id.pop(identifier, None) 

+

201 if obj: 201 ↛ 214line 201 didn't jump to line 214 because the condition on line 201 was always true

+

202 # Remove from reference map 

+

203 self._by_reference.pop(id(obj), None) 

+

204 

+

205 # Remove from type map 

+

206 type_name = self._get_type_name(obj) 

+

207 if type_name in self._by_type: 207 ↛ 213line 207 didn't jump to line 213 because the condition on line 207 was always true

+

208 try: 

+

209 self._by_type[type_name].remove(obj) 

+

210 except ValueError: 

+

211 pass 

+

212 

+

213 return True 

+

214 return False 

+

215 

+

216 def clear(self): 

+

217 """Clear all registered interactables.""" 

+

218 self._by_reference.clear() 

+

219 self._by_id.clear() 

+

220 self._by_type.clear() 

+

221 self._auto_counter = 0 

+

222 

+

223 def count(self) -> int: 

+

224 """ 

+

225 Get the total number of registered interactables. 

+

226 

+

227 Returns: 

+

228 Total count of registered objects 

+

229 """ 

+

230 return len(self._by_id) 

+

231 

+

232 def count_by_type(self, type_name: str) -> int: 

+

233 """ 

+

234 Get the count of interactables of a specific type. 

+

235 

+

236 Args: 

+

237 type_name: The type name to count 

+

238 

+

239 Returns: 

+

240 Number of objects of that type 

+

241 """ 

+

242 return len(self._by_type.get(type_name, [])) 

+

243 

+

244 def _get_type_name(self, obj: Interactable) -> str: 

+

245 """ 

+

246 Get a normalized type name for an interactable object. 

+

247 

+

248 Args: 

+

249 obj: The interactable object 

+

250 

+

251 Returns: 

+

252 Type name string (e.g., "link", "button", "form_field") 

+

253 """ 

+

254 # Import here to avoid circular imports 

+

255 from pyWebLayout.concrete.functional import LinkText, ButtonText, FormFieldText 

+

256 

+

257 if isinstance(obj, LinkText): 

+

258 return "link" 

+

259 elif isinstance(obj, ButtonText): 

+

260 return "button" 

+

261 elif isinstance(obj, FormFieldText): 261 ↛ 265line 261 didn't jump to line 265 because the condition on line 261 was always true

+

262 return "form_field" 

+

263 else: 

+

264 # Fallback to class name 

+

265 return obj.__class__.__name__.lower() 

+

266 

+

267 def __len__(self) -> int: 

+

268 """Support len() to get count of registered interactables.""" 

+

269 return self.count() 

+

270 

+

271 def __contains__(self, identifier: str) -> bool: 

+

272 """Support 'in' operator to check if an id is registered.""" 

+

273 return identifier in self._by_id 

+

274 

+

275 def __repr__(self) -> str: 

+

276 """String representation showing registry statistics.""" 

+

277 type_counts = {t: len(objs) for t, objs in self._by_type.items()} 

+

278 return f"CallbackRegistry(total={self.count()}, types={type_counts})" 

+
+ + + diff --git a/cov_info/htmlcov/z_40407af872b0cf37_highlight_py.html b/cov_info/htmlcov/z_40407af872b0cf37_highlight_py.html new file mode 100644 index 0000000..e86c61a --- /dev/null +++ b/cov_info/htmlcov/z_40407af872b0cf37_highlight_py.html @@ -0,0 +1,348 @@ + + + + + Coverage for pyWebLayout/core/highlight.py: 95% + + + + + +
+
+

+ Coverage for pyWebLayout/core/highlight.py: + 95% +

+ +

+ 95 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Text highlighting system for ebook reader. 

+

3 

+

4Provides data structures and utilities for highlighting text regions, 

+

5managing highlight collections, and rendering highlights on pages. 

+

6""" 

+

7 

+

8from __future__ import annotations 

+

9from dataclasses import dataclass 

+

10from typing import List, Tuple, Optional, Dict, Any 

+

11from enum import Enum 

+

12import json 

+

13from pathlib import Path 

+

14 

+

15 

+

16class HighlightColor(Enum): 

+

17 """Predefined highlight colors with RGBA values""" 

+

18 YELLOW = (255, 255, 0, 100) # Classic highlight yellow 

+

19 GREEN = (100, 255, 100, 100) # Green for verified/correct 

+

20 BLUE = (100, 200, 255, 100) # Blue for important 

+

21 PINK = (255, 150, 200, 100) # Pink for questions 

+

22 ORANGE = (255, 180, 100, 100) # Orange for warnings 

+

23 PURPLE = (200, 150, 255, 100) # Purple for definitions 

+

24 RED = (255, 100, 100, 100) # Red for errors/concerns 

+

25 

+

26 

+

27@dataclass 

+

28class Highlight: 

+

29 """ 

+

30 Represents a highlighted text region. 

+

31 

+

32 Highlights are stored with both pixel bounds (for rendering) and 

+

33 semantic bounds (text content, for persistence across font changes). 

+

34 """ 

+

35 # Identification 

+

36 id: str # Unique identifier 

+

37 

+

38 # Visual properties 

+

39 bounds: List[Tuple[int, int, int, int]] # List of (x, y, w, h) rectangles 

+

40 color: Tuple[int, int, int, int] # RGBA color 

+

41 

+

42 # Semantic properties (for persistence) 

+

43 text: str # The highlighted text 

+

44 start_word_index: Optional[int] = None # Word index in document (if available) 

+

45 end_word_index: Optional[int] = None 

+

46 

+

47 # Metadata 

+

48 note: Optional[str] = None # Optional annotation 

+

49 tags: List[str] = None # Optional categorization tags 

+

50 timestamp: Optional[float] = None # When created 

+

51 

+

52 def __post_init__(self): 

+

53 """Initialize default values""" 

+

54 if self.tags is None: 

+

55 self.tags = [] 

+

56 

+

57 def to_dict(self) -> Dict[str, Any]: 

+

58 """Serialize to dictionary""" 

+

59 return { 

+

60 'id': self.id, 

+

61 'bounds': self.bounds, 

+

62 'color': self.color, 

+

63 'text': self.text, 

+

64 'start_word_index': self.start_word_index, 

+

65 'end_word_index': self.end_word_index, 

+

66 'note': self.note, 

+

67 'tags': self.tags, 

+

68 'timestamp': self.timestamp 

+

69 } 

+

70 

+

71 @classmethod 

+

72 def from_dict(cls, data: Dict[str, Any]) -> 'Highlight': 

+

73 """Deserialize from dictionary""" 

+

74 return cls( 

+

75 id=data['id'], 

+

76 bounds=[tuple(b) for b in data['bounds']], 

+

77 color=tuple(data['color']), 

+

78 text=data['text'], 

+

79 start_word_index=data.get('start_word_index'), 

+

80 end_word_index=data.get('end_word_index'), 

+

81 note=data.get('note'), 

+

82 tags=data.get('tags', []), 

+

83 timestamp=data.get('timestamp') 

+

84 ) 

+

85 

+

86 

+

87class HighlightManager: 

+

88 """ 

+

89 Manages highlights for a document. 

+

90 

+

91 Handles adding, removing, listing, and persisting highlights. 

+

92 """ 

+

93 

+

94 def __init__(self, document_id: str, highlights_dir: str = "highlights"): 

+

95 """ 

+

96 Initialize highlight manager. 

+

97 

+

98 Args: 

+

99 document_id: Unique identifier for the document 

+

100 highlights_dir: Directory to store highlight data 

+

101 """ 

+

102 self.document_id = document_id 

+

103 self.highlights_dir = Path(highlights_dir) 

+

104 self.highlights: Dict[str, Highlight] = {} # id -> Highlight 

+

105 

+

106 # Create directory if it doesn't exist 

+

107 self.highlights_dir.mkdir(parents=True, exist_ok=True) 

+

108 

+

109 # Load existing highlights 

+

110 self._load_highlights() 

+

111 

+

112 def add_highlight(self, highlight: Highlight) -> None: 

+

113 """ 

+

114 Add a highlight. 

+

115 

+

116 Args: 

+

117 highlight: Highlight to add 

+

118 """ 

+

119 self.highlights[highlight.id] = highlight 

+

120 self._save_highlights() 

+

121 

+

122 def remove_highlight(self, highlight_id: str) -> bool: 

+

123 """ 

+

124 Remove a highlight by ID. 

+

125 

+

126 Args: 

+

127 highlight_id: ID of highlight to remove 

+

128 

+

129 Returns: 

+

130 True if removed, False if not found 

+

131 """ 

+

132 if highlight_id in self.highlights: 

+

133 del self.highlights[highlight_id] 

+

134 self._save_highlights() 

+

135 return True 

+

136 return False 

+

137 

+

138 def get_highlight(self, highlight_id: str) -> Optional[Highlight]: 

+

139 """Get a highlight by ID""" 

+

140 return self.highlights.get(highlight_id) 

+

141 

+

142 def list_highlights(self) -> List[Highlight]: 

+

143 """Get all highlights""" 

+

144 return list(self.highlights.values()) 

+

145 

+

146 def clear_all(self) -> None: 

+

147 """Remove all highlights""" 

+

148 self.highlights.clear() 

+

149 self._save_highlights() 

+

150 

+

151 def get_highlights_for_page( 

+

152 self, page_bounds: Tuple[int, int, int, int]) -> List[Highlight]: 

+

153 """ 

+

154 Get highlights that appear on a specific page. 

+

155 

+

156 Args: 

+

157 page_bounds: Page bounds (x, y, width, height) 

+

158 

+

159 Returns: 

+

160 List of highlights on this page 

+

161 """ 

+

162 page_x, page_y, page_w, page_h = page_bounds 

+

163 page_highlights = [] 

+

164 

+

165 for highlight in self.highlights.values(): 

+

166 # Check if any highlight bounds overlap with page 

+

167 for hx, hy, hw, hh in highlight.bounds: 

+

168 if (hx < page_x + page_w and hx + hw > page_x and 

+

169 hy < page_y + page_h and hy + hh > page_y): 

+

170 page_highlights.append(highlight) 

+

171 break 

+

172 

+

173 return page_highlights 

+

174 

+

175 def _get_filepath(self) -> Path: 

+

176 """Get filepath for this document's highlights""" 

+

177 return self.highlights_dir / f"{self.document_id}_highlights.json" 

+

178 

+

179 def _save_highlights(self) -> None: 

+

180 """Persist highlights to disk""" 

+

181 try: 

+

182 filepath = self._get_filepath() 

+

183 data = { 

+

184 'document_id': self.document_id, 

+

185 'highlights': [h.to_dict() for h in self.highlights.values()] 

+

186 } 

+

187 

+

188 with open(filepath, 'w') as f: 

+

189 json.dump(data, f, indent=2) 

+

190 except Exception as e: 

+

191 print(f"Error saving highlights: {e}") 

+

192 

+

193 def _load_highlights(self) -> None: 

+

194 """Load highlights from disk""" 

+

195 try: 

+

196 filepath = self._get_filepath() 

+

197 if not filepath.exists(): 

+

198 return 

+

199 

+

200 with open(filepath, 'r') as f: 

+

201 data = json.load(f) 

+

202 

+

203 self.highlights = { 

+

204 h['id']: Highlight.from_dict(h) 

+

205 for h in data.get('highlights', []) 

+

206 } 

+

207 except Exception as e: 

+

208 print(f"Error loading highlights: {e}") 

+

209 self.highlights = {} 

+

210 

+

211 

+

212def create_highlight_from_query_result( 

+

213 result, 

+

214 color: Tuple[int, int, int, int] = HighlightColor.YELLOW.value, 

+

215 note: Optional[str] = None, 

+

216 tags: Optional[List[str]] = None 

+

217) -> Highlight: 

+

218 """ 

+

219 Create a highlight from a QueryResult. 

+

220 

+

221 Args: 

+

222 result: QueryResult from query_pixel or query_range 

+

223 color: RGBA color tuple 

+

224 note: Optional annotation 

+

225 tags: Optional categorization tags 

+

226 

+

227 Returns: 

+

228 Highlight instance 

+

229 """ 

+

230 from time import time 

+

231 import uuid 

+

232 

+

233 # Handle single result or SelectionRange 

+

234 if hasattr(result, 'results'): # SelectionRange 

+

235 bounds = result.bounds_list 

+

236 text = result.text 

+

237 else: # Single QueryResult 

+

238 bounds = [result.bounds] 

+

239 text = result.text or "" 

+

240 

+

241 return Highlight( 

+

242 id=str(uuid.uuid4()), 

+

243 bounds=bounds, 

+

244 color=color, 

+

245 text=text, 

+

246 note=note, 

+

247 tags=tags or [], 

+

248 timestamp=time() 

+

249 ) 

+
+ + + diff --git a/cov_info/htmlcov/z_40407af872b0cf37_query_py.html b/cov_info/htmlcov/z_40407af872b0cf37_query_py.html new file mode 100644 index 0000000..30f64c2 --- /dev/null +++ b/cov_info/htmlcov/z_40407af872b0cf37_query_py.html @@ -0,0 +1,185 @@ + + + + + Coverage for pyWebLayout/core/query.py: 94% + + + + + +
+
+

+ Coverage for pyWebLayout/core/query.py: + 94% +

+ +

+ 33 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Query system for pixel-to-content mapping. 

+

3 

+

4This module provides data structures for querying rendered content, 

+

5enabling interactive features like link clicking, word definition lookup, 

+

6and text selection. 

+

7""" 

+

8 

+

9from __future__ import annotations 

+

10from dataclasses import dataclass 

+

11from typing import Optional, Tuple, List, Any, TYPE_CHECKING 

+

12 

+

13if TYPE_CHECKING: 13 ↛ 14line 13 didn't jump to line 14 because the condition on line 13 was never true

+

14 from pyWebLayout.core.base import Queriable 

+

15 

+

16 

+

17@dataclass 

+

18class QueryResult: 

+

19 """ 

+

20 Result of querying a point on a rendered page. 

+

21 

+

22 This encapsulates all information about what was found at a pixel location, 

+

23 including geometry, content, and interaction capabilities. 

+

24 """ 

+

25 # What was found 

+

26 object: 'Queriable' # The object at this point 

+

27 object_type: str # "link", "text", "image", "button", "word", "empty" 

+

28 

+

29 # Geometry 

+

30 bounds: Tuple[int, int, int, int] # (x, y, width, height) in page coordinates 

+

31 

+

32 # Content (for text/words) 

+

33 text: Optional[str] = None 

+

34 word_index: Optional[int] = None # Index in abstract document structure 

+

35 block_index: Optional[int] = None # Block index in document 

+

36 

+

37 # Interaction (for links/buttons) 

+

38 is_interactive: bool = False 

+

39 link_target: Optional[str] = None # URL or internal reference 

+

40 callback: Optional[Any] = None # Interaction callback 

+

41 

+

42 # Hierarchy (for debugging/traversal) 

+

43 parent_line: Optional[Any] = None 

+

44 parent_page: Optional[Any] = None 

+

45 

+

46 def to_dict(self) -> dict: 

+

47 """Convert to dictionary for serialization""" 

+

48 return { 

+

49 'object_type': self.object_type, 

+

50 'bounds': self.bounds, 

+

51 'text': self.text, 

+

52 'is_interactive': self.is_interactive, 

+

53 'link_target': self.link_target, 

+

54 'word_index': self.word_index, 

+

55 'block_index': self.block_index 

+

56 } 

+

57 

+

58 

+

59@dataclass 

+

60class SelectionRange: 

+

61 """ 

+

62 Represents a range of selected text between two points. 

+

63 """ 

+

64 start_point: Tuple[int, int] 

+

65 end_point: Tuple[int, int] 

+

66 results: List[QueryResult] # All query results in the range 

+

67 

+

68 @property 

+

69 def text(self) -> str: 

+

70 """Get concatenated text from all results""" 

+

71 return " ".join(r.text for r in self.results if r.text) 

+

72 

+

73 @property 

+

74 def bounds_list(self) -> List[Tuple[int, int, int, int]]: 

+

75 """Get list of all bounding boxes for highlighting""" 

+

76 return [r.bounds for r in self.results] 

+

77 

+

78 def to_dict(self) -> dict: 

+

79 """Convert to dictionary for serialization""" 

+

80 return { 

+

81 'start': self.start_point, 

+

82 'end': self.end_point, 

+

83 'text': self.text, 

+

84 'word_count': len(self.results), 

+

85 'bounds': self.bounds_list 

+

86 } 

+
+ + + diff --git a/cov_info/htmlcov/z_427cc3035faf7633___init___py.html b/cov_info/htmlcov/z_427cc3035faf7633___init___py.html new file mode 100644 index 0000000..dfbd0dc --- /dev/null +++ b/cov_info/htmlcov/z_427cc3035faf7633___init___py.html @@ -0,0 +1,110 @@ + + + + + Coverage for pyWebLayout/layout/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/layout/__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Typesetting module for the pyWebLayout library. 

+

3 

+

4This package handles the organization and arrangement of elements for rendering, including: 

+

5- Flow layout algorithms 

+

6- Container management 

+

7- Element positioning and sizing 

+

8- Content wrapping and overflow 

+

9- Coordinate systems and transformations 

+

10- Pagination for book-like content 

+

11""" 

+
+ + + diff --git a/cov_info/htmlcov/z_427cc3035faf7633_document_layouter_py.html b/cov_info/htmlcov/z_427cc3035faf7633_document_layouter_py.html new file mode 100644 index 0000000..7c752ae --- /dev/null +++ b/cov_info/htmlcov/z_427cc3035faf7633_document_layouter_py.html @@ -0,0 +1,822 @@ + + + + + Coverage for pyWebLayout/layout/document_layouter.py: 77% + + + + + +
+
+

+ Coverage for pyWebLayout/layout/document_layouter.py: + 77% +

+ +

+ 216 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from __future__ import annotations 

+

2 

+

3from typing import List, Tuple, Optional, Union 

+

4import numpy as np 

+

5 

+

6from pyWebLayout.concrete import Page, Line, Text 

+

7from pyWebLayout.concrete.image import RenderableImage 

+

8from pyWebLayout.concrete.functional import ButtonText, FormFieldText 

+

9from pyWebLayout.concrete.table import TableRenderer, TableStyle 

+

10from pyWebLayout.abstract import Paragraph, Word 

+

11from pyWebLayout.abstract.block import Image as AbstractImage, PageBreak, Table 

+

12from pyWebLayout.abstract.functional import Button, Form, FormField 

+

13from pyWebLayout.style.concrete_style import ConcreteStyleRegistry, RenderingContext, StyleResolver 

+

14from pyWebLayout.style import Font, Alignment 

+

15 

+

16 

+

17def paragraph_layouter(paragraph: Paragraph, 

+

18 page: Page, 

+

19 start_word: int = 0, 

+

20 pretext: Optional[Text] = None, 

+

21 alignment_override: Optional['Alignment'] = None) -> Tuple[bool, 

+

22 Optional[int], 

+

23 Optional[Text]]: 

+

24 """ 

+

25 Layout a paragraph of text within a given page. 

+

26 

+

27 This function extracts word spacing constraints from the style system 

+

28 and uses them to create properly spaced lines of text. 

+

29 

+

30 Args: 

+

31 paragraph: The paragraph to layout 

+

32 page: The page to layout the paragraph on 

+

33 start_word: Index of the first word to process (for continuation) 

+

34 pretext: Optional pretext from a previous hyphenated word 

+

35 alignment_override: Optional alignment to override the paragraph's default alignment 

+

36 

+

37 Returns: 

+

38 Tuple of: 

+

39 - bool: True if paragraph was completely laid out, False if page ran out of space 

+

40 - Optional[int]: Index of first word that didn't fit (if any) 

+

41 - Optional[Text]: Remaining pretext if word was hyphenated (if any) 

+

42 """ 

+

43 if not paragraph.words: 

+

44 return True, None, None 

+

45 

+

46 # Validate inputs 

+

47 if start_word >= len(paragraph.words): 

+

48 return True, None, None 

+

49 

+

50 # paragraph.style is already a Font object (concrete), not AbstractStyle 

+

51 # We need to get word spacing constraints from the Font's abstract style if available 

+

52 # For now, use reasonable defaults based on font size 

+

53 

+

54 if isinstance(paragraph.style, Font): 

+

55 # paragraph.style is already a Font (concrete style) 

+

56 font = paragraph.style 

+

57 # Use default word spacing constraints based on font size 

+

58 # Minimum spacing should be proportional to font size for better readability 

+

59 min_spacing = float(font.font_size) * 0.25 # 25% of font size 

+

60 max_spacing = float(font.font_size) * 0.5 # 50% of font size 

+

61 word_spacing_constraints = (int(min_spacing), int(max_spacing)) 

+

62 text_align = Alignment.LEFT # Default alignment 

+

63 else: 

+

64 # paragraph.style is an AbstractStyle, resolve it 

+

65 # Ensure font_size is an int (it could be a FontSize enum) 

+

66 from pyWebLayout.style.abstract_style import FontSize 

+

67 if isinstance(paragraph.style.font_size, FontSize): 67 ↛ 71line 67 didn't jump to line 71 because the condition on line 67 was always true

+

68 # Use a default base font size, the resolver will handle the semantic size 

+

69 base_font_size = 16 

+

70 else: 

+

71 base_font_size = int(paragraph.style.font_size) 

+

72 

+

73 rendering_context = RenderingContext(base_font_size=base_font_size) 

+

74 style_resolver = StyleResolver(rendering_context) 

+

75 style_registry = ConcreteStyleRegistry(style_resolver) 

+

76 concrete_style = style_registry.get_concrete_style(paragraph.style) 

+

77 font = concrete_style.create_font() 

+

78 word_spacing_constraints = ( 

+

79 int(concrete_style.word_spacing_min), 

+

80 int(concrete_style.word_spacing_max) 

+

81 ) 

+

82 text_align = concrete_style.text_align 

+

83 

+

84 # Apply page-level word spacing override if specified 

+

85 if hasattr( 85 ↛ 91line 85 didn't jump to line 91 because the condition on line 85 was never true

+

86 page.style, 

+

87 'word_spacing') and isinstance( 

+

88 page.style.word_spacing, 

+

89 int) and page.style.word_spacing > 0: 

+

90 # Add the page-level word spacing to both min and max constraints 

+

91 min_ws, max_ws = word_spacing_constraints 

+

92 word_spacing_constraints = ( 

+

93 min_ws + page.style.word_spacing, 

+

94 max_ws + page.style.word_spacing 

+

95 ) 

+

96 

+

97 # Apply alignment override if provided 

+

98 if alignment_override is not None: 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true

+

99 text_align = alignment_override 

+

100 

+

101 # Cap font size to page maximum if needed 

+

102 if font.font_size > page.style.max_font_size: 102 ↛ 104line 102 didn't jump to line 104 because the condition on line 102 was never true

+

103 # Use paragraph's font registry to create the capped font 

+

104 if hasattr(paragraph, 'get_or_create_font'): 

+

105 font = paragraph.get_or_create_font( 

+

106 font_path=font._font_path, 

+

107 font_size=page.style.max_font_size, 

+

108 colour=font.colour, 

+

109 weight=font.weight, 

+

110 style=font.style, 

+

111 decoration=font.decoration, 

+

112 background=font.background 

+

113 ) 

+

114 else: 

+

115 # Fallback to direct creation (will still use global cache) 

+

116 font = Font( 

+

117 font_path=font._font_path, 

+

118 font_size=page.style.max_font_size, 

+

119 colour=font.colour, 

+

120 weight=font.weight, 

+

121 style=font.style, 

+

122 decoration=font.decoration, 

+

123 background=font.background 

+

124 ) 

+

125 

+

126 # Calculate baseline-to-baseline spacing: font size + additional line spacing 

+

127 # This is the vertical distance between baselines of consecutive lines 

+

128 # Formula: baseline_spacing = font_size + line_spacing (absolute pixels) 

+

129 line_spacing_value = getattr(page.style, 'line_spacing', 5) 

+

130 # Ensure line_spacing is an int (could be Mock in tests) 

+

131 if not isinstance(line_spacing_value, int): 

+

132 line_spacing_value = 5 

+

133 baseline_spacing = font.font_size + line_spacing_value 

+

134 

+

135 # Get font metrics for boundary checking 

+

136 ascent, descent = font.font.getmetrics() 

+

137 

+

138 def create_new_line(word: Optional[Union[Word, Text]] = None, 

+

139 is_first_line: bool = False) -> Optional[Line]: 

+

140 """Helper function to create a new line, returns None if page is full.""" 

+

141 # Check if this line's baseline and descenders would fit on the page 

+

142 if not page.can_fit_line(baseline_spacing, ascent, descent): 

+

143 return None 

+

144 

+

145 # For the first line, position it so text starts at the top boundary 

+

146 # For subsequent lines, use current y_offset which tracks 

+

147 # baseline-to-baseline spacing 

+

148 if is_first_line: 148 ↛ 151line 148 didn't jump to line 151 because the condition on line 148 was never true

+

149 # Position line origin so that baseline (origin + ascent) is close to top 

+

150 # We want minimal space above the text, so origin should be at boundary 

+

151 y_cursor = page._current_y_offset 

+

152 else: 

+

153 y_cursor = page._current_y_offset 

+

154 x_cursor = page.border_size 

+

155 

+

156 # Create a temporary Text object to calculate word width 

+

157 if word: 

+

158 temp_text = Text.from_word(word, page.draw) 

+

159 temp_text.width 

+

160 else: 

+

161 pass 

+

162 

+

163 return Line( 

+

164 spacing=word_spacing_constraints, 

+

165 origin=(x_cursor, y_cursor), 

+

166 size=(page.available_width, baseline_spacing), 

+

167 draw=page.draw, 

+

168 font=font, 

+

169 halign=text_align 

+

170 ) 

+

171 

+

172 # Create initial line 

+

173 current_line = create_new_line() 

+

174 if not current_line: 

+

175 return False, start_word, pretext 

+

176 

+

177 page.add_child(current_line) 

+

178 # Note: add_child already updates _current_y_offset based on child's origin and size 

+

179 # No need to manually increment it here 

+

180 

+

181 # Track current position in paragraph 

+

182 current_pretext = pretext 

+

183 

+

184 # Process words starting from start_word 

+

185 for i, word in enumerate(paragraph.words[start_word:], start=start_word): 

+

186 # Check if this is a LinkedWord and needs special handling in concrete layer 

+

187 # Note: The Line.add_word method will create Text objects internally, 

+

188 # but we may want to create LinkText for LinkedWord instances in future 

+

189 # For now, the abstract layer (LinkedWord) carries the link info, 

+

190 # and the concrete layer (LinkText) would be created during rendering 

+

191 

+

192 success, overflow_text = current_line.add_word(word, current_pretext) 

+

193 

+

194 if success: 

+

195 # Word fit successfully 

+

196 if overflow_text is not None: 

+

197 # If there's overflow text, we need to start a new line with it 

+

198 current_pretext = overflow_text 

+

199 current_line = create_new_line(overflow_text) 

+

200 if not current_line: 

+

201 # If we can't create a new line, return with the current state 

+

202 return False, i, overflow_text 

+

203 page.add_child(current_line) 

+

204 # Note: add_child already updates _current_y_offset 

+

205 # Continue to the next word 

+

206 continue 

+

207 else: 

+

208 # No overflow, clear pretext 

+

209 current_pretext = None 

+

210 else: 

+

211 # Word didn't fit, need a new line 

+

212 current_line = create_new_line(word) 

+

213 if not current_line: 

+

214 # Page is full, return current position 

+

215 return False, i, overflow_text 

+

216 

+

217 # Check if the word will fit on the new line before adding it 

+

218 temp_text = Text.from_word(word, page.draw) 

+

219 if temp_text.width > current_line.size[0]: 

+

220 # Word is too wide for the line, we need to hyphenate it 

+

221 if len(word.text) >= 6: 221 ↛ 243line 221 didn't jump to line 243 because the condition on line 221 was always true

+

222 # Try to hyphenate the word 

+

223 splits = [ 

+

224 (Text( 

+

225 pair[0], 

+

226 word.style, 

+

227 page.draw, 

+

228 line=current_line, 

+

229 source=word), 

+

230 Text( 

+

231 pair[1], 

+

232 word.style, 

+

233 page.draw, 

+

234 line=current_line, 

+

235 source=word)) for pair in word.possible_hyphenation()] 

+

236 if len(splits) > 0: 236 ↛ 243line 236 didn't jump to line 243 because the condition on line 236 was always true

+

237 # Use the first hyphenation point 

+

238 first_part, second_part = splits[0] 

+

239 current_line.add_word(word, first_part) 

+

240 current_pretext = second_part 

+

241 continue 

+

242 

+

243 page.add_child(current_line) 

+

244 # Note: add_child already updates _current_y_offset 

+

245 

+

246 # Try to add the word to the new line 

+

247 success, overflow_text = current_line.add_word(word, current_pretext) 

+

248 

+

249 if not success: 249 ↛ 252line 249 didn't jump to line 252 because the condition on line 249 was never true

+

250 # Word still doesn't fit even on a new line 

+

251 # This might happen with very long words or narrow pages 

+

252 if overflow_text: 

+

253 # Word was hyphenated, continue with the overflow 

+

254 current_pretext = overflow_text 

+

255 continue 

+

256 else: 

+

257 # Word cannot be broken, skip it or handle as error 

+

258 # For now, we'll return indicating we couldn't process this word 

+

259 return False, i, None 

+

260 else: 

+

261 current_pretext = overflow_text # May be None or hyphenated remainder 

+

262 

+

263 # All words processed successfully 

+

264 return True, None, None 

+

265 

+

266 

+

267def pagebreak_layouter(page_break: PageBreak, page: Page) -> bool: 

+

268 """ 

+

269 Handle a page break element. 

+

270 

+

271 A page break signals that all subsequent content should start on a new page. 

+

272 This function always returns False to indicate that the current page is complete 

+

273 and a new page should be created for subsequent content. 

+

274 

+

275 Args: 

+

276 page_break: The PageBreak block 

+

277 page: The current page (not used, but kept for consistency) 

+

278 

+

279 Returns: 

+

280 bool: Always False to force creation of a new page 

+

281 """ 

+

282 # Page break always forces a new page 

+

283 return False 

+

284 

+

285 

+

286def image_layouter(image: AbstractImage, page: Page, max_width: Optional[int] = None, 

+

287 max_height: Optional[int] = None) -> bool: 

+

288 """ 

+

289 Layout an image within a given page. 

+

290 

+

291 This function places an image on the page, respecting size constraints 

+

292 and available space. Images are centered horizontally by default. 

+

293 

+

294 Args: 

+

295 image: The abstract Image object to layout 

+

296 page: The page to layout the image on 

+

297 max_width: Maximum width constraint (defaults to page available width) 

+

298 max_height: Maximum height constraint (defaults to remaining page height) 

+

299 

+

300 Returns: 

+

301 bool: True if image was successfully laid out, False if page ran out of space 

+

302 """ 

+

303 # Use page available width if max_width not specified 

+

304 if max_width is None: 

+

305 max_width = page.available_width 

+

306 

+

307 # Calculate available height on page 

+

308 available_height = page.size[1] - page._current_y_offset - page.border_size 

+

309 

+

310 # If no space available, image doesn't fit 

+

311 if available_height <= 0: 311 ↛ 312line 311 didn't jump to line 312 because the condition on line 311 was never true

+

312 return False 

+

313 

+

314 if max_height is None: 

+

315 max_height = available_height 

+

316 else: 

+

317 max_height = min(max_height, available_height) 

+

318 

+

319 # Calculate scaled dimensions 

+

320 scaled_width, scaled_height = image.calculate_scaled_dimensions( 

+

321 max_width, max_height) 

+

322 

+

323 # Check if image fits on current page 

+

324 if scaled_height is None or scaled_height > available_height: 

+

325 return False 

+

326 

+

327 # Create renderable image 

+

328 x_offset = page.border_size 

+

329 y_offset = page._current_y_offset 

+

330 

+

331 # Access page.draw to ensure canvas is initialized 

+

332 _ = page.draw 

+

333 

+

334 renderable_image = RenderableImage( 

+

335 image=image, 

+

336 canvas=page._canvas, 

+

337 max_width=max_width, 

+

338 max_height=max_height, 

+

339 origin=(x_offset, y_offset), 

+

340 size=(scaled_width or max_width, scaled_height or max_height), 

+

341 halign=Alignment.CENTER, 

+

342 valign=Alignment.TOP 

+

343 ) 

+

344 

+

345 # Add to page 

+

346 page.add_child(renderable_image) 

+

347 

+

348 return True 

+

349 

+

350 

+

351def table_layouter( 

+

352 table: Table, 

+

353 page: Page, 

+

354 style: Optional[TableStyle] = None) -> bool: 

+

355 """ 

+

356 Layout a table within a given page. 

+

357 

+

358 This function uses the TableRenderer to render the table at the current 

+

359 page position, advancing the page's y-offset after successful rendering. 

+

360 

+

361 Args: 

+

362 table: The abstract Table object to layout 

+

363 page: The page to layout the table on 

+

364 style: Optional table styling configuration 

+

365 

+

366 Returns: 

+

367 bool: True if table was successfully laid out, False if page ran out of space 

+

368 """ 

+

369 # Calculate available space 

+

370 available_width = page.available_width 

+

371 x_offset = page.border_size 

+

372 y_offset = page._current_y_offset 

+

373 

+

374 # Access page.draw to ensure canvas is initialized 

+

375 draw = page.draw 

+

376 canvas = page._canvas 

+

377 

+

378 # Create table renderer 

+

379 origin = (x_offset, y_offset) 

+

380 renderer = TableRenderer( 

+

381 table=table, 

+

382 origin=origin, 

+

383 available_width=available_width, 

+

384 draw=draw, 

+

385 style=style, 

+

386 canvas=canvas 

+

387 ) 

+

388 

+

389 # Check if table fits on current page 

+

390 table_height = renderer.size[1] 

+

391 available_height = page.size[1] - y_offset - page.border_size 

+

392 

+

393 if table_height > available_height: 

+

394 return False 

+

395 

+

396 # Render the table 

+

397 renderer.render() 

+

398 

+

399 # Update page y-offset 

+

400 page._current_y_offset = y_offset + table_height 

+

401 

+

402 return True 

+

403 

+

404 

+

405def button_layouter(button: Button, 

+

406 page: Page, 

+

407 font: Optional[Font] = None, 

+

408 padding: Tuple[int, 

+

409 int, 

+

410 int, 

+

411 int] = (4, 

+

412 8, 

+

413 4, 

+

414 8)) -> Tuple[bool, 

+

415 str]: 

+

416 """ 

+

417 Layout a button within a given page and register it for callback binding. 

+

418 

+

419 This function creates a ButtonText renderable, positions it on the page, 

+

420 and registers it in the page's callback registry using the button's html_id 

+

421 (if available) or an auto-generated id. 

+

422 

+

423 Args: 

+

424 button: The abstract Button object to layout 

+

425 page: The page to layout the button on 

+

426 font: Optional font for button text (defaults to page default) 

+

427 padding: Padding around button text (top, right, bottom, left) 

+

428 

+

429 Returns: 

+

430 Tuple of: 

+

431 - bool: True if button was successfully laid out, False if page ran out of space 

+

432 - str: The id used to register the button in the callback registry 

+

433 """ 

+

434 # Use provided font or create a default one 

+

435 if font is None: 

+

436 font = Font(font_size=14, colour=(255, 255, 255)) 

+

437 

+

438 # Calculate available space 

+

439 available_height = page.size[1] - page._current_y_offset - page.border_size 

+

440 

+

441 # Create ButtonText renderable 

+

442 button_text = ButtonText(button, font, page.draw, padding=padding) 

+

443 

+

444 # Check if button fits on current page 

+

445 button_height = button_text.size[1] 

+

446 if button_height > available_height: 

+

447 return False, "" 

+

448 

+

449 # Position the button 

+

450 x_offset = page.border_size 

+

451 y_offset = page._current_y_offset 

+

452 

+

453 button_text.set_origin(np.array([x_offset, y_offset])) 

+

454 

+

455 # Register in callback registry 

+

456 html_id = button.html_id 

+

457 registered_id = page.callbacks.register(button_text, html_id=html_id) 

+

458 

+

459 # Add to page 

+

460 page.add_child(button_text) 

+

461 

+

462 return True, registered_id 

+

463 

+

464 

+

465def form_field_layouter(field: FormField, page: Page, font: Optional[Font] = None, 

+

466 field_height: int = 24) -> Tuple[bool, str]: 

+

467 """ 

+

468 Layout a form field within a given page and register it for callback binding. 

+

469 

+

470 This function creates a FormFieldText renderable, positions it on the page, 

+

471 and registers it in the page's callback registry. 

+

472 

+

473 Args: 

+

474 field: The abstract FormField object to layout 

+

475 page: The page to layout the field on 

+

476 font: Optional font for field label (defaults to page default) 

+

477 field_height: Height of the input field area 

+

478 

+

479 Returns: 

+

480 Tuple of: 

+

481 - bool: True if field was successfully laid out, False if page ran out of space 

+

482 - str: The id used to register the field in the callback registry 

+

483 """ 

+

484 # Use provided font or create a default one 

+

485 if font is None: 485 ↛ 486line 485 didn't jump to line 486 because the condition on line 485 was never true

+

486 font = Font(font_size=12, colour=(0, 0, 0)) 

+

487 

+

488 # Calculate available space 

+

489 available_height = page.size[1] - page._current_y_offset - page.border_size 

+

490 

+

491 # Create FormFieldText renderable 

+

492 field_text = FormFieldText(field, font, page.draw, field_height=field_height) 

+

493 

+

494 # Check if field fits on current page 

+

495 total_field_height = field_text.size[1] 

+

496 if total_field_height > available_height: 496 ↛ 497line 496 didn't jump to line 497 because the condition on line 496 was never true

+

497 return False, "" 

+

498 

+

499 # Position the field 

+

500 x_offset = page.border_size 

+

501 y_offset = page._current_y_offset 

+

502 

+

503 field_text.set_origin(np.array([x_offset, y_offset])) 

+

504 

+

505 # Register in callback registry (use field name as html_id fallback) 

+

506 html_id = getattr(field, '_html_id', None) or field.name 

+

507 registered_id = page.callbacks.register(field_text, html_id=html_id) 

+

508 

+

509 # Add to page 

+

510 page.add_child(field_text) 

+

511 

+

512 return True, registered_id 

+

513 

+

514 

+

515def form_layouter(form: Form, page: Page, font: Optional[Font] = None, 

+

516 field_spacing: int = 10) -> Tuple[bool, List[str]]: 

+

517 """ 

+

518 Layout a complete form with all its fields within a given page. 

+

519 

+

520 This function creates FormFieldText renderables for all fields in the form, 

+

521 positions them vertically, and registers both the form and its fields in 

+

522 the page's callback registry. 

+

523 

+

524 Args: 

+

525 form: The abstract Form object to layout 

+

526 page: The page to layout the form on 

+

527 font: Optional font for field labels (defaults to page default) 

+

528 field_spacing: Vertical spacing between fields in pixels 

+

529 

+

530 Returns: 

+

531 Tuple of: 

+

532 - bool: True if form was successfully laid out, False if page ran out of space 

+

533 - List[str]: List of registered ids for all fields (empty if layout failed) 

+

534 """ 

+

535 # Use provided font or create a default one 

+

536 if font is None: 536 ↛ 537line 536 didn't jump to line 537 because the condition on line 536 was never true

+

537 font = Font(font_size=12, colour=(0, 0, 0)) 

+

538 

+

539 # Track registered field ids 

+

540 field_ids = [] 

+

541 

+

542 # Layout each field in the form 

+

543 for field_name, field in form._fields.items(): 

+

544 # Add spacing before each field (except the first) 

+

545 if field_ids: 

+

546 page._current_y_offset += field_spacing 

+

547 

+

548 # Layout the field 

+

549 success, field_id = form_field_layouter(field, page, font) 

+

550 

+

551 if not success: 551 ↛ 553line 551 didn't jump to line 553 because the condition on line 551 was never true

+

552 # Couldn't fit this field, return failure 

+

553 return False, [] 

+

554 

+

555 field_ids.append(field_id) 

+

556 

+

557 # Register the form itself (optional, for form submission) 

+

558 # Note: The form doesn't have a visual representation, but we can track it 

+

559 # for submission callbacks 

+

560 # form_id = page.callbacks.register(form, html_id=form.html_id) 

+

561 

+

562 return True, field_ids 

+

563 

+

564 

+

565class DocumentLayouter: 

+

566 """ 

+

567 Document layouter that orchestrates layout of various abstract elements. 

+

568 

+

569 Delegates to specialized layouters for different content types: 

+

570 - paragraph_layouter for text paragraphs 

+

571 - image_layouter for images 

+

572 - table_layouter for tables 

+

573 

+

574 This class acts as a coordinator, managing the overall document flow 

+

575 and page context while delegating specific layout tasks to specialized 

+

576 layouter functions. 

+

577 """ 

+

578 

+

579 def __init__(self, page: Page): 

+

580 """ 

+

581 Initialize the document layouter with a page. 

+

582 

+

583 Args: 

+

584 page: The page to layout content on 

+

585 """ 

+

586 self.page = page 

+

587 # Create a style resolver if page doesn't have one 

+

588 if hasattr(page, 'style_resolver'): 

+

589 style_resolver = page.style_resolver 

+

590 else: 

+

591 # Create a default rendering context and style resolver 

+

592 from pyWebLayout.style.concrete_style import RenderingContext 

+

593 context = RenderingContext() 

+

594 style_resolver = StyleResolver(context) 

+

595 self.style_registry = ConcreteStyleRegistry(style_resolver) 

+

596 

+

597 def layout_paragraph(self, 

+

598 paragraph: Paragraph, 

+

599 start_word: int = 0, 

+

600 pretext: Optional[Text] = None) -> Tuple[bool, 

+

601 Optional[int], 

+

602 Optional[Text]]: 

+

603 """ 

+

604 Layout a paragraph using the paragraph_layouter. 

+

605 

+

606 Args: 

+

607 paragraph: The paragraph to layout 

+

608 start_word: Index of the first word to process (for continuation) 

+

609 pretext: Optional pretext from a previous hyphenated word 

+

610 

+

611 Returns: 

+

612 Tuple of (success, failed_word_index, remaining_pretext) 

+

613 """ 

+

614 return paragraph_layouter(paragraph, self.page, start_word, pretext) 

+

615 

+

616 def layout_image(self, image: AbstractImage, max_width: Optional[int] = None, 

+

617 max_height: Optional[int] = None) -> bool: 

+

618 """ 

+

619 Layout an image using the image_layouter. 

+

620 

+

621 Args: 

+

622 image: The abstract Image object to layout 

+

623 max_width: Maximum width constraint (defaults to page available width) 

+

624 max_height: Maximum height constraint (defaults to remaining page height) 

+

625 

+

626 Returns: 

+

627 bool: True if image was successfully laid out, False if page ran out of space 

+

628 """ 

+

629 return image_layouter(image, self.page, max_width, max_height) 

+

630 

+

631 def layout_table(self, table: Table, style: Optional[TableStyle] = None) -> bool: 

+

632 """ 

+

633 Layout a table using the table_layouter. 

+

634 

+

635 Args: 

+

636 table: The abstract Table object to layout 

+

637 style: Optional table styling configuration 

+

638 

+

639 Returns: 

+

640 bool: True if table was successfully laid out, False if page ran out of space 

+

641 """ 

+

642 return table_layouter(table, self.page, style) 

+

643 

+

644 def layout_button(self, 

+

645 button: Button, 

+

646 font: Optional[Font] = None, 

+

647 padding: Tuple[int, 

+

648 int, 

+

649 int, 

+

650 int] = (4, 

+

651 8, 

+

652 4, 

+

653 8)) -> Tuple[bool, 

+

654 str]: 

+

655 """ 

+

656 Layout a button using the button_layouter. 

+

657 

+

658 Args: 

+

659 button: The abstract Button object to layout 

+

660 font: Optional font for button text 

+

661 padding: Padding around button text 

+

662 

+

663 Returns: 

+

664 Tuple of (success, registered_id) 

+

665 """ 

+

666 return button_layouter(button, self.page, font, padding) 

+

667 

+

668 def layout_form(self, form: Form, font: Optional[Font] = None, 

+

669 field_spacing: int = 10) -> Tuple[bool, List[str]]: 

+

670 """ 

+

671 Layout a form using the form_layouter. 

+

672 

+

673 Args: 

+

674 form: The abstract Form object to layout 

+

675 font: Optional font for field labels 

+

676 field_spacing: Vertical spacing between fields 

+

677 

+

678 Returns: 

+

679 Tuple of (success, list_of_field_ids) 

+

680 """ 

+

681 return form_layouter(form, self.page, font, field_spacing) 

+

682 

+

683 def layout_document( 

+

684 self, elements: List[Union[Paragraph, AbstractImage, Table, Button, Form]]) -> bool: 

+

685 """ 

+

686 Layout a list of abstract elements (paragraphs, images, tables, buttons, and forms). 

+

687 

+

688 This method delegates to specialized layouters based on element type: 

+

689 - Paragraphs are handled by layout_paragraph 

+

690 - Images are handled by layout_image 

+

691 - Tables are handled by layout_table 

+

692 - Buttons are handled by layout_button 

+

693 - Forms are handled by layout_form 

+

694 

+

695 Args: 

+

696 elements: List of abstract elements to layout 

+

697 

+

698 Returns: 

+

699 True if all elements were successfully laid out, False otherwise 

+

700 """ 

+

701 for element in elements: 

+

702 if isinstance(element, Paragraph): 

+

703 success, _, _ = self.layout_paragraph(element) 

+

704 if not success: 

+

705 return False 

+

706 elif isinstance(element, AbstractImage): 

+

707 success = self.layout_image(element) 

+

708 if not success: 708 ↛ 709line 708 didn't jump to line 709 because the condition on line 708 was never true

+

709 return False 

+

710 elif isinstance(element, Table): 710 ↛ 714line 710 didn't jump to line 714 because the condition on line 710 was always true

+

711 success = self.layout_table(element) 

+

712 if not success: 

+

713 return False 

+

714 elif isinstance(element, Button): 

+

715 success, _ = self.layout_button(element) 

+

716 if not success: 

+

717 return False 

+

718 elif isinstance(element, Form): 

+

719 success, _ = self.layout_form(element) 

+

720 if not success: 

+

721 return False 

+

722 # Future: elif isinstance(element, CodeBlock): use code_layouter 

+

723 return True 

+
+ + + diff --git a/cov_info/htmlcov/z_427cc3035faf7633_ereader_layout_py.html b/cov_info/htmlcov/z_427cc3035faf7633_ereader_layout_py.html new file mode 100644 index 0000000..a8d6df8 --- /dev/null +++ b/cov_info/htmlcov/z_427cc3035faf7633_ereader_layout_py.html @@ -0,0 +1,853 @@ + + + + + Coverage for pyWebLayout/layout/ereader_layout.py: 83% + + + + + +
+
+

+ Coverage for pyWebLayout/layout/ereader_layout.py: + 83% +

+ +

+ 279 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Enhanced ereader layout system with position tracking, font scaling, and multi-page support. 

+

3 

+

4This module provides the core infrastructure for building high-performance ereader applications 

+

5with features like: 

+

6- Precise position tracking tied to abstract document structure 

+

7- Font scaling support 

+

8- Bidirectional page rendering (forward/backward) 

+

9- Chapter navigation based on HTML headings 

+

10- Multi-process page buffering 

+

11- Sub-second page rendering performance 

+

12""" 

+

13 

+

14from __future__ import annotations 

+

15from dataclasses import dataclass, asdict 

+

16from typing import List, Dict, Tuple, Optional, Any 

+

17 

+

18from pyWebLayout.abstract.block import Block, Paragraph, Heading, HeadingLevel, Table, HList, Image 

+

19from pyWebLayout.abstract.inline import Word 

+

20from pyWebLayout.concrete.page import Page 

+

21from pyWebLayout.concrete.text import Text 

+

22from pyWebLayout.style.page_style import PageStyle 

+

23from pyWebLayout.style import Font 

+

24from pyWebLayout.style.fonts import BundledFont, get_bundled_font_path, FontWeight, FontStyle 

+

25from pyWebLayout.layout.document_layouter import paragraph_layouter, image_layouter 

+

26 

+

27 

+

28@dataclass 

+

29class RenderingPosition: 

+

30 """ 

+

31 Complete state for resuming rendering at any point in a document. 

+

32 Position is tied to abstract document structure for stability across font changes. 

+

33 """ 

+

34 chapter_index: int = 0 # Which chapter (based on headings) 

+

35 block_index: int = 0 # Which block within chapter 

+

36 # Which word within block (for paragraphs) 

+

37 word_index: int = 0 

+

38 table_row: int = 0 # Which row for tables 

+

39 table_col: int = 0 # Which column for tables 

+

40 list_item_index: int = 0 # Which item for lists 

+

41 remaining_pretext: Optional[str] = None # Hyphenated word continuation 

+

42 page_y_offset: int = 0 # Vertical position on page 

+

43 

+

44 def to_dict(self) -> Dict[str, Any]: 

+

45 """Serialize position for saving to file/database""" 

+

46 return asdict(self) 

+

47 

+

48 @classmethod 

+

49 def from_dict(cls, data: Dict[str, Any]) -> 'RenderingPosition': 

+

50 """Deserialize position from saved state""" 

+

51 return cls(**data) 

+

52 

+

53 def copy(self) -> 'RenderingPosition': 

+

54 """Create a copy of this position""" 

+

55 return RenderingPosition(**asdict(self)) 

+

56 

+

57 def __eq__(self, other) -> bool: 

+

58 """Check if two positions are equal""" 

+

59 if not isinstance(other, RenderingPosition): 

+

60 return False 

+

61 return asdict(self) == asdict(other) 

+

62 

+

63 def __hash__(self) -> int: 

+

64 """Make position hashable for use as dict key""" 

+

65 return hash(tuple(asdict(self).values())) 

+

66 

+

67 

+

68class ChapterInfo: 

+

69 """Information about a chapter/section in the document""" 

+

70 

+

71 def __init__( 

+

72 self, 

+

73 title: str, 

+

74 level: HeadingLevel, 

+

75 position: RenderingPosition, 

+

76 block_index: int): 

+

77 self.title = title 

+

78 self.level = level 

+

79 self.position = position 

+

80 self.block_index = block_index 

+

81 

+

82 

+

83class ChapterNavigator: 

+

84 """ 

+

85 Handles chapter/section navigation based on HTML heading structure (H1-H6). 

+

86 Builds a table of contents and provides navigation capabilities. 

+

87 """ 

+

88 

+

89 def __init__(self, blocks: List[Block]): 

+

90 self.blocks = blocks 

+

91 self.chapters: List[ChapterInfo] = [] 

+

92 self._build_chapter_map() 

+

93 

+

94 def _build_chapter_map(self): 

+

95 """Scan blocks for headings and build chapter navigation map""" 

+

96 current_chapter_index = 0 

+

97 

+

98 # Check if first block is a cover image and add it to TOC 

+

99 if self.blocks and isinstance(self.blocks[0], Image): 

+

100 cover_position = RenderingPosition( 

+

101 chapter_index=0, 

+

102 block_index=0, 

+

103 word_index=0, 

+

104 table_row=0, 

+

105 table_col=0, 

+

106 list_item_index=0 

+

107 ) 

+

108 

+

109 cover_info = ChapterInfo( 

+

110 title="Cover", 

+

111 level=HeadingLevel.H1, # Treat as top-level entry 

+

112 position=cover_position, 

+

113 block_index=0 

+

114 ) 

+

115 

+

116 self.chapters.append(cover_info) 

+

117 

+

118 for block_index, block in enumerate(self.blocks): 

+

119 if isinstance(block, Heading): 

+

120 # Create position for this heading 

+

121 position = RenderingPosition( 

+

122 chapter_index=current_chapter_index, 

+

123 block_index=block_index, # Use actual block index 

+

124 word_index=0, 

+

125 table_row=0, 

+

126 table_col=0, 

+

127 list_item_index=0 

+

128 ) 

+

129 

+

130 # Extract heading text 

+

131 heading_text = self._extract_heading_text(block) 

+

132 

+

133 chapter_info = ChapterInfo( 

+

134 title=heading_text, 

+

135 level=block.level, 

+

136 position=position, 

+

137 block_index=block_index 

+

138 ) 

+

139 

+

140 self.chapters.append(chapter_info) 

+

141 

+

142 # Only increment chapter index for top-level headings (H1) 

+

143 if block.level == HeadingLevel.H1: 

+

144 current_chapter_index += 1 

+

145 

+

146 def _extract_heading_text(self, heading: Heading) -> str: 

+

147 """Extract text content from a heading block""" 

+

148 words = [] 

+

149 for position, word in heading.words_iter(): 

+

150 if isinstance(word, Word): 150 ↛ 149line 150 didn't jump to line 149 because the condition on line 150 was always true

+

151 words.append(word.text) 

+

152 return " ".join(words) 

+

153 

+

154 def get_table_of_contents( 

+

155 self) -> List[Tuple[str, HeadingLevel, RenderingPosition]]: 

+

156 """Generate table of contents from heading structure""" 

+

157 return [(chapter.title, chapter.level, chapter.position) 

+

158 for chapter in self.chapters] 

+

159 

+

160 def get_chapter_position(self, chapter_title: str) -> Optional[RenderingPosition]: 

+

161 """Get rendering position for a chapter by title""" 

+

162 for chapter in self.chapters: 

+

163 if chapter.title.lower() == chapter_title.lower(): 

+

164 return chapter.position 

+

165 return None 

+

166 

+

167 def get_current_chapter(self, position: RenderingPosition) -> Optional[ChapterInfo]: 

+

168 """Determine which chapter contains the current position""" 

+

169 if not self.chapters: 

+

170 return None 

+

171 

+

172 # Find the chapter that contains this position 

+

173 for i, chapter in enumerate(self.chapters): 173 ↛ 182line 173 didn't jump to line 182 because the loop on line 173 didn't complete

+

174 # Check if this is the last chapter or if position is before next chapter 

+

175 if i == len(self.chapters) - 1: 

+

176 return chapter 

+

177 

+

178 next_chapter = self.chapters[i + 1] 

+

179 if position.chapter_index < next_chapter.position.chapter_index: 

+

180 return chapter 

+

181 

+

182 return self.chapters[0] if self.chapters else None 

+

183 

+

184 

+

185class FontFamilyOverride: 

+

186 """ 

+

187 Manages font family preferences for ereader rendering. 

+

188 Allows dynamic font family switching without modifying source blocks. 

+

189 """ 

+

190 

+

191 def __init__(self, preferred_family: Optional[BundledFont] = None): 

+

192 """ 

+

193 Initialize font family override. 

+

194 

+

195 Args: 

+

196 preferred_family: Preferred bundled font family (None = use original fonts) 

+

197 """ 

+

198 self.preferred_family = preferred_family 

+

199 

+

200 def override_font(self, font: Font) -> Font: 

+

201 """ 

+

202 Create a new font with the preferred family while preserving other attributes. 

+

203 

+

204 Args: 

+

205 font: Original font object 

+

206 

+

207 Returns: 

+

208 Font with overridden family, or original if no override is set 

+

209 """ 

+

210 if self.preferred_family is None: 

+

211 return font 

+

212 

+

213 # Get the appropriate font path for the preferred family 

+

214 # preserving the original font's weight and style 

+

215 new_font_path = get_bundled_font_path( 

+

216 family=self.preferred_family, 

+

217 weight=font.weight, 

+

218 style=font.style 

+

219 ) 

+

220 

+

221 # If we couldn't find a matching font, fall back to original 

+

222 if new_font_path is None: 

+

223 return font 

+

224 

+

225 # Create a new font with the overridden path 

+

226 return Font( 

+

227 font_path=new_font_path, 

+

228 font_size=font.font_size, 

+

229 colour=font.colour, 

+

230 weight=font.weight, 

+

231 style=font.style, 

+

232 decoration=font.decoration, 

+

233 background=font.background, 

+

234 language=font.language, 

+

235 min_hyphenation_width=font.min_hyphenation_width 

+

236 ) 

+

237 

+

238 

+

239class FontScaler: 

+

240 """ 

+

241 Handles font scaling operations for ereader font size adjustments. 

+

242 Applies scaling at layout/render time while preserving original font objects. 

+

243 """ 

+

244 

+

245 @staticmethod 

+

246 def scale_font(font: Font, scale_factor: float, family_override: Optional[FontFamilyOverride] = None) -> Font: 

+

247 """ 

+

248 Create a scaled version of a font for layout calculations. 

+

249 

+

250 Args: 

+

251 font: Original font object 

+

252 scale_factor: Scaling factor (1.0 = no change, 2.0 = double size, etc.) 

+

253 family_override: Optional font family override 

+

254 

+

255 Returns: 

+

256 New Font object with scaled size and optional family override 

+

257 """ 

+

258 # Apply family override first if specified 

+

259 working_font = font 

+

260 if family_override is not None: 260 ↛ 261line 260 didn't jump to line 261 because the condition on line 260 was never true

+

261 working_font = family_override.override_font(font) 

+

262 

+

263 # Then apply scaling 

+

264 if scale_factor == 1.0: 

+

265 return working_font 

+

266 

+

267 scaled_size = max(1, int(working_font.font_size * scale_factor)) 

+

268 

+

269 return Font( 

+

270 font_path=working_font._font_path, 

+

271 font_size=scaled_size, 

+

272 colour=working_font.colour, 

+

273 weight=working_font.weight, 

+

274 style=working_font.style, 

+

275 decoration=working_font.decoration, 

+

276 background=working_font.background, 

+

277 language=working_font.language, 

+

278 min_hyphenation_width=working_font.min_hyphenation_width 

+

279 ) 

+

280 

+

281 @staticmethod 

+

282 def scale_word_spacing(spacing: Tuple[int, int], 

+

283 scale_factor: float) -> Tuple[int, int]: 

+

284 """Scale word spacing constraints proportionally""" 

+

285 if scale_factor == 1.0: 

+

286 return spacing 

+

287 

+

288 min_spacing, max_spacing = spacing 

+

289 return ( 

+

290 max(1, int(min_spacing * scale_factor)), 

+

291 max(2, int(max_spacing * scale_factor)) 

+

292 ) 

+

293 

+

294 

+

295class BidirectionalLayouter: 

+

296 """ 

+

297 Core layout engine supporting both forward and backward page rendering. 

+

298 Handles font scaling and maintains position state. 

+

299 """ 

+

300 

+

301 def __init__(self, 

+

302 blocks: List[Block], 

+

303 page_style: PageStyle, 

+

304 page_size: Tuple[int, 

+

305 int] = (800, 

+

306 600), 

+

307 alignment_override=None, 

+

308 font_family_override: Optional[FontFamilyOverride] = None): 

+

309 self.blocks = blocks 

+

310 self.page_style = page_style 

+

311 self.page_size = page_size 

+

312 self.chapter_navigator = ChapterNavigator(blocks) 

+

313 self.alignment_override = alignment_override 

+

314 self.font_family_override = font_family_override 

+

315 

+

316 def render_page_forward(self, position: RenderingPosition, 

+

317 font_scale: float = 1.0) -> Tuple[Page, RenderingPosition]: 

+

318 """ 

+

319 Render a page starting from the given position, moving forward through the document. 

+

320 

+

321 Args: 

+

322 position: Starting position in document 

+

323 font_scale: Font scaling factor 

+

324 

+

325 Returns: 

+

326 Tuple of (rendered_page, next_position) 

+

327 """ 

+

328 page = Page(size=self.page_size, style=self.page_style) 

+

329 current_pos = position.copy() 

+

330 

+

331 # Start laying out blocks from the current position 

+

332 while current_pos.block_index < len(self.blocks) and page.free_space()[1] > 0: 

+

333 # Additional bounds check to prevent IndexError 

+

334 if current_pos.block_index >= len(self.blocks): 334 ↛ 335line 334 didn't jump to line 335 because the condition on line 334 was never true

+

335 break 

+

336 

+

337 block = self.blocks[current_pos.block_index] 

+

338 

+

339 # Apply font scaling to the block 

+

340 scaled_block = self._scale_block_fonts(block, font_scale) 

+

341 

+

342 # Try to fit the block on the current page 

+

343 success, new_pos = self._layout_block_on_page( 

+

344 scaled_block, page, current_pos, font_scale) 

+

345 

+

346 if not success: 

+

347 # Block doesn't fit, we're done with this page 

+

348 break 

+

349 

+

350 # Add inter-block spacing after successfully laying out a block 

+

351 # Only add if we're not at the end of the document and there's space 

+

352 if new_pos.block_index < len(self.blocks): 

+

353 page._current_y_offset += self.page_style.inter_block_spacing 

+

354 

+

355 # Ensure new position doesn't go beyond bounds 

+

356 if new_pos.block_index >= len(self.blocks): 

+

357 # We've reached the end of the document 

+

358 current_pos = new_pos 

+

359 break 

+

360 

+

361 current_pos = new_pos 

+

362 

+

363 return page, current_pos 

+

364 

+

365 def render_page_backward(self, 

+

366 end_position: RenderingPosition, 

+

367 font_scale: float = 1.0) -> Tuple[Page, 

+

368 RenderingPosition]: 

+

369 """ 

+

370 Render a page that ends at the given position, filling backward. 

+

371 Critical for "previous page" navigation. 

+

372 

+

373 Uses iterative refinement to find the correct start position that 

+

374 results in a page ending at (or very close to) the target position. 

+

375 

+

376 Args: 

+

377 end_position: Position where page should end 

+

378 font_scale: Font scaling factor 

+

379 

+

380 Returns: 

+

381 Tuple of (rendered_page, start_position) 

+

382 """ 

+

383 # Handle edge case: already at beginning 

+

384 if end_position.block_index == 0 and end_position.word_index == 0: 384 ↛ 385line 384 didn't jump to line 385 because the condition on line 384 was never true

+

385 return self.render_page_forward(end_position, font_scale) 

+

386 

+

387 # Start with initial estimate 

+

388 estimated_start = self._estimate_page_start(end_position, font_scale) 

+

389 

+

390 # Iterative refinement: keep adjusting until we converge or hit max iterations 

+

391 max_iterations = 10 

+

392 best_page = None 

+

393 best_start = estimated_start 

+

394 best_distance = float('inf') 

+

395 

+

396 for iteration in range(max_iterations): 

+

397 # Render forward from current estimate 

+

398 page, actual_end = self.render_page_forward(estimated_start, font_scale) 

+

399 

+

400 # Calculate how far we are from target 

+

401 comparison = self._position_compare(actual_end, end_position) 

+

402 

+

403 # Perfect match or close enough (within same block) 

+

404 # BUT: ensure we actually moved backward (estimated_start < end_position) 

+

405 if comparison == 0: 

+

406 # Check if we actually found a valid previous page 

+

407 if self._position_compare(estimated_start, end_position) < 0: 407 ↛ 411line 407 didn't jump to line 411 because the condition on line 407 was always true

+

408 return page, estimated_start 

+

409 # If estimated_start >= end_position, we haven't moved backward 

+

410 # Continue iterating to find a better position 

+

411 elif iteration == 0: 

+

412 # On first iteration, if we can't find a previous position, 

+

413 # we're likely at or near the beginning 

+

414 break 

+

415 

+

416 # Track best result so far 

+

417 distance = abs(actual_end.block_index - end_position.block_index) 

+

418 if distance < best_distance: 

+

419 best_distance = distance 

+

420 best_page = page 

+

421 best_start = estimated_start.copy() 

+

422 

+

423 # Adjust estimate for next iteration 

+

424 estimated_start = self._adjust_start_estimate( 

+

425 estimated_start, end_position, actual_end) 

+

426 

+

427 # Safety: don't go before document start 

+

428 if estimated_start.block_index < 0: 428 ↛ 429line 428 didn't jump to line 429 because the condition on line 428 was never true

+

429 estimated_start.block_index = 0 

+

430 estimated_start.word_index = 0 

+

431 

+

432 # If we exhausted iterations, return best result found 

+

433 # BUT: ensure we didn't return the same position (no backward progress) 

+

434 final_page = best_page if best_page else page 

+

435 final_start = best_start 

+

436 

+

437 # Safety check: if final_start >= end_position, we failed to move backward 

+

438 # This can happen at the beginning of the document or when estimation failed 

+

439 if self._position_compare(final_start, end_position) >= 0: 439 ↛ 441line 439 didn't jump to line 441 because the condition on line 439 was never true

+

440 # Can't go further back - check if we're at the absolute beginning 

+

441 if end_position.block_index == 0 and end_position.word_index == 0: 

+

442 # Already at beginning, return as-is 

+

443 return final_page, final_start 

+

444 

+

445 # Fallback strategy: try a more aggressive backward jump 

+

446 # Start from several blocks before the current position 

+

447 blocks_to_jump = max(1, min(5, end_position.block_index)) 

+

448 fallback_pos = RenderingPosition( 

+

449 chapter_index=end_position.chapter_index, 

+

450 block_index=max(0, end_position.block_index - blocks_to_jump), 

+

451 word_index=0 

+

452 ) 

+

453 

+

454 # Render forward from the fallback position 

+

455 fallback_page, fallback_end = self.render_page_forward(fallback_pos, font_scale) 

+

456 

+

457 # Verify the fallback actually moved us backward 

+

458 if self._position_compare(fallback_pos, end_position) < 0: 

+

459 return fallback_page, fallback_pos 

+

460 

+

461 # If even the fallback didn't work, we're likely at the beginning 

+

462 # Return a page starting from the beginning 

+

463 return self.render_page_forward(RenderingPosition(), font_scale) 

+

464 

+

465 return final_page, final_start 

+

466 

+

467 def _scale_block_fonts(self, block: Block, font_scale: float) -> Block: 

+

468 """Apply font scaling and font family override to all fonts in a block""" 

+

469 # Check if we need to do any transformation 

+

470 if font_scale == 1.0 and self.font_family_override is None: 

+

471 return block 

+

472 

+

473 # This is a simplified implementation 

+

474 # In practice, we'd need to handle each block type appropriately 

+

475 if isinstance(block, (Paragraph, Heading)): 475 ↛ 491line 475 didn't jump to line 491 because the condition on line 475 was always true

+

476 scaled_block_style = FontScaler.scale_font(block.style, font_scale, self.font_family_override) 

+

477 if isinstance(block, Heading): 

+

478 scaled_block = Heading(block.level, scaled_block_style) 

+

479 else: 

+

480 scaled_block = Paragraph(scaled_block_style) 

+

481 

+

482 # words_iter() returns tuples of (position, word) 

+

483 for position, word in block.words_iter(): 

+

484 if isinstance(word, Word): 484 ↛ 483line 484 didn't jump to line 483 because the condition on line 484 was always true

+

485 scaled_word = Word( 

+

486 word.text, FontScaler.scale_font( 

+

487 word.style, font_scale, self.font_family_override)) 

+

488 scaled_block.add_word(scaled_word) 

+

489 return scaled_block 

+

490 

+

491 return block 

+

492 

+

493 def _layout_block_on_page(self, 

+

494 block: Block, 

+

495 page: Page, 

+

496 position: RenderingPosition, 

+

497 font_scale: float) -> Tuple[bool, 

+

498 RenderingPosition]: 

+

499 """ 

+

500 Try to layout a block on the page starting from the given position. 

+

501 

+

502 Returns: 

+

503 Tuple of (success, new_position) 

+

504 """ 

+

505 if isinstance(block, Paragraph): 

+

506 return self._layout_paragraph_on_page(block, page, position, font_scale) 

+

507 elif isinstance(block, Heading): 507 ↛ 508line 507 didn't jump to line 508 because the condition on line 507 was never true

+

508 return self._layout_heading_on_page(block, page, position, font_scale) 

+

509 elif isinstance(block, Table): 509 ↛ 510line 509 didn't jump to line 510 because the condition on line 509 was never true

+

510 return self._layout_table_on_page(block, page, position, font_scale) 

+

511 elif isinstance(block, HList): 511 ↛ 512line 511 didn't jump to line 512 because the condition on line 511 was never true

+

512 return self._layout_list_on_page(block, page, position, font_scale) 

+

513 elif isinstance(block, Image): 

+

514 return self._layout_image_on_page(block, page, position, font_scale) 

+

515 else: 

+

516 # Skip unknown block types 

+

517 new_pos = position.copy() 

+

518 new_pos.block_index += 1 

+

519 return True, new_pos 

+

520 

+

521 def _layout_paragraph_on_page(self, 

+

522 paragraph: Paragraph, 

+

523 page: Page, 

+

524 position: RenderingPosition, 

+

525 font_scale: float) -> Tuple[bool, 

+

526 RenderingPosition]: 

+

527 """ 

+

528 Layout a paragraph on the page using the core paragraph_layouter. 

+

529 Integrates font scaling and position tracking with the proven layout logic. 

+

530 

+

531 Args: 

+

532 paragraph: The paragraph to layout (already scaled if font_scale != 1.0) 

+

533 page: The page to layout on 

+

534 position: Current rendering position 

+

535 font_scale: Font scaling factor (used for context, paragraph should already be scaled) 

+

536 

+

537 Returns: 

+

538 Tuple of (success, new_position) 

+

539 """ 

+

540 # Convert remaining_pretext from string to Text object if needed 

+

541 pretext_obj = None 

+

542 if position.remaining_pretext: 

+

543 # Create a Text object from the pretext string 

+

544 pretext_obj = Text( 

+

545 position.remaining_pretext, 

+

546 paragraph.style, 

+

547 page.draw, 

+

548 line=None, 

+

549 source=None 

+

550 ) 

+

551 

+

552 # Call the core paragraph layouter with alignment override if set 

+

553 success, failed_word_index, remaining_pretext = paragraph_layouter( 

+

554 paragraph, 

+

555 page, 

+

556 start_word=position.word_index, 

+

557 pretext=pretext_obj, 

+

558 alignment_override=self.alignment_override 

+

559 ) 

+

560 

+

561 # Create new position based on the result 

+

562 new_pos = position.copy() 

+

563 

+

564 if success: 

+

565 # Paragraph was fully laid out, move to next block 

+

566 new_pos.block_index += 1 

+

567 new_pos.word_index = 0 

+

568 new_pos.remaining_pretext = None 

+

569 return True, new_pos 

+

570 else: 

+

571 # Paragraph was not fully laid out 

+

572 if failed_word_index is not None: 572 ↛ 586line 572 didn't jump to line 586 because the condition on line 572 was always true

+

573 # Update position to the word that didn't fit 

+

574 new_pos.word_index = failed_word_index 

+

575 

+

576 # Convert Text object back to string if there's remaining pretext 

+

577 if remaining_pretext is not None and hasattr(remaining_pretext, 'text'): 577 ↛ 578line 577 didn't jump to line 578 because the condition on line 577 was never true

+

578 new_pos.remaining_pretext = remaining_pretext.text 

+

579 else: 

+

580 new_pos.remaining_pretext = None 

+

581 

+

582 return False, new_pos 

+

583 else: 

+

584 # No specific word failed, but layout wasn't successful 

+

585 # This shouldn't normally happen, but handle it gracefully 

+

586 return False, position 

+

587 

+

588 def _layout_heading_on_page(self, 

+

589 heading: Heading, 

+

590 page: Page, 

+

591 position: RenderingPosition, 

+

592 font_scale: float) -> Tuple[bool, 

+

593 RenderingPosition]: 

+

594 """Layout a heading on the page""" 

+

595 # Similar to paragraph but with heading-specific styling 

+

596 return self._layout_paragraph_on_page(heading, page, position, font_scale) 

+

597 

+

598 def _layout_table_on_page(self, 

+

599 table: Table, 

+

600 page: Page, 

+

601 position: RenderingPosition, 

+

602 font_scale: float) -> Tuple[bool, 

+

603 RenderingPosition]: 

+

604 """Layout a table on the page with column fitting and row continuation""" 

+

605 # This is a complex operation that would need full table layout logic 

+

606 # For now, skip tables 

+

607 new_pos = position.copy() 

+

608 new_pos.block_index += 1 

+

609 new_pos.table_row = 0 

+

610 new_pos.table_col = 0 

+

611 return True, new_pos 

+

612 

+

613 def _layout_list_on_page(self, 

+

614 hlist: HList, 

+

615 page: Page, 

+

616 position: RenderingPosition, 

+

617 font_scale: float) -> Tuple[bool, 

+

618 RenderingPosition]: 

+

619 """Layout a list on the page""" 

+

620 # This would need list-specific layout logic 

+

621 # For now, skip lists 

+

622 new_pos = position.copy() 

+

623 new_pos.block_index += 1 

+

624 new_pos.list_item_index = 0 

+

625 return True, new_pos 

+

626 

+

627 def _layout_image_on_page(self, 

+

628 image: Image, 

+

629 page: Page, 

+

630 position: RenderingPosition, 

+

631 font_scale: float) -> Tuple[bool, 

+

632 RenderingPosition]: 

+

633 """ 

+

634 Layout an image on the page using the image_layouter. 

+

635 

+

636 Args: 

+

637 image: The Image block to layout 

+

638 page: The page to layout on 

+

639 position: Current rendering position (should be at the start of this image block) 

+

640 font_scale: Font scaling factor (not used for images, but kept for consistency) 

+

641 

+

642 Returns: 

+

643 Tuple of (success, new_position) 

+

644 - success: True if image was laid out, False if page ran out of space 

+

645 - new_position: Updated position (next block if success, same block if failed) 

+

646 """ 

+

647 # Try to layout the image on the current page 

+

648 success = image_layouter( 

+

649 image=image, 

+

650 page=page, 

+

651 max_width=None, # Use page available width 

+

652 max_height=None # Use page available height 

+

653 ) 

+

654 

+

655 new_pos = position.copy() 

+

656 

+

657 if success: 

+

658 # Image was successfully laid out, move to next block 

+

659 new_pos.block_index += 1 

+

660 new_pos.word_index = 0 

+

661 return True, new_pos 

+

662 else: 

+

663 # Image didn't fit on current page, signal to continue on next page 

+

664 # Keep same position so it will be attempted on the next page 

+

665 return False, position 

+

666 

+

667 def _estimate_page_start( 

+

668 self, 

+

669 end_position: RenderingPosition, 

+

670 font_scale: float) -> RenderingPosition: 

+

671 """Estimate where a page should start to end at the given position""" 

+

672 # This is a simplified heuristic - a full implementation would be more 

+

673 # sophisticated 

+

674 estimated_start = end_position.copy() 

+

675 

+

676 # Move back by an estimated number of blocks that would fit on a page 

+

677 estimated_blocks_per_page = max(1, int(10 / font_scale)) # Rough estimate 

+

678 estimated_start.block_index = max( 

+

679 0, end_position.block_index - estimated_blocks_per_page) 

+

680 estimated_start.word_index = 0 

+

681 

+

682 return estimated_start 

+

683 

+

684 def _adjust_start_estimate( 

+

685 self, 

+

686 current_start: RenderingPosition, 

+

687 target_end: RenderingPosition, 

+

688 actual_end: RenderingPosition) -> RenderingPosition: 

+

689 """ 

+

690 Adjust start position estimate based on overshoot/undershoot. 

+

691 Uses proportional adjustment to converge faster. 

+

692 """ 

+

693 adjusted = current_start.copy() 

+

694 

+

695 # Calculate the difference between actual and target end positions 

+

696 block_diff = actual_end.block_index - target_end.block_index 

+

697 

+

698 comparison = self._position_compare(actual_end, target_end) 

+

699 

+

700 if comparison < 0: # Undershot - rendered to block X but need to reach block Y where X < Y 

+

701 # We didn't render far enough forward 

+

702 # Need to start at a LATER block (higher index) so the page includes more content 

+

703 adjustment = max(1, abs(block_diff) // 2) 

+

704 new_index = adjusted.block_index + adjustment 

+

705 # Clamp to valid range 

+

706 if len(self.blocks) > 0: 706 ↛ 707line 706 didn't jump to line 707 because the condition on line 706 was never true

+

707 adjusted.block_index = min(len(self.blocks) - 1, max(0, new_index)) 

+

708 else: 

+

709 adjusted.block_index = max(0, new_index) 

+

710 elif comparison > 0: # Overshot - rendered past the target 

+

711 # We rendered too far forward 

+

712 # Need to start at an EARLIER block (lower index) so the page doesn't go as far 

+

713 adjustment = max(1, abs(block_diff) // 2) 

+

714 adjusted.block_index = max(0, adjusted.block_index - adjustment) 

+

715 

+

716 # Reset word index when adjusting blocks 

+

717 adjusted.word_index = 0 

+

718 

+

719 return adjusted 

+

720 

+

721 def _position_compare(self, pos1: RenderingPosition, 

+

722 pos2: RenderingPosition) -> int: 

+

723 """Compare two positions (-1: pos1 < pos2, 0: equal, 1: pos1 > pos2)""" 

+

724 if pos1.chapter_index != pos2.chapter_index: 

+

725 return 1 if pos1.chapter_index > pos2.chapter_index else -1 

+

726 if pos1.block_index != pos2.block_index: 

+

727 return 1 if pos1.block_index > pos2.block_index else -1 

+

728 if pos1.word_index != pos2.word_index: 

+

729 return 1 if pos1.word_index > pos2.word_index else -1 

+

730 return 0 

+

731 

+

732 

+

733# Add can_fit_line method to Page class if it doesn't exist 

+

734def _add_page_methods(): 

+

735 """Add missing methods to Page class""" 

+

736 if not hasattr(Page, 'can_fit_line'): 736 ↛ 737line 736 didn't jump to line 737 because the condition on line 736 was never true

+

737 def can_fit_line(self, line_height: int) -> bool: 

+

738 """Check if a line of given height can fit on the page""" 

+

739 available_height = self.content_size[1] - self._current_y_offset 

+

740 return available_height >= line_height 

+

741 

+

742 Page.can_fit_line = can_fit_line 

+

743 

+

744 if not hasattr(Page, 'available_width'): 744 ↛ 745line 744 didn't jump to line 745 because the condition on line 744 was never true

+

745 @property 

+

746 def available_width(self) -> int: 

+

747 """Get available width for content""" 

+

748 return self.content_size[0] 

+

749 

+

750 Page.available_width = available_width 

+

751 

+

752 

+

753# Apply the page methods 

+

754_add_page_methods() 

+
+ + + diff --git a/cov_info/htmlcov/z_427cc3035faf7633_ereader_manager_py.html b/cov_info/htmlcov/z_427cc3035faf7633_ereader_manager_py.html new file mode 100644 index 0000000..4489cb7 --- /dev/null +++ b/cov_info/htmlcov/z_427cc3035faf7633_ereader_manager_py.html @@ -0,0 +1,987 @@ + + + + + Coverage for pyWebLayout/layout/ereader_manager.py: 84% + + + + + +
+
+

+ Coverage for pyWebLayout/layout/ereader_manager.py: + 84% +

+ +

+ 292 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2High-performance ereader layout manager with sub-second page rendering. 

+

3 

+

4This module provides the main interface for ereader applications, combining 

+

5position tracking, font scaling, chapter navigation, and intelligent page buffering 

+

6into a unified, easy-to-use API. 

+

7""" 

+

8 

+

9from __future__ import annotations 

+

10from typing import List, Dict, Optional, Tuple, Any, Callable 

+

11import json 

+

12from pathlib import Path 

+

13 

+

14from .ereader_layout import RenderingPosition, ChapterNavigator, ChapterInfo 

+

15from .page_buffer import BufferedPageRenderer 

+

16from pyWebLayout.abstract.block import Block, HeadingLevel, Image, BlockType 

+

17from pyWebLayout.concrete.page import Page 

+

18from pyWebLayout.concrete.image import RenderableImage 

+

19from pyWebLayout.style.page_style import PageStyle 

+

20from pyWebLayout.style.fonts import BundledFont 

+

21from pyWebLayout.layout.document_layouter import image_layouter 

+

22 

+

23 

+

24class BookmarkManager: 

+

25 """ 

+

26 Manages bookmarks and reading position persistence for ereader applications. 

+

27 """ 

+

28 

+

29 def __init__(self, document_id: str, bookmarks_dir: str = "bookmarks"): 

+

30 """ 

+

31 Initialize bookmark manager. 

+

32 

+

33 Args: 

+

34 document_id: Unique identifier for the document 

+

35 bookmarks_dir: Directory to store bookmark files 

+

36 """ 

+

37 self.document_id = document_id 

+

38 self.bookmarks_dir = Path(bookmarks_dir) 

+

39 self.bookmarks_dir.mkdir(exist_ok=True) 

+

40 

+

41 self.bookmarks_file = self.bookmarks_dir / f"{document_id}_bookmarks.json" 

+

42 self.position_file = self.bookmarks_dir / f"{document_id}_position.json" 

+

43 

+

44 self._bookmarks: Dict[str, RenderingPosition] = {} 

+

45 self._load_bookmarks() 

+

46 

+

47 def _load_bookmarks(self): 

+

48 """Load bookmarks from file""" 

+

49 if self.bookmarks_file.exists(): 

+

50 try: 

+

51 with open(self.bookmarks_file, 'r') as f: 

+

52 data = json.load(f) 

+

53 self._bookmarks = { 

+

54 name: RenderingPosition.from_dict(pos_data) 

+

55 for name, pos_data in data.items() 

+

56 } 

+

57 except Exception as e: 

+

58 print(f"Failed to load bookmarks: {e}") 

+

59 self._bookmarks = {} 

+

60 

+

61 def _save_bookmarks(self): 

+

62 """Save bookmarks to file""" 

+

63 try: 

+

64 data = { 

+

65 name: position.to_dict() 

+

66 for name, position in self._bookmarks.items() 

+

67 } 

+

68 with open(self.bookmarks_file, 'w') as f: 

+

69 json.dump(data, f, indent=2) 

+

70 except Exception as e: 

+

71 print(f"Failed to save bookmarks: {e}") 

+

72 

+

73 def add_bookmark(self, name: str, position: RenderingPosition): 

+

74 """ 

+

75 Add a bookmark at the given position. 

+

76 

+

77 Args: 

+

78 name: Bookmark name 

+

79 position: Position to bookmark 

+

80 """ 

+

81 self._bookmarks[name] = position 

+

82 self._save_bookmarks() 

+

83 

+

84 def remove_bookmark(self, name: str) -> bool: 

+

85 """ 

+

86 Remove a bookmark. 

+

87 

+

88 Args: 

+

89 name: Bookmark name to remove 

+

90 

+

91 Returns: 

+

92 True if bookmark was removed, False if not found 

+

93 """ 

+

94 if name in self._bookmarks: 

+

95 del self._bookmarks[name] 

+

96 self._save_bookmarks() 

+

97 return True 

+

98 return False 

+

99 

+

100 def get_bookmark(self, name: str) -> Optional[RenderingPosition]: 

+

101 """ 

+

102 Get a bookmark position. 

+

103 

+

104 Args: 

+

105 name: Bookmark name 

+

106 

+

107 Returns: 

+

108 Bookmark position or None if not found 

+

109 """ 

+

110 return self._bookmarks.get(name) 

+

111 

+

112 def list_bookmarks(self) -> List[Tuple[str, RenderingPosition]]: 

+

113 """ 

+

114 Get all bookmarks. 

+

115 

+

116 Returns: 

+

117 List of (name, position) tuples 

+

118 """ 

+

119 return list(self._bookmarks.items()) 

+

120 

+

121 def save_reading_position(self, position: RenderingPosition): 

+

122 """ 

+

123 Save the current reading position. 

+

124 

+

125 Args: 

+

126 position: Current reading position 

+

127 """ 

+

128 try: 

+

129 with open(self.position_file, 'w') as f: 

+

130 json.dump(position.to_dict(), f, indent=2) 

+

131 except Exception as e: 

+

132 print(f"Failed to save reading position: {e}") 

+

133 

+

134 def load_reading_position(self) -> Optional[RenderingPosition]: 

+

135 """ 

+

136 Load the last reading position. 

+

137 

+

138 Returns: 

+

139 Last reading position or None if not found 

+

140 """ 

+

141 if self.position_file.exists(): 

+

142 try: 

+

143 with open(self.position_file, 'r') as f: 

+

144 data = json.load(f) 

+

145 return RenderingPosition.from_dict(data) 

+

146 except Exception as e: 

+

147 print(f"Failed to load reading position: {e}") 

+

148 return None 

+

149 

+

150 

+

151class EreaderLayoutManager: 

+

152 """ 

+

153 High-level ereader layout manager providing a complete interface for ereader applications. 

+

154 

+

155 Features: 

+

156 - Sub-second page rendering with intelligent buffering 

+

157 - Font scaling support 

+

158 - Dynamic font family switching (Sans, Serif, Monospace) 

+

159 - Chapter navigation 

+

160 - Bookmark management 

+

161 - Position persistence 

+

162 - Progress tracking 

+

163 """ 

+

164 

+

165 def __init__(self, 

+

166 blocks: List[Block], 

+

167 page_size: Tuple[int, int], 

+

168 document_id: str = "default", 

+

169 buffer_size: int = 5, 

+

170 page_style: Optional[PageStyle] = None, 

+

171 bookmarks_dir: str = "bookmarks"): 

+

172 """ 

+

173 Initialize the ereader layout manager. 

+

174 

+

175 Args: 

+

176 blocks: Document blocks to render 

+

177 page_size: Page size (width, height) in pixels 

+

178 document_id: Unique identifier for the document (for bookmarks/position) 

+

179 buffer_size: Number of pages to cache in each direction 

+

180 page_style: Custom page styling (uses default if None) 

+

181 bookmarks_dir: Directory to store bookmark files 

+

182 """ 

+

183 self.blocks = blocks 

+

184 self.page_size = page_size 

+

185 self.document_id = document_id 

+

186 

+

187 # Initialize page style 

+

188 if page_style is None: 

+

189 page_style = PageStyle() 

+

190 self.page_style = page_style 

+

191 

+

192 # Initialize core components 

+

193 self.renderer = BufferedPageRenderer(blocks, page_style, buffer_size, page_size) 

+

194 self.chapter_navigator = ChapterNavigator(blocks) 

+

195 self.bookmark_manager = BookmarkManager(document_id, bookmarks_dir) 

+

196 

+

197 # Current state 

+

198 self.current_position = RenderingPosition() 

+

199 self.font_scale = 1.0 

+

200 

+

201 # Cover page handling 

+

202 self._has_cover = self._detect_cover() 

+

203 self._on_cover_page = self._has_cover # Start on cover if one exists 

+

204 

+

205 # Page position history for fast backward navigation 

+

206 # List of (position, font_scale) tuples representing the start of each page visited 

+

207 self._page_history: List[Tuple[RenderingPosition, float]] = [] 

+

208 self._max_history_size = 50 # Keep last 50 page positions 

+

209 

+

210 # Load last reading position if available 

+

211 saved_position = self.bookmark_manager.load_reading_position() 

+

212 if saved_position: 

+

213 self.current_position = saved_position 

+

214 self._on_cover_page = False # If we have a saved position, we're past the cover 

+

215 

+

216 # Callbacks for UI updates 

+

217 self.position_changed_callback: Optional[Callable[[ 

+

218 RenderingPosition], None]] = None 

+

219 self.chapter_changed_callback: Optional[Callable[[ 

+

220 Optional[ChapterInfo]], None]] = None 

+

221 

+

222 def set_position_changed_callback( 

+

223 self, callback: Callable[[RenderingPosition], None]): 

+

224 """Set callback for position changes""" 

+

225 self.position_changed_callback = callback 

+

226 

+

227 def set_chapter_changed_callback( 

+

228 self, callback: Callable[[Optional[ChapterInfo]], None]): 

+

229 """Set callback for chapter changes""" 

+

230 self.chapter_changed_callback = callback 

+

231 

+

232 def _detect_cover(self) -> bool: 

+

233 """ 

+

234 Detect if the document has a cover page. 

+

235 

+

236 A cover is detected if: 

+

237 1. The first block is an Image block, OR 

+

238 2. The document has cover metadata (future enhancement) 

+

239 

+

240 Returns: 

+

241 True if a cover page should be rendered 

+

242 """ 

+

243 if not self.blocks: 

+

244 return False 

+

245 

+

246 # Check if first block is an image - treat it as a cover 

+

247 first_block = self.blocks[0] 

+

248 if isinstance(first_block, Image): 

+

249 return True 

+

250 

+

251 return False 

+

252 

+

253 def _render_cover_page(self) -> Page: 

+

254 """ 

+

255 Render a dedicated cover page. 

+

256 

+

257 The cover page displays the first image block (if it exists) 

+

258 using the standard image layouter with maximum dimensions to fill the page. 

+

259 

+

260 Returns: 

+

261 Rendered cover page 

+

262 """ 

+

263 # Create a new page for the cover 

+

264 page = Page(self.page_size, self.page_style) 

+

265 

+

266 if not self.blocks or not isinstance(self.blocks[0], Image): 266 ↛ 268line 266 didn't jump to line 268 because the condition on line 266 was never true

+

267 # No cover image, return blank page 

+

268 return page 

+

269 

+

270 cover_image_block = self.blocks[0] 

+

271 

+

272 # Use the image layouter to render the cover image 

+

273 # Use full page dimensions (minus borders/padding) for cover 

+

274 try: 

+

275 max_width = self.page_size[0] - 2 * self.page_style.border_width 

+

276 max_height = self.page_size[1] - 2 * self.page_style.border_width 

+

277 

+

278 # Layout the image on the page 

+

279 success = image_layouter( 

+

280 image=cover_image_block, 

+

281 page=page, 

+

282 max_width=max_width, 

+

283 max_height=max_height 

+

284 ) 

+

285 

+

286 if not success: 286 ↛ 293line 286 didn't jump to line 293 because the condition on line 286 was always true

+

287 print("Warning: Failed to layout cover image") 

+

288 

+

289 except Exception as e: 

+

290 # If image loading fails, just return the blank page 

+

291 print(f"Warning: Failed to load cover image: {e}") 

+

292 

+

293 return page 

+

294 

+

295 def _notify_position_changed(self): 

+

296 """Notify UI of position change""" 

+

297 if self.position_changed_callback: 

+

298 self.position_changed_callback(self.current_position) 

+

299 

+

300 # Check if chapter changed 

+

301 current_chapter = self.chapter_navigator.get_current_chapter( 

+

302 self.current_position) 

+

303 if self.chapter_changed_callback: 

+

304 self.chapter_changed_callback(current_chapter) 

+

305 

+

306 # Auto-save reading position 

+

307 self.bookmark_manager.save_reading_position(self.current_position) 

+

308 

+

309 def get_current_page(self) -> Page: 

+

310 """ 

+

311 Get the page at the current reading position. 

+

312 

+

313 If on the cover page, returns the rendered cover. 

+

314 Otherwise, returns the regular content page. 

+

315 

+

316 Returns: 

+

317 Rendered page 

+

318 """ 

+

319 # Check if we're on the cover page 

+

320 if self._on_cover_page and self._has_cover: 

+

321 return self._render_cover_page() 

+

322 

+

323 page, _ = self.renderer.render_page(self.current_position, self.font_scale) 

+

324 return page 

+

325 

+

326 def next_page(self) -> Optional[Page]: 

+

327 """ 

+

328 Advance to the next page. 

+

329 

+

330 If currently on the cover page, advances to the first content page. 

+

331 Otherwise, advances to the next content page. 

+

332 

+

333 Returns: 

+

334 Next page or None if at end of document 

+

335 """ 

+

336 # Special case: transitioning from cover to first content page 

+

337 if self._on_cover_page and self._has_cover: 

+

338 self._on_cover_page = False 

+

339 # If first block is an image (the cover), skip it and start from block 1 

+

340 if self.blocks and isinstance(self.blocks[0], Image): 340 ↛ 343line 340 didn't jump to line 343 because the condition on line 340 was always true

+

341 self.current_position = RenderingPosition(chapter_index=0, block_index=1) 

+

342 else: 

+

343 self.current_position = RenderingPosition() 

+

344 self._notify_position_changed() 

+

345 return self.get_current_page() 

+

346 

+

347 # Save current position to history before moving forward 

+

348 self._add_to_history(self.current_position, self.font_scale) 

+

349 

+

350 page, next_position = self.renderer.render_page( 

+

351 self.current_position, self.font_scale) 

+

352 

+

353 # Check if we made progress 

+

354 if next_position != self.current_position: 

+

355 self.current_position = next_position 

+

356 self._notify_position_changed() 

+

357 return self.get_current_page() 

+

358 

+

359 return None # At end of document 

+

360 

+

361 def previous_page(self) -> Optional[Page]: 

+

362 """ 

+

363 Go to the previous page. 

+

364 

+

365 Uses cached page history for instant navigation when available, 

+

366 falls back to iterative refinement algorithm when needed. 

+

367 Can navigate back to the cover page if it exists. 

+

368 

+

369 Returns: 

+

370 Previous page or None if at beginning of document (or on cover) 

+

371 """ 

+

372 # Special case: if at the beginning of content and there's a cover, go back to it 

+

373 if self._has_cover and self._is_at_beginning() and not self._on_cover_page: 

+

374 self._on_cover_page = True 

+

375 self._notify_position_changed() 

+

376 return self.get_current_page() 

+

377 

+

378 # Can't go before the cover 

+

379 if self._on_cover_page: 379 ↛ 380line 379 didn't jump to line 380 because the condition on line 379 was never true

+

380 return None 

+

381 

+

382 if self._is_at_beginning(): 

+

383 return None 

+

384 

+

385 # Fast path: Check if we have this position in history 

+

386 previous_position = self._get_from_history(self.current_position, self.font_scale) 

+

387 

+

388 if previous_position is not None: 

+

389 # Cache hit! Use the cached position for instant navigation 

+

390 self.current_position = previous_position 

+

391 self._notify_position_changed() 

+

392 return self.get_current_page() 

+

393 

+

394 # Slow path: Use backward rendering to find the previous page 

+

395 # This uses the iterative refinement algorithm we just fixed 

+

396 page, start_position = self.renderer.render_page_backward( 

+

397 self.current_position, self.font_scale) 

+

398 

+

399 if start_position != self.current_position: 399 ↛ 407line 399 didn't jump to line 407 because the condition on line 399 was always true

+

400 # Save this calculated position to history for future use 

+

401 self._add_to_history(start_position, self.font_scale) 

+

402 

+

403 self.current_position = start_position 

+

404 self._notify_position_changed() 

+

405 return page 

+

406 

+

407 return None # At beginning of document 

+

408 

+

409 def _is_at_beginning(self) -> bool: 

+

410 """ 

+

411 Check if we're at the beginning of the document content. 

+

412 

+

413 If a cover exists (first block is an Image), the beginning of content 

+

414 is at block_index=1. Otherwise, it's at block_index=0. 

+

415 """ 

+

416 # Determine the first content block index 

+

417 first_content_block = 1 if (self._has_cover and self.blocks and isinstance(self.blocks[0], Image)) else 0 

+

418 

+

419 return (self.current_position.chapter_index == 0 and 

+

420 self.current_position.block_index == first_content_block and 

+

421 self.current_position.word_index == 0) 

+

422 

+

423 def jump_to_position(self, position: RenderingPosition) -> Page: 

+

424 """ 

+

425 Jump to a specific position in the document. 

+

426 

+

427 Args: 

+

428 position: Position to jump to 

+

429 

+

430 Returns: 

+

431 Page at the new position 

+

432 """ 

+

433 self.current_position = position 

+

434 self._on_cover_page = False # Jumping to a position means we're past the cover 

+

435 self._notify_position_changed() 

+

436 return self.get_current_page() 

+

437 

+

438 def jump_to_chapter(self, chapter_title: str) -> Optional[Page]: 

+

439 """ 

+

440 Jump to a specific chapter by title. 

+

441 

+

442 Args: 

+

443 chapter_title: Title of the chapter to jump to 

+

444 

+

445 Returns: 

+

446 Page at chapter start or None if chapter not found 

+

447 """ 

+

448 position = self.chapter_navigator.get_chapter_position(chapter_title) 

+

449 if position: 

+

450 return self.jump_to_position(position) 

+

451 return None 

+

452 

+

453 def jump_to_chapter_index(self, chapter_index: int) -> Optional[Page]: 

+

454 """ 

+

455 Jump to a chapter by index. 

+

456 

+

457 Args: 

+

458 chapter_index: Index of the chapter (0-based) 

+

459 

+

460 Returns: 

+

461 Page at chapter start or None if index invalid 

+

462 """ 

+

463 chapters = self.chapter_navigator.chapters 

+

464 if 0 <= chapter_index < len(chapters): 

+

465 return self.jump_to_position(chapters[chapter_index].position) 

+

466 return None 

+

467 

+

468 def _add_to_history(self, position: RenderingPosition, font_scale: float): 

+

469 """ 

+

470 Add a page position to the navigation history. 

+

471 

+

472 Args: 

+

473 position: The page start position to remember 

+

474 font_scale: The font scale at this position 

+

475 """ 

+

476 # Only add if it's different from the last entry 

+

477 if not self._page_history or \ 

+

478 self._page_history[-1][0] != position or \ 

+

479 self._page_history[-1][1] != font_scale: 

+

480 

+

481 self._page_history.append((position.copy(), font_scale)) 

+

482 

+

483 # Trim history if it exceeds max size 

+

484 if len(self._page_history) > self._max_history_size: 484 ↛ 485line 484 didn't jump to line 485 because the condition on line 484 was never true

+

485 self._page_history.pop(0) 

+

486 

+

487 def _get_from_history( 

+

488 self, 

+

489 current_position: RenderingPosition, 

+

490 current_font_scale: float) -> Optional[RenderingPosition]: 

+

491 """ 

+

492 Get the previous page position from history. 

+

493 

+

494 Searches backward through history to find the last position that 

+

495 comes before the current position at the same font scale. 

+

496 

+

497 Args: 

+

498 current_position: Current page position 

+

499 current_font_scale: Current font scale 

+

500 

+

501 Returns: 

+

502 Previous page position or None if not found in history 

+

503 """ 

+

504 # Search backward through history 

+

505 for i in range(len(self._page_history) - 1, -1, -1): 

+

506 hist_position, hist_font_scale = self._page_history[i] 

+

507 

+

508 # Must match font scale 

+

509 if hist_font_scale != current_font_scale: 509 ↛ 510line 509 didn't jump to line 510 because the condition on line 509 was never true

+

510 continue 

+

511 

+

512 # Must be before current position 

+

513 if (hist_position.chapter_index < current_position.chapter_index or 

+

514 (hist_position.chapter_index == current_position.chapter_index and 

+

515 hist_position.block_index < current_position.block_index) or 

+

516 (hist_position.chapter_index == current_position.chapter_index and 

+

517 hist_position.block_index == current_position.block_index and 

+

518 hist_position.word_index < current_position.word_index)): 

+

519 

+

520 # Found a previous position - remove it and everything after from history 

+

521 # since we're navigating backward 

+

522 self._page_history = self._page_history[:i] 

+

523 return hist_position.copy() 

+

524 

+

525 return None 

+

526 

+

527 def _clear_history(self): 

+

528 """Clear the page navigation history.""" 

+

529 self._page_history.clear() 

+

530 

+

531 def set_font_scale(self, scale: float) -> Page: 

+

532 """ 

+

533 Change the font scale and re-render current page. 

+

534 

+

535 Clears page history since font changes invalidate all cached positions. 

+

536 

+

537 Args: 

+

538 scale: Font scaling factor (1.0 = normal, 2.0 = double size, etc.) 

+

539 

+

540 Returns: 

+

541 Re-rendered page with new font scale 

+

542 """ 

+

543 if scale != self.font_scale: 

+

544 self.font_scale = scale 

+

545 # Clear history since font scale changes invalidate all cached positions 

+

546 self._clear_history() 

+

547 # The renderer will handle cache invalidation 

+

548 

+

549 return self.get_current_page() 

+

550 

+

551 def get_font_scale(self) -> float: 

+

552 """Get the current font scale""" 

+

553 return self.font_scale 

+

554 

+

555 def set_font_family(self, family: Optional[BundledFont]) -> Page: 

+

556 """ 

+

557 Change the font family and re-render current page. 

+

558 

+

559 Switches all text in the document to use the specified bundled font family 

+

560 while preserving font weights, styles, sizes, and other attributes. 

+

561 Clears page history and cache since font changes invalidate all cached positions. 

+

562 

+

563 Args: 

+

564 family: Bundled font family to use (SANS, SERIF, MONOSPACE, or None for original fonts) 

+

565 

+

566 Returns: 

+

567 Re-rendered page with new font family 

+

568 

+

569 Example: 

+

570 >>> from pyWebLayout.style.fonts import BundledFont 

+

571 >>> manager.set_font_family(BundledFont.SERIF) # Switch to serif 

+

572 >>> manager.set_font_family(BundledFont.SANS) # Switch to sans 

+

573 >>> manager.set_font_family(None) # Restore original fonts 

+

574 """ 

+

575 # Update the renderer's font family 

+

576 self.renderer.set_font_family(family) 

+

577 

+

578 # Clear history since font changes invalidate all cached positions 

+

579 self._clear_history() 

+

580 

+

581 return self.get_current_page() 

+

582 

+

583 def get_font_family(self) -> Optional[BundledFont]: 

+

584 """ 

+

585 Get the current font family override. 

+

586 

+

587 Returns: 

+

588 Current font family (SANS, SERIF, MONOSPACE) or None if using original fonts 

+

589 """ 

+

590 return self.renderer.get_font_family() 

+

591 

+

592 def increase_line_spacing(self, amount: int = 2) -> Page: 

+

593 """ 

+

594 Increase line spacing and re-render current page. 

+

595 

+

596 Clears page history since spacing changes invalidate all cached positions. 

+

597 

+

598 Args: 

+

599 amount: Pixels to add to line spacing (default: 2) 

+

600 

+

601 Returns: 

+

602 Re-rendered page with increased line spacing 

+

603 """ 

+

604 self.page_style.line_spacing += amount 

+

605 self.renderer.page_style = self.page_style # Update renderer's reference 

+

606 self.renderer.buffer.invalidate_all() # Clear cache to force re-render 

+

607 self._clear_history() # Clear position history 

+

608 return self.get_current_page() 

+

609 

+

610 def decrease_line_spacing(self, amount: int = 2) -> Page: 

+

611 """ 

+

612 Decrease line spacing and re-render current page. 

+

613 

+

614 Clears page history since spacing changes invalidate all cached positions. 

+

615 

+

616 Args: 

+

617 amount: Pixels to remove from line spacing (default: 2) 

+

618 

+

619 Returns: 

+

620 Re-rendered page with decreased line spacing 

+

621 """ 

+

622 self.page_style.line_spacing = max(0, self.page_style.line_spacing - amount) 

+

623 self.renderer.page_style = self.page_style # Update renderer's reference 

+

624 self.renderer.buffer.invalidate_all() # Clear cache to force re-render 

+

625 self._clear_history() # Clear position history 

+

626 return self.get_current_page() 

+

627 

+

628 def increase_inter_block_spacing(self, amount: int = 5) -> Page: 

+

629 """ 

+

630 Increase spacing between blocks and re-render current page. 

+

631 

+

632 Clears page history since spacing changes invalidate all cached positions. 

+

633 

+

634 Args: 

+

635 amount: Pixels to add to inter-block spacing (default: 5) 

+

636 

+

637 Returns: 

+

638 Re-rendered page with increased block spacing 

+

639 """ 

+

640 self.page_style.inter_block_spacing += amount 

+

641 self.renderer.page_style = self.page_style # Update renderer's reference 

+

642 self.renderer.buffer.invalidate_all() # Clear cache to force re-render 

+

643 self._clear_history() # Clear position history 

+

644 return self.get_current_page() 

+

645 

+

646 def decrease_inter_block_spacing(self, amount: int = 5) -> Page: 

+

647 """ 

+

648 Decrease spacing between blocks and re-render current page. 

+

649 

+

650 Clears page history since spacing changes invalidate all cached positions. 

+

651 

+

652 Args: 

+

653 amount: Pixels to remove from inter-block spacing (default: 5) 

+

654 

+

655 Returns: 

+

656 Re-rendered page with decreased block spacing 

+

657 """ 

+

658 self.page_style.inter_block_spacing = max( 

+

659 0, self.page_style.inter_block_spacing - amount) 

+

660 self.renderer.page_style = self.page_style # Update renderer's reference 

+

661 self.renderer.buffer.invalidate_all() # Clear cache to force re-render 

+

662 self._clear_history() # Clear position history 

+

663 return self.get_current_page() 

+

664 

+

665 def increase_word_spacing(self, amount: int = 2) -> Page: 

+

666 """ 

+

667 Increase spacing between words and re-render current page. 

+

668 

+

669 Clears page history since spacing changes invalidate all cached positions. 

+

670 

+

671 Args: 

+

672 amount: Pixels to add to word spacing (default: 2) 

+

673 

+

674 Returns: 

+

675 Re-rendered page with increased word spacing 

+

676 """ 

+

677 self.page_style.word_spacing += amount 

+

678 self.renderer.page_style = self.page_style # Update renderer's reference 

+

679 self.renderer.buffer.invalidate_all() # Clear cache to force re-render 

+

680 self._clear_history() # Clear position history 

+

681 return self.get_current_page() 

+

682 

+

683 def decrease_word_spacing(self, amount: int = 2) -> Page: 

+

684 """ 

+

685 Decrease spacing between words and re-render current page. 

+

686 

+

687 Clears page history since spacing changes invalidate all cached positions. 

+

688 

+

689 Args: 

+

690 amount: Pixels to remove from word spacing (default: 2) 

+

691 

+

692 Returns: 

+

693 Re-rendered page with decreased word spacing 

+

694 """ 

+

695 self.page_style.word_spacing = max(0, self.page_style.word_spacing - amount) 

+

696 self.renderer.page_style = self.page_style # Update renderer's reference 

+

697 self.renderer.buffer.invalidate_all() # Clear cache to force re-render 

+

698 self._clear_history() # Clear position history 

+

699 return self.get_current_page() 

+

700 

+

701 def get_table_of_contents( 

+

702 self) -> List[Tuple[str, HeadingLevel, RenderingPosition]]: 

+

703 """ 

+

704 Get the table of contents. 

+

705 

+

706 Returns: 

+

707 List of (title, level, position) tuples 

+

708 """ 

+

709 return self.chapter_navigator.get_table_of_contents() 

+

710 

+

711 def get_current_chapter(self) -> Optional[ChapterInfo]: 

+

712 """ 

+

713 Get information about the current chapter. 

+

714 

+

715 Returns: 

+

716 Current chapter info or None if no chapters 

+

717 """ 

+

718 return self.chapter_navigator.get_current_chapter(self.current_position) 

+

719 

+

720 def add_bookmark(self, name: str) -> bool: 

+

721 """ 

+

722 Add a bookmark at the current position. 

+

723 

+

724 Args: 

+

725 name: Bookmark name 

+

726 

+

727 Returns: 

+

728 True if bookmark was added successfully 

+

729 """ 

+

730 try: 

+

731 self.bookmark_manager.add_bookmark(name, self.current_position) 

+

732 return True 

+

733 except Exception: 

+

734 return False 

+

735 

+

736 def remove_bookmark(self, name: str) -> bool: 

+

737 """ 

+

738 Remove a bookmark. 

+

739 

+

740 Args: 

+

741 name: Bookmark name 

+

742 

+

743 Returns: 

+

744 True if bookmark was removed 

+

745 """ 

+

746 return self.bookmark_manager.remove_bookmark(name) 

+

747 

+

748 def jump_to_bookmark(self, name: str) -> Optional[Page]: 

+

749 """ 

+

750 Jump to a bookmark. 

+

751 

+

752 Args: 

+

753 name: Bookmark name 

+

754 

+

755 Returns: 

+

756 Page at bookmark position or None if bookmark not found 

+

757 """ 

+

758 position = self.bookmark_manager.get_bookmark(name) 

+

759 if position: 

+

760 return self.jump_to_position(position) 

+

761 return None 

+

762 

+

763 def list_bookmarks(self) -> List[Tuple[str, RenderingPosition]]: 

+

764 """ 

+

765 Get all bookmarks. 

+

766 

+

767 Returns: 

+

768 List of (name, position) tuples 

+

769 """ 

+

770 return self.bookmark_manager.list_bookmarks() 

+

771 

+

772 def get_reading_progress(self) -> float: 

+

773 """ 

+

774 Get reading progress as a percentage. 

+

775 

+

776 Returns: 

+

777 Progress from 0.0 to 1.0 

+

778 """ 

+

779 if not self.blocks: 

+

780 return 0.0 

+

781 

+

782 # Simple progress calculation based on block index 

+

783 # A more sophisticated version would consider word positions 

+

784 total_blocks = len(self.blocks) 

+

785 current_block = min(self.current_position.block_index, total_blocks - 1) 

+

786 

+

787 return current_block / max(1, total_blocks - 1) 

+

788 

+

789 def has_cover(self) -> bool: 

+

790 """ 

+

791 Check if the document has a cover page. 

+

792 

+

793 Returns: 

+

794 True if a cover page is available 

+

795 """ 

+

796 return self._has_cover 

+

797 

+

798 def is_on_cover(self) -> bool: 

+

799 """ 

+

800 Check if currently viewing the cover page. 

+

801 

+

802 Returns: 

+

803 True if on the cover page 

+

804 """ 

+

805 return self._on_cover_page 

+

806 

+

807 def jump_to_cover(self) -> Optional[Page]: 

+

808 """ 

+

809 Jump to the cover page if one exists. 

+

810 

+

811 Returns: 

+

812 Cover page or None if no cover exists 

+

813 """ 

+

814 if not self._has_cover: 814 ↛ 815line 814 didn't jump to line 815 because the condition on line 814 was never true

+

815 return None 

+

816 

+

817 self._on_cover_page = True 

+

818 self._notify_position_changed() 

+

819 return self.get_current_page() 

+

820 

+

821 def get_position_info(self) -> Dict[str, Any]: 

+

822 """ 

+

823 Get detailed information about the current position. 

+

824 

+

825 Returns: 

+

826 Dictionary with position details 

+

827 """ 

+

828 current_chapter = self.get_current_chapter() 

+

829 font_family = self.get_font_family() 

+

830 

+

831 return { 

+

832 'position': self.current_position.to_dict(), 

+

833 'on_cover': self._on_cover_page, 

+

834 'has_cover': self._has_cover, 

+

835 'chapter': { 

+

836 'title': current_chapter.title if current_chapter else None, 

+

837 'level': current_chapter.level if current_chapter else None, 

+

838 'index': current_chapter.block_index if current_chapter else None 

+

839 }, 

+

840 'progress': self.get_reading_progress(), 

+

841 'font_scale': self.font_scale, 

+

842 'font_family': font_family.value if font_family else None, 

+

843 'page_size': self.page_size 

+

844 } 

+

845 

+

846 def get_cache_stats(self) -> Dict[str, Any]: 

+

847 """ 

+

848 Get cache statistics for debugging/monitoring. 

+

849 

+

850 Returns: 

+

851 Dictionary with cache statistics 

+

852 """ 

+

853 return self.renderer.get_cache_stats() 

+

854 

+

855 def shutdown(self): 

+

856 """ 

+

857 Shutdown the ereader manager and clean up resources. 

+

858 Call this when the application is closing. 

+

859 """ 

+

860 # Save current position 

+

861 self.bookmark_manager.save_reading_position(self.current_position) 

+

862 

+

863 # Shutdown renderer and buffer 

+

864 self.renderer.shutdown() 

+

865 

+

866 def __del__(self): 

+

867 """Cleanup on destruction""" 

+

868 self.shutdown() 

+

869 

+

870 

+

871# Convenience function for quick setup 

+

872def create_ereader_manager(blocks: List[Block], 

+

873 page_size: Tuple[int, int], 

+

874 document_id: str = "default", 

+

875 **kwargs) -> EreaderLayoutManager: 

+

876 """ 

+

877 Convenience function to create an ereader manager with sensible defaults. 

+

878 

+

879 Args: 

+

880 blocks: Document blocks to render 

+

881 page_size: Page size (width, height) in pixels 

+

882 document_id: Unique identifier for the document 

+

883 **kwargs: Additional arguments passed to EreaderLayoutManager 

+

884 

+

885 Returns: 

+

886 Configured EreaderLayoutManager instance 

+

887 """ 

+

888 return EreaderLayoutManager(blocks, page_size, document_id, **kwargs) 

+
+ + + diff --git a/cov_info/htmlcov/z_427cc3035faf7633_page_buffer_py.html b/cov_info/htmlcov/z_427cc3035faf7633_page_buffer_py.html new file mode 100644 index 0000000..7d5a508 --- /dev/null +++ b/cov_info/htmlcov/z_427cc3035faf7633_page_buffer_py.html @@ -0,0 +1,619 @@ + + + + + Coverage for pyWebLayout/layout/page_buffer.py: 83% + + + + + +
+
+

+ Coverage for pyWebLayout/layout/page_buffer.py: + 83% +

+ +

+ 195 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Multi-process page buffering system for high-performance ereader navigation. 

+

3 

+

4This module provides intelligent page caching with background rendering using 

+

5multiprocessing to achieve sub-second page navigation performance. 

+

6""" 

+

7 

+

8from __future__ import annotations 

+

9from typing import Dict, Optional, List, Tuple, Any 

+

10from collections import OrderedDict 

+

11from concurrent.futures import ProcessPoolExecutor, Future 

+

12import threading 

+

13import pickle 

+

14 

+

15from .ereader_layout import RenderingPosition, BidirectionalLayouter, FontFamilyOverride 

+

16from pyWebLayout.concrete.page import Page 

+

17from pyWebLayout.abstract.block import Block 

+

18from pyWebLayout.style.page_style import PageStyle 

+

19from pyWebLayout.style.fonts import BundledFont 

+

20 

+

21 

+

22def _render_page_worker(args: Tuple[List[Block], 

+

23 PageStyle, 

+

24 RenderingPosition, 

+

25 float, 

+

26 bool, 

+

27 Optional[BundledFont]]) -> Tuple[RenderingPosition, 

+

28 bytes, 

+

29 RenderingPosition]: 

+

30 """ 

+

31 Worker function for multiprocess page rendering. 

+

32 

+

33 Args: 

+

34 args: Tuple of (blocks, page_style, position, font_scale, is_backward, font_family) 

+

35 

+

36 Returns: 

+

37 Tuple of (original_position, pickled_page, next_position) 

+

38 """ 

+

39 blocks, page_style, position, font_scale, is_backward, font_family = args 

+

40 

+

41 # Create font family override if specified 

+

42 font_family_override = FontFamilyOverride(font_family) if font_family else None 

+

43 

+

44 layouter = BidirectionalLayouter(blocks, page_style, font_family_override=font_family_override) 

+

45 

+

46 if is_backward: 

+

47 page, next_pos = layouter.render_page_backward(position, font_scale) 

+

48 else: 

+

49 page, next_pos = layouter.render_page_forward(position, font_scale) 

+

50 

+

51 # Serialize the page for inter-process communication 

+

52 pickled_page = pickle.dumps(page) 

+

53 

+

54 return position, pickled_page, next_pos 

+

55 

+

56 

+

57class PageBuffer: 

+

58 """ 

+

59 Intelligent page caching system with LRU eviction and background rendering. 

+

60 Maintains separate forward and backward buffers for optimal navigation performance. 

+

61 """ 

+

62 

+

63 def __init__(self, buffer_size: int = 5, max_workers: int = 4): 

+

64 """ 

+

65 Initialize the page buffer. 

+

66 

+

67 Args: 

+

68 buffer_size: Number of pages to cache in each direction 

+

69 max_workers: Maximum number of worker processes for background rendering 

+

70 """ 

+

71 self.buffer_size = buffer_size 

+

72 self.max_workers = max_workers 

+

73 

+

74 # LRU caches for forward and backward pages 

+

75 self.forward_buffer: OrderedDict[RenderingPosition, Page] = OrderedDict() 

+

76 self.backward_buffer: OrderedDict[RenderingPosition, Page] = OrderedDict() 

+

77 

+

78 # Position tracking for next/previous positions 

+

79 self.position_map: Dict[RenderingPosition, 

+

80 RenderingPosition] = {} # current -> next 

+

81 self.reverse_position_map: Dict[RenderingPosition, 

+

82 RenderingPosition] = {} # current -> previous 

+

83 

+

84 # Background rendering 

+

85 self.executor: Optional[ProcessPoolExecutor] = None 

+

86 self.pending_renders: Dict[RenderingPosition, Future] = {} 

+

87 self.render_lock = threading.Lock() 

+

88 

+

89 # Document state 

+

90 self.blocks: Optional[List[Block]] = None 

+

91 self.page_style: Optional[PageStyle] = None 

+

92 self.current_font_scale: float = 1.0 

+

93 self.current_font_family: Optional[BundledFont] = None 

+

94 

+

95 def initialize( 

+

96 self, 

+

97 blocks: List[Block], 

+

98 page_style: PageStyle, 

+

99 font_scale: float = 1.0, 

+

100 font_family: Optional[BundledFont] = None): 

+

101 """ 

+

102 Initialize the buffer with document blocks and page style. 

+

103 

+

104 Args: 

+

105 blocks: Document blocks to render 

+

106 page_style: Page styling configuration 

+

107 font_scale: Current font scaling factor 

+

108 font_family: Optional font family override 

+

109 """ 

+

110 self.blocks = blocks 

+

111 self.page_style = page_style 

+

112 self.current_font_scale = font_scale 

+

113 self.current_font_family = font_family 

+

114 

+

115 # Start the process pool 

+

116 if self.executor is None: 116 ↛ exitline 116 didn't return from function 'initialize' because the condition on line 116 was always true

+

117 self.executor = ProcessPoolExecutor(max_workers=self.max_workers) 

+

118 

+

119 def get_page(self, position: RenderingPosition) -> Optional[Page]: 

+

120 """ 

+

121 Get a cached page if available. 

+

122 

+

123 Args: 

+

124 position: Position to get page for 

+

125 

+

126 Returns: 

+

127 Cached page or None if not available 

+

128 """ 

+

129 # Check forward buffer first 

+

130 if position in self.forward_buffer: 

+

131 # Move to end (most recently used) 

+

132 page = self.forward_buffer.pop(position) 

+

133 self.forward_buffer[position] = page 

+

134 return page 

+

135 

+

136 # Check backward buffer 

+

137 if position in self.backward_buffer: 

+

138 # Move to end (most recently used) 

+

139 page = self.backward_buffer.pop(position) 

+

140 self.backward_buffer[position] = page 

+

141 return page 

+

142 

+

143 return None 

+

144 

+

145 def cache_page( 

+

146 self, 

+

147 position: RenderingPosition, 

+

148 page: Page, 

+

149 next_position: Optional[RenderingPosition] = None, 

+

150 is_backward: bool = False): 

+

151 """ 

+

152 Cache a rendered page with LRU eviction. 

+

153 

+

154 Args: 

+

155 position: Position of the page 

+

156 page: Rendered page to cache 

+

157 next_position: Position of the next page (for forward navigation) 

+

158 is_backward: Whether this is a backward-rendered page 

+

159 """ 

+

160 target_buffer = self.backward_buffer if is_backward else self.forward_buffer 

+

161 

+

162 # Add to cache 

+

163 target_buffer[position] = page 

+

164 

+

165 # Track position relationships 

+

166 if next_position: 166 ↛ 173line 166 didn't jump to line 173 because the condition on line 166 was always true

+

167 if is_backward: 

+

168 self.reverse_position_map[next_position] = position 

+

169 else: 

+

170 self.position_map[position] = next_position 

+

171 

+

172 # Evict oldest if buffer is full 

+

173 if len(target_buffer) > self.buffer_size: 

+

174 oldest_pos, _ = target_buffer.popitem(last=False) 

+

175 # Clean up position maps 

+

176 self.position_map.pop(oldest_pos, None) 

+

177 self.reverse_position_map.pop(oldest_pos, None) 

+

178 

+

179 def start_background_rendering( 

+

180 self, 

+

181 current_position: RenderingPosition, 

+

182 direction: str = 'forward'): 

+

183 """ 

+

184 Start background rendering of upcoming pages. 

+

185 

+

186 Args: 

+

187 current_position: Current reading position 

+

188 direction: 'forward', 'backward', or 'both' 

+

189 """ 

+

190 if not self.blocks or not self.page_style or not self.executor: 190 ↛ 191line 190 didn't jump to line 191 because the condition on line 190 was never true

+

191 return 

+

192 

+

193 with self.render_lock: 

+

194 if direction in ['forward', 'both']: 194 ↛ 197line 194 didn't jump to line 197 because the condition on line 194 was always true

+

195 self._queue_forward_renders(current_position) 

+

196 

+

197 if direction in ['backward', 'both']: 

+

198 self._queue_backward_renders(current_position) 

+

199 

+

200 def _queue_forward_renders(self, start_position: RenderingPosition): 

+

201 """Queue forward page renders starting from the given position""" 

+

202 current_pos = start_position 

+

203 

+

204 for i in range(self.buffer_size): 

+

205 # Skip if already cached or being rendered 

+

206 if current_pos in self.forward_buffer or current_pos in self.pending_renders: 

+

207 # Try to get next position from cache 

+

208 current_pos = self.position_map.get(current_pos) 

+

209 if not current_pos: 

+

210 break 

+

211 continue 

+

212 

+

213 # Queue render job 

+

214 args = ( 

+

215 self.blocks, 

+

216 self.page_style, 

+

217 current_pos, 

+

218 self.current_font_scale, 

+

219 False, 

+

220 self.current_font_family) 

+

221 future = self.executor.submit(_render_page_worker, args) 

+

222 self.pending_renders[current_pos] = future 

+

223 

+

224 # We don't know the next position yet, so we'll update it when the render 

+

225 # completes 

+

226 break 

+

227 

+

228 def _queue_backward_renders(self, start_position: RenderingPosition): 

+

229 """Queue backward page renders ending at the given position""" 

+

230 current_pos = start_position 

+

231 

+

232 for i in range(self.buffer_size): 232 ↛ exitline 232 didn't return from function '_queue_backward_renders' because the loop on line 232 didn't complete

+

233 # Skip if already cached or being rendered 

+

234 if current_pos in self.backward_buffer or current_pos in self.pending_renders: 

+

235 # Try to get previous position from cache 

+

236 current_pos = self.reverse_position_map.get(current_pos) 

+

237 if not current_pos: 

+

238 break 

+

239 continue 

+

240 

+

241 # Queue render job 

+

242 args = ( 

+

243 self.blocks, 

+

244 self.page_style, 

+

245 current_pos, 

+

246 self.current_font_scale, 

+

247 True, 

+

248 self.current_font_family) 

+

249 future = self.executor.submit(_render_page_worker, args) 

+

250 self.pending_renders[current_pos] = future 

+

251 

+

252 # We don't know the previous position yet, so we'll update it when the 

+

253 # render completes 

+

254 break 

+

255 

+

256 def check_completed_renders(self): 

+

257 """Check for completed background renders and cache the results""" 

+

258 if not self.pending_renders: 258 ↛ 259line 258 didn't jump to line 259 because the condition on line 258 was never true

+

259 return 

+

260 

+

261 completed = [] 

+

262 

+

263 with self.render_lock: 

+

264 for position, future in self.pending_renders.items(): 

+

265 if future.done(): 

+

266 try: 

+

267 original_pos, pickled_page, next_pos = future.result() 

+

268 

+

269 # Deserialize the page 

+

270 page = pickle.loads(pickled_page) 

+

271 

+

272 # Cache the page 

+

273 self.cache_page(original_pos, page, next_pos, is_backward=False) 

+

274 

+

275 completed.append(position) 

+

276 

+

277 except Exception as e: 

+

278 print(f"Background render failed for position {position}: {e}") 

+

279 completed.append(position) 

+

280 

+

281 # Remove completed renders 

+

282 for pos in completed: 

+

283 self.pending_renders.pop(pos, None) 

+

284 

+

285 def invalidate_all(self): 

+

286 """Clear all cached pages and cancel pending renders""" 

+

287 with self.render_lock: 

+

288 # Cancel pending renders 

+

289 for future in self.pending_renders.values(): 

+

290 future.cancel() 

+

291 self.pending_renders.clear() 

+

292 

+

293 # Clear caches 

+

294 self.forward_buffer.clear() 

+

295 self.backward_buffer.clear() 

+

296 self.position_map.clear() 

+

297 self.reverse_position_map.clear() 

+

298 

+

299 def set_font_scale(self, font_scale: float): 

+

300 """ 

+

301 Update font scale and invalidate cache. 

+

302 

+

303 Args: 

+

304 font_scale: New font scaling factor 

+

305 """ 

+

306 if font_scale != self.current_font_scale: 306 ↛ exitline 306 didn't return from function 'set_font_scale' because the condition on line 306 was always true

+

307 self.current_font_scale = font_scale 

+

308 self.invalidate_all() 

+

309 

+

310 def set_font_family(self, font_family: Optional[BundledFont]): 

+

311 """ 

+

312 Update font family and invalidate cache. 

+

313 

+

314 Args: 

+

315 font_family: New font family (None = use original fonts) 

+

316 """ 

+

317 if font_family != self.current_font_family: 

+

318 self.current_font_family = font_family 

+

319 self.invalidate_all() 

+

320 

+

321 def get_cache_stats(self) -> Dict[str, Any]: 

+

322 """Get cache statistics for debugging/monitoring""" 

+

323 return { 

+

324 'forward_buffer_size': len(self.forward_buffer), 

+

325 'backward_buffer_size': len(self.backward_buffer), 

+

326 'pending_renders': len(self.pending_renders), 

+

327 'position_mappings': len(self.position_map), 

+

328 'reverse_position_mappings': len(self.reverse_position_map), 

+

329 'current_font_scale': self.current_font_scale, 

+

330 'current_font_family': self.current_font_family.value if self.current_font_family else None 

+

331 } 

+

332 

+

333 def shutdown(self): 

+

334 """Shutdown the page buffer and clean up resources""" 

+

335 if self.executor: 

+

336 # Cancel pending renders 

+

337 with self.render_lock: 

+

338 for future in self.pending_renders.values(): 

+

339 future.cancel() 

+

340 

+

341 # Shutdown executor 

+

342 self.executor.shutdown(wait=True) 

+

343 self.executor = None 

+

344 

+

345 # Clear all caches 

+

346 self.invalidate_all() 

+

347 

+

348 def __del__(self): 

+

349 """Cleanup on destruction""" 

+

350 self.shutdown() 

+

351 

+

352 

+

353class BufferedPageRenderer: 

+

354 """ 

+

355 High-level interface for buffered page rendering with automatic background caching. 

+

356 """ 

+

357 

+

358 def __init__(self, 

+

359 blocks: List[Block], 

+

360 page_style: PageStyle, 

+

361 buffer_size: int = 5, 

+

362 page_size: Tuple[int, 

+

363 int] = (800, 

+

364 600), 

+

365 font_family: Optional[BundledFont] = None): 

+

366 """ 

+

367 Initialize the buffered renderer. 

+

368 

+

369 Args: 

+

370 blocks: Document blocks to render 

+

371 page_style: Page styling configuration 

+

372 buffer_size: Number of pages to cache in each direction 

+

373 page_size: Page size (width, height) in pixels 

+

374 font_family: Optional font family override 

+

375 """ 

+

376 # Create font family override if specified 

+

377 font_family_override = FontFamilyOverride(font_family) if font_family else None 

+

378 

+

379 self.layouter = BidirectionalLayouter(blocks, page_style, page_size, font_family_override=font_family_override) 

+

380 self.buffer = PageBuffer(buffer_size) 

+

381 self.buffer.initialize(blocks, page_style, font_family=font_family) 

+

382 self.page_size = page_size 

+

383 self.blocks = blocks 

+

384 self.page_style = page_style 

+

385 

+

386 self.current_position = RenderingPosition() 

+

387 self.font_scale = 1.0 

+

388 self.font_family = font_family 

+

389 

+

390 def render_page(self, position: RenderingPosition, 

+

391 font_scale: float = 1.0) -> Tuple[Page, RenderingPosition]: 

+

392 """ 

+

393 Render a page with intelligent caching. 

+

394 

+

395 Args: 

+

396 position: Position to render from 

+

397 font_scale: Font scaling factor 

+

398 

+

399 Returns: 

+

400 Tuple of (rendered_page, next_position) 

+

401 """ 

+

402 # Update font scale if changed 

+

403 if font_scale != self.font_scale: 

+

404 self.font_scale = font_scale 

+

405 self.buffer.set_font_scale(font_scale) 

+

406 

+

407 # Check cache first 

+

408 cached_page = self.buffer.get_page(position) 

+

409 if cached_page: 

+

410 # Get next position from position map 

+

411 next_pos = self.buffer.position_map.get(position) 

+

412 

+

413 # Only use cache if we have the forward position mapping 

+

414 # Otherwise, we need to compute it 

+

415 if next_pos is not None: 

+

416 # Start background rendering for upcoming pages 

+

417 self.buffer.start_background_rendering(position, 'forward') 

+

418 

+

419 return cached_page, next_pos 

+

420 

+

421 # Cache hit for the page, but we don't have the forward position 

+

422 # Fall through to compute it below 

+

423 

+

424 # Render the page directly 

+

425 page, next_pos = self.layouter.render_page_forward(position, font_scale) 

+

426 

+

427 # Cache the result 

+

428 self.buffer.cache_page(position, page, next_pos) 

+

429 

+

430 # Start background rendering 

+

431 self.buffer.start_background_rendering(position, 'both') 

+

432 

+

433 # Check for completed background renders 

+

434 self.buffer.check_completed_renders() 

+

435 

+

436 return page, next_pos 

+

437 

+

438 def render_page_backward(self, 

+

439 end_position: RenderingPosition, 

+

440 font_scale: float = 1.0) -> Tuple[Page, 

+

441 RenderingPosition]: 

+

442 """ 

+

443 Render a page ending at the given position with intelligent caching. 

+

444 

+

445 Args: 

+

446 end_position: Position where page should end 

+

447 font_scale: Font scaling factor 

+

448 

+

449 Returns: 

+

450 Tuple of (rendered_page, start_position) 

+

451 """ 

+

452 # Update font scale if changed 

+

453 if font_scale != self.font_scale: 453 ↛ 454line 453 didn't jump to line 454 because the condition on line 453 was never true

+

454 self.font_scale = font_scale 

+

455 self.buffer.set_font_scale(font_scale) 

+

456 

+

457 # Check cache first 

+

458 cached_page = self.buffer.get_page(end_position) 

+

459 if cached_page: 459 ↛ 461line 459 didn't jump to line 461 because the condition on line 459 was never true

+

460 # Get previous position from reverse position map 

+

461 prev_pos = self.buffer.reverse_position_map.get(end_position) 

+

462 

+

463 # Only use cache if we have the reverse position mapping 

+

464 # Otherwise, we need to compute it 

+

465 if prev_pos is not None: 

+

466 # Start background rendering for previous pages 

+

467 self.buffer.start_background_rendering(end_position, 'backward') 

+

468 

+

469 return cached_page, prev_pos 

+

470 

+

471 # Cache hit for the page, but we don't have the reverse position 

+

472 # Fall through to compute it below 

+

473 

+

474 # Render the page directly 

+

475 page, start_pos = self.layouter.render_page_backward(end_position, font_scale) 

+

476 

+

477 # Cache the result 

+

478 self.buffer.cache_page(start_pos, page, end_position, is_backward=True) 

+

479 

+

480 # Start background rendering 

+

481 self.buffer.start_background_rendering(end_position, 'both') 

+

482 

+

483 # Check for completed background renders 

+

484 self.buffer.check_completed_renders() 

+

485 

+

486 return page, start_pos 

+

487 

+

488 def set_font_family(self, font_family: Optional[BundledFont]): 

+

489 """ 

+

490 Change the font family and invalidate cache. 

+

491 

+

492 Args: 

+

493 font_family: New font family (None = use original fonts) 

+

494 """ 

+

495 if font_family != self.font_family: 

+

496 self.font_family = font_family 

+

497 

+

498 # Update buffer 

+

499 self.buffer.set_font_family(font_family) 

+

500 

+

501 # Recreate layouter with new font family override 

+

502 font_family_override = FontFamilyOverride(font_family) if font_family else None 

+

503 self.layouter = BidirectionalLayouter( 

+

504 self.blocks, 

+

505 self.page_style, 

+

506 self.page_size, 

+

507 font_family_override=font_family_override 

+

508 ) 

+

509 

+

510 def get_font_family(self) -> Optional[BundledFont]: 

+

511 """Get the current font family override""" 

+

512 return self.font_family 

+

513 

+

514 def get_cache_stats(self) -> Dict[str, Any]: 

+

515 """Get cache statistics""" 

+

516 return self.buffer.get_cache_stats() 

+

517 

+

518 def shutdown(self): 

+

519 """Shutdown the renderer and clean up resources""" 

+

520 self.buffer.shutdown() 

+
+ + + diff --git a/cov_info/htmlcov/z_427cc3035faf7633_table_optimizer_py.html b/cov_info/htmlcov/z_427cc3035faf7633_table_optimizer_py.html new file mode 100644 index 0000000..7218740 --- /dev/null +++ b/cov_info/htmlcov/z_427cc3035faf7633_table_optimizer_py.html @@ -0,0 +1,495 @@ + + + + + Coverage for pyWebLayout/layout/table_optimizer.py: 88% + + + + + +
+
+

+ Coverage for pyWebLayout/layout/table_optimizer.py: + 88% +

+ +

+ 151 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Table column width optimization for pyWebLayout. 

+

3 

+

4This module provides intelligent column width distribution for tables, 

+

5ensuring optimal space usage while respecting content constraints. 

+

6""" 

+

7 

+

8from typing import List, Tuple, Optional, Dict 

+

9from pyWebLayout.abstract.block import Table, TableRow 

+

10 

+

11 

+

12def optimize_table_layout(table: Table, 

+

13 available_width: int, 

+

14 sample_size: int = 5, 

+

15 style=None) -> List[int]: 

+

16 """ 

+

17 Optimize column widths for a table. 

+

18 

+

19 Strategy: 

+

20 1. Check for HTML width overrides (colspan, width attributes) 

+

21 2. Sample first ~5 rows to estimate column requirements (performance) 

+

22 3. Calculate minimum width for each column (longest unbreakable word) 

+

23 4. Calculate preferred width for each column (no wrapping) 

+

24 5. If total preferred fits: use preferred 

+

25 6. Otherwise: distribute available space proportionally 

+

26 7. Ensure no column < min_width 

+

27 

+

28 Note: Hyphenation threshold is controlled by Font.min_hyphenation_width, 

+

29 not passed as a parameter here to avoid duplication. 

+

30 

+

31 Args: 

+

32 table: The table to optimize 

+

33 available_width: Total width available 

+

34 sample_size: Number of rows to sample for measurement (default 5) 

+

35 style: Optional table style for border/padding calculations 

+

36 

+

37 Returns: 

+

38 List of optimized column widths 

+

39 """ 

+

40 from pyWebLayout.concrete.dynamic_page import DynamicPage 

+

41 

+

42 n_cols = get_column_count(table) 

+

43 if n_cols == 0: 

+

44 return [] 

+

45 

+

46 # Account for table borders/padding overhead 

+

47 if style: 

+

48 overhead = calculate_table_overhead(n_cols, style) 

+

49 available_for_content = available_width - overhead 

+

50 else: 

+

51 # Default border overhead 

+

52 border_width = 1 

+

53 overhead = border_width * (n_cols + 1) 

+

54 available_for_content = available_width - overhead 

+

55 

+

56 # Phase 0: Check for HTML width overrides 

+

57 html_widths = extract_html_column_widths(table) 

+

58 fixed_columns = {i: width for i, width in enumerate(html_widths) if width is not None} 

+

59 

+

60 # Phase 1: Sample rows and measure constraints for each column 

+

61 min_widths = [] # Minimum without breaking words (Font handles hyphenation) 

+

62 pref_widths = [] # Preferred (no wrapping) 

+

63 

+

64 # Sample first ~5 rows from each section (header, body, footer) 

+

65 sampled_rows = sample_table_rows(table, sample_size) 

+

66 

+

67 for col_idx in range(n_cols): 

+

68 # Check if this column has HTML width override 

+

69 if col_idx in fixed_columns: 69 ↛ 70line 69 didn't jump to line 70 because the condition on line 69 was never true

+

70 fixed_width = fixed_columns[col_idx] 

+

71 min_widths.append(fixed_width) 

+

72 pref_widths.append(fixed_width) 

+

73 continue 

+

74 

+

75 col_min = 50 # Absolute minimum 

+

76 col_pref = 50 

+

77 

+

78 # Check sampled cells in this column 

+

79 for row in sampled_rows: 

+

80 cells = list(row.cells()) 

+

81 if col_idx >= len(cells): 81 ↛ 82line 81 didn't jump to line 82 because the condition on line 81 was never true

+

82 continue 

+

83 

+

84 cell = cells[col_idx] 

+

85 

+

86 # Create a DynamicPage for this cell with no padding/borders 

+

87 # (we're just measuring content, not rendering a full page) 

+

88 from pyWebLayout.style.page_style import PageStyle 

+

89 measurement_style = PageStyle(padding=(0, 0, 0, 0), border_width=0) 

+

90 cell_page = DynamicPage(style=measurement_style) 

+

91 

+

92 # Add cell content to page 

+

93 layout_cell_content(cell_page, cell) 

+

94 

+

95 # Measure minimum width (Font's min_hyphenation_width controls breaking) 

+

96 # DynamicPage returns pure content width (no padding since we set it to 0) 

+

97 # TableRenderer will add cell padding later 

+

98 cell_min = cell_page.get_min_width() 

+

99 col_min = max(col_min, cell_min) 

+

100 

+

101 # Measure preferred width (no wrapping) 

+

102 cell_pref = cell_page.get_preferred_width() 

+

103 col_pref = max(col_pref, cell_pref) 

+

104 

+

105 min_widths.append(col_min) 

+

106 pref_widths.append(col_pref) 

+

107 

+

108 # Phase 2: Distribute width (respecting fixed columns) 

+

109 return distribute_column_widths( 

+

110 min_widths, 

+

111 pref_widths, 

+

112 available_for_content, 

+

113 fixed_columns 

+

114 ) 

+

115 

+

116 

+

117def layout_cell_content(page, cell): 

+

118 """ 

+

119 Layout cell content onto a DynamicPage. 

+

120 

+

121 This adds all blocks from the cell (paragraphs, images, etc.) 

+

122 as children of the page so they can be measured. 

+

123 

+

124 Args: 

+

125 page: DynamicPage to add content to 

+

126 cell: TableCell containing blocks 

+

127 """ 

+

128 from pyWebLayout.concrete.text import Line, Text 

+

129 from pyWebLayout.style.fonts import Font 

+

130 from pyWebLayout.style import FontWeight, Alignment 

+

131 from pyWebLayout.abstract.block import Paragraph, Heading 

+

132 from PIL import Image as PILImage, ImageDraw 

+

133 

+

134 # Default font for measurement 

+

135 font_size = 12 

+

136 font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" 

+

137 font = Font(font_path=font_path, font_size=font_size) 

+

138 

+

139 # Create a minimal draw context for Text measurement 

+

140 # (Text needs this for width calculation) 

+

141 dummy_img = PILImage.new('RGB', (1, 1)) 

+

142 dummy_draw = ImageDraw.Draw(dummy_img) 

+

143 

+

144 # Get all blocks from the cell 

+

145 for block in cell.blocks(): 

+

146 if isinstance(block, (Paragraph, Heading)): 146 ↛ 145line 146 didn't jump to line 145 because the condition on line 146 was always true

+

147 # Get words from the block 

+

148 word_items = block.words() if callable(block.words) else block.words 

+

149 words = list(word_items) 

+

150 

+

151 if not words: 151 ↛ 152line 151 didn't jump to line 152 because the condition on line 151 was never true

+

152 continue 

+

153 

+

154 # Create a line for measurement 

+

155 line = Line( 

+

156 spacing=(3, 6), # word spacing 

+

157 origin=(0, 0), 

+

158 size=(1000, 20), # Large size for measurement 

+

159 draw=dummy_draw, 

+

160 font=font, 

+

161 halign=Alignment.LEFT 

+

162 ) 

+

163 

+

164 # Add all words to estimate width 

+

165 for word_item in words: 

+

166 # Handle word tuples (index, word_obj) 

+

167 if isinstance(word_item, tuple) and len(word_item) >= 2: 167 ↛ 168line 167 didn't jump to line 168 because the condition on line 167 was never true

+

168 word_obj = word_item[1] 

+

169 else: 

+

170 word_obj = word_item 

+

171 

+

172 # Extract text from the word 

+

173 word_text = word_obj.text if hasattr(word_obj, 'text') else str(word_obj) 

+

174 

+

175 # Create Text object for the word 

+

176 # Text constructor: (text, style, draw) 

+

177 text_obj = Text( 

+

178 text=word_text, 

+

179 style=font, # Font is the style 

+

180 draw=dummy_draw 

+

181 ) 

+

182 

+

183 line._text_objects.append(text_obj) 

+

184 

+

185 # Add line to page 

+

186 page.add_child(line) 

+

187 

+

188 

+

189def get_column_count(table: Table) -> int: 

+

190 """ 

+

191 Get the number of columns in a table. 

+

192 

+

193 Args: 

+

194 table: The table to analyze 

+

195 

+

196 Returns: 

+

197 Number of columns 

+

198 """ 

+

199 all_rows = list(table.all_rows()) 

+

200 if not all_rows: 

+

201 return 0 

+

202 

+

203 # Get from first row 

+

204 first_row = all_rows[0][1] 

+

205 return first_row.cell_count 

+

206 

+

207 

+

208def sample_table_rows(table: Table, sample_size: int) -> List[TableRow]: 

+

209 """ 

+

210 Sample first ~sample_size rows from each table section. 

+

211 

+

212 Args: 

+

213 table: The table to sample 

+

214 sample_size: Number of rows to sample per section 

+

215 

+

216 Returns: 

+

217 List of sampled rows 

+

218 """ 

+

219 sampled = [] 

+

220 

+

221 for section in ["header", "body", "footer"]: 

+

222 section_rows = [row for sec, row in table.all_rows() if sec == section] 

+

223 # Take first sample_size rows (or fewer if section is smaller) 

+

224 sampled.extend(section_rows[:sample_size]) 

+

225 

+

226 return sampled 

+

227 

+

228 

+

229def extract_html_column_widths(table: Table) -> List[Optional[int]]: 

+

230 """ 

+

231 Extract column width overrides from HTML attributes. 

+

232 

+

233 Checks for: 

+

234 - <col width="100px"> elements 

+

235 - <td width="100px"> in first row 

+

236 - <th width="100px"> in header 

+

237 

+

238 Args: 

+

239 table: The table to check 

+

240 

+

241 Returns: 

+

242 List of widths (None for auto-layout columns) 

+

243 """ 

+

244 n_cols = get_column_count(table) 

+

245 widths = [None] * n_cols 

+

246 

+

247 # Check for <col> elements with width 

+

248 if hasattr(table, 'col_widths'): 248 ↛ 249line 248 didn't jump to line 249 because the condition on line 248 was never true

+

249 for i, width in enumerate(table.col_widths): 

+

250 if width is not None: 

+

251 widths[i] = parse_html_width(width) 

+

252 

+

253 # Check first row cells for width attributes 

+

254 all_rows = list(table.all_rows()) 

+

255 if all_rows: 255 ↛ 262line 255 didn't jump to line 262 because the condition on line 255 was always true

+

256 first_row = all_rows[0][1] 

+

257 cells = list(first_row.cells()) 

+

258 for i, cell in enumerate(cells): 

+

259 if i < len(widths) and hasattr(cell, 'width') and cell.width is not None: 

+

260 widths[i] = parse_html_width(cell.width) 

+

261 

+

262 return widths 

+

263 

+

264 

+

265def parse_html_width(width_value) -> Optional[int]: 

+

266 """ 

+

267 Parse HTML width value (e.g., "100px", "20%", "100"). 

+

268 

+

269 Args: 

+

270 width_value: HTML width attribute value 

+

271 

+

272 Returns: 

+

273 Width in pixels, or None if percentage/invalid 

+

274 """ 

+

275 if isinstance(width_value, int): 

+

276 return width_value 

+

277 

+

278 if isinstance(width_value, str): 278 ↛ 299line 278 didn't jump to line 299 because the condition on line 278 was always true

+

279 # Remove whitespace 

+

280 width_value = width_value.strip() 

+

281 

+

282 # Percentage widths not supported yet 

+

283 if '%' in width_value: 

+

284 return None 

+

285 

+

286 # Parse pixel values 

+

287 if width_value.endswith('px'): 

+

288 try: 

+

289 return int(width_value[:-2]) 

+

290 except ValueError: 

+

291 return None 

+

292 

+

293 # Plain number 

+

294 try: 

+

295 return int(width_value) 

+

296 except ValueError: 

+

297 return None 

+

298 

+

299 return None 

+

300 

+

301 

+

302def distribute_column_widths(min_widths: List[int], 

+

303 pref_widths: List[int], 

+

304 available_width: int, 

+

305 fixed_columns: Dict[int, int]) -> List[int]: 

+

306 """ 

+

307 Distribute width among columns, respecting fixed column widths. 

+

308 

+

309 Args: 

+

310 min_widths: Minimum width for each column 

+

311 pref_widths: Preferred width for each column 

+

312 available_width: Total width available 

+

313 fixed_columns: Dict mapping column index to fixed width 

+

314 

+

315 Returns: 

+

316 List of final column widths 

+

317 """ 

+

318 n_cols = len(min_widths) 

+

319 if n_cols == 0: 

+

320 return [] 

+

321 

+

322 # Calculate available space for flexible columns 

+

323 fixed_total = sum(fixed_columns.values()) 

+

324 flexible_available = available_width - fixed_total 

+

325 

+

326 # Get indices of flexible columns 

+

327 flexible_cols = [i for i in range(n_cols) if i not in fixed_columns] 

+

328 

+

329 if not flexible_cols: 

+

330 # All columns fixed - return as-is 

+

331 return [fixed_columns.get(i, min_widths[i]) for i in range(n_cols)] 

+

332 

+

333 # Calculate totals for flexible columns only 

+

334 flex_min_total = sum(min_widths[i] for i in flexible_cols) 

+

335 flex_pref_total = sum(pref_widths[i] for i in flexible_cols) 

+

336 

+

337 # Distribute space among flexible columns 

+

338 widths = [0] * n_cols 

+

339 

+

340 # Set fixed columns 

+

341 for i, width in fixed_columns.items(): 

+

342 widths[i] = width 

+

343 

+

344 # Distribute to flexible columns 

+

345 if flex_pref_total <= flexible_available: 

+

346 # Preferred widths fit - distribute remaining space proportionally 

+

347 extra_space = flexible_available - flex_pref_total 

+

348 

+

349 if extra_space > 0 and flex_pref_total > 0: 

+

350 # Distribute extra space proportionally based on preferred widths 

+

351 for i in flexible_cols: 

+

352 proportion = pref_widths[i] / flex_pref_total 

+

353 widths[i] = int(pref_widths[i] + (extra_space * proportion)) 

+

354 else: 

+

355 # No extra space, just use preferred widths 

+

356 for i in flexible_cols: 

+

357 widths[i] = pref_widths[i] 

+

358 elif flex_min_total > flexible_available: 

+

359 # Can't satisfy minimum - force it anyway (graceful degradation) 

+

360 for i in flexible_cols: 

+

361 widths[i] = min_widths[i] 

+

362 else: 

+

363 # Proportional distribution between min and pref 

+

364 extra_space = flexible_available - flex_min_total 

+

365 flex_pref_over_min = flex_pref_total - flex_min_total 

+

366 

+

367 for i in flexible_cols: 

+

368 if flex_pref_over_min > 0: 368 ↛ 374line 368 didn't jump to line 374 because the condition on line 368 was always true

+

369 pref_over_min = pref_widths[i] - min_widths[i] 

+

370 proportion = pref_over_min / flex_pref_over_min 

+

371 extra = extra_space * proportion 

+

372 widths[i] = int(min_widths[i] + extra) 

+

373 else: 

+

374 widths[i] = int(min_widths[i]) 

+

375 

+

376 return widths 

+

377 

+

378 

+

379def calculate_table_overhead(n_cols: int, style) -> int: 

+

380 """ 

+

381 Calculate the pixel overhead for table borders and spacing. 

+

382 

+

383 Args: 

+

384 n_cols: Number of columns 

+

385 style: TableStyle object 

+

386 

+

387 Returns: 

+

388 Total pixel overhead 

+

389 """ 

+

390 # Border on each side of each column + outer borders 

+

391 border_overhead = style.border_width * (n_cols + 1) 

+

392 

+

393 # Cell spacing if any 

+

394 spacing_overhead = style.cell_spacing * (n_cols - 1) if n_cols > 1 else 0 

+

395 

+

396 return border_overhead + spacing_overhead 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2___init___py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2___init___py.html new file mode 100644 index 0000000..3f4b332 --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2___init___py.html @@ -0,0 +1,124 @@ + + + + + Coverage for pyWebLayout/concrete/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/__init__.py: + 100% +

+ +

+ 7 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Concrete layer for the pyWebLayout library. 

+

3 

+

4This package contains concrete implementations that can be directly rendered. 

+

5""" 

+

6 

+

7from .text import Text, Line 

+

8from .box import Box 

+

9from .image import RenderableImage 

+

10from .page import Page 

+

11from pyWebLayout.abstract.block import Table, TableRow as Row, TableCell as Cell 

+

12from .functional import LinkText, ButtonText 

+

13 

+

14__all__ = [ 

+

15 'Text', 

+

16 'Line', 

+

17 'Box', 

+

18 'RenderableImage', 

+

19 'Page', 

+

20 'Table', 

+

21 'Row', 

+

22 'Cell', 

+

23 'LinkText', 

+

24 'ButtonText', 

+

25] 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_box_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_box_py.html new file mode 100644 index 0000000..8137ff2 --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_box_py.html @@ -0,0 +1,139 @@ + + + + + Coverage for pyWebLayout/concrete/box.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/box.py: + 100% +

+ +

+ 19 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from __future__ import annotations 

+

2import numpy as np 

+

3from PIL import Image 

+

4 

+

5from pyWebLayout.core.base import Renderable, Queriable 

+

6from pyWebLayout.core import Geometric 

+

7from pyWebLayout.style import Alignment 

+

8 

+

9 

+

10class Box(Geometric, Renderable, Queriable): 

+

11 """ 

+

12 A box with geometric properties (origin and size). 

+

13 

+

14 Uses Geometric mixin for origin and size management. 

+

15 """ 

+

16 

+

17 def __init__( 

+

18 self, 

+

19 origin, 

+

20 size, 

+

21 callback=None, 

+

22 sheet: Image = None, 

+

23 mode: bool = None, 

+

24 halign=Alignment.CENTER, 

+

25 valign=Alignment.CENTER): 

+

26 super().__init__(origin=origin, size=size) 

+

27 self._end = self._origin + self._size 

+

28 self._callback = callback 

+

29 self._sheet: Image = sheet 

+

30 if self._sheet is None: 

+

31 self._mode = mode 

+

32 else: 

+

33 self._mode = sheet.mode 

+

34 self._halign = halign 

+

35 self._valign = valign 

+

36 

+

37 # origin and size properties are provided by Geometric mixin 

+

38 

+

39 def in_shape(self, point): 

+

40 return np.all((point >= self._origin) & (point < self._end), axis=-1) 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_dynamic_page_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_dynamic_page_py.html new file mode 100644 index 0000000..13b80f6 --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_dynamic_page_py.html @@ -0,0 +1,517 @@ + + + + + Coverage for pyWebLayout/concrete/dynamic_page.py: 68% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/dynamic_page.py: + 68% +

+ +

+ 178 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2DynamicPage implementation for pyWebLayout. 

+

3 

+

4A DynamicPage is a page that dynamically sizes itself based on content and constraints. 

+

5Unlike a regular Page with fixed size, a DynamicPage measures its content first and 

+

6then layouts within the allocated space. 

+

7 

+

8Use cases: 

+

9- Table cells that need to fit content 

+

10- Containers that should grow with content 

+

11- Responsive layouts that adapt to constraints 

+

12""" 

+

13 

+

14from typing import Tuple, Optional, List 

+

15from dataclasses import dataclass 

+

16import numpy as np 

+

17from PIL import Image 

+

18 

+

19from pyWebLayout.concrete.page import Page 

+

20from pyWebLayout.style.page_style import PageStyle 

+

21from pyWebLayout.core.base import Renderable 

+

22 

+

23 

+

24@dataclass 

+

25class SizeConstraints: 

+

26 """Size constraints for dynamic layout.""" 

+

27 min_width: Optional[int] = None 

+

28 max_width: Optional[int] = None 

+

29 min_height: Optional[int] = None 

+

30 max_height: Optional[int] = None 

+

31 # Note: Hyphenation threshold is controlled by Font.min_hyphenation_width 

+

32 # Don't duplicate that logic here 

+

33 

+

34 

+

35class DynamicPage(Page): 

+

36 """ 

+

37 A page that dynamically sizes itself based on content and constraints. 

+

38 

+

39 The layout process has two phases: 

+

40 1. Measurement: Calculate intrinsic size needed for content 

+

41 2. Layout: Position content within allocated size 

+

42 

+

43 This allows containers (like tables) to optimize space allocation before rendering. 

+

44 """ 

+

45 

+

46 def __init__(self, 

+

47 constraints: Optional[SizeConstraints] = None, 

+

48 style: Optional[PageStyle] = None): 

+

49 """ 

+

50 Initialize a dynamic page. 

+

51 

+

52 Args: 

+

53 constraints: Optional size constraints (min/max width/height) 

+

54 style: The PageStyle defining borders, spacing, and appearance 

+

55 """ 

+

56 # Start with zero size - will be determined during measurement/layout 

+

57 super().__init__(size=(0, 0), style=style) 

+

58 self._constraints = constraints if constraints is not None else SizeConstraints() 

+

59 

+

60 # Measurement state 

+

61 self._is_measured = False 

+

62 self._intrinsic_size: Optional[Tuple[int, int]] = None 

+

63 self._min_width_cache: Optional[int] = None 

+

64 self._preferred_width_cache: Optional[int] = None 

+

65 self._content_height_cache: Optional[int] = None 

+

66 

+

67 # Pagination state 

+

68 self._render_offset = 0 # For partial rendering (pagination) 

+

69 self._is_laid_out = False 

+

70 

+

71 @property 

+

72 def constraints(self) -> SizeConstraints: 

+

73 """Get the size constraints for this page.""" 

+

74 return self._constraints 

+

75 

+

76 def measure(self, available_width: Optional[int] = None) -> Tuple[int, int]: 

+

77 """ 

+

78 Measure the intrinsic size needed for content. 

+

79 

+

80 This walks through all children and calculates how much space they need. 

+

81 The measurement respects constraints (min/max width/height). 

+

82 

+

83 Args: 

+

84 available_width: Optional width constraint for wrapping content 

+

85 

+

86 Returns: 

+

87 Tuple of (width, height) needed 

+

88 """ 

+

89 if self._is_measured and self._intrinsic_size is not None: 

+

90 return self._intrinsic_size 

+

91 

+

92 # Apply constraints to available width 

+

93 if available_width is not None: 

+

94 if self._constraints.max_width is not None: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true

+

95 available_width = min(available_width, self._constraints.max_width) 

+

96 if self._constraints.min_width is not None: 

+

97 available_width = max(available_width, self._constraints.min_width) 

+

98 

+

99 # Measure content 

+

100 # For now, walk through children and sum their sizes 

+

101 total_width = 0 

+

102 total_height = 0 

+

103 

+

104 for child in self._children: 104 ↛ 105line 104 didn't jump to line 105 because the loop on line 104 never started

+

105 if hasattr(child, 'measure'): 

+

106 # Child is also dynamic - ask it to measure 

+

107 child_size = child.measure(available_width) 

+

108 child_width, child_height = child_size 

+

109 else: 

+

110 # Child has fixed size 

+

111 child_width = child.size[0] if hasattr(child, 'size') else 0 

+

112 child_height = child.size[1] if hasattr(child, 'size') else 0 

+

113 

+

114 total_width = max(total_width, child_width) 

+

115 total_height += child_height 

+

116 

+

117 # Add page padding/borders 

+

118 total_width += self._style.total_horizontal_padding + self._style.total_border_width 

+

119 total_height += self._style.total_vertical_padding + self._style.total_border_width 

+

120 

+

121 # Apply constraints 

+

122 if self._constraints.min_width is not None: 

+

123 total_width = max(total_width, self._constraints.min_width) 

+

124 if self._constraints.max_width is not None: 124 ↛ 125line 124 didn't jump to line 125 because the condition on line 124 was never true

+

125 total_width = min(total_width, self._constraints.max_width) 

+

126 if self._constraints.min_height is not None: 

+

127 total_height = max(total_height, self._constraints.min_height) 

+

128 if self._constraints.max_height is not None: 128 ↛ 129line 128 didn't jump to line 129 because the condition on line 128 was never true

+

129 total_height = min(total_height, self._constraints.max_height) 

+

130 

+

131 self._intrinsic_size = (total_width, total_height) 

+

132 self._is_measured = True 

+

133 

+

134 return self._intrinsic_size 

+

135 

+

136 def get_min_width(self) -> int: 

+

137 """ 

+

138 Get minimum width needed to render content. 

+

139 

+

140 This finds the widest word/element that cannot be broken, 

+

141 using Font.min_hyphenation_width for hyphenation control. 

+

142 

+

143 Returns: 

+

144 Minimum width in pixels 

+

145 """ 

+

146 # Check cache 

+

147 if self._min_width_cache is not None: 147 ↛ 148line 147 didn't jump to line 148 because the condition on line 147 was never true

+

148 return self._min_width_cache 

+

149 

+

150 # Calculate minimum width based on content 

+

151 from pyWebLayout.concrete.text import Line, Text 

+

152 

+

153 min_width = 0 

+

154 

+

155 # Walk through children and find longest unbreakable segment 

+

156 for child in self._children: 

+

157 if isinstance(child, Line): 157 ↛ 170line 157 didn't jump to line 170 because the condition on line 157 was always true

+

158 # Check all words in the line 

+

159 # Font's min_hyphenation_width already controls breaking 

+

160 for text_obj in getattr(child, '_text_objects', []): 

+

161 if isinstance(text_obj, Text) and hasattr(text_obj, '_text'): 161 ↛ 160line 161 didn't jump to line 160 because the condition on line 161 was always true

+

162 word_text = text_obj._text 

+

163 # Text stores font in _style, not _font 

+

164 font = getattr(text_obj, '_style', None) 

+

165 

+

166 if font: 166 ↛ 160line 166 didn't jump to line 160 because the condition on line 166 was always true

+

167 # Just measure the word - Font handles hyphenation rules 

+

168 word_width = int(font.font.getlength(word_text)) 

+

169 min_width = max(min_width, word_width) 

+

170 elif hasattr(child, 'get_min_width'): 

+

171 # Child supports min width calculation 

+

172 child_min = child.get_min_width() 

+

173 min_width = max(min_width, child_min) 

+

174 elif hasattr(child, 'size'): 

+

175 # Use actual width 

+

176 min_width = max(min_width, child.size[0]) 

+

177 

+

178 # Add padding/borders 

+

179 min_width += self._style.total_horizontal_padding + self._style.total_border_width 

+

180 

+

181 # Apply minimum constraint 

+

182 if self._constraints.min_width is not None: 182 ↛ 183line 182 didn't jump to line 183 because the condition on line 182 was never true

+

183 min_width = max(min_width, self._constraints.min_width) 

+

184 

+

185 self._min_width_cache = min_width 

+

186 return min_width 

+

187 

+

188 def get_preferred_width(self) -> int: 

+

189 """ 

+

190 Get preferred width (no wrapping). 

+

191 

+

192 This returns the width needed to render all content without any 

+

193 line wrapping. 

+

194 

+

195 Returns: 

+

196 Preferred width in pixels 

+

197 """ 

+

198 # Check cache 

+

199 if self._preferred_width_cache is not None: 199 ↛ 200line 199 didn't jump to line 200 because the condition on line 199 was never true

+

200 return self._preferred_width_cache 

+

201 

+

202 # Calculate preferred width (no wrapping) 

+

203 from pyWebLayout.concrete.text import Line 

+

204 

+

205 pref_width = 0 

+

206 

+

207 for child in self._children: 

+

208 if isinstance(child, Line): 208 ↛ 227line 208 didn't jump to line 227 because the condition on line 208 was always true

+

209 # Get line width without wrapping (including spacing between words) 

+

210 text_objects = getattr(child, '_text_objects', []) 

+

211 if text_objects: 211 ↛ 207line 211 didn't jump to line 207 because the condition on line 211 was always true

+

212 line_width = 0 

+

213 for i, text_obj in enumerate(text_objects): 

+

214 if hasattr(text_obj, '_text') and hasattr(text_obj, '_style'): 214 ↛ 213line 214 didn't jump to line 213 because the condition on line 214 was always true

+

215 # Text stores font in _style, not _font 

+

216 word_width = text_obj._style.font.getlength(text_obj._text) 

+

217 line_width += word_width 

+

218 

+

219 # Add spacing after word (except last word) 

+

220 if i < len(text_objects) - 1: 

+

221 # Get spacing from Line if available, otherwise use default 

+

222 spacing = getattr(child, '_spacing', (3, 6)) 

+

223 # Use minimum spacing for preferred width calculation 

+

224 line_width += spacing[0] if isinstance(spacing, tuple) else 3 

+

225 

+

226 pref_width = max(pref_width, line_width) 

+

227 elif hasattr(child, 'get_preferred_width'): 

+

228 child_pref = child.get_preferred_width() 

+

229 pref_width = max(pref_width, child_pref) 

+

230 elif hasattr(child, 'size'): 

+

231 # Use actual size 

+

232 pref_width = max(pref_width, child.size[0]) 

+

233 

+

234 # Add padding/borders 

+

235 pref_width += self._style.total_horizontal_padding + self._style.total_border_width 

+

236 

+

237 # Apply constraints 

+

238 if self._constraints.max_width is not None: 238 ↛ 239line 238 didn't jump to line 239 because the condition on line 238 was never true

+

239 pref_width = min(pref_width, self._constraints.max_width) 

+

240 if self._constraints.min_width is not None: 240 ↛ 241line 240 didn't jump to line 241 because the condition on line 240 was never true

+

241 pref_width = max(pref_width, self._constraints.min_width) 

+

242 

+

243 self._preferred_width_cache = pref_width 

+

244 return pref_width 

+

245 

+

246 def measure_content_height(self) -> int: 

+

247 """ 

+

248 Measure total height needed to render all content. 

+

249 

+

250 This is used for pagination to know how much content remains. 

+

251 

+

252 Returns: 

+

253 Total height in pixels 

+

254 """ 

+

255 # Check cache 

+

256 if self._content_height_cache is not None: 

+

257 return self._content_height_cache 

+

258 

+

259 total_height = 0 

+

260 

+

261 for child in self._children: 261 ↛ 262line 261 didn't jump to line 262 because the loop on line 261 never started

+

262 if hasattr(child, 'measure_content_height'): 

+

263 child_height = child.measure_content_height() 

+

264 elif hasattr(child, 'size'): 

+

265 child_height = child.size[1] 

+

266 else: 

+

267 child_height = 0 

+

268 

+

269 total_height += child_height 

+

270 

+

271 # Add padding/borders 

+

272 total_height += self._style.total_vertical_padding + self._style.total_border_width 

+

273 

+

274 self._content_height_cache = total_height 

+

275 return total_height 

+

276 

+

277 def layout(self, size: Tuple[int, int]): 

+

278 """ 

+

279 Layout content within the given size. 

+

280 

+

281 This is called after measurement to position children within 

+

282 the allocated space. 

+

283 

+

284 Args: 

+

285 size: The final size allocated to this page (width, height) 

+

286 """ 

+

287 # Set the page size 

+

288 self._size = size 

+

289 

+

290 # Position children sequentially 

+

291 # Use the same logic as Page but now we know our final size 

+

292 content_x = self._style.border_width + self._style.padding_left 

+

293 content_y = self._style.border_width + self._style.padding_top 

+

294 

+

295 self._current_y_offset = content_y 

+

296 self._is_first_line = True 

+

297 

+

298 # Children position themselves, we just track y_offset 

+

299 # The actual positioning happens when children render 

+

300 

+

301 self._is_laid_out = True 

+

302 self._dirty = True # Mark for re-render 

+

303 

+

304 def render(self) -> Image.Image: 

+

305 """ 

+

306 Render the page with all its children. 

+

307 

+

308 If not yet measured/laid out, use intrinsic sizing. 

+

309 

+

310 Returns: 

+

311 PIL Image containing the rendered page 

+

312 """ 

+

313 # Ensure we have a valid size 

+

314 if self._size[0] == 0 or self._size[1] == 0: 

+

315 if not self._is_measured: 315 ↛ 319line 315 didn't jump to line 319 because the condition on line 315 was always true

+

316 # Auto-measure with no constraints 

+

317 self.measure() 

+

318 

+

319 if self._intrinsic_size: 319 ↛ 323line 319 didn't jump to line 323 because the condition on line 319 was always true

+

320 self._size = self._intrinsic_size 

+

321 else: 

+

322 # Fallback to minimum size 

+

323 self._size = (100, 100) 

+

324 

+

325 # Use parent's render implementation 

+

326 return super().render() 

+

327 

+

328 # Pagination Support 

+

329 # ------------------ 

+

330 

+

331 def render_partial(self, available_height: int) -> int: 

+

332 """ 

+

333 Render as much content as fits in available_height. 

+

334 

+

335 This is used for pagination when a page needs to be split across 

+

336 multiple output pages. 

+

337 

+

338 Args: 

+

339 available_height: Height available on current page 

+

340 

+

341 Returns: 

+

342 Amount of content rendered (in pixels) 

+

343 """ 

+

344 # Calculate how many children fit in available height 

+

345 rendered_height = 0 

+

346 content_start_y = self._style.border_width + self._style.padding_top 

+

347 

+

348 for i, child in enumerate(self._children): 348 ↛ 350line 348 didn't jump to line 350 because the loop on line 348 never started

+

349 # Skip already rendered children 

+

350 if rendered_height < self._render_offset: 

+

351 if hasattr(child, 'size'): 

+

352 rendered_height += child.size[1] 

+

353 continue 

+

354 

+

355 # Check if this child fits 

+

356 child_height = child.size[1] if hasattr(child, 'size') else 0 

+

357 

+

358 if rendered_height + child_height <= available_height: 

+

359 # Child fits - render it 

+

360 if hasattr(child, 'render'): 

+

361 child.render() 

+

362 rendered_height += child_height 

+

363 else: 

+

364 # No more space 

+

365 break 

+

366 

+

367 # Update render offset for next call 

+

368 self._render_offset = rendered_height 

+

369 

+

370 return rendered_height 

+

371 

+

372 def has_more_content(self) -> bool: 

+

373 """ 

+

374 Check if there's unrendered content remaining. 

+

375 

+

376 Returns: 

+

377 True if more content needs to be rendered 

+

378 """ 

+

379 total_height = self.measure_content_height() 

+

380 return self._render_offset < total_height 

+

381 

+

382 def reset_pagination(self): 

+

383 """Reset pagination to render from beginning.""" 

+

384 self._render_offset = 0 

+

385 

+

386 def invalidate_caches(self): 

+

387 """Invalidate all measurement caches (call when children change).""" 

+

388 self._is_measured = False 

+

389 self._intrinsic_size = None 

+

390 self._min_width_cache = None 

+

391 self._preferred_width_cache = None 

+

392 self._content_height_cache = None 

+

393 self._is_laid_out = False 

+

394 

+

395 def add_child(self, child: Renderable) -> 'DynamicPage': 

+

396 """ 

+

397 Add a child and invalidate caches. 

+

398 

+

399 Args: 

+

400 child: The renderable object to add 

+

401 

+

402 Returns: 

+

403 Self for method chaining 

+

404 """ 

+

405 super().add_child(child) 

+

406 self.invalidate_caches() 

+

407 return self 

+

408 

+

409 def clear_children(self) -> 'DynamicPage': 

+

410 """ 

+

411 Remove all children and invalidate caches. 

+

412 

+

413 Returns: 

+

414 Self for method chaining 

+

415 """ 

+

416 super().clear_children() 

+

417 self.invalidate_caches() 

+

418 return self 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_functional_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_functional_py.html new file mode 100644 index 0000000..729566c --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_functional_py.html @@ -0,0 +1,560 @@ + + + + + Coverage for pyWebLayout/concrete/functional.py: 87% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/functional.py: + 87% +

+ +

+ 165 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from __future__ import annotations 

+

2from typing import Optional, Tuple 

+

3import numpy as np 

+

4from PIL import ImageDraw 

+

5 

+

6from pyWebLayout.core.base import Interactable, Queriable 

+

7from pyWebLayout.abstract.functional import Link, Button, FormField, LinkType, FormFieldType 

+

8from pyWebLayout.style import Font, TextDecoration 

+

9from .text import Text 

+

10 

+

11 

+

12class LinkText(Text, Interactable, Queriable): 

+

13 """ 

+

14 A Text subclass that can handle Link interactions. 

+

15 Combines text rendering with clickable link functionality. 

+

16 """ 

+

17 

+

18 def __init__(self, link: Link, text: str, font: Font, draw: ImageDraw.Draw, 

+

19 source=None, line=None, page=None): 

+

20 """ 

+

21 Initialize a linkable text object. 

+

22 

+

23 Args: 

+

24 link: The abstract Link object to handle interactions 

+

25 text: The text content to render 

+

26 font: The base font style 

+

27 draw: The drawing context 

+

28 source: Optional source object 

+

29 line: Optional line container 

+

30 page: Optional parent page (for dirty flag management) 

+

31 """ 

+

32 # Create link-styled font (underlined and colored based on link type) 

+

33 link_font = font.with_decoration(TextDecoration.UNDERLINE) 

+

34 if link.link_type == LinkType.INTERNAL: 

+

35 link_font = link_font.with_colour((0, 0, 200)) # Blue for internal links 

+

36 elif link.link_type == LinkType.EXTERNAL: 

+

37 link_font = link_font.with_colour( 

+

38 (0, 0, 180)) # Darker blue for external links 

+

39 elif link.link_type == LinkType.API: 

+

40 link_font = link_font.with_colour((150, 0, 0)) # Red for API links 

+

41 elif link.link_type == LinkType.FUNCTION: 41 ↛ 45line 41 didn't jump to line 45 because the condition on line 41 was always true

+

42 link_font = link_font.with_colour((0, 120, 0)) # Green for function links 

+

43 

+

44 # Initialize Text with the styled font 

+

45 Text.__init__(self, text, link_font, draw, source, line) 

+

46 

+

47 # Initialize Interactable with the link's execute method 

+

48 Interactable.__init__(self, link.execute) 

+

49 

+

50 # Store the link object and page reference 

+

51 self._link = link 

+

52 self._page = page 

+

53 self._hovered = False 

+

54 self._pressed = False 

+

55 

+

56 # Ensure _origin is initialized as numpy array 

+

57 if not hasattr(self, '_origin') or self._origin is None: 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true

+

58 self._origin = np.array([0, 0]) 

+

59 

+

60 @property 

+

61 def link(self) -> Link: 

+

62 """Get the associated Link object""" 

+

63 return self._link 

+

64 

+

65 def set_hovered(self, hovered: bool): 

+

66 """Set the hover state for visual feedback""" 

+

67 self._hovered = hovered 

+

68 self._mark_page_dirty() 

+

69 

+

70 def set_pressed(self, pressed: bool): 

+

71 """Set the pressed state for visual feedback""" 

+

72 self._pressed = pressed 

+

73 self._mark_page_dirty() 

+

74 

+

75 def _mark_page_dirty(self): 

+

76 """Mark the parent page as dirty if available""" 

+

77 if self._page and hasattr(self._page, 'mark_dirty'): 77 ↛ 78line 77 didn't jump to line 78 because the condition on line 77 was never true

+

78 self._page.mark_dirty() 

+

79 

+

80 def render(self, next_text: Optional['Text'] = None, spacing: int = 0): 

+

81 """ 

+

82 Render the link text with optional hover and pressed effects. 

+

83 

+

84 Args: 

+

85 next_text: The next Text object in the line (if any) 

+

86 spacing: The spacing to the next text object 

+

87 """ 

+

88 # Handle mock objects in tests 

+

89 size = self.size 

+

90 if hasattr(size, '__call__'): # It's a Mock 90 ↛ 92line 90 didn't jump to line 92 because the condition on line 90 was never true

+

91 # Use default size for tests 

+

92 size = np.array([100, 20]) 

+

93 else: 

+

94 size = np.array(size) 

+

95 

+

96 # Ensure origin is a numpy array 

+

97 origin = np.array( 

+

98 self._origin) if not isinstance( 

+

99 self._origin, 

+

100 np.ndarray) else self._origin 

+

101 

+

102 # Draw background based on state (before text is rendered) 

+

103 if self._pressed: 103 ↛ 105line 103 didn't jump to line 105 because the condition on line 103 was never true

+

104 # Pressed state - stronger, darker highlight 

+

105 bg_color = (180, 180, 255, 180) # Stronger blue with more opacity 

+

106 self._draw.rectangle([origin, origin + size], fill=bg_color) 

+

107 elif self._hovered: 107 ↛ 109line 107 didn't jump to line 109 because the condition on line 107 was never true

+

108 # Hover state - subtle highlight 

+

109 bg_color = (220, 220, 255, 100) # Light blue with alpha 

+

110 self._draw.rectangle([origin, origin + size], fill=bg_color) 

+

111 

+

112 # Call the parent Text render method with parameters 

+

113 super().render(next_text, spacing) 

+

114 

+

115 

+

116class ButtonText(Text, Interactable, Queriable): 

+

117 """ 

+

118 A Text subclass that can handle Button interactions. 

+

119 Renders text as a clickable button with visual states. 

+

120 """ 

+

121 

+

122 def __init__(self, button: Button, font: Font, draw: ImageDraw.Draw, 

+

123 padding: Tuple[int, int, int, int] = (4, 8, 4, 8), 

+

124 source=None, line=None, page=None): 

+

125 """ 

+

126 Initialize a button text object. 

+

127 

+

128 Args: 

+

129 button: The abstract Button object to handle interactions 

+

130 font: The base font style 

+

131 draw: The drawing context 

+

132 padding: Padding around the button text (top, right, bottom, left) 

+

133 source: Optional source object 

+

134 line: Optional line container 

+

135 page: Optional parent page (for dirty flag management) 

+

136 """ 

+

137 # Initialize Text with the button label 

+

138 Text.__init__(self, button.label, font, draw, source, line) 

+

139 

+

140 # Initialize Interactable with the button's execute method 

+

141 Interactable.__init__(self, button.execute) 

+

142 

+

143 # Store button properties 

+

144 self._button = button 

+

145 self._padding = padding 

+

146 self._page = page 

+

147 self._pressed = False 

+

148 self._hovered = False 

+

149 

+

150 # Recalculate dimensions to include padding 

+

151 # Use getattr to handle mock objects in tests 

+

152 text_width = getattr( 

+

153 self, '_width', 0) if not hasattr( 

+

154 self._width, '__call__') else 0 

+

155 self._padded_width = text_width + padding[1] + padding[3] 

+

156 self._padded_height = self._style.font_size + padding[0] + padding[2] 

+

157 

+

158 @property 

+

159 def button(self) -> Button: 

+

160 """Get the associated Button object""" 

+

161 return self._button 

+

162 

+

163 @property 

+

164 def size(self) -> np.ndarray: 

+

165 """Get the padded size of the button""" 

+

166 return np.array([self._padded_width, self._padded_height]) 

+

167 

+

168 def set_pressed(self, pressed: bool): 

+

169 """Set the pressed state""" 

+

170 self._pressed = pressed 

+

171 self._mark_page_dirty() 

+

172 

+

173 def set_hovered(self, hovered: bool): 

+

174 """Set the hover state""" 

+

175 self._hovered = hovered 

+

176 self._mark_page_dirty() 

+

177 

+

178 def set_page(self, page): 

+

179 """ 

+

180 Set the parent page reference for dirty flag management. 

+

181 

+

182 Args: 

+

183 page: The Page object containing this element 

+

184 """ 

+

185 self._page = page 

+

186 

+

187 def _mark_page_dirty(self): 

+

188 """Mark the parent page as dirty if available""" 

+

189 if self._page and hasattr(self._page, 'mark_dirty'): 189 ↛ 190line 189 didn't jump to line 190 because the condition on line 189 was never true

+

190 self._page.mark_dirty() 

+

191 

+

192 def render(self): 

+

193 """ 

+

194 Render the button with background, border, and text. 

+

195 """ 

+

196 # Determine button colors based on state 

+

197 if not self._button.enabled: 

+

198 # Disabled button 

+

199 bg_color = (200, 200, 200) 

+

200 border_color = (150, 150, 150) 

+

201 text_color = (100, 100, 100) 

+

202 elif self._pressed: 202 ↛ 204line 202 didn't jump to line 204 because the condition on line 202 was never true

+

203 # Pressed button 

+

204 bg_color = (70, 130, 180) 

+

205 border_color = (50, 100, 150) 

+

206 text_color = (255, 255, 255) 

+

207 elif self._hovered: 207 ↛ 209line 207 didn't jump to line 209 because the condition on line 207 was never true

+

208 # Hovered button 

+

209 bg_color = (100, 160, 220) 

+

210 border_color = (70, 130, 180) 

+

211 text_color = (255, 255, 255) 

+

212 else: 

+

213 # Normal button 

+

214 bg_color = (100, 150, 200) 

+

215 border_color = (70, 120, 170) 

+

216 text_color = (255, 255, 255) 

+

217 

+

218 # Draw button background with rounded corners 

+

219 # rounded_rectangle expects [x0, y0, x1, y1] format 

+

220 button_rect = [ 

+

221 int(self._origin[0]), 

+

222 int(self._origin[1]), 

+

223 int(self._origin[0] + self.size[0]), 

+

224 int(self._origin[1] + self.size[1]) 

+

225 ] 

+

226 self._draw.rounded_rectangle(button_rect, fill=bg_color, 

+

227 outline=border_color, width=1, radius=4) 

+

228 

+

229 # Update text color and render text centered within padding 

+

230 self._style = self._style.with_colour(text_color) 

+

231 text_x = self._origin[0] + self._padding[3] # left padding 

+

232 

+

233 # Center text vertically within button 

+

234 # Get font metrics to properly center the baseline 

+

235 ascent, descent = self._style.font.getmetrics() 

+

236 

+

237 # Total button height minus top and bottom padding gives us text area height 

+

238 text_area_height = self._padded_height - self._padding[0] - self._padding[2] 

+

239 

+

240 # Center the text visual height (ascent + descent) within the text area 

+

241 # The y position is where the baseline sits 

+

242 # Visual center = area_height/2, baseline should be at center + descent/2 

+

243 vertical_center = text_area_height / 2 

+

244 text_y = self._origin[1] + self._padding[0] + vertical_center + (descent / 2) 

+

245 

+

246 # Temporarily set origin for text rendering 

+

247 original_origin = self._origin.copy() 

+

248 self._origin = np.array([text_x, text_y]) 

+

249 

+

250 # Call parent render method for the text 

+

251 super().render() 

+

252 

+

253 # Restore original origin 

+

254 self._origin = original_origin 

+

255 

+

256 def in_object(self, point) -> bool: 

+

257 """ 

+

258 Check if a point is within this button. 

+

259 

+

260 Args: 

+

261 point: The coordinates to check 

+

262 

+

263 Returns: 

+

264 True if the point is within the button bounds (including padding) 

+

265 """ 

+

266 point_array = np.array(point) 

+

267 relative_point = point_array - self._origin 

+

268 

+

269 # Check if the point is within the padded button boundaries 

+

270 return (0 <= relative_point[0] < self._padded_width and 

+

271 0 <= relative_point[1] < self._padded_height) 

+

272 

+

273 

+

274class FormFieldText(Text, Interactable, Queriable): 

+

275 """ 

+

276 A Text subclass that can handle FormField interactions. 

+

277 Renders form field labels and input areas. 

+

278 """ 

+

279 

+

280 def __init__(self, field: FormField, font: Font, draw: ImageDraw.Draw, 

+

281 field_height: int = 24, source=None, line=None): 

+

282 """ 

+

283 Initialize a form field text object. 

+

284 

+

285 Args: 

+

286 field: The abstract FormField object to handle interactions 

+

287 font: The base font style for the label 

+

288 draw: The drawing context 

+

289 field_height: Height of the input field area 

+

290 source: Optional source object 

+

291 line: Optional line container 

+

292 """ 

+

293 # Initialize Text with the field label 

+

294 Text.__init__(self, field.label, font, draw, source, line) 

+

295 

+

296 # Initialize Interactable - form fields don't have direct callbacks 

+

297 # but can notify of focus/value changes 

+

298 Interactable.__init__(self, None) 

+

299 

+

300 # Store field properties 

+

301 self._field = field 

+

302 self._field_height = field_height 

+

303 self._focused = False 

+

304 

+

305 # Calculate total height (label + gap + field) 

+

306 self._total_height = self._style.font_size + 5 + field_height 

+

307 

+

308 # Field width should be at least as wide as the label 

+

309 # Use getattr to handle mock objects in tests 

+

310 text_width = getattr( 

+

311 self, '_width', 0) if not hasattr( 

+

312 self._width, '__call__') else 0 

+

313 self._field_width = max(text_width, 150) 

+

314 

+

315 @property 

+

316 def field(self) -> FormField: 

+

317 """Get the associated FormField object""" 

+

318 return self._field 

+

319 

+

320 @property 

+

321 def size(self) -> np.ndarray: 

+

322 """Get the total size including label and field""" 

+

323 return np.array([self._field_width, self._total_height]) 

+

324 

+

325 def set_focused(self, focused: bool): 

+

326 """Set the focus state""" 

+

327 self._focused = focused 

+

328 

+

329 def render(self): 

+

330 """ 

+

331 Render the form field with label and input area. 

+

332 """ 

+

333 # Render the label 

+

334 super().render() 

+

335 

+

336 # Calculate field position (below label with 5px gap) 

+

337 field_x = self._origin[0] 

+

338 field_y = self._origin[1] + self._style.font_size + 5 

+

339 

+

340 # Draw field background and border 

+

341 bg_color = (255, 255, 255) 

+

342 border_color = (100, 150, 200) if self._focused else (200, 200, 200) 

+

343 

+

344 field_rect = [(field_x, field_y), 

+

345 (field_x + self._field_width, field_y + self._field_height)] 

+

346 self._draw.rectangle(field_rect, fill=bg_color, outline=border_color, width=1) 

+

347 

+

348 # Render field value if present 

+

349 if self._field.value is not None: 

+

350 value_text = str(self._field.value) 

+

351 

+

352 # For password fields, mask the text 

+

353 if self._field.field_type == FormFieldType.PASSWORD: 

+

354 value_text = "•" * len(value_text) 

+

355 

+

356 # Create a temporary Text object for the value 

+

357 value_font = self._style.with_colour((0, 0, 0)) 

+

358 

+

359 # Position value text within field (with some padding) 

+

360 # Get font metrics to properly center the baseline 

+

361 ascent, descent = value_font.font.getmetrics() 

+

362 

+

363 # Center the text vertically within the field 

+

364 # The y coordinate is where the baseline sits (anchor="ls") 

+

365 vertical_center = self._field_height / 2 

+

366 value_x = field_x + 5 

+

367 value_y = field_y + vertical_center + (descent / 2) 

+

368 

+

369 # Draw the value text 

+

370 self._draw.text((value_x, value_y), value_text, 

+

371 font=value_font.font, fill=value_font.colour, anchor="ls") 

+

372 

+

373 def handle_click(self, point) -> bool: 

+

374 """ 

+

375 Handle clicks on the form field. 

+

376 

+

377 Args: 

+

378 point: The click coordinates relative to this field 

+

379 

+

380 Returns: 

+

381 True if the field was clicked and focused 

+

382 """ 

+

383 # Calculate field area 

+

384 field_y = self._style.font_size + 5 

+

385 

+

386 # Check if click is within the input field area (not just the label) 

+

387 if (0 <= point[0] <= self._field_width and 

+

388 field_y <= point[1] <= field_y + self._field_height): 

+

389 self.set_focused(True) 

+

390 return True 

+

391 

+

392 return False 

+

393 

+

394 def in_object(self, point) -> bool: 

+

395 """ 

+

396 Check if a point is within this form field (including label and input area). 

+

397 

+

398 Args: 

+

399 point: The coordinates to check 

+

400 

+

401 Returns: 

+

402 True if the point is within the field bounds 

+

403 """ 

+

404 point_array = np.array(point) 

+

405 relative_point = point_array - self._origin 

+

406 

+

407 # Check if the point is within the total field area 

+

408 return (0 <= relative_point[0] < self._field_width and 

+

409 0 <= relative_point[1] < self._total_height) 

+

410 

+

411 

+

412# Factory functions for creating functional text objects 

+

413def create_link_text(link: Link, text: str, font: Font, 

+

414 draw: ImageDraw.Draw) -> LinkText: 

+

415 """ 

+

416 Factory function to create a LinkText object. 

+

417 

+

418 Args: 

+

419 link: The Link object to associate with the text 

+

420 text: The text content to display 

+

421 font: The base font style 

+

422 draw: The drawing context 

+

423 

+

424 Returns: 

+

425 A LinkText object ready for rendering and interaction 

+

426 """ 

+

427 return LinkText(link, text, font, draw) 

+

428 

+

429 

+

430def create_button_text(button: Button, font: Font, draw: ImageDraw.Draw, 

+

431 padding: Tuple[int, int, int, int] = (4, 8, 4, 8)) -> ButtonText: 

+

432 """ 

+

433 Factory function to create a ButtonText object. 

+

434 

+

435 Args: 

+

436 button: The Button object to associate with the text 

+

437 font: The base font style 

+

438 draw: The drawing context 

+

439 padding: Padding around the button text 

+

440 

+

441 Returns: 

+

442 A ButtonText object ready for rendering and interaction 

+

443 """ 

+

444 return ButtonText(button, font, draw, padding) 

+

445 

+

446 

+

447def create_form_field_text(field: FormField, font: Font, draw: ImageDraw.Draw, 

+

448 field_height: int = 24) -> FormFieldText: 

+

449 """ 

+

450 Factory function to create a FormFieldText object. 

+

451 

+

452 Args: 

+

453 field: The FormField object to associate with the text 

+

454 font: The base font style for the label 

+

455 draw: The drawing context 

+

456 field_height: Height of the input field area 

+

457 

+

458 Returns: 

+

459 A FormFieldText object ready for rendering and interaction 

+

460 """ 

+

461 return FormFieldText(field, font, draw, field_height) 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_image_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_image_py.html new file mode 100644 index 0000000..785c97d --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_image_py.html @@ -0,0 +1,379 @@ + + + + + Coverage for pyWebLayout/concrete/image.py: 93% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/image.py: + 93% +

+ +

+ 134 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1import os 

+

2from typing import Optional 

+

3import numpy as np 

+

4from PIL import Image as PILImage, ImageDraw, ImageFont 

+

5from pyWebLayout.core.base import Renderable, Queriable 

+

6from pyWebLayout.abstract.block import Image as AbstractImage 

+

7from pyWebLayout.style import Alignment 

+

8 

+

9 

+

10class RenderableImage(Renderable, Queriable): 

+

11 """ 

+

12 A concrete implementation for rendering Image objects. 

+

13 """ 

+

14 

+

15 def __init__(self, image: AbstractImage, canvas: PILImage.Image, 

+

16 max_width: Optional[int] = None, max_height: Optional[int] = None, 

+

17 origin=None, size=None, callback=None, sheet=None, mode=None, 

+

18 halign=Alignment.CENTER, valign=Alignment.CENTER): 

+

19 """ 

+

20 Initialize a renderable image. 

+

21 

+

22 Args: 

+

23 image: The abstract Image object to render 

+

24 draw: The PIL ImageDraw object to draw on 

+

25 max_width: Maximum width constraint for the image 

+

26 max_height: Maximum height constraint for the image 

+

27 origin: Optional origin coordinates 

+

28 size: Optional size override 

+

29 callback: Optional callback function 

+

30 sheet: Optional sheet for rendering 

+

31 mode: Optional image mode 

+

32 halign: Horizontal alignment 

+

33 valign: Vertical alignment 

+

34 """ 

+

35 super().__init__() 

+

36 self._abstract_image = image 

+

37 self._canvas = canvas 

+

38 self._pil_image = None 

+

39 self._error_message = None 

+

40 self._halign = halign 

+

41 self._valign = valign 

+

42 

+

43 # Set origin as numpy array 

+

44 self._origin = np.array(origin) if origin is not None else np.array([0, 0]) 

+

45 

+

46 # Try to load the image 

+

47 self._load_image() 

+

48 

+

49 # Calculate the size if not provided 

+

50 if size is None: 

+

51 size = image.calculate_scaled_dimensions(max_width, max_height) 

+

52 # Ensure we have valid dimensions, fallback to defaults if None 

+

53 if size[0] is None or size[1] is None: 

+

54 size = (100, 100) # Default size when image dimensions are unavailable 

+

55 

+

56 # Ensure dimensions are positive (can be negative if calculated from insufficient space) 

+

57 size = (max(1, size[0]), max(1, size[1])) 

+

58 

+

59 # Set size as numpy array 

+

60 self._size = np.array(size) 

+

61 

+

62 @property 

+

63 def origin(self) -> np.ndarray: 

+

64 """Get the origin of the image""" 

+

65 return self._origin 

+

66 

+

67 @property 

+

68 def size(self) -> np.ndarray: 

+

69 """Get the size of the image""" 

+

70 return self._size 

+

71 

+

72 @property 

+

73 def width(self) -> int: 

+

74 """Get the width of the image""" 

+

75 return self._size[0] 

+

76 

+

77 def set_origin(self, origin: np.ndarray): 

+

78 """Set the origin of this image element""" 

+

79 self._origin = origin 

+

80 

+

81 def _load_image(self): 

+

82 """Load the image from the source path""" 

+

83 try: 

+

84 # Check if the image has already been loaded into memory 

+

85 if hasattr( 85 ↛ 88line 85 didn't jump to line 88 because the condition on line 85 was never true

+

86 self._abstract_image, 

+

87 '_loaded_image') and self._abstract_image._loaded_image is not None: 

+

88 self._pil_image = self._abstract_image._loaded_image 

+

89 return 

+

90 

+

91 source = self._abstract_image.source 

+

92 

+

93 # Handle different types of sources 

+

94 if os.path.isfile(source): 

+

95 # Local file 

+

96 self._pil_image = PILImage.open(source) 

+

97 self._abstract_image._loaded_image = self._pil_image 

+

98 elif source.startswith(('http://', 'https://')): 

+

99 # URL - requires requests library 

+

100 try: 

+

101 import requests 

+

102 from io import BytesIO 

+

103 

+

104 response = requests.get(source, stream=True) 

+

105 if response.status_code == 200: 

+

106 self._pil_image = PILImage.open(BytesIO(response.content)) 

+

107 self._abstract_image._loaded_image = self._pil_image 

+

108 else: 

+

109 self._error_message = f"Failed to load image: HTTP status {response.status_code}" 

+

110 except ImportError: 

+

111 self._error_message = "Requests library not available for URL loading" 

+

112 else: 

+

113 self._error_message = f"Unable to load image from source: {source}" 

+

114 

+

115 except Exception as e: 

+

116 self._error_message = f"Error loading image: {str(e)}" 

+

117 self._abstract_image._error = self._error_message 

+

118 

+

119 def render(self): 

+

120 """ 

+

121 Render the image directly into the canvas using the provided draw object. 

+

122 """ 

+

123 if self._pil_image: 

+

124 # Resize the image to fit the box while maintaining aspect ratio 

+

125 resized_image = self._resize_image() 

+

126 

+

127 # Calculate position based on alignment 

+

128 img_width, img_height = resized_image.size 

+

129 box_width, box_height = self._size 

+

130 

+

131 # Horizontal alignment 

+

132 if self._halign == Alignment.LEFT: 

+

133 x_offset = 0 

+

134 elif self._halign == Alignment.RIGHT: 

+

135 x_offset = box_width - img_width 

+

136 else: # CENTER is default 

+

137 x_offset = (box_width - img_width) // 2 

+

138 

+

139 # Vertical alignment 

+

140 if self._valign == Alignment.TOP: 

+

141 y_offset = 0 

+

142 elif self._valign == Alignment.BOTTOM: 

+

143 y_offset = box_height - img_height 

+

144 else: # CENTER is default 

+

145 y_offset = (box_height - img_height) // 2 

+

146 

+

147 # Calculate final position on canvas 

+

148 final_x = int(self._origin[0] + x_offset) 

+

149 final_y = int(self._origin[1] + y_offset) 

+

150 

+

151 # Get the underlying image from the draw object to paste onto 

+

152 

+

153 self._canvas.paste( 

+

154 resized_image, 

+

155 (final_x, 

+

156 final_y, 

+

157 final_x + 

+

158 img_width, 

+

159 final_y + 

+

160 img_height)) 

+

161 else: 

+

162 # Draw error placeholder 

+

163 self._draw_error_placeholder() 

+

164 

+

165 def _resize_image(self) -> PILImage.Image: 

+

166 """ 

+

167 Resize the image to fit within the box while maintaining aspect ratio. 

+

168 

+

169 Returns: 

+

170 A resized PIL Image 

+

171 """ 

+

172 if not self._pil_image: 

+

173 return PILImage.new('RGBA', tuple(self._size), (200, 200, 200, 100)) 

+

174 

+

175 # Get the target dimensions 

+

176 target_width, target_height = self._size 

+

177 

+

178 # Ensure target dimensions are positive 

+

179 target_width = max(1, int(target_width)) 

+

180 target_height = max(1, int(target_height)) 

+

181 

+

182 # Get the original dimensions 

+

183 orig_width, orig_height = self._pil_image.size 

+

184 

+

185 # Calculate the scaling factor to maintain aspect ratio 

+

186 width_ratio = target_width / orig_width 

+

187 height_ratio = target_height / orig_height 

+

188 

+

189 # Use the smaller ratio to ensure the image fits within the box 

+

190 ratio = min(width_ratio, height_ratio) 

+

191 

+

192 # Calculate new dimensions 

+

193 new_width = max(1, int(orig_width * ratio)) 

+

194 new_height = max(1, int(orig_height * ratio)) 

+

195 

+

196 # Resize the image 

+

197 if self._pil_image.mode == 'RGBA': 197 ↛ 198line 197 didn't jump to line 198 because the condition on line 197 was never true

+

198 resized = self._pil_image.resize((new_width, new_height), PILImage.LANCZOS) 

+

199 else: 

+

200 # Convert to RGBA if needed 

+

201 resized = self._pil_image.convert('RGBA').resize( 

+

202 (new_width, new_height), PILImage.LANCZOS) 

+

203 

+

204 return resized 

+

205 

+

206 def _draw_error_placeholder(self): 

+

207 """ 

+

208 Draw a placeholder for when the image can't be loaded. 

+

209 """ 

+

210 # Calculate the rectangle coordinates with origin offset 

+

211 x1 = int(self._origin[0]) 

+

212 y1 = int(self._origin[1]) 

+

213 x2 = int(self._origin[0] + self._size[0]) 

+

214 y2 = int(self._origin[1] + self._size[1]) 

+

215 

+

216 self._draw = ImageDraw.Draw(self._canvas) 

+

217 # Draw a gray box with a border 

+

218 self._draw.rectangle([(x1, y1), (x2, y2)], fill=( 

+

219 240, 240, 240), outline=(180, 180, 180), width=2) 

+

220 

+

221 # Draw an X across the box 

+

222 self._draw.line([(x1, y1), (x2, y2)], fill=(180, 180, 180), width=2) 

+

223 self._draw.line([(x1, y2), (x2, y1)], fill=(180, 180, 180), width=2) 

+

224 

+

225 # Add error text if available 

+

226 if self._error_message: 226 ↛ exitline 226 didn't return from function '_draw_error_placeholder' because the condition on line 226 was always true

+

227 try: 

+

228 # Try to use a basic font 

+

229 font = ImageFont.load_default() 

+

230 

+

231 # Draw the error message, wrapped to fit 

+

232 error_text = "Error: " + self._error_message 

+

233 

+

234 # Simple text wrapping - split by words and add lines 

+

235 words = error_text.split() 

+

236 lines = [] 

+

237 current_line = "" 

+

238 

+

239 for word in words: 

+

240 test_line = current_line + " " + word if current_line else word 

+

241 text_bbox = self._draw.textbbox((0, 0), test_line, font=font) 

+

242 text_width = text_bbox[2] - text_bbox[0] 

+

243 

+

244 if text_width <= self._size[0] - 20: # 10px padding on each side 

+

245 current_line = test_line 

+

246 else: 

+

247 lines.append(current_line) 

+

248 current_line = word 

+

249 

+

250 if current_line: 250 ↛ 254line 250 didn't jump to line 254 because the condition on line 250 was always true

+

251 lines.append(current_line) 

+

252 

+

253 # Draw each line 

+

254 y_pos = y1 + 10 

+

255 for line in lines: 

+

256 text_bbox = self._draw.textbbox((0, 0), line, font=font) 

+

257 text_width = text_bbox[2] - text_bbox[0] 

+

258 text_height = text_bbox[3] - text_bbox[1] 

+

259 

+

260 # Center the text horizontally 

+

261 x_pos = x1 + (self._size[0] - text_width) // 2 

+

262 

+

263 # Draw the text 

+

264 self._draw.text((x_pos, y_pos), line, fill=(80, 80, 80), font=font) 

+

265 

+

266 # Move to the next line 

+

267 y_pos += text_height + 2 

+

268 

+

269 except Exception: 

+

270 # If text rendering fails, just draw a generic error indicator 

+

271 pass 

+

272 

+

273 def in_object(self, point): 

+

274 """Check if a point is within this image""" 

+

275 point_array = np.array(point) 

+

276 relative_point = point_array - self._origin 

+

277 

+

278 # Check if the point is within the image boundaries 

+

279 return (0 <= relative_point[0] < self._size[0] and 

+

280 0 <= relative_point[1] < self._size[1]) 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_interaction_handler_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_interaction_handler_py.html new file mode 100644 index 0000000..c85afe1 --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_interaction_handler_py.html @@ -0,0 +1,409 @@ + + + + + Coverage for pyWebLayout/concrete/interaction_handler.py: 0% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/interaction_handler.py: + 0% +

+ +

+ 99 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Interaction handler for managing button/link press-release lifecycle with visual feedback. 

+

3 

+

4This module provides utilities for handling interactive element states and rendering 

+

5frames at different stages of interaction (pressed, released). 

+

6""" 

+

7 

+

8from typing import Optional, Tuple, Callable, Any 

+

9from PIL import Image 

+

10import time 

+

11import numpy as np 

+

12 

+

13from pyWebLayout.concrete.functional import LinkText, ButtonText 

+

14from pyWebLayout.concrete.page import Page 

+

15 

+

16 

+

17class InteractionHandler: 

+

18 """ 

+

19 Manages the press-release lifecycle for interactive elements. 

+

20 

+

21 This class handles the timing and state management needed to show 

+

22 visual feedback when buttons or links are clicked. It can generate 

+

23 multiple rendered frames showing the pressed and released states. 

+

24 

+

25 Usage patterns: 

+

26 

+

27 Pattern A - Simple one-shot with automatic frames: 

+

28 handler = InteractionHandler(page) 

+

29 frames = handler.execute_with_feedback(button_element, point) 

+

30 # Returns: [pressed_frame, released_frame] 

+

31 # Show frames in sequence with brief delay 

+

32 

+

33 Pattern B - Manual state management for custom event loops: 

+

34 handler = InteractionHandler(page) 

+

35 handler.set_pressed_state(button_element, True) 

+

36 pressed_frame = handler.render_current_state() 

+

37 # ... show frame, wait, execute action ... 

+

38 handler.set_pressed_state(button_element, False) 

+

39 released_frame = handler.render_current_state() 

+

40 """ 

+

41 

+

42 def __init__(self, page: Page, press_duration_ms: int = 150): 

+

43 """ 

+

44 Initialize the interaction handler. 

+

45 

+

46 Args: 

+

47 page: The Page object containing the interactive elements 

+

48 press_duration_ms: How long to show the pressed state (default: 150ms) 

+

49 """ 

+

50 self._page = page 

+

51 self._press_duration_ms = press_duration_ms 

+

52 

+

53 def set_pressed_state(self, element, pressed: bool): 

+

54 """ 

+

55 Set the pressed state of an interactive element. 

+

56 

+

57 Args: 

+

58 element: A LinkText or ButtonText object 

+

59 pressed: True to show pressed, False to show released 

+

60 """ 

+

61 if isinstance(element, (LinkText, ButtonText)): 

+

62 # Ensure element has page reference for dirty flag 

+

63 if not hasattr(element, '_page') or element._page is None: 

+

64 element.set_page(self._page) 

+

65 element.set_pressed(pressed) 

+

66 else: 

+

67 raise TypeError( 

+

68 f"Element must be LinkText or ButtonText, got {type(element)}") 

+

69 

+

70 def set_hovered_state(self, element, hovered: bool): 

+

71 """ 

+

72 Set the hovered state of an interactive element. 

+

73 

+

74 Args: 

+

75 element: A LinkText or ButtonText object 

+

76 hovered: True to show hovered, False for normal 

+

77 """ 

+

78 if isinstance(element, (LinkText, ButtonText)): 

+

79 # Ensure element has page reference for dirty flag 

+

80 if not hasattr(element, '_page') or element._page is None: 

+

81 element.set_page(self._page) 

+

82 element.set_hovered(hovered) 

+

83 else: 

+

84 raise TypeError( 

+

85 f"Element must be LinkText or ButtonText, got {type(element)}") 

+

86 

+

87 def render_current_state(self) -> Image.Image: 

+

88 """ 

+

89 Render the page with current element states. 

+

90 

+

91 Returns: 

+

92 PIL Image of the rendered page 

+

93 """ 

+

94 return self._page.render() 

+

95 

+

96 def execute_with_feedback( 

+

97 self, 

+

98 element, 

+

99 point: Optional[np.ndarray] = None, 

+

100 callback: Optional[Callable] = None) -> Tuple[Image.Image, Image.Image, Any]: 

+

101 """ 

+

102 Execute an interaction with visual feedback at each stage. 

+

103 

+

104 This is the high-level "all-in-one" method that: 

+

105 1. Sets pressed state and renders 

+

106 2. Waits for press_duration_ms 

+

107 3. Executes the element's callback (or provided callback) 

+

108 4. Sets released state and renders 

+

109 

+

110 Args: 

+

111 element: A LinkText or ButtonText object 

+

112 point: Optional point where interaction occurred 

+

113 callback: Optional custom callback (overrides element's callback) 

+

114 

+

115 Returns: 

+

116 Tuple of (pressed_frame, released_frame, callback_result) 

+

117 """ 

+

118 # Step 1: Render pressed state 

+

119 self.set_pressed_state(element, True) 

+

120 pressed_frame = self.render_current_state() 

+

121 

+

122 # Step 2: Wait for visual feedback duration 

+

123 time.sleep(self._press_duration_ms / 1000.0) 

+

124 

+

125 # Step 3: Execute callback 

+

126 callback_result = None 

+

127 if callback: 

+

128 callback_result = callback(point) if point is not None else callback() 

+

129 elif hasattr(element, 'interact'): 

+

130 callback_result = element.interact(point) 

+

131 

+

132 # Step 4: Render released state 

+

133 self.set_pressed_state(element, False) 

+

134 released_frame = self.render_current_state() 

+

135 

+

136 return pressed_frame, released_frame, callback_result 

+

137 

+

138 def execute_async_with_feedback( 

+

139 self, 

+

140 element, 

+

141 point: Optional[np.ndarray] = None) -> Tuple[Image.Image, Callable, Image.Image]: 

+

142 """ 

+

143 Execute an interaction with visual feedback, returning frames immediately 

+

144 without blocking. 

+

145 

+

146 This method returns the frames and a callback to execute later, allowing 

+

147 the caller to control when the action actually happens. 

+

148 

+

149 Args: 

+

150 element: A LinkText or ButtonText object 

+

151 point: Optional point where interaction occurred 

+

152 

+

153 Returns: 

+

154 Tuple of (pressed_frame, execute_callback, released_frame) 

+

155 where execute_callback is a function that will execute the interaction 

+

156 """ 

+

157 # Render pressed state 

+

158 self.set_pressed_state(element, True) 

+

159 pressed_frame = self.render_current_state() 

+

160 

+

161 # Create callback that will execute the interaction and reset state 

+

162 def execute_callback(): 

+

163 result = None 

+

164 if hasattr(element, 'interact'): 

+

165 result = element.interact(point) 

+

166 self.set_pressed_state(element, False) 

+

167 return result 

+

168 

+

169 # Pre-render the released state (element state is still pressed) 

+

170 # We'll return this frame but the caller controls when to show it 

+

171 self.set_pressed_state(element, False) 

+

172 released_frame = self.render_current_state() 

+

173 

+

174 # Reset back to pressed for consistency 

+

175 # (caller will call execute_callback which sets to False) 

+

176 self.set_pressed_state(element, True) 

+

177 

+

178 return pressed_frame, execute_callback, released_frame 

+

179 

+

180 

+

181class InteractionStateManager: 

+

182 """ 

+

183 Manages interaction states for multiple elements on a page. 

+

184 

+

185 Useful for applications that need to track hover/press states 

+

186 across many interactive elements simultaneously. 

+

187 """ 

+

188 

+

189 def __init__(self, page: Page): 

+

190 """ 

+

191 Initialize the state manager. 

+

192 

+

193 Args: 

+

194 page: The Page object containing interactive elements 

+

195 """ 

+

196 self._page = page 

+

197 self._hovered_element = None 

+

198 self._pressed_element = None 

+

199 

+

200 def update_hover(self, point: Tuple[int, int]) -> Optional[Image.Image]: 

+

201 """ 

+

202 Update hover state based on cursor position. 

+

203 

+

204 Queries the page to find what's under the cursor and updates 

+

205 hover states accordingly. 

+

206 

+

207 Args: 

+

208 point: Cursor position (x, y) 

+

209 

+

210 Returns: 

+

211 New rendered frame if hover state changed, None otherwise 

+

212 """ 

+

213 # Query what's at this point 

+

214 result = self._page.query_point(point) 

+

215 

+

216 if not result or not result.is_interactive: 

+

217 # Nothing interactive under cursor 

+

218 if self._hovered_element: 

+

219 # Clear previous hover 

+

220 if isinstance(self._hovered_element, (LinkText, ButtonText)): 

+

221 self._hovered_element.set_hovered(False) 

+

222 self._hovered_element = None 

+

223 return self._page.render() 

+

224 return None 

+

225 

+

226 # Something interactive is under cursor 

+

227 element = result.object 

+

228 if element != self._hovered_element: 

+

229 # Hover changed 

+

230 # Clear old hover 

+

231 if self._hovered_element and isinstance( 

+

232 self._hovered_element, (LinkText, ButtonText)): 

+

233 self._hovered_element.set_hovered(False) 

+

234 

+

235 # Set new hover 

+

236 if isinstance(element, (LinkText, ButtonText)): 

+

237 element.set_hovered(True) 

+

238 

+

239 self._hovered_element = element 

+

240 return self._page.render() 

+

241 

+

242 return None 

+

243 

+

244 def handle_mouse_down(self, point: Tuple[int, int]) -> Optional[Image.Image]: 

+

245 """ 

+

246 Handle mouse button press at a point. 

+

247 

+

248 Args: 

+

249 point: Click position (x, y) 

+

250 

+

251 Returns: 

+

252 New rendered frame showing pressed state, or None if nothing interactive 

+

253 """ 

+

254 result = self._page.query_point(point) 

+

255 

+

256 if not result or not result.is_interactive: 

+

257 return None 

+

258 

+

259 element = result.object 

+

260 if isinstance(element, (LinkText, ButtonText)): 

+

261 element.set_pressed(True) 

+

262 self._pressed_element = element 

+

263 return self._page.render() 

+

264 

+

265 return None 

+

266 

+

267 def handle_mouse_up( 

+

268 self, 

+

269 point: Tuple[int, 

+

270 int]) -> Tuple[Optional[Image.Image], 

+

271 Any]: 

+

272 """ 

+

273 Handle mouse button release at a point. 

+

274 

+

275 Args: 

+

276 point: Release position (x, y) 

+

277 

+

278 Returns: 

+

279 Tuple of (rendered_frame, callback_result) 

+

280 Frame shows released state, result is from executing the callback 

+

281 """ 

+

282 if not self._pressed_element: 

+

283 return None, None 

+

284 

+

285 # Execute the interaction 

+

286 callback_result = None 

+

287 if hasattr(self._pressed_element, 'interact'): 

+

288 callback_result = self._pressed_element.interact( 

+

289 np.array(point)) 

+

290 

+

291 # Release the pressed state 

+

292 if isinstance(self._pressed_element, (LinkText, ButtonText)): 

+

293 self._pressed_element.set_pressed(False) 

+

294 

+

295 self._pressed_element = None 

+

296 

+

297 return self._page.render(), callback_result 

+

298 

+

299 def reset(self): 

+

300 """Reset all interaction states.""" 

+

301 if self._hovered_element and isinstance( 

+

302 self._hovered_element, (LinkText, ButtonText)): 

+

303 self._hovered_element.set_hovered(False) 

+

304 

+

305 if self._pressed_element and isinstance( 

+

306 self._pressed_element, (LinkText, ButtonText)): 

+

307 self._pressed_element.set_pressed(False) 

+

308 

+

309 self._hovered_element = None 

+

310 self._pressed_element = None 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_page_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_page_py.html new file mode 100644 index 0000000..ec1f913 --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_page_py.html @@ -0,0 +1,636 @@ + + + + + Coverage for pyWebLayout/concrete/page.py: 66% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/page.py: + 66% +

+ +

+ 204 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from typing import List, Tuple, Optional 

+

2import numpy as np 

+

3from PIL import Image, ImageDraw 

+

4 

+

5from pyWebLayout.core.base import Renderable, Queriable 

+

6from pyWebLayout.core.query import QueryResult, SelectionRange 

+

7from pyWebLayout.core.callback_registry import CallbackRegistry 

+

8from pyWebLayout.style.page_style import PageStyle 

+

9 

+

10 

+

11class Page(Renderable, Queriable): 

+

12 """ 

+

13 A page represents a canvas that can hold and render child renderable objects. 

+

14 It handles layout, rendering, and provides query capabilities to find which child 

+

15 contains a given point. 

+

16 """ 

+

17 

+

18 def __init__(self, size: Tuple[int, int], style: Optional[PageStyle] = None): 

+

19 """ 

+

20 Initialize a new page. 

+

21 

+

22 Args: 

+

23 size: The total size of the page (width, height) including borders 

+

24 style: The PageStyle defining borders, spacing, and appearance 

+

25 """ 

+

26 self._size = size 

+

27 self._style = style if style is not None else PageStyle() 

+

28 self._children: List[Renderable] = [] 

+

29 self._canvas: Optional[Image.Image] = None 

+

30 self._draw: Optional[ImageDraw.Draw] = None 

+

31 # Initialize y_offset to start of content area 

+

32 # Position the first line so its baseline is close to the top boundary 

+

33 # For subsequent lines, baseline-to-baseline spacing is used 

+

34 self._current_y_offset = self._style.border_width + self._style.padding_top 

+

35 self._is_first_line = True # Track if we're placing the first line 

+

36 # Callback registry for managing interactable elements 

+

37 self._callbacks = CallbackRegistry() 

+

38 # Dirty flag to track if page needs re-rendering due to state changes 

+

39 self._dirty = True 

+

40 

+

41 def free_space(self) -> Tuple[int, int]: 

+

42 """Get the remaining space on the page""" 

+

43 return (self._size[0], self._size[1] - self._current_y_offset) 

+

44 

+

45 def can_fit_line( 

+

46 self, 

+

47 baseline_spacing: int, 

+

48 ascent: int = 0, 

+

49 descent: int = 0) -> bool: 

+

50 """ 

+

51 Check if a line with the given metrics can fit on the page. 

+

52 

+

53 Args: 

+

54 baseline_spacing: Distance from current position to next baseline 

+

55 ascent: Font ascent (height above baseline), defaults to 0 for backward compat 

+

56 descent: Font descent (height below baseline), defaults to 0 for backward compat 

+

57 

+

58 Returns: 

+

59 True if the line fits within page boundaries 

+

60 """ 

+

61 # Calculate the maximum Y position allowed (bottom boundary) 

+

62 max_y = self._size[1] - self._style.border_width - self._style.padding_bottom 

+

63 

+

64 # If ascent/descent not provided, use simple check (backward compatibility) 

+

65 if ascent == 0 and descent == 0: 

+

66 return (self._current_y_offset + baseline_spacing) <= max_y 

+

67 

+

68 # Calculate where the bottom of the text would be 

+

69 # Text bottom = current_y_offset + ascent + descent 

+

70 text_bottom = self._current_y_offset + ascent + descent 

+

71 

+

72 # Check if text bottom would exceed the boundary 

+

73 return text_bottom <= max_y 

+

74 

+

75 @property 

+

76 def size(self) -> Tuple[int, int]: 

+

77 """Get the total page size including borders""" 

+

78 return self._size 

+

79 

+

80 @property 

+

81 def canvas_size(self) -> Tuple[int, int]: 

+

82 """Get the canvas size (page size minus borders)""" 

+

83 border_reduction = self._style.total_border_width 

+

84 return ( 

+

85 self._size[0] - border_reduction, 

+

86 self._size[1] - border_reduction 

+

87 ) 

+

88 

+

89 @property 

+

90 def content_size(self) -> Tuple[int, int]: 

+

91 """Get the content area size (canvas minus padding)""" 

+

92 canvas_w, canvas_h = self.canvas_size 

+

93 return ( 

+

94 canvas_w - self._style.total_horizontal_padding, 

+

95 canvas_h - self._style.total_vertical_padding 

+

96 ) 

+

97 

+

98 @property 

+

99 def border_size(self) -> int: 

+

100 """Get the border width""" 

+

101 return self._style.border_width 

+

102 

+

103 @property 

+

104 def available_width(self) -> int: 

+

105 """Get the available width for content (content area width)""" 

+

106 return self.content_size[0] 

+

107 

+

108 @property 

+

109 def style(self) -> PageStyle: 

+

110 """Get the page style""" 

+

111 return self._style 

+

112 

+

113 @property 

+

114 def callbacks(self) -> CallbackRegistry: 

+

115 """Get the callback registry for managing interactable elements""" 

+

116 return self._callbacks 

+

117 

+

118 @property 

+

119 def is_dirty(self) -> bool: 

+

120 """Check if the page needs re-rendering due to state changes""" 

+

121 return self._dirty 

+

122 

+

123 def mark_dirty(self): 

+

124 """Mark the page as needing re-rendering""" 

+

125 self._dirty = True 

+

126 

+

127 def mark_clean(self): 

+

128 """Mark the page as clean (up-to-date render)""" 

+

129 self._dirty = False 

+

130 

+

131 @property 

+

132 def draw(self) -> Optional[ImageDraw.Draw]: 

+

133 """Get the ImageDraw object for drawing on this page's canvas""" 

+

134 if self._draw is None: 

+

135 # Initialize canvas and draw context if not already done 

+

136 self._canvas = self._create_canvas() 

+

137 self._draw = ImageDraw.Draw(self._canvas) 

+

138 return self._draw 

+

139 

+

140 def add_child(self, child: Renderable) -> 'Page': 

+

141 """ 

+

142 Add a child renderable object to this page. 

+

143 

+

144 Args: 

+

145 child: The renderable object to add 

+

146 

+

147 Returns: 

+

148 Self for method chaining 

+

149 """ 

+

150 self._children.append(child) 

+

151 self._current_y_offset = child.origin[1] + child.size[1] 

+

152 # Invalidate the canvas when children change 

+

153 self._canvas = None 

+

154 return self 

+

155 

+

156 def remove_child(self, child: Renderable) -> bool: 

+

157 """ 

+

158 Remove a child from the page. 

+

159 

+

160 Args: 

+

161 child: The child to remove 

+

162 

+

163 Returns: 

+

164 True if the child was found and removed, False otherwise 

+

165 """ 

+

166 try: 

+

167 self._children.remove(child) 

+

168 self._canvas = None 

+

169 return True 

+

170 except ValueError: 

+

171 return False 

+

172 

+

173 def clear_children(self) -> 'Page': 

+

174 """ 

+

175 Remove all children from the page. 

+

176 

+

177 Returns: 

+

178 Self for method chaining 

+

179 """ 

+

180 self._children.clear() 

+

181 self._canvas = None 

+

182 # Clear callback registry when clearing children 

+

183 self._callbacks.clear() 

+

184 # Reset y_offset to start of content area (after border and padding) 

+

185 self._current_y_offset = self._style.border_width + self._style.padding_top 

+

186 return self 

+

187 

+

188 @property 

+

189 def children(self) -> List[Renderable]: 

+

190 """Get a copy of the children list""" 

+

191 return self._children.copy() 

+

192 

+

193 def _get_child_property(self, child: Renderable, private_attr: str, 

+

194 public_attr: str, index: Optional[int] = None, 

+

195 default: Optional[int] = None) -> Optional[int]: 

+

196 """ 

+

197 Generic helper to extract properties from child objects with multiple fallback strategies. 

+

198 

+

199 Args: 

+

200 child: The child object 

+

201 private_attr: Name of the private attribute (e.g., '_size') 

+

202 public_attr: Name of the public property (e.g., 'size') 

+

203 index: Optional index for array-like properties (0 for width, 1 for height) 

+

204 default: Default value if property cannot be determined 

+

205 

+

206 Returns: 

+

207 Property value or default 

+

208 """ 

+

209 # Try private attribute first 

+

210 if hasattr(child, private_attr): 

+

211 value = getattr(child, private_attr) 

+

212 if value is not None: 

+

213 if isinstance(value, (list, tuple, np.ndarray)): 

+

214 if index is not None and len(value) > index: 

+

215 return int(value[index]) 

+

216 elif index is None: 

+

217 return value 

+

218 

+

219 # Try public property 

+

220 if hasattr(child, public_attr): 

+

221 value = getattr(child, public_attr) 

+

222 if value is not None: 

+

223 if isinstance(value, (list, tuple, np.ndarray)): 

+

224 if index is not None and len(value) > index: 

+

225 return int(value[index]) 

+

226 elif index is None: 

+

227 return value 

+

228 else: 

+

229 return int(value) 

+

230 

+

231 return default 

+

232 

+

233 def _get_child_height(self, child: Renderable) -> int: 

+

234 """ 

+

235 Get the height of a child object. 

+

236 

+

237 Args: 

+

238 child: The child to measure 

+

239 

+

240 Returns: 

+

241 Height in pixels 

+

242 """ 

+

243 # Try to get height from size property (index 1) 

+

244 height = self._get_child_property(child, '_size', 'size', index=1) 

+

245 if height is not None: 

+

246 return height 

+

247 

+

248 # Try direct height attribute 

+

249 height = self._get_child_property(child, '_height', 'height') 

+

250 if height is not None: 

+

251 return height 

+

252 

+

253 # Default fallback height 

+

254 return 20 

+

255 

+

256 def render_children(self): 

+

257 """ 

+

258 Call render on all children in the list. 

+

259 Children draw directly onto the page's canvas via the shared ImageDraw object. 

+

260 """ 

+

261 for child in self._children: 

+

262 # Synchronize draw context for Line objects before rendering 

+

263 if hasattr(child, '_draw'): 

+

264 child._draw = self._draw 

+

265 # Synchronize canvas for Image objects before rendering 

+

266 if hasattr(child, '_canvas'): 266 ↛ 267line 266 didn't jump to line 267 because the condition on line 266 was never true

+

267 child._canvas = self._canvas 

+

268 if hasattr(child, 'render'): 268 ↛ 261line 268 didn't jump to line 261 because the condition on line 268 was always true

+

269 child.render() 

+

270 

+

271 def render(self) -> Image.Image: 

+

272 """ 

+

273 Render the page with all its children. 

+

274 

+

275 Returns: 

+

276 PIL Image containing the rendered page 

+

277 """ 

+

278 # Create the base canvas and draw object 

+

279 self._canvas = self._create_canvas() 

+

280 self._draw = ImageDraw.Draw(self._canvas) 

+

281 

+

282 # Render all children - they draw directly onto the canvas 

+

283 self.render_children() 

+

284 

+

285 # Mark as clean after rendering 

+

286 self._dirty = False 

+

287 

+

288 return self._canvas 

+

289 

+

290 def _create_canvas(self) -> Image.Image: 

+

291 """ 

+

292 Create the base canvas with background and borders. 

+

293 

+

294 Returns: 

+

295 PIL Image with background and borders applied 

+

296 """ 

+

297 # Create base image 

+

298 canvas = Image.new('RGBA', self._size, (*self._style.background_color, 255)) 

+

299 

+

300 # Draw borders if needed 

+

301 if self._style.border_width > 0: 

+

302 draw = ImageDraw.Draw(canvas) 

+

303 border_color = (*self._style.border_color, 255) 

+

304 

+

305 # Draw border rectangle inside the content area 

+

306 border_offset = self._style.border_width 

+

307 draw.rectangle([ 

+

308 (border_offset, border_offset), 

+

309 (self._size[0] - border_offset - 1, self._size[1] - border_offset - 1) 

+

310 ], outline=border_color) 

+

311 

+

312 return canvas 

+

313 

+

314 def _get_child_position(self, child: Renderable) -> Tuple[int, int]: 

+

315 """ 

+

316 Get the position where a child should be rendered. 

+

317 

+

318 Args: 

+

319 child: The child object 

+

320 

+

321 Returns: 

+

322 Tuple of (x, y) coordinates 

+

323 """ 

+

324 # Try to get x coordinate 

+

325 x = self._get_child_property(child, '_origin', 'position', index=0, default=0) 

+

326 # Try to get y coordinate 

+

327 y = self._get_child_property(child, '_origin', 'position', index=1, default=0) 

+

328 

+

329 return (x, y) 

+

330 

+

331 def query_point(self, point: Tuple[int, int]) -> Optional[QueryResult]: 

+

332 """ 

+

333 Query a point to find the deepest object at that location. 

+

334 Traverses children and uses Queriable.in_object() for hit-testing. 

+

335 

+

336 Args: 

+

337 point: The (x, y) coordinates to query 

+

338 

+

339 Returns: 

+

340 QueryResult with metadata about what was found, or None if nothing hit 

+

341 """ 

+

342 point_array = np.array(point) 

+

343 

+

344 # Check each child (in reverse order so topmost child is found first) 

+

345 for child in reversed(self._children): 

+

346 # Use Queriable mixin's in_object() for hit-testing 

+

347 if isinstance(child, Queriable) and child.in_object(point_array): 

+

348 # If child can also query (has children of its own), recurse 

+

349 if hasattr(child, 'query_point'): 

+

350 result = child.query_point(point) 

+

351 if result: 351 ↛ 355line 351 didn't jump to line 355 because the condition on line 351 was always true

+

352 result.parent_page = self 

+

353 return result 

+

354 # If child's query returned None, continue to next child 

+

355 continue 

+

356 

+

357 # Otherwise, package this child as the result 

+

358 return self._make_query_result(child, point) 

+

359 

+

360 # Nothing hit - return empty result 

+

361 return QueryResult( 

+

362 object=self, 

+

363 object_type="empty", 

+

364 bounds=(int(point[0]), int(point[1]), 0, 0) 

+

365 ) 

+

366 

+

367 def _point_in_child(self, point: np.ndarray, child: Renderable) -> bool: 

+

368 """ 

+

369 Check if a point is within a child's bounds. 

+

370 

+

371 Args: 

+

372 point: The point to check 

+

373 child: The child to check against 

+

374 

+

375 Returns: 

+

376 True if the point is within the child's bounds 

+

377 """ 

+

378 # If child implements Queriable interface, use it 

+

379 if isinstance(child, Queriable) and hasattr(child, 'in_object'): 

+

380 try: 

+

381 return child.in_object(point) 

+

382 except BaseException: 

+

383 pass # Fall back to bounds checking 

+

384 

+

385 # Get child position and size for bounds checking 

+

386 child_pos = self._get_child_position(child) 

+

387 child_size = self._get_child_size(child) 

+

388 

+

389 if child_size is None: 

+

390 return False 

+

391 

+

392 # Check if point is within child bounds 

+

393 return ( 

+

394 child_pos[0] <= point[0] < child_pos[0] + child_size[0] and 

+

395 child_pos[1] <= point[1] < child_pos[1] + child_size[1] 

+

396 ) 

+

397 

+

398 def _get_child_size(self, child: Renderable) -> Optional[Tuple[int, int]]: 

+

399 """ 

+

400 Get the size of a child object. 

+

401 

+

402 Args: 

+

403 child: The child to measure 

+

404 

+

405 Returns: 

+

406 Tuple of (width, height) or None if size cannot be determined 

+

407 """ 

+

408 # Try to get width and height from size property 

+

409 width = self._get_child_property(child, '_size', 'size', index=0) 

+

410 height = self._get_child_property(child, '_size', 'size', index=1) 

+

411 

+

412 # If size property worked, return it 

+

413 if width is not None and height is not None: 

+

414 return (width, height) 

+

415 

+

416 # Try direct width/height attributes 

+

417 width = self._get_child_property(child, '_width', 'width') 

+

418 height = self._get_child_property(child, '_height', 'height') 

+

419 

+

420 if width is not None and height is not None: 

+

421 return (width, height) 

+

422 

+

423 return None 

+

424 

+

425 def _make_query_result(self, obj, point: Tuple[int, int]) -> QueryResult: 

+

426 """ 

+

427 Package an object into a QueryResult with metadata. 

+

428 

+

429 Args: 

+

430 obj: The object to package 

+

431 point: The query point 

+

432 

+

433 Returns: 

+

434 QueryResult with extracted metadata 

+

435 """ 

+

436 from .text import Text 

+

437 from .functional import LinkText, ButtonText 

+

438 

+

439 # Extract bounds 

+

440 origin = getattr(obj, '_origin', np.array([0, 0])) 

+

441 size = getattr(obj, 'size', np.array([0, 0])) 

+

442 bounds = ( 

+

443 int(origin[0]), 

+

444 int(origin[1]), 

+

445 int(size[0]) if hasattr(size, '__getitem__') else 0, 

+

446 int(size[1]) if hasattr(size, '__getitem__') else 0 

+

447 ) 

+

448 

+

449 # Determine type and extract metadata 

+

450 if isinstance(obj, LinkText): 

+

451 return QueryResult( 

+

452 object=obj, 

+

453 object_type="link", 

+

454 bounds=bounds, 

+

455 text=obj._text, 

+

456 is_interactive=True, 

+

457 link_target=obj._link.location if hasattr(obj, '_link') else None 

+

458 ) 

+

459 elif isinstance(obj, ButtonText): 459 ↛ 460line 459 didn't jump to line 460 because the condition on line 459 was never true

+

460 return QueryResult( 

+

461 object=obj, 

+

462 object_type="button", 

+

463 bounds=bounds, 

+

464 text=obj._text, 

+

465 is_interactive=True, 

+

466 callback=obj._callback if hasattr(obj, '_callback') else None 

+

467 ) 

+

468 elif isinstance(obj, Text): 

+

469 return QueryResult( 

+

470 object=obj, 

+

471 object_type="text", 

+

472 bounds=bounds, 

+

473 text=obj._text if hasattr(obj, '_text') else None 

+

474 ) 

+

475 else: 

+

476 return QueryResult( 

+

477 object=obj, 

+

478 object_type="unknown", 

+

479 bounds=bounds 

+

480 ) 

+

481 

+

482 def query_range(self, start: Tuple[int, int], 

+

483 end: Tuple[int, int]) -> SelectionRange: 

+

484 """ 

+

485 Query all text objects between two points (for text selection). 

+

486 Uses Queriable.in_object() to determine which objects are in range. 

+

487 

+

488 Args: 

+

489 start: Starting (x, y) point 

+

490 end: Ending (x, y) point 

+

491 

+

492 Returns: 

+

493 SelectionRange with all text objects between the points 

+

494 """ 

+

495 results = [] 

+

496 in_selection = False 

+

497 

+

498 start_result = self.query_point(start) 

+

499 end_result = self.query_point(end) 

+

500 

+

501 if not start_result or not end_result: 501 ↛ 502line 501 didn't jump to line 502 because the condition on line 501 was never true

+

502 return SelectionRange(start, end, []) 

+

503 

+

504 # Walk through all children (Lines) and their text objects 

+

505 from .text import Line, Text 

+

506 

+

507 for child in self._children: 

+

508 if isinstance(child, Line) and hasattr(child, '_text_objects'): 508 ↛ 507line 508 didn't jump to line 507 because the condition on line 508 was always true

+

509 for text_obj in child._text_objects: 509 ↛ 507line 509 didn't jump to line 507 because the loop on line 509 didn't complete

+

510 # Check if this text is the start or is between start and end 

+

511 if text_obj == start_result.object: 

+

512 in_selection = True 

+

513 

+

514 if in_selection and isinstance(text_obj, Text): 514 ↛ 518line 514 didn't jump to line 518 because the condition on line 514 was always true

+

515 result = self._make_query_result(text_obj, start) 

+

516 results.append(result) 

+

517 

+

518 if text_obj == end_result.object: 

+

519 in_selection = False 

+

520 break 

+

521 

+

522 return SelectionRange(start, end, results) 

+

523 

+

524 def in_object(self, point: Tuple[int, int]) -> bool: 

+

525 """ 

+

526 Check if a point is within this page's bounds. 

+

527 

+

528 Args: 

+

529 point: The (x, y) coordinates to check 

+

530 

+

531 Returns: 

+

532 True if the point is within the page bounds 

+

533 """ 

+

534 return ( 

+

535 0 <= point[0] < self._size[0] and 

+

536 0 <= point[1] < self._size[1] 

+

537 ) 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_table_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_table_py.html new file mode 100644 index 0000000..40752ac --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_table_py.html @@ -0,0 +1,805 @@ + + + + + Coverage for pyWebLayout/concrete/table.py: 70% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/table.py: + 70% +

+ +

+ 303 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Concrete table rendering implementation for pyWebLayout. 

+

3 

+

4This module provides the concrete rendering classes for tables, including: 

+

5- TableRenderer: Main table rendering with borders and spacing 

+

6- TableRowRenderer: Individual row rendering 

+

7- TableCellRenderer: Cell rendering with support for nested content (text, images, links) 

+

8""" 

+

9 

+

10from __future__ import annotations 

+

11from typing import Tuple, List, Optional, Dict 

+

12from PIL import Image, ImageDraw 

+

13from dataclasses import dataclass 

+

14 

+

15from pyWebLayout.core.base import Renderable 

+

16from pyWebLayout.concrete.box import Box 

+

17from pyWebLayout.abstract.block import Table, TableRow, TableCell, Paragraph, Heading, Image as AbstractImage 

+

18from pyWebLayout.abstract.interactive_image import InteractiveImage 

+

19 

+

20 

+

21@dataclass 

+

22class TableStyle: 

+

23 """Styling configuration for table rendering.""" 

+

24 

+

25 # Border configuration 

+

26 border_width: int = 1 

+

27 border_color: Tuple[int, int, int] = (0, 0, 0) 

+

28 

+

29 # Cell padding 

+

30 cell_padding: Tuple[int, int, int, int] = (5, 5, 5, 5) # top, right, bottom, left 

+

31 

+

32 # Header styling 

+

33 header_bg_color: Tuple[int, int, int] = (240, 240, 240) 

+

34 header_text_bold: bool = True 

+

35 

+

36 # Cell background 

+

37 cell_bg_color: Tuple[int, int, int] = (255, 255, 255) 

+

38 alternate_row_color: Optional[Tuple[int, int, int]] = (250, 250, 250) 

+

39 

+

40 # Spacing 

+

41 cell_spacing: int = 0 # Space between cells (for separated borders model) 

+

42 

+

43 

+

44class TableCellRenderer(Box): 

+

45 """ 

+

46 Renders a single table cell with its content. 

+

47 Supports paragraphs, headings, images, and links within cells. 

+

48 """ 

+

49 

+

50 def __init__(self, 

+

51 cell: TableCell, 

+

52 origin: Tuple[int, 

+

53 int], 

+

54 size: Tuple[int, 

+

55 int], 

+

56 draw: ImageDraw.Draw, 

+

57 style: TableStyle, 

+

58 is_header_section: bool = False, 

+

59 canvas: Optional[Image.Image] = None): 

+

60 """ 

+

61 Initialize a table cell renderer. 

+

62 

+

63 Args: 

+

64 cell: The abstract TableCell to render 

+

65 origin: Top-left position of the cell 

+

66 size: Width and height of the cell 

+

67 draw: PIL ImageDraw object for rendering 

+

68 style: Table styling configuration 

+

69 is_header_section: Whether this cell is in the header section 

+

70 canvas: Optional PIL Image for pasting images (required for image rendering) 

+

71 """ 

+

72 super().__init__(origin, size) 

+

73 self._cell = cell 

+

74 self._draw = draw 

+

75 self._style = style 

+

76 self._is_header_section = is_header_section or cell.is_header 

+

77 self._canvas = canvas 

+

78 self._children: List[Renderable] = [] 

+

79 

+

80 def render(self) -> Image.Image: 

+

81 """Render the table cell.""" 

+

82 # Determine background color 

+

83 if self._is_header_section: 

+

84 bg_color = self._style.header_bg_color 

+

85 else: 

+

86 bg_color = self._style.cell_bg_color 

+

87 

+

88 # Draw cell background 

+

89 x, y = self._origin 

+

90 w, h = self._size 

+

91 self._draw.rectangle( 

+

92 [x, y, x + w, y + h], 

+

93 fill=bg_color, 

+

94 outline=self._style.border_color, 

+

95 width=self._style.border_width 

+

96 ) 

+

97 

+

98 # Calculate content area (inside padding) 

+

99 padding = self._style.cell_padding 

+

100 content_x = x + padding[3] # left padding 

+

101 content_y = y + padding[0] # top padding 

+

102 content_width = w - (padding[1] + padding[3]) # minus left and right padding 

+

103 content_height = h - (padding[0] + padding[2]) # minus top and bottom padding 

+

104 

+

105 # Render cell content (text) 

+

106 self._render_cell_content(content_x, content_y, content_width, content_height) 

+

107 

+

108 return None # Cell rendering is done directly on the page 

+

109 

+

110 def _render_cell_content(self, x: int, y: int, width: int, height: int): 

+

111 """Render the content inside the cell (text and images) with line wrapping.""" 

+

112 from pyWebLayout.concrete.text import Line, Text 

+

113 from pyWebLayout.style.fonts import Font 

+

114 from pyWebLayout.style import FontWeight, Alignment 

+

115 

+

116 current_y = y + 2 

+

117 available_height = height - 4 # Account for top/bottom padding 

+

118 

+

119 # Create font for the cell 

+

120 font_size = 12 

+

121 font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" 

+

122 if self._is_header_section and self._style.header_text_bold: 

+

123 font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" 

+

124 

+

125 font = Font( 

+

126 font_path=font_path, 

+

127 font_size=font_size, 

+

128 weight=FontWeight.BOLD if self._is_header_section and self._style.header_text_bold else FontWeight.NORMAL 

+

129 ) 

+

130 

+

131 # Word spacing constraints (min, max) 

+

132 min_spacing = int(font_size * 0.25) 

+

133 max_spacing = int(font_size * 0.5) 

+

134 word_spacing = (min_spacing, max_spacing) 

+

135 

+

136 # Line height (baseline spacing) 

+

137 line_height = font_size + 4 

+

138 ascent, descent = font.font.getmetrics() 

+

139 

+

140 # Render each block in the cell 

+

141 for block in self._cell.blocks(): 

+

142 if isinstance(block, AbstractImage): 142 ↛ 144line 142 didn't jump to line 144 because the condition on line 142 was never true

+

143 # Render image 

+

144 current_y = self._render_image_in_cell( 

+

145 block, x, current_y, width, height - (current_y - y)) 

+

146 elif isinstance(block, (Paragraph, Heading)): 146 ↛ 225line 146 didn't jump to line 225 because the condition on line 146 was always true

+

147 # Get words from the block 

+

148 from pyWebLayout.abstract.inline import Word as AbstractWord 

+

149 

+

150 word_items = block.words() if callable(block.words) else block.words 

+

151 words = list(word_items) 

+

152 

+

153 if not words: 153 ↛ 154line 153 didn't jump to line 154 because the condition on line 153 was never true

+

154 continue 

+

155 

+

156 # Create new Word objects with the table cell's font 

+

157 # The words from the paragraph may have AbstractStyle, but we need Font objects 

+

158 wrapped_words = [] 

+

159 for word_item in words: 

+

160 # Handle word tuples (index, word_obj) 

+

161 if isinstance(word_item, tuple) and len(word_item) >= 2: 161 ↛ 162line 161 didn't jump to line 162 because the condition on line 161 was never true

+

162 word_obj = word_item[1] 

+

163 else: 

+

164 word_obj = word_item 

+

165 

+

166 # Extract text from the word 

+

167 word_text = word_obj.text if hasattr(word_obj, 'text') else str(word_obj) 

+

168 

+

169 # Create a new Word with the cell's Font 

+

170 new_word = AbstractWord(word_text, font) 

+

171 wrapped_words.append(new_word) 

+

172 

+

173 # Layout words using Line objects with wrapping 

+

174 word_index = 0 

+

175 pretext = None 

+

176 

+

177 while word_index < len(wrapped_words): 

+

178 # Check if we have space for another line 

+

179 if current_y + ascent + descent > y + available_height: 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true

+

180 break # No more space in cell 

+

181 

+

182 # Create a new line 

+

183 line = Line( 

+

184 spacing=word_spacing, 

+

185 origin=(x + 2, current_y), 

+

186 size=(width - 4, line_height), 

+

187 draw=self._draw, 

+

188 font=font, 

+

189 halign=Alignment.LEFT 

+

190 ) 

+

191 

+

192 # Add words to this line until it's full 

+

193 line_has_content = False 

+

194 while word_index < len(wrapped_words): 

+

195 word = wrapped_words[word_index] 

+

196 

+

197 # Try to add word to line 

+

198 success, overflow = line.add_word(word, pretext) 

+

199 pretext = None # Clear pretext after use 

+

200 

+

201 if success: 201 ↛ 214line 201 didn't jump to line 214 because the condition on line 201 was always true

+

202 line_has_content = True 

+

203 if overflow: 203 ↛ 207line 203 didn't jump to line 207 because the condition on line 203 was never true

+

204 # Word was hyphenated, carry over to next line 

+

205 # DON'T increment word_index - we need to add the overflow 

+

206 # to the next line with the same word 

+

207 pretext = overflow 

+

208 break # Move to next line 

+

209 else: 

+

210 # Word fit completely, move to next word 

+

211 word_index += 1 

+

212 else: 

+

213 # Word doesn't fit on this line 

+

214 if not line_has_content: 

+

215 # Even first word doesn't fit, force it anyway and advance 

+

216 # This prevents infinite loops with words that truly can't fit 

+

217 word_index += 1 

+

218 break 

+

219 

+

220 # Render the line if it has content 

+

221 if line_has_content or len(line.text_objects) > 0: 221 ↛ 177line 221 didn't jump to line 177 because the condition on line 221 was always true

+

222 line.render() 

+

223 current_y += line_height 

+

224 

+

225 if current_y > y + height - 10: # Don't overflow cell 225 ↛ 226line 225 didn't jump to line 226 because the condition on line 225 was never true

+

226 break 

+

227 

+

228 # If no structured content, try to get any text representation 

+

229 if current_y == y + 2 and hasattr(self._cell, '_text_content'): 229 ↛ 231line 229 didn't jump to line 231 because the condition on line 229 was never true

+

230 # Use simple text rendering for fallback case 

+

231 from PIL import ImageFont 

+

232 try: 

+

233 pil_font = ImageFont.truetype(font_path, font_size) 

+

234 except BaseException: 

+

235 pil_font = ImageFont.load_default() 

+

236 

+

237 self._draw.text( 

+

238 (x + 2, current_y), 

+

239 self._cell._text_content, 

+

240 fill=(0, 0, 0), 

+

241 font=pil_font 

+

242 ) 

+

243 

+

244 def _render_image_in_cell(self, image_block: AbstractImage, x: int, y: int, 

+

245 max_width: int, max_height: int) -> int: 

+

246 """ 

+

247 Render an image block inside a table cell. 

+

248 

+

249 Returns: 

+

250 The new Y position after the image 

+

251 """ 

+

252 try: 

+

253 # Get the image path from the block 

+

254 image_path = None 

+

255 if hasattr(image_block, 'source'): 

+

256 image_path = image_block.source 

+

257 elif hasattr(image_block, '_source'): 

+

258 image_path = image_block._source 

+

259 elif hasattr(image_block, 'path'): 

+

260 image_path = image_block.path 

+

261 elif hasattr(image_block, 'src'): 

+

262 image_path = image_block.src 

+

263 elif hasattr(image_block, '_path'): 

+

264 image_path = image_block._path 

+

265 elif hasattr(image_block, '_src'): 

+

266 image_path = image_block._src 

+

267 

+

268 if not image_path: 

+

269 return y + 20 # Skip if no image path 

+

270 

+

271 # Load and resize image to fit in cell 

+

272 img = Image.open(image_path) 

+

273 

+

274 # Calculate scaling to fit within max dimensions 

+

275 # Use more of the cell space for images 

+

276 img_width, img_height = img.size 

+

277 scale_w = max_width / img_width if img_width > max_width else 1 

+

278 scale_h = (max_height - 10) / \ 

+

279 img_height if img_height > (max_height - 10) else 1 

+

280 scale = min(scale_w, scale_h, 1.0) # Don't upscale 

+

281 

+

282 new_width = int(img_width * scale) 

+

283 new_height = int(img_height * scale) 

+

284 

+

285 if scale < 1.0: 

+

286 img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) 

+

287 

+

288 # Center image horizontally in cell 

+

289 img_x = x + (max_width - new_width) // 2 

+

290 

+

291 # Paste the image onto the canvas if available 

+

292 if self._canvas is not None: 

+

293 if img.mode == 'RGBA': 

+

294 self._canvas.paste(img, (img_x, y), img) 

+

295 else: 

+

296 self._canvas.paste(img, (img_x, y)) 

+

297 else: 

+

298 # Fallback: draw a placeholder if no canvas provided 

+

299 self._draw.rectangle( 

+

300 [img_x, y, img_x + new_width, y + new_height], 

+

301 fill=(200, 200, 200), 

+

302 outline=(150, 150, 150) 

+

303 ) 

+

304 

+

305 # Draw image indicator text 

+

306 from PIL import ImageFont 

+

307 try: 

+

308 small_font = ImageFont.truetype( 

+

309 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 9) 

+

310 except BaseException: 

+

311 small_font = ImageFont.load_default() 

+

312 

+

313 text = f"[Image: {new_width}x{new_height}]" 

+

314 bbox = self._draw.textbbox((0, 0), text, font=small_font) 

+

315 text_width = bbox[2] - bbox[0] 

+

316 text_x = img_x + (new_width - text_width) // 2 

+

317 text_y = y + (new_height - 12) // 2 

+

318 self._draw.text( 

+

319 (text_x, text_y), text, fill=( 

+

320 100, 100, 100), font=small_font) 

+

321 

+

322 # Set bounds on InteractiveImage objects for tap detection 

+

323 if isinstance(image_block, InteractiveImage): 

+

324 image_block.set_rendered_bounds( 

+

325 origin=(img_x, y), 

+

326 size=(new_width, new_height) 

+

327 ) 

+

328 

+

329 return y + new_height + 5 # Add some spacing after image 

+

330 

+

331 except Exception: 

+

332 # If image loading fails, just return current position 

+

333 return y + 20 

+

334 

+

335 

+

336class TableRowRenderer(Box): 

+

337 """ 

+

338 Renders a single table row containing multiple cells. 

+

339 """ 

+

340 

+

341 def __init__(self, 

+

342 row: TableRow, 

+

343 origin: Tuple[int, 

+

344 int], 

+

345 column_widths: List[int], 

+

346 row_height: int, 

+

347 draw: ImageDraw.Draw, 

+

348 style: TableStyle, 

+

349 is_header_section: bool = False, 

+

350 canvas: Optional[Image.Image] = None): 

+

351 """ 

+

352 Initialize a table row renderer. 

+

353 

+

354 Args: 

+

355 row: The abstract TableRow to render 

+

356 origin: Top-left position of the row 

+

357 column_widths: List of widths for each column 

+

358 row_height: Height of this row 

+

359 draw: PIL ImageDraw object for rendering 

+

360 style: Table styling configuration 

+

361 is_header_section: Whether this row is in the header section 

+

362 canvas: Optional PIL Image for pasting images 

+

363 """ 

+

364 width = sum(column_widths) + style.border_width * (len(column_widths) + 1) 

+

365 super().__init__(origin, (width, row_height)) 

+

366 self._row = row 

+

367 self._column_widths = column_widths 

+

368 self._row_height = row_height 

+

369 self._draw = draw 

+

370 self._style = style 

+

371 self._is_header_section = is_header_section 

+

372 self._canvas = canvas 

+

373 self._cell_renderers: List[TableCellRenderer] = [] 

+

374 

+

375 def render(self) -> Image.Image: 

+

376 """Render the table row by rendering each cell.""" 

+

377 x, y = self._origin 

+

378 current_x = x 

+

379 

+

380 # Render each cell 

+

381 cells = list(self._row.cells()) 

+

382 for i, cell in enumerate(cells): 

+

383 if i < len(self._column_widths): 383 ↛ 382line 383 didn't jump to line 382 because the condition on line 383 was always true

+

384 cell_width = self._column_widths[i] 

+

385 

+

386 # Handle colspan 

+

387 if cell.colspan > 1 and i + cell.colspan <= len(self._column_widths): 

+

388 # Sum up widths for spanned columns 

+

389 cell_width = sum(self._column_widths[i:i + cell.colspan]) 

+

390 cell_width += self._style.border_width * (cell.colspan - 1) 

+

391 

+

392 # Create and render cell 

+

393 cell_renderer = TableCellRenderer( 

+

394 cell, 

+

395 (current_x, y), 

+

396 (cell_width, self._row_height), 

+

397 self._draw, 

+

398 self._style, 

+

399 self._is_header_section, 

+

400 self._canvas 

+

401 ) 

+

402 cell_renderer.render() 

+

403 self._cell_renderers.append(cell_renderer) 

+

404 

+

405 current_x += cell_width + self._style.border_width 

+

406 

+

407 return None # Row rendering is done directly on the page 

+

408 

+

409 

+

410class TableRenderer(Box): 

+

411 """ 

+

412 Main table renderer that orchestrates the rendering of an entire table. 

+

413 Handles layout calculation, row/cell placement, and overall table structure. 

+

414 """ 

+

415 

+

416 def __init__(self, 

+

417 table: Table, 

+

418 origin: Tuple[int, 

+

419 int], 

+

420 available_width: int, 

+

421 draw: ImageDraw.Draw, 

+

422 style: Optional[TableStyle] = None, 

+

423 canvas: Optional[Image.Image] = None): 

+

424 """ 

+

425 Initialize a table renderer. 

+

426 

+

427 Args: 

+

428 table: The abstract Table to render 

+

429 origin: Top-left position where the table should be rendered 

+

430 available_width: Maximum width available for the table 

+

431 draw: PIL ImageDraw object for rendering 

+

432 style: Optional table styling configuration 

+

433 canvas: Optional PIL Image for pasting images 

+

434 """ 

+

435 self._table = table 

+

436 self._draw = draw 

+

437 self._style = style or TableStyle() 

+

438 self._available_width = available_width 

+

439 self._canvas = canvas 

+

440 

+

441 # Calculate table dimensions 

+

442 self._column_widths, self._row_heights = self._calculate_dimensions() 

+

443 total_width = sum(self._column_widths) + \ 

+

444 self._style.border_width * (len(self._column_widths) + 1) 

+

445 total_height = sum(self._row_heights.values()) + \ 

+

446 self._style.border_width * (len(self._row_heights) + 1) 

+

447 

+

448 super().__init__(origin, (total_width, total_height)) 

+

449 self._row_renderers: List[TableRowRenderer] = [] 

+

450 

+

451 def _calculate_dimensions(self) -> Tuple[List[int], Dict[str, int]]: 

+

452 """ 

+

453 Calculate column widths and row heights for the table. 

+

454 

+

455 Uses the table optimizer for intelligent column width distribution. 

+

456 

+

457 Returns: 

+

458 Tuple of (column_widths, row_heights_dict) 

+

459 """ 

+

460 from pyWebLayout.layout.table_optimizer import optimize_table_layout 

+

461 

+

462 all_rows = list(self._table.all_rows()) 

+

463 

+

464 if not all_rows: 

+

465 return ([100], {"header": 30, "body": 30, "footer": 30}) 

+

466 

+

467 # Use optimizer for column widths! 

+

468 column_widths = optimize_table_layout( 

+

469 self._table, 

+

470 self._available_width, 

+

471 sample_size=5, 

+

472 style=self._style 

+

473 ) 

+

474 

+

475 if not column_widths: 475 ↛ 477line 475 didn't jump to line 477 because the condition on line 475 was never true

+

476 # Fallback if table is empty 

+

477 column_widths = [100] 

+

478 

+

479 # Calculate row heights dynamically based on optimized column widths 

+

480 header_height = self._calculate_row_height_for_section( 

+

481 all_rows, "header", column_widths) if any( 

+

482 1 for section, _ in all_rows if section == "header") else 0 

+

483 

+

484 body_height = self._calculate_row_height_for_section( 

+

485 all_rows, "body", column_widths) 

+

486 

+

487 footer_height = self._calculate_row_height_for_section( 

+

488 all_rows, "footer", column_widths) if any( 

+

489 1 for section, _ in all_rows if section == "footer") else 0 

+

490 

+

491 row_heights = { 

+

492 "header": header_height, 

+

493 "body": body_height, 

+

494 "footer": footer_height 

+

495 } 

+

496 

+

497 return (column_widths, row_heights) 

+

498 

+

499 def _calculate_row_height_for_section( 

+

500 self, 

+

501 all_rows: List, 

+

502 section: str, 

+

503 column_widths: List[int]) -> int: 

+

504 """ 

+

505 Calculate the maximum required height for rows in a specific section. 

+

506 

+

507 Args: 

+

508 all_rows: List of all rows in the table 

+

509 section: Section name ('header', 'body', or 'footer') 

+

510 column_widths: List of column widths 

+

511 

+

512 Returns: 

+

513 Maximum height needed for rows in this section 

+

514 """ 

+

515 from pyWebLayout.concrete.text import Text 

+

516 from pyWebLayout.style.fonts import Font 

+

517 from pyWebLayout.abstract.inline import Word as AbstractWord 

+

518 

+

519 # Font configuration 

+

520 font_size = 12 

+

521 line_height = font_size + 4 

+

522 padding = self._style.cell_padding 

+

523 vertical_padding = padding[0] + padding[2] # top + bottom 

+

524 horizontal_padding = padding[1] + padding[3] # left + right 

+

525 

+

526 max_height = 40 # Minimum height 

+

527 

+

528 for row_section, row in all_rows: 

+

529 if row_section != section: 

+

530 continue 

+

531 

+

532 row_max_height = 40 # Minimum for this row 

+

533 

+

534 for cell_idx, cell in enumerate(row.cells()): 

+

535 if cell_idx >= len(column_widths): 535 ↛ 536line 535 didn't jump to line 536 because the condition on line 535 was never true

+

536 continue 

+

537 

+

538 # Get cell width (accounting for colspan) 

+

539 cell_width = column_widths[cell_idx] 

+

540 if cell.colspan > 1 and cell_idx + \ 540 ↛ 542line 540 didn't jump to line 542 because the condition on line 540 was never true

+

541 cell.colspan <= len(column_widths): 

+

542 cell_width = sum( 

+

543 column_widths[cell_idx:cell_idx + cell.colspan]) 

+

544 cell_width += self._style.border_width * (cell.colspan - 1) 

+

545 

+

546 # Calculate content width (minus padding) 

+

547 content_width = cell_width - horizontal_padding - 4 # Extra margin 

+

548 

+

549 cell_height = vertical_padding + 4 # Base height with padding 

+

550 

+

551 # Analyze each block in the cell 

+

552 for block in cell.blocks(): 

+

553 if isinstance(block, AbstractImage): 553 ↛ 555line 553 didn't jump to line 555 because the condition on line 553 was never true

+

554 # Images need more space 

+

555 cell_height = max(cell_height, 120) 

+

556 elif isinstance(block, (Paragraph, Heading)): 556 ↛ 552line 556 didn't jump to line 552 because the condition on line 556 was always true

+

557 # Calculate text wrapping height 

+

558 word_items = block.words() if callable( 

+

559 block.words) else block.words 

+

560 words = list(word_items) 

+

561 

+

562 if not words: 562 ↛ 563line 562 didn't jump to line 563 because the condition on line 562 was never true

+

563 continue 

+

564 

+

565 # Simulate text wrapping to count lines 

+

566 lines_needed = self._estimate_wrapped_lines( 

+

567 words, content_width, font_size) 

+

568 text_height = lines_needed * line_height 

+

569 cell_height = max( 

+

570 cell_height, text_height + vertical_padding + 4) 

+

571 

+

572 row_max_height = max(row_max_height, cell_height) 

+

573 

+

574 max_height = max(max_height, row_max_height) 

+

575 

+

576 return max_height 

+

577 

+

578 def _estimate_wrapped_lines( 

+

579 self, 

+

580 words: List, 

+

581 available_width: int, 

+

582 font_size: int) -> int: 

+

583 """ 

+

584 Estimate how many lines are needed to render the given words. 

+

585 

+

586 Args: 

+

587 words: List of word objects 

+

588 available_width: Available width for text 

+

589 font_size: Font size in pixels 

+

590 

+

591 Returns: 

+

592 Number of lines needed 

+

593 """ 

+

594 from pyWebLayout.concrete.text import Text 

+

595 from pyWebLayout.style.fonts import Font 

+

596 

+

597 # Create a temporary font for measurement 

+

598 font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" 

+

599 font = Font(font_path=font_path, font_size=font_size) 

+

600 

+

601 # Word spacing (approximate) 

+

602 word_spacing = int(font_size * 0.25) 

+

603 

+

604 lines = 1 

+

605 current_line_width = 0 

+

606 

+

607 for word_item in words: 

+

608 # Handle word tuples (index, word_obj) 

+

609 if isinstance(word_item, tuple) and len(word_item) >= 2: 609 ↛ 610line 609 didn't jump to line 610 because the condition on line 609 was never true

+

610 word_obj = word_item[1] 

+

611 else: 

+

612 word_obj = word_item 

+

613 

+

614 # Extract text from the word 

+

615 word_text = word_obj.text if hasattr( 

+

616 word_obj, 'text') else str(word_obj) 

+

617 

+

618 # Measure word width 

+

619 word_width = font.font.getlength(word_text) 

+

620 

+

621 # Check if word fits on current line 

+

622 if current_line_width > 0: # Not first word on line 

+

623 needed_width = current_line_width + word_spacing + word_width 

+

624 if needed_width > available_width: 624 ↛ 626line 624 didn't jump to line 626 because the condition on line 624 was never true

+

625 # Need new line 

+

626 lines += 1 

+

627 current_line_width = word_width 

+

628 else: 

+

629 current_line_width = needed_width 

+

630 else: 

+

631 # First word on line 

+

632 if word_width > available_width: 632 ↛ 634line 632 didn't jump to line 634 because the condition on line 632 was never true

+

633 # Word needs to be hyphenated, assume it takes 1 line 

+

634 lines += 1 

+

635 current_line_width = 0 

+

636 else: 

+

637 current_line_width = word_width 

+

638 

+

639 return lines 

+

640 

+

641 def render(self) -> Image.Image: 

+

642 """Render the complete table.""" 

+

643 x, y = self._origin 

+

644 current_y = y 

+

645 

+

646 # Render caption if present 

+

647 if self._table.caption: 

+

648 current_y = self._render_caption(x, current_y) 

+

649 current_y += 10 # Space after caption 

+

650 

+

651 # Render header rows 

+

652 for section, row in self._table.all_rows(): 

+

653 if section == "header": 

+

654 row_height = self._row_heights["header"] 

+

655 elif section == "footer": 

+

656 row_height = self._row_heights["footer"] 

+

657 else: 

+

658 row_height = self._row_heights["body"] 

+

659 

+

660 is_header = (section == "header") 

+

661 

+

662 row_renderer = TableRowRenderer( 

+

663 row, 

+

664 (x, current_y), 

+

665 self._column_widths, 

+

666 row_height, 

+

667 self._draw, 

+

668 self._style, 

+

669 is_header, 

+

670 self._canvas 

+

671 ) 

+

672 row_renderer.render() 

+

673 self._row_renderers.append(row_renderer) 

+

674 

+

675 current_y += row_height + self._style.border_width 

+

676 

+

677 return None # Table rendering is done directly on the page 

+

678 

+

679 def _render_caption(self, x: int, y: int) -> int: 

+

680 """Render the table caption and return the new Y position.""" 

+

681 from PIL import ImageFont 

+

682 

+

683 try: 

+

684 font = ImageFont.truetype( 

+

685 "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 13) 

+

686 except BaseException: 

+

687 font = ImageFont.load_default() 

+

688 

+

689 # Center the caption 

+

690 bbox = self._draw.textbbox((0, 0), self._table.caption, font=font) 

+

691 text_width = bbox[2] - bbox[0] 

+

692 caption_x = x + (self._size[0] - text_width) // 2 

+

693 

+

694 self._draw.text((caption_x, y), self._table.caption, fill=(0, 0, 0), font=font) 

+

695 

+

696 return y + 20 # Caption height 

+

697 

+

698 @property 

+

699 def height(self) -> int: 

+

700 """Get the total height of the rendered table.""" 

+

701 return int(self._size[1]) 

+

702 

+

703 @property 

+

704 def width(self) -> int: 

+

705 """Get the total width of the rendered table.""" 

+

706 return int(self._size[0]) 

+
+ + + diff --git a/cov_info/htmlcov/z_7d48e1f4c6486fa2_text_py.html b/cov_info/htmlcov/z_7d48e1f4c6486fa2_text_py.html new file mode 100644 index 0000000..9701e66 --- /dev/null +++ b/cov_info/htmlcov/z_7d48e1f4c6486fa2_text_py.html @@ -0,0 +1,846 @@ + + + + + Coverage for pyWebLayout/concrete/text.py: 90% + + + + + +
+
+

+ Coverage for pyWebLayout/concrete/text.py: + 90% +

+ +

+ 283 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from __future__ import annotations 

+

2from pyWebLayout.core.base import Renderable, Queriable 

+

3from pyWebLayout.core.query import QueryResult 

+

4from .box import Box 

+

5from pyWebLayout.style import Alignment, Font, TextDecoration 

+

6from pyWebLayout.abstract import Word 

+

7from pyWebLayout.abstract.inline import LinkedWord 

+

8from pyWebLayout.abstract.functional import Link 

+

9from PIL import ImageDraw 

+

10from typing import Tuple, List, Optional 

+

11import numpy as np 

+

12from abc import ABC, abstractmethod 

+

13 

+

14 

+

15class AlignmentHandler(ABC): 

+

16 """ 

+

17 Abstract base class for text alignment handlers. 

+

18 Each handler implements a specific alignment strategy. 

+

19 """ 

+

20 

+

21 @abstractmethod 

+

22 def calculate_spacing_and_position(self, text_objects: List['Text'], 

+

23 available_width: int, min_spacing: int, 

+

24 max_spacing: int) -> Tuple[int, int, bool]: 

+

25 """ 

+

26 Calculate the spacing between words and starting position for the line. 

+

27 

+

28 Args: 

+

29 text_objects: List of Text objects in the line 

+

30 available_width: Total width available for the line 

+

31 min_spacing: Minimum spacing between words 

+

32 max_spacing: Maximum spacing between words 

+

33 

+

34 Returns: 

+

35 Tuple of (spacing_between_words, starting_x_position) 

+

36 """ 

+

37 

+

38 

+

39class LeftAlignmentHandler(AlignmentHandler): 

+

40 """Handler for left-aligned text.""" 

+

41 

+

42 def calculate_spacing_and_position(self, 

+

43 text_objects: List['Text'], 

+

44 available_width: int, 

+

45 min_spacing: int, 

+

46 max_spacing: int) -> Tuple[int, int, bool]: 

+

47 """ 

+

48 Calculate spacing and position for left-aligned text objects. 

+

49 CREngine-inspired: never allow negative spacing, always use minimum spacing for overflow. 

+

50 

+

51 Args: 

+

52 text_objects (List[Text]): A list of text objects to be laid out. 

+

53 available_width (int): The total width available for layout. 

+

54 min_spacing (int): Minimum spacing between text objects. 

+

55 max_spacing (int): Maximum spacing between text objects. 

+

56 

+

57 Returns: 

+

58 Tuple[int, int, bool]: Spacing, start position, and overflow flag. 

+

59 """ 

+

60 # Handle single word case 

+

61 if len(text_objects) <= 1: 

+

62 return 0, 0, False 

+

63 

+

64 # Calculate the total length of all text objects 

+

65 text_length = sum([text.width for text in text_objects]) 

+

66 

+

67 # Calculate number of gaps between texts 

+

68 num_gaps = len(text_objects) - 1 

+

69 

+

70 # Calculate minimum space needed (text + minimum gaps) 

+

71 min_total_width = text_length + (min_spacing * num_gaps) 

+

72 

+

73 # Check if we have overflow (CREngine pattern: always use min_spacing for 

+

74 # overflow) 

+

75 if min_total_width > available_width: 

+

76 return min_spacing, 0, True # Overflow - but use safe minimum spacing 

+

77 

+

78 # Calculate residual space left after accounting for text lengths 

+

79 residual_space = available_width - text_length 

+

80 

+

81 # Calculate ideal spacing 

+

82 actual_spacing = residual_space // num_gaps 

+

83 # Clamp within bounds (CREngine pattern: respect max_spacing) 

+

84 if actual_spacing > max_spacing: 

+

85 return max_spacing, 0, False 

+

86 elif actual_spacing < min_spacing: 86 ↛ 88line 86 didn't jump to line 88 because the condition on line 86 was never true

+

87 # Ensure we never return spacing less than min_spacing 

+

88 return min_spacing, 0, False 

+

89 else: 

+

90 return actual_spacing, 0, False # Use calculated spacing 

+

91 

+

92 

+

93class CenterRightAlignmentHandler(AlignmentHandler): 

+

94 """Handler for center and right-aligned text.""" 

+

95 

+

96 def __init__(self, alignment: Alignment): 

+

97 self._alignment = alignment 

+

98 

+

99 def calculate_spacing_and_position(self, text_objects: List['Text'], 

+

100 available_width: int, min_spacing: int, 

+

101 max_spacing: int) -> Tuple[int, int, bool]: 

+

102 """Center/right alignment uses minimum spacing with calculated start position.""" 

+

103 word_length = sum([word.width for word in text_objects]) 

+

104 residual_space = available_width - word_length 

+

105 

+

106 # Handle single word case 

+

107 if len(text_objects) <= 1: 

+

108 if self._alignment == Alignment.CENTER: 

+

109 start_position = (available_width - word_length) // 2 

+

110 else: # RIGHT 

+

111 start_position = available_width - word_length 

+

112 return 0, max(0, start_position), False 

+

113 

+

114 actual_spacing = residual_space // (len(text_objects) - 1) 

+

115 ideal_space = (min_spacing + max_spacing) / 2 

+

116 if actual_spacing > 0.5 * (min_spacing + max_spacing): 

+

117 actual_spacing = 0.5 * (min_spacing + max_spacing) 

+

118 

+

119 content_length = word_length + (len(text_objects) - 1) * actual_spacing 

+

120 if self._alignment == Alignment.CENTER: 

+

121 start_position = (available_width - content_length) // 2 

+

122 else: 

+

123 start_position = available_width - content_length 

+

124 

+

125 if actual_spacing < min_spacing: 

+

126 return actual_spacing, max(0, start_position), True 

+

127 

+

128 return ideal_space, max(0, start_position), False 

+

129 

+

130 

+

131class JustifyAlignmentHandler(AlignmentHandler): 

+

132 """Handler for justified text with full justification.""" 

+

133 

+

134 def __init__(self): 

+

135 # Store variable spacing for each gap to distribute remainder pixels 

+

136 self._gap_spacings: List[int] = [] 

+

137 

+

138 def calculate_spacing_and_position(self, text_objects: List['Text'], 

+

139 available_width: int, min_spacing: int, 

+

140 max_spacing: int) -> Tuple[int, int, bool]: 

+

141 """ 

+

142 Justified alignment distributes space to fill the entire line width. 

+

143 

+

144 For justified text, we ALWAYS try to fill the entire width by distributing 

+

145 space between words, regardless of max_spacing constraints. The only limit 

+

146 is min_spacing to ensure readability. 

+

147 """ 

+

148 

+

149 word_length = sum([word.width for word in text_objects]) 

+

150 residual_space = available_width - word_length 

+

151 num_gaps = max(1, len(text_objects) - 1) 

+

152 

+

153 # For justified text, calculate the actual spacing needed to fill the line 

+

154 base_spacing = int(residual_space // num_gaps) 

+

155 remainder = int(residual_space % num_gaps) # The extra pixels to distribute 

+

156 

+

157 # Check if we have enough space for minimum spacing 

+

158 if base_spacing < min_spacing: 158 ↛ 160line 158 didn't jump to line 160 because the condition on line 158 was never true

+

159 # Not enough space - this is overflow 

+

160 self._gap_spacings = [min_spacing] * num_gaps 

+

161 return min_spacing, 0, True 

+

162 

+

163 # Distribute remainder pixels across the first 'remainder' gaps 

+

164 # This ensures the line fills the entire width exactly 

+

165 self._gap_spacings = [] 

+

166 for i in range(num_gaps): 

+

167 if i < remainder: 167 ↛ 168line 167 didn't jump to line 168 because the condition on line 167 was never true

+

168 self._gap_spacings.append(base_spacing + 1) 

+

169 else: 

+

170 self._gap_spacings.append(base_spacing) 

+

171 

+

172 return base_spacing, 0, False 

+

173 

+

174 

+

175class Text(Renderable, Queriable): 

+

176 """ 

+

177 Concrete implementation for rendering text. 

+

178 This class handles the visual representation of text fragments. 

+

179 """ 

+

180 

+

181 def __init__( 

+

182 self, 

+

183 text: str, 

+

184 style: Font, 

+

185 draw: ImageDraw.Draw, 

+

186 source: Optional[Word] = None, 

+

187 line: Optional[Line] = None): 

+

188 """ 

+

189 Initialize a Text object. 

+

190 

+

191 Args: 

+

192 text: The text content to render 

+

193 style: The font style to use for rendering 

+

194 """ 

+

195 super().__init__() 

+

196 self._text = text 

+

197 self._style = style 

+

198 self._line = line 

+

199 self._source = source 

+

200 self._origin = np.array([0, 0]) 

+

201 self._draw = draw 

+

202 

+

203 # Calculate dimensions 

+

204 self._calculate_dimensions() 

+

205 

+

206 def _calculate_dimensions(self): 

+

207 """Calculate the width and height of the text based on the font metrics""" 

+

208 # Get the size using PIL's text size functionality 

+

209 font = self._style.font 

+

210 self._width = self._draw.textlength(self._text, font=font) 

+

211 ascent, descent = font.getmetrics() 

+

212 self._ascent = ascent 

+

213 self._middle_y = ascent - descent / 2 

+

214 

+

215 @classmethod 

+

216 def from_word(cls, word: Word, draw: ImageDraw.Draw): 

+

217 return cls(word.text, word.style, draw) 

+

218 

+

219 @property 

+

220 def text(self) -> str: 

+

221 """Get the text content""" 

+

222 return self._text 

+

223 

+

224 @property 

+

225 def style(self) -> Font: 

+

226 """Get the text style""" 

+

227 return self._style 

+

228 

+

229 @property 

+

230 def origin(self) -> np.ndarray: 

+

231 """Get the origin of the text""" 

+

232 return self._origin 

+

233 

+

234 @property 

+

235 def line(self) -> Optional[Line]: 

+

236 """Get the line containing this text""" 

+

237 return self._line 

+

238 

+

239 @line.setter 

+

240 def line(self, line): 

+

241 """Set the line containing this text""" 

+

242 self._line = line 

+

243 

+

244 @property 

+

245 def width(self) -> int: 

+

246 """Get the width of the text""" 

+

247 return self._width 

+

248 

+

249 @property 

+

250 def size(self) -> int: 

+

251 """Get the width and height of the text""" 

+

252 # Return actual rendered height (ascent + descent) not just font_size 

+

253 ascent, descent = self._style.font.getmetrics() 

+

254 actual_height = ascent + descent 

+

255 return np.array((self._width, actual_height)) 

+

256 

+

257 def set_origin(self, origin: np.generic): 

+

258 """Set the origin (left baseline ("ls")) of this text element""" 

+

259 self._origin = origin 

+

260 

+

261 def add_line(self, line): 

+

262 """Add this text to a line""" 

+

263 self._line = line 

+

264 

+

265 def in_object(self, point: np.generic): 

+

266 """ 

+

267 Check if a point is in the text object. 

+

268 

+

269 Override Queriable.in_object() because Text uses baseline-anchored positioning. 

+

270 The origin is at the baseline (anchor="ls"), not the top-left corner. 

+

271 

+

272 Args: 

+

273 point: The coordinates to check 

+

274 

+

275 Returns: 

+

276 True if the point is within the text bounds 

+

277 """ 

+

278 point_array = np.array(point) 

+

279 

+

280 # Text origin is at baseline, so visual top is origin[1] - ascent 

+

281 visual_top = self._origin[1] - self._ascent 

+

282 visual_bottom = self._origin[1] + (self.size[1] - self._ascent) 

+

283 

+

284 # Check if point is within bounds 

+

285 # X: origin[0] to origin[0] + width 

+

286 # Y: visual_top to visual_bottom 

+

287 return (self._origin[0] <= point_array[0] < self._origin[0] + self.size[0] and 

+

288 visual_top <= point_array[1] < visual_bottom) 

+

289 

+

290 def _apply_decoration(self, next_text: Optional['Text'] = None, spacing: int = 0): 

+

291 """ 

+

292 Apply text decoration (underline or strikethrough). 

+

293 

+

294 Args: 

+

295 next_text: The next Text object in the line (if any) 

+

296 spacing: The spacing to the next text object 

+

297 """ 

+

298 if self._style.decoration == TextDecoration.UNDERLINE: 

+

299 # Draw underline at about 90% of the height 

+

300 y_position = self._origin[1] - 0.1 * self._style.font_size 

+

301 line_width = max(1, int(self._style.font_size / 15)) 

+

302 

+

303 # Determine end x-coordinate 

+

304 end_x = self._origin[0] + self._width 

+

305 

+

306 # If next text also has underline decoration, extend to connect them 

+

307 if (next_text is not None and 

+

308 next_text.style.decoration == TextDecoration.UNDERLINE and 

+

309 next_text.style.colour == self._style.colour): 

+

310 # Extend the underline through the spacing to connect with next word 

+

311 end_x += spacing 

+

312 

+

313 self._draw.line([(self._origin[0], y_position), (end_x, y_position)], 

+

314 fill=self._style.colour, width=line_width) 

+

315 

+

316 elif self._style.decoration == TextDecoration.STRIKETHROUGH: 316 ↛ 318line 316 didn't jump to line 318 because the condition on line 316 was never true

+

317 # Draw strikethrough at about 50% of the height 

+

318 y_position = self._origin[1] + self._middle_y 

+

319 line_width = max(1, int(self._style.font_size / 15)) 

+

320 

+

321 # Determine end x-coordinate 

+

322 end_x = self._origin[0] + self._width 

+

323 

+

324 # If next text also has strikethrough decoration, extend to connect them 

+

325 if (next_text is not None and 

+

326 next_text.style.decoration == TextDecoration.STRIKETHROUGH and 

+

327 next_text.style.colour == self._style.colour): 

+

328 # Extend the strikethrough through the spacing to connect with next word 

+

329 end_x += spacing 

+

330 

+

331 self._draw.line([(self._origin[0], y_position), (end_x, y_position)], 

+

332 fill=self._style.colour, width=line_width) 

+

333 

+

334 def render(self, next_text: Optional['Text'] = None, spacing: int = 0): 

+

335 """ 

+

336 Render the text to an image. 

+

337 

+

338 Args: 

+

339 next_text: The next Text object in the line (if any) 

+

340 spacing: The spacing to the next text object 

+

341 

+

342 Returns: 

+

343 A PIL Image containing the rendered text 

+

344 """ 

+

345 

+

346 # Draw the text background if specified 

+

347 if self._style.background and self._style.background[3] > 0: # If alpha > 0 347 ↛ 348line 347 didn't jump to line 348 because the condition on line 347 was never true

+

348 self._draw.rectangle([self._origin, self._origin + 

+

349 self._size], fill=self._style.background) 

+

350 

+

351 # Draw the text using baseline as anchor point ("ls" = left-baseline) 

+

352 # This ensures the origin represents the baseline, not the top-left 

+

353 self._draw.text( 

+

354 (self.origin[0], 

+

355 self._origin[1]), 

+

356 self._text, 

+

357 font=self._style.font, 

+

358 fill=self._style.colour, 

+

359 anchor="ls") 

+

360 

+

361 # Apply any text decorations with knowledge of next text 

+

362 self._apply_decoration(next_text, spacing) 

+

363 

+

364 

+

365class Line(Box): 

+

366 """ 

+

367 A line of text consisting of Text objects with consistent spacing. 

+

368 Each Text represents a word or word fragment that can be rendered. 

+

369 """ 

+

370 

+

371 def __init__(self, 

+

372 spacing: Tuple[int, 

+

373 int], 

+

374 origin, 

+

375 size, 

+

376 draw: ImageDraw.Draw, 

+

377 font: Optional[Font] = None, 

+

378 callback=None, 

+

379 sheet=None, 

+

380 mode=None, 

+

381 halign=Alignment.CENTER, 

+

382 valign=Alignment.CENTER, 

+

383 previous=None, 

+

384 min_word_length_for_brute_force: int = 8, 

+

385 min_chars_before_hyphen: int = 2, 

+

386 min_chars_after_hyphen: int = 2): 

+

387 """ 

+

388 Initialize a new line. 

+

389 

+

390 Args: 

+

391 spacing: A tuple of (min_spacing, max_spacing) between words 

+

392 origin: The top-left position of the line 

+

393 size: The width and height of the line 

+

394 font: The default font to use for text in this line 

+

395 callback: Optional callback function 

+

396 sheet: Optional image sheet 

+

397 mode: Optional image mode 

+

398 halign: Horizontal alignment of text within the line 

+

399 valign: Vertical alignment of text within the line 

+

400 previous: Reference to the previous line 

+

401 min_word_length_for_brute_force: Minimum word length to attempt brute force hyphenation (default: 8) 

+

402 min_chars_before_hyphen: Minimum characters before hyphen in any split (default: 2) 

+

403 min_chars_after_hyphen: Minimum characters after hyphen in any split (default: 2) 

+

404 """ 

+

405 super().__init__(origin, size, callback, sheet, mode, halign, valign) 

+

406 self._text_objects: List['Text'] = [] # Store Text objects directly 

+

407 self._spacing = spacing # (min_spacing, max_spacing) 

+

408 self._font = font if font else Font() # Use default font if none provided 

+

409 self._current_width = 0 # Track the current width used 

+

410 self._words: List['Word'] = [] 

+

411 self._previous = previous 

+

412 self._next = None 

+

413 ascent, descent = self._font.font.getmetrics() 

+

414 # Store baseline as offset from line origin (top), not absolute position 

+

415 self._baseline = ascent 

+

416 self._draw = draw 

+

417 self._spacing_render = (spacing[0] + spacing[1]) // 2 

+

418 self._position_render = 0 

+

419 

+

420 # Hyphenation configuration parameters 

+

421 self._min_word_length_for_brute_force = min_word_length_for_brute_force 

+

422 self._min_chars_before_hyphen = min_chars_before_hyphen 

+

423 self._min_chars_after_hyphen = min_chars_after_hyphen 

+

424 

+

425 # Create the appropriate alignment handler 

+

426 self._alignment_handler = self._create_alignment_handler(halign) 

+

427 

+

428 def _create_alignment_handler(self, alignment: Alignment) -> AlignmentHandler: 

+

429 """ 

+

430 Create the appropriate alignment handler based on the alignment type. 

+

431 

+

432 Args: 

+

433 alignment: The alignment type 

+

434 

+

435 Returns: 

+

436 The appropriate alignment handler instance 

+

437 """ 

+

438 if alignment == Alignment.LEFT: 

+

439 return LeftAlignmentHandler() 

+

440 elif alignment == Alignment.JUSTIFY: 

+

441 return JustifyAlignmentHandler() 

+

442 else: # CENTER or RIGHT 

+

443 return CenterRightAlignmentHandler(alignment) 

+

444 

+

445 @property 

+

446 def text_objects(self) -> List[Text]: 

+

447 """Get the list of Text objects in this line""" 

+

448 return self._text_objects 

+

449 

+

450 def set_next(self, line: Line): 

+

451 """Set the next line in sequence""" 

+

452 self._next = line 

+

453 

+

454 def add_word(self, 

+

455 word: 'Word', 

+

456 part: Optional[Text] = None) -> Tuple[bool, 

+

457 Optional['Text']]: 

+

458 """ 

+

459 Add a word to this line using intelligent word fitting strategies. 

+

460 

+

461 Args: 

+

462 word: The word to add to the line 

+

463 part: Optional pretext from a previous hyphenated word 

+

464 

+

465 Returns: 

+

466 Tuple of (success, overflow_text): 

+

467 - success: True if word/part was added, False if it couldn't fit 

+

468 - overflow_text: Remaining text if word was hyphenated, None otherwise 

+

469 """ 

+

470 # First, add any pretext from previous hyphenation 

+

471 if part is not None: 

+

472 self._text_objects.append(part) 

+

473 self._words.append(word) 

+

474 part.add_line(self) 

+

475 

+

476 # Try to add the full word - create LinkText for LinkedWord, regular Text 

+

477 # otherwise 

+

478 if isinstance(word, LinkedWord): 

+

479 # Import here to avoid circular dependency 

+

480 from .functional import LinkText 

+

481 # Create a LinkText which includes the link functionality 

+

482 # LinkText constructor needs: (link, text, font, draw, source, line) 

+

483 # But LinkedWord itself contains the link properties 

+

484 # We'll create a Link object from the LinkedWord properties 

+

485 link = Link( 

+

486 location=word.location, 

+

487 link_type=word.link_type, 

+

488 callback=word.link_callback, 

+

489 params=word.params, 

+

490 title=word.link_title 

+

491 ) 

+

492 text = LinkText( 

+

493 link, 

+

494 word.text, 

+

495 word.style, 

+

496 self._draw, 

+

497 source=word, 

+

498 line=self) 

+

499 else: 

+

500 text = Text.from_word(word, self._draw) 

+

501 self._text_objects.append(text) 

+

502 spacing, position, overflow = self._alignment_handler.calculate_spacing_and_position( 

+

503 self._text_objects, self._size[0], self._spacing[0], self._spacing[1]) 

+

504 

+

505 if not overflow: 

+

506 # Word fits! Add it completely 

+

507 self._words.append(word) 

+

508 word.add_concete(text) 

+

509 text.add_line(self) 

+

510 self._position_render = position 

+

511 self._spacing_render = spacing 

+

512 return True, None 

+

513 

+

514 # Word doesn't fit, remove it and try hyphenation 

+

515 _ = self._text_objects.pop() 

+

516 

+

517 # Step 1: Try pyphen hyphenation 

+

518 pyphen_splits = word.possible_hyphenation() 

+

519 valid_splits = [] 

+

520 

+

521 if pyphen_splits: 

+

522 # Create Text objects for each possible split and check if they fit 

+

523 for pair in pyphen_splits: 

+

524 first_part_text = pair[0] + "-" 

+

525 second_part_text = pair[1] 

+

526 

+

527 # Validate minimum character requirements 

+

528 if len(pair[0]) < self._min_chars_before_hyphen: 528 ↛ 529line 528 didn't jump to line 529 because the condition on line 528 was never true

+

529 continue 

+

530 if len(pair[1]) < self._min_chars_after_hyphen: 530 ↛ 531line 530 didn't jump to line 531 because the condition on line 530 was never true

+

531 continue 

+

532 

+

533 # Create Text objects 

+

534 first_text = Text( 

+

535 first_part_text, 

+

536 word.style, 

+

537 self._draw, 

+

538 line=self, 

+

539 source=word) 

+

540 second_text = Text( 

+

541 second_part_text, 

+

542 word.style, 

+

543 self._draw, 

+

544 line=self, 

+

545 source=word) 

+

546 

+

547 # Check if first part fits 

+

548 self._text_objects.append(first_text) 

+

549 spacing, position, overflow = self._alignment_handler.calculate_spacing_and_position( 

+

550 self._text_objects, self._size[0], self._spacing[0], self._spacing[1]) 

+

551 _ = self._text_objects.pop() 

+

552 

+

553 if not overflow: 

+

554 # This split fits! Add it to valid options 

+

555 valid_splits.append((first_text, second_text, spacing, position)) 

+

556 

+

557 # Step 2: If we have valid pyphen splits, choose the best one 

+

558 if valid_splits: 

+

559 # Select the split with the best (minimum) spacing 

+

560 best_split = min(valid_splits, key=lambda x: x[2]) 

+

561 first_text, second_text, spacing, position = best_split 

+

562 

+

563 # Apply the split 

+

564 self._text_objects.append(first_text) 

+

565 first_text.line = self 

+

566 word.add_concete((first_text, second_text)) 

+

567 self._spacing_render = spacing 

+

568 self._position_render = position 

+

569 self._words.append(word) 

+

570 return True, second_text 

+

571 

+

572 # Step 3: Try brute force hyphenation (only for long words) 

+

573 if len(word.text) >= self._min_word_length_for_brute_force: 

+

574 # Calculate available space for the word 

+

575 word_length = sum([text.width for text in self._text_objects]) 

+

576 spacing_length = self._spacing[0] * max(0, len(self._text_objects) - 1) 

+

577 remaining = self._size[0] - word_length - spacing_length 

+

578 

+

579 if remaining > 0: 

+

580 # Create a hyphenated version to measure 

+

581 test_text = Text(word.text + "-", word.style, self._draw) 

+

582 

+

583 if test_text.width > 0: 583 ↛ 637line 583 didn't jump to line 637 because the condition on line 583 was always true

+

584 # Calculate what fraction of the hyphenated word fits 

+

585 fraction = remaining / test_text.width 

+

586 

+

587 # Convert fraction to character position 

+

588 # We need at least min_chars_before_hyphen and leave at least 

+

589 # min_chars_after_hyphen 

+

590 max_split_pos = len(word.text) - self._min_chars_after_hyphen 

+

591 min_split_pos = self._min_chars_before_hyphen 

+

592 

+

593 # Calculate ideal split position based on available space 

+

594 ideal_split = int(fraction * len(word.text)) 

+

595 split_pos = max(min_split_pos, min(ideal_split, max_split_pos)) 

+

596 

+

597 # Ensure we meet minimum requirements 

+

598 if (split_pos >= self._min_chars_before_hyphen and 598 ↛ 637line 598 didn't jump to line 637 because the condition on line 598 was always true

+

599 len(word.text) - split_pos >= self._min_chars_after_hyphen): 

+

600 

+

601 # Create the split 

+

602 first_part_text = word.text[:split_pos] + "-" 

+

603 second_part_text = word.text[split_pos:] 

+

604 

+

605 first_text = Text( 

+

606 first_part_text, 

+

607 word.style, 

+

608 self._draw, 

+

609 line=self, 

+

610 source=word) 

+

611 second_text = Text( 

+

612 second_part_text, 

+

613 word.style, 

+

614 self._draw, 

+

615 line=self, 

+

616 source=word) 

+

617 

+

618 # Verify the first part actually fits 

+

619 self._text_objects.append(first_text) 

+

620 spacing, position, overflow = self._alignment_handler.calculate_spacing_and_position( 

+

621 self._text_objects, self._size[0], self._spacing[0], self._spacing[1]) 

+

622 

+

623 if not overflow: 623 ↛ 625line 623 didn't jump to line 625 because the condition on line 623 was never true

+

624 # Brute force split works! 

+

625 first_text.line = self 

+

626 second_text.line = self 

+

627 word.add_concete((first_text, second_text)) 

+

628 self._spacing_render = spacing 

+

629 self._position_render = position 

+

630 self._words.append(word) 

+

631 return True, second_text 

+

632 else: 

+

633 # Doesn't fit, remove it 

+

634 _ = self._text_objects.pop() 

+

635 

+

636 # Step 4: Word cannot be hyphenated or split, move to next line 

+

637 return False, None 

+

638 

+

639 def render(self): 

+

640 """ 

+

641 Render the line with all its text objects using the alignment handler system. 

+

642 

+

643 Returns: 

+

644 A PIL Image containing the rendered line 

+

645 """ 

+

646 # Recalculate spacing and position for current text objects to ensure accuracy 

+

647 if len(self._text_objects) > 0: 

+

648 spacing, position, overflow = self._alignment_handler.calculate_spacing_and_position( 

+

649 self._text_objects, self._size[0], self._spacing[0], self._spacing[1]) 

+

650 self._spacing_render = spacing 

+

651 self._position_render = position 

+

652 

+

653 y_cursor = self._origin[1] + self._baseline 

+

654 

+

655 # Start x_cursor at line origin plus any alignment offset 

+

656 x_cursor = self._origin[0] + self._position_render 

+

657 for i, text in enumerate(self._text_objects): 

+

658 # Update text draw context to current draw context 

+

659 text._draw = self._draw 

+

660 text.set_origin(np.array([x_cursor, y_cursor])) 

+

661 

+

662 # Determine next text object for continuous decoration 

+

663 next_text = self._text_objects[i + 1] if i + \ 

+

664 1 < len(self._text_objects) else None 

+

665 

+

666 # Get the spacing for this specific gap (variable for justified text) 

+

667 if isinstance(self._alignment_handler, JustifyAlignmentHandler) and \ 

+

668 hasattr(self._alignment_handler, '_gap_spacings') and \ 

+

669 i < len(self._alignment_handler._gap_spacings): 

+

670 current_spacing = self._alignment_handler._gap_spacings[i] 

+

671 else: 

+

672 current_spacing = self._spacing_render 

+

673 

+

674 # Render with next text information for continuous underline/strikethrough 

+

675 text.render(next_text, current_spacing) 

+

676 # Add text width, then spacing only if there are more words 

+

677 x_cursor += text.width 

+

678 if i < len(self._text_objects) - 1: 

+

679 x_cursor += current_spacing 

+

680 

+

681 def query_point(self, point: Tuple[int, int]) -> Optional['QueryResult']: 

+

682 """ 

+

683 Find which Text object contains the given point. 

+

684 Uses Queriable.in_object() mixin for hit-testing. 

+

685 

+

686 Args: 

+

687 point: (x, y) coordinates to query 

+

688 

+

689 Returns: 

+

690 QueryResult from the text object at that point, or None 

+

691 """ 

+

692 point_array = np.array(point) 

+

693 

+

694 # Check each text object in this line 

+

695 for text_obj in self._text_objects: 

+

696 # Use Queriable mixin's in_object() for hit-testing 

+

697 if isinstance(text_obj, Queriable) and text_obj.in_object(point_array): 

+

698 # Extract metadata based on text type 

+

699 origin = text_obj._origin 

+

700 size = text_obj.size 

+

701 

+

702 # Text origin is at baseline (anchor="ls"), so visual top is origin[1] - ascent 

+

703 # Bounds should be (x, visual_top, width, height) for proper 

+

704 # highlighting 

+

705 visual_top = int(origin[1] - text_obj._ascent) 

+

706 bounds = ( 

+

707 int(origin[0]), 

+

708 visual_top, 

+

709 int(size[0]) if hasattr(size, '__getitem__') else 0, 

+

710 int(size[1]) if hasattr(size, '__getitem__') else 0 

+

711 ) 

+

712 

+

713 # Import here to avoid circular dependency 

+

714 from .functional import LinkText, ButtonText 

+

715 

+

716 if isinstance(text_obj, LinkText): 

+

717 result = QueryResult( 

+

718 object=text_obj, 

+

719 object_type="link", 

+

720 bounds=bounds, 

+

721 text=text_obj._text, 

+

722 is_interactive=True, 

+

723 link_target=text_obj._link.location if hasattr( 

+

724 text_obj, 

+

725 '_link') else None) 

+

726 elif isinstance(text_obj, ButtonText): 726 ↛ 727line 726 didn't jump to line 727 because the condition on line 726 was never true

+

727 result = QueryResult( 

+

728 object=text_obj, 

+

729 object_type="button", 

+

730 bounds=bounds, 

+

731 text=text_obj._text, 

+

732 is_interactive=True, 

+

733 callback=text_obj._callback if hasattr( 

+

734 text_obj, 

+

735 '_callback') else None) 

+

736 else: 

+

737 result = QueryResult( 

+

738 object=text_obj, 

+

739 object_type="text", 

+

740 bounds=bounds, 

+

741 text=text_obj._text if hasattr(text_obj, '_text') else None 

+

742 ) 

+

743 

+

744 result.parent_line = self 

+

745 return result 

+

746 

+

747 return None 

+
+ + + diff --git a/cov_info/htmlcov/z_af715639580e2d86___init___py.html b/cov_info/htmlcov/z_af715639580e2d86___init___py.html new file mode 100644 index 0000000..47dbaa4 --- /dev/null +++ b/cov_info/htmlcov/z_af715639580e2d86___init___py.html @@ -0,0 +1,121 @@ + + + + + Coverage for pyWebLayout/abstract/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/abstract/__init__.py: + 100% +

+ +

+ 5 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Abstract layer for the pyWebLayout library. 

+

3 

+

4This package contains abstract representations of document elements that are 

+

5independent of rendering specifics. 

+

6""" 

+

7 

+

8from .inline import Word, FormattedSpan 

+

9from .block import Paragraph, Heading, Image, HeadingLevel 

+

10from .document import Document 

+

11from .functional import LinkType 

+

12 

+

13__all__ = [ 

+

14 'Word', 

+

15 'FormattedSpan', 

+

16 'Paragraph', 

+

17 'Heading', 

+

18 'Image', 

+

19 'HeadingLevel', 

+

20 'Document', 

+

21 'LinkType', 

+

22] 

+
+ + + diff --git a/cov_info/htmlcov/z_af715639580e2d86_block_py.html b/cov_info/htmlcov/z_af715639580e2d86_block_py.html new file mode 100644 index 0000000..2509bb7 --- /dev/null +++ b/cov_info/htmlcov/z_af715639580e2d86_block_py.html @@ -0,0 +1,1514 @@ + + + + + Coverage for pyWebLayout/abstract/block.py: 79% + + + + + +
+
+

+ Coverage for pyWebLayout/abstract/block.py: + 79% +

+ +

+ 489 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from typing import List, Iterator, Tuple, Dict, Optional, Any 

+

2from enum import Enum 

+

3import os 

+

4import tempfile 

+

5import urllib.request 

+

6import urllib.parse 

+

7from PIL import Image as PILImage 

+

8from .inline import Word, FormattedSpan 

+

9from ..core import Hierarchical, Styleable, FontRegistry, ContainerAware, BlockContainer 

+

10 

+

11 

+

12class BlockType(Enum): 

+

13 """Enumeration of different block types for classification purposes""" 

+

14 PARAGRAPH = 1 

+

15 HEADING = 2 

+

16 QUOTE = 3 

+

17 CODE_BLOCK = 4 

+

18 LIST = 5 

+

19 LIST_ITEM = 6 

+

20 TABLE = 7 

+

21 TABLE_ROW = 8 

+

22 TABLE_CELL = 9 

+

23 HORIZONTAL_RULE = 10 

+

24 LINE_BREAK = 11 

+

25 IMAGE = 12 

+

26 PAGE_BREAK = 13 

+

27 

+

28 

+

29class Block(Hierarchical): 

+

30 """ 

+

31 Base class for all block-level elements. 

+

32 Block elements typically represent visual blocks of content that stack vertically. 

+

33 

+

34 Uses Hierarchical mixin for parent-child relationship management. 

+

35 """ 

+

36 

+

37 def __init__(self, block_type: BlockType): 

+

38 """ 

+

39 Initialize a block element. 

+

40 

+

41 Args: 

+

42 block_type: The type of block this element represents 

+

43 """ 

+

44 super().__init__() 

+

45 self._block_type = block_type 

+

46 

+

47 @property 

+

48 def block_type(self) -> BlockType: 

+

49 """Get the type of this block element""" 

+

50 return self._block_type 

+

51 

+

52 

+

53class Paragraph(Styleable, FontRegistry, ContainerAware, Block): 

+

54 """ 

+

55 A paragraph is a block-level element that contains a sequence of words. 

+

56 

+

57 Uses Styleable mixin for style property management. 

+

58 Uses FontRegistry mixin for font caching with parent delegation. 

+

59 """ 

+

60 

+

61 def __init__(self, style=None): 

+

62 """ 

+

63 Initialize an empty paragraph 

+

64 

+

65 Args: 

+

66 style: Optional default style for words in this paragraph 

+

67 """ 

+

68 super().__init__(style=style, block_type=BlockType.PARAGRAPH) 

+

69 self._words: List[Word] = [] 

+

70 self._spans: List[FormattedSpan] = [] 

+

71 

+

72 @classmethod 

+

73 def create_and_add_to(cls, container, style=None) -> 'Paragraph': 

+

74 """ 

+

75 Create a new Paragraph and add it to a container, inheriting style from 

+

76 the container if not explicitly provided. 

+

77 

+

78 Args: 

+

79 container: The container to add the paragraph to (must have add_block method and style property) 

+

80 style: Optional style override. If None, inherits from container 

+

81 

+

82 Returns: 

+

83 The newly created Paragraph object 

+

84 

+

85 Raises: 

+

86 AttributeError: If the container doesn't have the required add_block method 

+

87 """ 

+

88 # Validate container and inherit style using ContainerAware utilities 

+

89 cls._validate_container(container) 

+

90 style = cls._inherit_style(container, style) 

+

91 

+

92 # Create the new paragraph 

+

93 paragraph = cls(style) 

+

94 

+

95 # Add the paragraph to the container 

+

96 container.add_block(paragraph) 

+

97 

+

98 return paragraph 

+

99 

+

100 def add_word(self, word: Word): 

+

101 """ 

+

102 Add a word to this paragraph. 

+

103 

+

104 Args: 

+

105 word: The Word object to add 

+

106 """ 

+

107 self._words.append(word) 

+

108 

+

109 def create_word(self, text: str, style=None, background=None) -> Word: 

+

110 """ 

+

111 Create a new word and add it to this paragraph, inheriting paragraph's style if not specified. 

+

112 

+

113 This is a convenience method that uses Word.create_and_add_to() to create words 

+

114 that automatically inherit styling from this paragraph. 

+

115 

+

116 Args: 

+

117 text: The text content of the word 

+

118 style: Optional Font style override. If None, attempts to inherit from paragraph 

+

119 background: Optional background color override 

+

120 

+

121 Returns: 

+

122 The newly created Word object 

+

123 """ 

+

124 return Word.create_and_add_to(text, self, style, background) 

+

125 

+

126 def add_span(self, span: FormattedSpan): 

+

127 """ 

+

128 Add a formatted span to this paragraph. 

+

129 

+

130 Args: 

+

131 span: The FormattedSpan object to add 

+

132 """ 

+

133 self._spans.append(span) 

+

134 

+

135 def create_span(self, style=None, background=None) -> FormattedSpan: 

+

136 """ 

+

137 Create a new formatted span with inherited style. 

+

138 

+

139 Args: 

+

140 style: Optional Font style override. If None, inherits from paragraph 

+

141 background: Optional background color override 

+

142 

+

143 Returns: 

+

144 The newly created FormattedSpan object 

+

145 """ 

+

146 return FormattedSpan.create_and_add_to(self, style, background) 

+

147 

+

148 @property 

+

149 def words(self) -> List[Word]: 

+

150 """Get the list of words in this paragraph""" 

+

151 return self._words 

+

152 

+

153 def words_iter(self) -> Iterator[Tuple[int, Word]]: 

+

154 """ 

+

155 Iterate over the words in this paragraph. 

+

156 

+

157 Yields: 

+

158 Tuples of (index, word) for each word in the paragraph 

+

159 """ 

+

160 for i, word in enumerate(self._words): 

+

161 yield i, word 

+

162 

+

163 def spans(self) -> Iterator[FormattedSpan]: 

+

164 """ 

+

165 Iterate over the formatted spans in this paragraph. 

+

166 

+

167 Yields: 

+

168 Each FormattedSpan in the paragraph 

+

169 """ 

+

170 for span in self._spans: 

+

171 yield span 

+

172 

+

173 @property 

+

174 def word_count(self) -> int: 

+

175 """Get the number of words in this paragraph""" 

+

176 return len(self._words) 

+

177 

+

178 def __len__(self): 

+

179 return self.word_count 

+

180 

+

181 # get_or_create_font() is provided by FontRegistry mixin 

+

182 

+

183 

+

184class HeadingLevel(Enum): 

+

185 """Enumeration representing HTML heading levels (h1-h6)""" 

+

186 H1 = 1 

+

187 H2 = 2 

+

188 H3 = 3 

+

189 H4 = 4 

+

190 H5 = 5 

+

191 H6 = 6 

+

192 

+

193 

+

194class Heading(Paragraph): 

+

195 """ 

+

196 A heading element (h1, h2, h3, etc.) that contains text with a specific heading level. 

+

197 Headings inherit from Paragraph as they contain words but have additional properties. 

+

198 """ 

+

199 

+

200 def __init__(self, level: HeadingLevel = HeadingLevel.H1, style=None): 

+

201 """ 

+

202 Initialize a heading element. 

+

203 

+

204 Args: 

+

205 level: The heading level (h1-h6) 

+

206 style: Optional default style for words in this heading 

+

207 """ 

+

208 super().__init__(style) 

+

209 self._block_type = BlockType.HEADING 

+

210 self._level = level 

+

211 

+

212 @classmethod 

+

213 def create_and_add_to( 

+

214 cls, 

+

215 container, 

+

216 level: HeadingLevel = HeadingLevel.H1, 

+

217 style=None) -> 'Heading': 

+

218 """ 

+

219 Create a new Heading and add it to a container, inheriting style from 

+

220 the container if not explicitly provided. 

+

221 

+

222 Args: 

+

223 container: The container to add the heading to (must have add_block method and style property) 

+

224 level: The heading level (h1-h6) 

+

225 style: Optional style override. If None, inherits from container 

+

226 

+

227 Returns: 

+

228 The newly created Heading object 

+

229 

+

230 Raises: 

+

231 AttributeError: If the container doesn't have the required add_block method 

+

232 """ 

+

233 # Validate container and inherit style using ContainerAware utilities 

+

234 cls._validate_container(container) 

+

235 style = cls._inherit_style(container, style) 

+

236 

+

237 # Create the new heading 

+

238 heading = cls(level, style) 

+

239 

+

240 # Add the heading to the container 

+

241 container.add_block(heading) 

+

242 

+

243 return heading 

+

244 

+

245 @property 

+

246 def level(self) -> HeadingLevel: 

+

247 """Get the heading level""" 

+

248 return self._level 

+

249 

+

250 @level.setter 

+

251 def level(self, level: HeadingLevel): 

+

252 """Set the heading level""" 

+

253 self._level = level 

+

254 

+

255 

+

256class Quote(BlockContainer, ContainerAware, Block): 

+

257 """ 

+

258 A blockquote element that can contain other block elements. 

+

259 """ 

+

260 

+

261 def __init__(self, style=None): 

+

262 """ 

+

263 Initialize an empty blockquote 

+

264 

+

265 Args: 

+

266 style: Optional default style for child blocks 

+

267 """ 

+

268 super().__init__(BlockType.QUOTE) 

+

269 self._style = style 

+

270 

+

271 @classmethod 

+

272 def create_and_add_to(cls, container, style=None) -> 'Quote': 

+

273 """ 

+

274 Create a new Quote and add it to a container, inheriting style from 

+

275 the container if not explicitly provided. 

+

276 

+

277 Args: 

+

278 container: The container to add the quote to (must have add_block method and style property) 

+

279 style: Optional style override. If None, inherits from container 

+

280 

+

281 Returns: 

+

282 The newly created Quote object 

+

283 

+

284 Raises: 

+

285 AttributeError: If the container doesn't have the required add_block method 

+

286 """ 

+

287 # Validate container and inherit style using ContainerAware utilities 

+

288 cls._validate_container(container) 

+

289 style = cls._inherit_style(container, style) 

+

290 

+

291 # Create the new quote 

+

292 quote = cls(style) 

+

293 

+

294 # Add the quote to the container 

+

295 container.add_block(quote) 

+

296 

+

297 return quote 

+

298 

+

299 @property 

+

300 def style(self): 

+

301 """Get the default style for this quote""" 

+

302 return self._style 

+

303 

+

304 @style.setter 

+

305 def style(self, style): 

+

306 """Set the default style for this quote""" 

+

307 self._style = style 

+

308 

+

309 

+

310class CodeBlock(Block): 

+

311 """ 

+

312 A code block element containing pre-formatted text with syntax highlighting. 

+

313 """ 

+

314 

+

315 def __init__(self, language: str = ""): 

+

316 """ 

+

317 Initialize a code block. 

+

318 

+

319 Args: 

+

320 language: The programming language for syntax highlighting 

+

321 """ 

+

322 super().__init__(BlockType.CODE_BLOCK) 

+

323 self._language = language 

+

324 self._lines: List[str] = [] 

+

325 

+

326 @classmethod 

+

327 def create_and_add_to(cls, container, language: str = "") -> 'CodeBlock': 

+

328 """ 

+

329 Create a new CodeBlock and add it to a container. 

+

330 

+

331 Args: 

+

332 container: The container to add the code block to (must have add_block method) 

+

333 language: The programming language for syntax highlighting 

+

334 

+

335 Returns: 

+

336 The newly created CodeBlock object 

+

337 

+

338 Raises: 

+

339 AttributeError: If the container doesn't have the required add_block method 

+

340 """ 

+

341 # Create the new code block 

+

342 code_block = cls(language) 

+

343 

+

344 # Add the code block to the container 

+

345 if hasattr(container, 'add_block'): 

+

346 container.add_block(code_block) 

+

347 else: 

+

348 raise AttributeError( 

+

349 f"Container {type(container).__name__} must have an 'add_block' method" 

+

350 ) 

+

351 

+

352 return code_block 

+

353 

+

354 @property 

+

355 def language(self) -> str: 

+

356 """Get the programming language""" 

+

357 return self._language 

+

358 

+

359 @language.setter 

+

360 def language(self, language: str): 

+

361 """Set the programming language""" 

+

362 self._language = language 

+

363 

+

364 def add_line(self, line: str): 

+

365 """ 

+

366 Add a line of code to this code block. 

+

367 

+

368 Args: 

+

369 line: The line of code to add 

+

370 """ 

+

371 self._lines.append(line) 

+

372 

+

373 def lines(self) -> Iterator[Tuple[int, str]]: 

+

374 """ 

+

375 Iterate over the lines in this code block. 

+

376 

+

377 Yields: 

+

378 Tuples of (line_number, line_text) for each line 

+

379 """ 

+

380 for i, line in enumerate(self._lines): 

+

381 yield i, line 

+

382 

+

383 @property 

+

384 def line_count(self) -> int: 

+

385 """Get the number of lines in this code block""" 

+

386 return len(self._lines) 

+

387 

+

388 

+

389class ListStyle(Enum): 

+

390 """Enumeration of list styles""" 

+

391 UNORDERED = 1 # <ul> 

+

392 ORDERED = 2 # <ol> 

+

393 DEFINITION = 3 # <dl> 

+

394 

+

395 

+

396class HList(ContainerAware, Block): 

+

397 """ 

+

398 An HTML list element (ul, ol, dl). 

+

399 """ 

+

400 

+

401 def __init__(self, style: ListStyle = ListStyle.UNORDERED, default_style=None): 

+

402 """ 

+

403 Initialize a list. 

+

404 

+

405 Args: 

+

406 style: The style of list (unordered, ordered, definition) 

+

407 default_style: Optional default style for child items 

+

408 """ 

+

409 super().__init__(BlockType.LIST) 

+

410 self._style = style 

+

411 self._items: List[ListItem] = [] 

+

412 self._default_style = default_style 

+

413 

+

414 @classmethod 

+

415 def create_and_add_to( 

+

416 cls, 

+

417 container, 

+

418 style: ListStyle = ListStyle.UNORDERED, 

+

419 default_style=None) -> 'HList': 

+

420 """ 

+

421 Create a new HList and add it to a container, inheriting style from 

+

422 the container if not explicitly provided. 

+

423 

+

424 Args: 

+

425 container: The container to add the list to (must have add_block method) 

+

426 style: The style of list (unordered, ordered, definition) 

+

427 default_style: Optional default style for child items. If None, inherits from container 

+

428 

+

429 Returns: 

+

430 The newly created HList object 

+

431 

+

432 Raises: 

+

433 AttributeError: If the container doesn't have the required add_block method 

+

434 """ 

+

435 # Validate container and inherit style using ContainerAware utilities 

+

436 cls._validate_container(container) 

+

437 default_style = cls._inherit_style(container, default_style) 

+

438 

+

439 # Create the new list 

+

440 hlist = cls(style, default_style) 

+

441 

+

442 # Add the list to the container 

+

443 container.add_block(hlist) 

+

444 

+

445 return hlist 

+

446 

+

447 @property 

+

448 def style(self) -> ListStyle: 

+

449 """Get the list style""" 

+

450 return self._style 

+

451 

+

452 @style.setter 

+

453 def style(self, style: ListStyle): 

+

454 """Set the list style""" 

+

455 self._style = style 

+

456 

+

457 @property 

+

458 def default_style(self): 

+

459 """Get the default style for list items""" 

+

460 return self._default_style 

+

461 

+

462 @default_style.setter 

+

463 def default_style(self, style): 

+

464 """Set the default style for list items""" 

+

465 self._default_style = style 

+

466 

+

467 def add_item(self, item: 'ListItem'): 

+

468 """ 

+

469 Add an item to this list. 

+

470 

+

471 Args: 

+

472 item: The ListItem to add 

+

473 """ 

+

474 self._items.append(item) 

+

475 item.parent = self 

+

476 

+

477 def create_item(self, term: Optional[str] = None, style=None) -> 'ListItem': 

+

478 """ 

+

479 Create a new list item and add it to this list. 

+

480 

+

481 Args: 

+

482 term: Optional term for definition lists 

+

483 style: Optional style override. If None, inherits from list 

+

484 

+

485 Returns: 

+

486 The newly created ListItem object 

+

487 """ 

+

488 return ListItem.create_and_add_to(self, term, style) 

+

489 

+

490 def items(self) -> Iterator['ListItem']: 

+

491 """ 

+

492 Iterate over the items in this list. 

+

493 

+

494 Yields: 

+

495 Each ListItem in the list 

+

496 """ 

+

497 for item in self._items: 

+

498 yield item 

+

499 

+

500 @property 

+

501 def item_count(self) -> int: 

+

502 """Get the number of items in this list""" 

+

503 return len(self._items) 

+

504 

+

505 

+

506class ListItem(BlockContainer, ContainerAware, Block): 

+

507 """ 

+

508 A list item element that can contain other block elements. 

+

509 """ 

+

510 

+

511 def __init__(self, term: Optional[str] = None, style=None): 

+

512 """ 

+

513 Initialize a list item. 

+

514 

+

515 Args: 

+

516 term: Optional term for definition lists (dt element) 

+

517 style: Optional default style for child blocks 

+

518 """ 

+

519 super().__init__(BlockType.LIST_ITEM) 

+

520 self._term = term 

+

521 self._style = style 

+

522 

+

523 @classmethod 

+

524 def create_and_add_to( 

+

525 cls, 

+

526 container, 

+

527 term: Optional[str] = None, 

+

528 style=None) -> 'ListItem': 

+

529 """ 

+

530 Create a new ListItem and add it to a container, inheriting style from 

+

531 the container if not explicitly provided. 

+

532 

+

533 Args: 

+

534 container: The container to add the list item to (must have add_item method) 

+

535 term: Optional term for definition lists (dt element) 

+

536 style: Optional style override. If None, inherits from container 

+

537 

+

538 Returns: 

+

539 The newly created ListItem object 

+

540 

+

541 Raises: 

+

542 AttributeError: If the container doesn't have the required add_item method 

+

543 """ 

+

544 # Validate container and inherit style using ContainerAware utilities 

+

545 cls._validate_container(container, required_method='add_item') 

+

546 style = cls._inherit_style(container, style) 

+

547 

+

548 # Create the new list item 

+

549 item = cls(term, style) 

+

550 

+

551 # Add the list item to the container 

+

552 container.add_item(item) 

+

553 

+

554 return item 

+

555 

+

556 @property 

+

557 def term(self) -> Optional[str]: 

+

558 """Get the definition term (for definition lists)""" 

+

559 return self._term 

+

560 

+

561 @term.setter 

+

562 def term(self, term: str): 

+

563 """Set the definition term""" 

+

564 self._term = term 

+

565 

+

566 @property 

+

567 def style(self): 

+

568 """Get the default style for this list item""" 

+

569 return self._style 

+

570 

+

571 @style.setter 

+

572 def style(self, style): 

+

573 """Set the default style for this list item""" 

+

574 self._style = style 

+

575 

+

576 

+

577class TableCell(BlockContainer, ContainerAware, Block): 

+

578 """ 

+

579 A table cell element that can contain other block elements. 

+

580 """ 

+

581 

+

582 def __init__( 

+

583 self, 

+

584 is_header: bool = False, 

+

585 colspan: int = 1, 

+

586 rowspan: int = 1, 

+

587 style=None): 

+

588 """ 

+

589 Initialize a table cell. 

+

590 

+

591 Args: 

+

592 is_header: Whether this cell is a header cell (th) or data cell (td) 

+

593 colspan: Number of columns this cell spans 

+

594 rowspan: Number of rows this cell spans 

+

595 style: Optional default style for child blocks 

+

596 """ 

+

597 super().__init__(BlockType.TABLE_CELL) 

+

598 self._is_header = is_header 

+

599 self._colspan = colspan 

+

600 self._rowspan = rowspan 

+

601 self._style = style 

+

602 

+

603 @classmethod 

+

604 def create_and_add_to(cls, container, is_header: bool = False, colspan: int = 1, 

+

605 rowspan: int = 1, style=None) -> 'TableCell': 

+

606 """ 

+

607 Create a new TableCell and add it to a container, inheriting style from 

+

608 the container if not explicitly provided. 

+

609 

+

610 Args: 

+

611 container: The container to add the cell to (must have add_cell method) 

+

612 is_header: Whether this cell is a header cell (th) or data cell (td) 

+

613 colspan: Number of columns this cell spans 

+

614 rowspan: Number of rows this cell spans 

+

615 style: Optional style override. If None, inherits from container 

+

616 

+

617 Returns: 

+

618 The newly created TableCell object 

+

619 

+

620 Raises: 

+

621 AttributeError: If the container doesn't have the required add_cell method 

+

622 """ 

+

623 # Validate container and inherit style using ContainerAware utilities 

+

624 cls._validate_container(container, required_method='add_cell') 

+

625 style = cls._inherit_style(container, style) 

+

626 

+

627 # Create the new table cell 

+

628 cell = cls(is_header, colspan, rowspan, style) 

+

629 

+

630 # Add the cell to the container 

+

631 container.add_cell(cell) 

+

632 

+

633 return cell 

+

634 

+

635 @property 

+

636 def is_header(self) -> bool: 

+

637 """Check if this is a header cell""" 

+

638 return self._is_header 

+

639 

+

640 @is_header.setter 

+

641 def is_header(self, is_header: bool): 

+

642 """Set whether this is a header cell""" 

+

643 self._is_header = is_header 

+

644 

+

645 @property 

+

646 def colspan(self) -> int: 

+

647 """Get the column span""" 

+

648 return self._colspan 

+

649 

+

650 @colspan.setter 

+

651 def colspan(self, colspan: int): 

+

652 """Set the column span""" 

+

653 self._colspan = max(1, colspan) # Ensure minimum of 1 

+

654 

+

655 @property 

+

656 def rowspan(self) -> int: 

+

657 """Get the row span""" 

+

658 return self._rowspan 

+

659 

+

660 @rowspan.setter 

+

661 def rowspan(self, rowspan: int): 

+

662 """Set the row span""" 

+

663 self._rowspan = max(1, rowspan) # Ensure minimum of 1 

+

664 

+

665 @property 

+

666 def style(self): 

+

667 """Get the default style for this table cell""" 

+

668 return self._style 

+

669 

+

670 @style.setter 

+

671 def style(self, style): 

+

672 """Set the default style for this table cell""" 

+

673 self._style = style 

+

674 

+

675 

+

676class TableRow(ContainerAware, Block): 

+

677 """ 

+

678 A table row element containing table cells. 

+

679 """ 

+

680 

+

681 def __init__(self, style=None): 

+

682 """ 

+

683 Initialize an empty table row 

+

684 

+

685 Args: 

+

686 style: Optional default style for child cells 

+

687 """ 

+

688 super().__init__(BlockType.TABLE_ROW) 

+

689 self._cells: List[TableCell] = [] 

+

690 self._style = style 

+

691 

+

692 @classmethod 

+

693 def create_and_add_to( 

+

694 cls, 

+

695 container, 

+

696 section: str = "body", 

+

697 style=None) -> 'TableRow': 

+

698 """ 

+

699 Create a new TableRow and add it to a container, inheriting style from 

+

700 the container if not explicitly provided. 

+

701 

+

702 Args: 

+

703 container: The container to add the row to (must have add_row method) 

+

704 section: The section to add the row to ("header", "body", or "footer") 

+

705 style: Optional style override. If None, inherits from container 

+

706 

+

707 Returns: 

+

708 The newly created TableRow object 

+

709 

+

710 Raises: 

+

711 AttributeError: If the container doesn't have the required add_row method 

+

712 """ 

+

713 # Validate container and inherit style using ContainerAware utilities 

+

714 cls._validate_container(container, required_method='add_row') 

+

715 style = cls._inherit_style(container, style) 

+

716 

+

717 # Create the new table row 

+

718 row = cls(style) 

+

719 

+

720 # Add the row to the container 

+

721 container.add_row(row, section) 

+

722 

+

723 return row 

+

724 

+

725 @property 

+

726 def style(self): 

+

727 """Get the default style for this table row""" 

+

728 return self._style 

+

729 

+

730 @style.setter 

+

731 def style(self, style): 

+

732 """Set the default style for this table row""" 

+

733 self._style = style 

+

734 

+

735 def add_cell(self, cell: TableCell): 

+

736 """ 

+

737 Add a cell to this row. 

+

738 

+

739 Args: 

+

740 cell: The TableCell to add 

+

741 """ 

+

742 self._cells.append(cell) 

+

743 cell.parent = self 

+

744 

+

745 def create_cell( 

+

746 self, 

+

747 is_header: bool = False, 

+

748 colspan: int = 1, 

+

749 rowspan: int = 1, 

+

750 style=None) -> TableCell: 

+

751 """ 

+

752 Create a new table cell and add it to this row. 

+

753 

+

754 Args: 

+

755 is_header: Whether this cell is a header cell 

+

756 colspan: Number of columns this cell spans 

+

757 rowspan: Number of rows this cell spans 

+

758 style: Optional style override. If None, inherits from row 

+

759 

+

760 Returns: 

+

761 The newly created TableCell object 

+

762 """ 

+

763 return TableCell.create_and_add_to(self, is_header, colspan, rowspan, style) 

+

764 

+

765 def cells(self) -> Iterator[TableCell]: 

+

766 """ 

+

767 Iterate over the cells in this row. 

+

768 

+

769 Yields: 

+

770 Each TableCell in the row 

+

771 """ 

+

772 for cell in self._cells: 

+

773 yield cell 

+

774 

+

775 @property 

+

776 def cell_count(self) -> int: 

+

777 """Get the number of cells in this row""" 

+

778 return len(self._cells) 

+

779 

+

780 

+

781class Table(ContainerAware, Block): 

+

782 """ 

+

783 A table element containing rows and cells. 

+

784 """ 

+

785 

+

786 def __init__(self, caption: Optional[str] = None, style=None): 

+

787 """ 

+

788 Initialize a table. 

+

789 

+

790 Args: 

+

791 caption: Optional caption for the table 

+

792 style: Optional default style for child rows 

+

793 """ 

+

794 super().__init__(BlockType.TABLE) 

+

795 self._caption = caption 

+

796 self._rows: List[TableRow] = [] 

+

797 self._header_rows: List[TableRow] = [] 

+

798 self._footer_rows: List[TableRow] = [] 

+

799 self._style = style 

+

800 

+

801 @classmethod 

+

802 def create_and_add_to( 

+

803 cls, 

+

804 container, 

+

805 caption: Optional[str] = None, 

+

806 style=None) -> 'Table': 

+

807 """ 

+

808 Create a new Table and add it to a container, inheriting style from 

+

809 the container if not explicitly provided. 

+

810 

+

811 Args: 

+

812 container: The container to add the table to (must have add_block method) 

+

813 caption: Optional caption for the table 

+

814 style: Optional style override. If None, inherits from container 

+

815 

+

816 Returns: 

+

817 The newly created Table object 

+

818 

+

819 Raises: 

+

820 AttributeError: If the container doesn't have the required add_block method 

+

821 """ 

+

822 # Validate container and inherit style using ContainerAware utilities 

+

823 cls._validate_container(container) 

+

824 style = cls._inherit_style(container, style) 

+

825 

+

826 # Create the new table 

+

827 table = cls(caption, style) 

+

828 

+

829 # Add the table to the container 

+

830 container.add_block(table) 

+

831 

+

832 return table 

+

833 

+

834 @property 

+

835 def caption(self) -> Optional[str]: 

+

836 """Get the table caption""" 

+

837 return self._caption 

+

838 

+

839 @caption.setter 

+

840 def caption(self, caption: Optional[str]): 

+

841 """Set the table caption""" 

+

842 self._caption = caption 

+

843 

+

844 @property 

+

845 def style(self): 

+

846 """Get the default style for this table""" 

+

847 return self._style 

+

848 

+

849 @style.setter 

+

850 def style(self, style): 

+

851 """Set the default style for this table""" 

+

852 self._style = style 

+

853 

+

854 def add_row(self, row: TableRow, section: str = "body"): 

+

855 """ 

+

856 Add a row to this table. 

+

857 

+

858 Args: 

+

859 row: The TableRow to add 

+

860 section: The section to add the row to ("header", "body", or "footer") 

+

861 """ 

+

862 row.parent = self 

+

863 

+

864 if section.lower() == "header": 

+

865 self._header_rows.append(row) 

+

866 elif section.lower() == "footer": 

+

867 self._footer_rows.append(row) 

+

868 else: # Default to body 

+

869 self._rows.append(row) 

+

870 

+

871 def create_row(self, section: str = "body", style=None) -> TableRow: 

+

872 """ 

+

873 Create a new table row and add it to this table. 

+

874 

+

875 Args: 

+

876 section: The section to add the row to ("header", "body", or "footer") 

+

877 style: Optional style override. If None, inherits from table 

+

878 

+

879 Returns: 

+

880 The newly created TableRow object 

+

881 """ 

+

882 return TableRow.create_and_add_to(self, section, style) 

+

883 

+

884 def header_rows(self) -> Iterator[TableRow]: 

+

885 """ 

+

886 Iterate over the header rows in this table. 

+

887 

+

888 Yields: 

+

889 Each TableRow in the header section 

+

890 """ 

+

891 for row in self._header_rows: 

+

892 yield row 

+

893 

+

894 def body_rows(self) -> Iterator[TableRow]: 

+

895 """ 

+

896 Iterate over the body rows in this table. 

+

897 

+

898 Yields: 

+

899 Each TableRow in the body section 

+

900 """ 

+

901 for row in self._rows: 

+

902 yield row 

+

903 

+

904 def footer_rows(self) -> Iterator[TableRow]: 

+

905 """ 

+

906 Iterate over the footer rows in this table. 

+

907 

+

908 Yields: 

+

909 Each TableRow in the footer section 

+

910 """ 

+

911 for row in self._footer_rows: 

+

912 yield row 

+

913 

+

914 def all_rows(self) -> Iterator[Tuple[str, TableRow]]: 

+

915 """ 

+

916 Iterate over all rows in this table with their section labels. 

+

917 

+

918 Yields: 

+

919 Tuples of (section, row) for each row in the table 

+

920 """ 

+

921 for row in self._header_rows: 

+

922 yield ("header", row) 

+

923 for row in self._rows: 

+

924 yield ("body", row) 

+

925 for row in self._footer_rows: 

+

926 yield ("footer", row) 

+

927 

+

928 @property 

+

929 def row_count(self) -> Dict[str, int]: 

+

930 """Get the row counts by section""" 

+

931 return { 

+

932 "header": len(self._header_rows), 

+

933 "body": len(self._rows), 

+

934 "footer": len(self._footer_rows), 

+

935 "total": len(self._header_rows) + len(self._rows) + len(self._footer_rows) 

+

936 } 

+

937 

+

938 

+

939class Image(Block): 

+

940 """ 

+

941 An image element with source, dimensions, and alternative text. 

+

942 """ 

+

943 

+

944 def __init__( 

+

945 self, 

+

946 source: str = "", 

+

947 alt_text: str = "", 

+

948 width: Optional[int] = None, 

+

949 height: Optional[int] = None): 

+

950 """ 

+

951 Initialize an image element. 

+

952 

+

953 Args: 

+

954 source: The image source URL or path 

+

955 alt_text: Alternative text for accessibility 

+

956 width: Optional image width in pixels 

+

957 height: Optional image height in pixels 

+

958 """ 

+

959 super().__init__(BlockType.IMAGE) 

+

960 self._source = source 

+

961 self._alt_text = alt_text 

+

962 self._width = width 

+

963 self._height = height 

+

964 

+

965 @classmethod 

+

966 def create_and_add_to( 

+

967 cls, 

+

968 container, 

+

969 source: str = "", 

+

970 alt_text: str = "", 

+

971 width: Optional[int] = None, 

+

972 height: Optional[int] = None) -> 'Image': 

+

973 """ 

+

974 Create a new Image and add it to a container. 

+

975 

+

976 Args: 

+

977 container: The container to add the image to (must have add_block method) 

+

978 source: The image source URL or path 

+

979 alt_text: Alternative text for accessibility 

+

980 width: Optional image width in pixels 

+

981 height: Optional image height in pixels 

+

982 

+

983 Returns: 

+

984 The newly created Image object 

+

985 

+

986 Raises: 

+

987 AttributeError: If the container doesn't have the required add_block method 

+

988 """ 

+

989 # Create the new image 

+

990 image = cls(source, alt_text, width, height) 

+

991 

+

992 # Add the image to the container 

+

993 if hasattr(container, 'add_block'): 

+

994 container.add_block(image) 

+

995 else: 

+

996 raise AttributeError( 

+

997 f"Container {type(container).__name__} must have an 'add_block' method" 

+

998 ) 

+

999 

+

1000 return image 

+

1001 

+

1002 @property 

+

1003 def source(self) -> str: 

+

1004 """Get the image source""" 

+

1005 return self._source 

+

1006 

+

1007 @source.setter 

+

1008 def source(self, source: str): 

+

1009 """Set the image source""" 

+

1010 self._source = source 

+

1011 

+

1012 @property 

+

1013 def alt_text(self) -> str: 

+

1014 """Get the alternative text""" 

+

1015 return self._alt_text 

+

1016 

+

1017 @alt_text.setter 

+

1018 def alt_text(self, alt_text: str): 

+

1019 """Set the alternative text""" 

+

1020 self._alt_text = alt_text 

+

1021 

+

1022 @property 

+

1023 def width(self) -> Optional[int]: 

+

1024 """Get the image width""" 

+

1025 return self._width 

+

1026 

+

1027 @width.setter 

+

1028 def width(self, width: Optional[int]): 

+

1029 """Set the image width""" 

+

1030 self._width = width 

+

1031 

+

1032 @property 

+

1033 def height(self) -> Optional[int]: 

+

1034 """Get the image height""" 

+

1035 return self._height 

+

1036 

+

1037 @height.setter 

+

1038 def height(self, height: Optional[int]): 

+

1039 """Set the image height""" 

+

1040 self._height = height 

+

1041 

+

1042 def get_dimensions(self) -> Tuple[Optional[int], Optional[int]]: 

+

1043 """ 

+

1044 Get the image dimensions as a tuple. 

+

1045 

+

1046 Returns: 

+

1047 Tuple of (width, height) 

+

1048 """ 

+

1049 return (self._width, self._height) 

+

1050 

+

1051 def get_aspect_ratio(self) -> Optional[float]: 

+

1052 """ 

+

1053 Calculate the aspect ratio of the image. 

+

1054 

+

1055 Returns: 

+

1056 The aspect ratio (width/height) or None if either dimension is missing 

+

1057 """ 

+

1058 if self._width is not None and self._height is not None and self._height > 0: 

+

1059 return self._width / self._height 

+

1060 return None 

+

1061 

+

1062 def calculate_scaled_dimensions(self, 

+

1063 max_width: Optional[int] = None, 

+

1064 max_height: Optional[int] = None) -> Tuple[Optional[int], 

+

1065 Optional[int]]: 

+

1066 """ 

+

1067 Calculate scaled dimensions that fit within the given constraints. 

+

1068 

+

1069 Args: 

+

1070 max_width: Maximum allowed width 

+

1071 max_height: Maximum allowed height 

+

1072 

+

1073 Returns: 

+

1074 Tuple of (scaled_width, scaled_height) 

+

1075 """ 

+

1076 if self._width is None or self._height is None: 

+

1077 return (self._width, self._height) 

+

1078 

+

1079 width, height = self._width, self._height 

+

1080 

+

1081 # Scale down if needed 

+

1082 if max_width is not None and width > max_width: 

+

1083 height = int(height * max_width / width) 

+

1084 width = max_width 

+

1085 

+

1086 if max_height is not None and height > max_height: 1086 ↛ 1087line 1086 didn't jump to line 1087 because the condition on line 1086 was never true

+

1087 width = int(width * max_height / height) 

+

1088 height = max_height 

+

1089 

+

1090 return (width, height) 

+

1091 

+

1092 def _is_url(self, source: str) -> bool: 

+

1093 """ 

+

1094 Check if the source is a URL. 

+

1095 

+

1096 Args: 

+

1097 source: The source string to check 

+

1098 

+

1099 Returns: 

+

1100 True if the source appears to be a URL, False otherwise 

+

1101 """ 

+

1102 parsed = urllib.parse.urlparse(source) 

+

1103 return bool(parsed.scheme and parsed.netloc) 

+

1104 

+

1105 def _download_to_temp(self, url: str) -> str: 

+

1106 """ 

+

1107 Download an image from a URL to a temporary file. 

+

1108 

+

1109 Args: 

+

1110 url: The URL to download from 

+

1111 

+

1112 Returns: 

+

1113 Path to the temporary file 

+

1114 

+

1115 Raises: 

+

1116 urllib.error.URLError: If the download fails 

+

1117 """ 

+

1118 # Create a temporary file 

+

1119 temp_fd, temp_path = tempfile.mkstemp(suffix='.tmp') 

+

1120 

+

1121 try: 

+

1122 # Download the image 

+

1123 with urllib.request.urlopen(url) as response: 

+

1124 # Write the response data to the temporary file 

+

1125 with os.fdopen(temp_fd, 'wb') as temp_file: 

+

1126 temp_file.write(response.read()) 

+

1127 

+

1128 return temp_path 

+

1129 except BaseException: 

+

1130 # Clean up the temporary file if download fails 

+

1131 try: 

+

1132 os.close(temp_fd) 

+

1133 except BaseException: 

+

1134 pass 

+

1135 try: 

+

1136 os.unlink(temp_path) 

+

1137 except BaseException: 

+

1138 pass 

+

1139 raise 

+

1140 

+

1141 def load_image_data(self, 

+

1142 auto_update_dimensions: bool = True) -> Tuple[Optional[str], 

+

1143 Optional[PILImage.Image]]: 

+

1144 """ 

+

1145 Load image data using PIL, handling both local files and URLs. 

+

1146 

+

1147 Args: 

+

1148 auto_update_dimensions: If True, automatically update width and height from the loaded image 

+

1149 

+

1150 Returns: 

+

1151 Tuple of (file_path, PIL_Image_object). For URLs, file_path is the temporary file path. 

+

1152 Returns (None, None) if loading fails. 

+

1153 """ 

+

1154 if not self._source: 

+

1155 return None, None 

+

1156 

+

1157 file_path = None 

+

1158 temp_file = None 

+

1159 

+

1160 try: 

+

1161 if self._is_url(self._source): 

+

1162 # Download to temporary file 

+

1163 temp_file = self._download_to_temp(self._source) 

+

1164 file_path = temp_file 

+

1165 else: 

+

1166 # Use local file path 

+

1167 file_path = self._source 

+

1168 

+

1169 # Open with PIL 

+

1170 with PILImage.open(file_path) as img: 

+

1171 # Load the image data 

+

1172 img.load() 

+

1173 

+

1174 # Update dimensions if requested 

+

1175 if auto_update_dimensions: 

+

1176 self._width, self._height = img.size 

+

1177 

+

1178 # Return a copy to avoid issues with the context manager 

+

1179 return file_path, img.copy() 

+

1180 

+

1181 except Exception: 

+

1182 # Clean up temporary file on error 

+

1183 if temp_file and os.path.exists(temp_file): 1183 ↛ 1184line 1183 didn't jump to line 1184 because the condition on line 1183 was never true

+

1184 try: 

+

1185 os.unlink(temp_file) 

+

1186 except BaseException: 

+

1187 pass 

+

1188 return None, None 

+

1189 

+

1190 def get_image_info(self) -> Dict[str, Any]: 

+

1191 """ 

+

1192 Get detailed information about the image using PIL. 

+

1193 

+

1194 Returns: 

+

1195 Dictionary containing image information including format, mode, size, etc. 

+

1196 Returns empty dict if image cannot be loaded. 

+

1197 """ 

+

1198 file_path, img = self.load_image_data(auto_update_dimensions=False) 

+

1199 

+

1200 if img is None: 

+

1201 return {} 

+

1202 

+

1203 # Try to determine format from the image, file extension, or source 

+

1204 img_format = img.format 

+

1205 if img_format is None: 1205 ↛ 1229line 1205 didn't jump to line 1229 because the condition on line 1205 was always true

+

1206 # Try to determine format from file extension 

+

1207 format_map = { 

+

1208 '.jpg': 'JPEG', 

+

1209 '.jpeg': 'JPEG', 

+

1210 '.png': 'PNG', 

+

1211 '.gif': 'GIF', 

+

1212 '.bmp': 'BMP', 

+

1213 '.tiff': 'TIFF', 

+

1214 '.tif': 'TIFF' 

+

1215 } 

+

1216 

+

1217 # First try the actual file path if available 

+

1218 if file_path: 1218 ↛ 1223line 1218 didn't jump to line 1223 because the condition on line 1218 was always true

+

1219 ext = os.path.splitext(file_path)[1].lower() 

+

1220 img_format = format_map.get(ext) 

+

1221 

+

1222 # If still no format and we have a URL source, try the original URL 

+

1223 if img_format is None and self._is_url(self._source): 

+

1224 ext = os.path.splitext( 

+

1225 urllib.parse.urlparse( 

+

1226 self._source).path)[1].lower() 

+

1227 img_format = format_map.get(ext) 

+

1228 

+

1229 info = { 

+

1230 'format': img_format, 

+

1231 'mode': img.mode, 

+

1232 'size': img.size, 

+

1233 'width': img.width, 

+

1234 'height': img.height, 

+

1235 } 

+

1236 

+

1237 # Add additional info if available 

+

1238 if hasattr(img, 'info'): 1238 ↛ 1242line 1238 didn't jump to line 1242 because the condition on line 1238 was always true

+

1239 info['info'] = img.info 

+

1240 

+

1241 # Clean up temporary file if it was created 

+

1242 if file_path and self._is_url(self._source): 

+

1243 try: 

+

1244 os.unlink(file_path) 

+

1245 except BaseException: 

+

1246 pass 

+

1247 

+

1248 return info 

+

1249 

+

1250 

+

1251class LinkedImage(Image): 

+

1252 """ 

+

1253 An Image that is also a Link - clickable images that navigate or trigger callbacks. 

+

1254 """ 

+

1255 

+

1256 def __init__(self, source: str, alt_text: str, location: str, 

+

1257 width: Optional[int] = None, height: Optional[int] = None, 

+

1258 link_type=None, 

+

1259 callback: Optional[Any] = None, 

+

1260 params: Optional[Dict[str, Any]] = None, 

+

1261 title: Optional[str] = None): 

+

1262 """ 

+

1263 Initialize a linked image. 

+

1264 

+

1265 Args: 

+

1266 source: The image source URL or path 

+

1267 alt_text: Alternative text for accessibility 

+

1268 location: The link target (URL, bookmark, etc.) 

+

1269 width: Optional image width in pixels 

+

1270 height: Optional image height in pixels 

+

1271 link_type: Type of link (INTERNAL, EXTERNAL, etc.) 

+

1272 callback: Optional callback for link activation 

+

1273 params: Parameters for the link 

+

1274 title: Tooltip/title for the link 

+

1275 """ 

+

1276 # Initialize Image 

+

1277 super().__init__(source, alt_text, width, height) 

+

1278 

+

1279 # Store link properties 

+

1280 # Import here to avoid circular imports at module level 

+

1281 from pyWebLayout.abstract.functional import LinkType 

+

1282 self._location = location 

+

1283 self._link_type = link_type or LinkType.EXTERNAL 

+

1284 self._callback = callback 

+

1285 self._params = params or {} 

+

1286 self._link_title = title 

+

1287 

+

1288 @property 

+

1289 def location(self) -> str: 

+

1290 """Get the link target location""" 

+

1291 return self._location 

+

1292 

+

1293 @property 

+

1294 def link_type(self): 

+

1295 """Get the type of link""" 

+

1296 return self._link_type 

+

1297 

+

1298 @property 

+

1299 def link_callback(self) -> Optional[Any]: 

+

1300 """Get the link callback""" 

+

1301 return self._callback 

+

1302 

+

1303 @property 

+

1304 def params(self) -> Dict[str, Any]: 

+

1305 """Get the link parameters""" 

+

1306 return self._params 

+

1307 

+

1308 @property 

+

1309 def link_title(self) -> Optional[str]: 

+

1310 """Get the link title/tooltip""" 

+

1311 return self._link_title 

+

1312 

+

1313 def execute_link(self, context: Optional[Dict[str, Any]] = None) -> Any: 

+

1314 """ 

+

1315 Execute the link action. 

+

1316 

+

1317 Args: 

+

1318 context: Optional context dict (e.g., {'alt_text': image.alt_text}) 

+

1319 

+

1320 Returns: 

+

1321 The result of the link execution 

+

1322 """ 

+

1323 from pyWebLayout.abstract.functional import LinkType 

+

1324 

+

1325 # Add image info to context 

+

1326 full_context = { 

+

1327 **self._params, 

+

1328 'alt_text': self._alt_text, 

+

1329 'source': self._source} 

+

1330 if context: 1330 ↛ 1331line 1330 didn't jump to line 1331 because the condition on line 1330 was never true

+

1331 full_context.update(context) 

+

1332 

+

1333 if self._link_type in (LinkType.API, LinkType.FUNCTION) and self._callback: 

+

1334 return self._callback(self._location, **full_context) 

+

1335 else: 

+

1336 # For INTERNAL and EXTERNAL links, return the location 

+

1337 return self._location 

+

1338 

+

1339 

+

1340class HorizontalRule(Block): 

+

1341 """ 

+

1342 A horizontal rule element (hr tag). 

+

1343 """ 

+

1344 

+

1345 def __init__(self): 

+

1346 """Initialize a horizontal rule element.""" 

+

1347 super().__init__(BlockType.HORIZONTAL_RULE) 

+

1348 

+

1349 @classmethod 

+

1350 def create_and_add_to(cls, container) -> 'HorizontalRule': 

+

1351 """ 

+

1352 Create a new HorizontalRule and add it to a container. 

+

1353 

+

1354 Args: 

+

1355 container: The container to add the horizontal rule to (must have add_block method) 

+

1356 

+

1357 Returns: 

+

1358 The newly created HorizontalRule object 

+

1359 

+

1360 Raises: 

+

1361 AttributeError: If the container doesn't have the required add_block method 

+

1362 """ 

+

1363 # Create the new horizontal rule 

+

1364 hr = cls() 

+

1365 

+

1366 # Add the horizontal rule to the container 

+

1367 if hasattr(container, 'add_block'): 

+

1368 container.add_block(hr) 

+

1369 else: 

+

1370 raise AttributeError( 

+

1371 f"Container {type(container).__name__} must have an 'add_block' method" 

+

1372 ) 

+

1373 

+

1374 return hr 

+

1375 

+

1376 

+

1377class PageBreak(Block): 

+

1378 """ 

+

1379 A page break element that forces content to start on a new page. 

+

1380 

+

1381 When encountered during layout, this block signals that all subsequent 

+

1382 content should be placed on a new page, even if the current page has 

+

1383 available space. 

+

1384 """ 

+

1385 

+

1386 def __init__(self): 

+

1387 """Initialize a page break element.""" 

+

1388 super().__init__(BlockType.PAGE_BREAK) 

+

1389 

+

1390 @classmethod 

+

1391 def create_and_add_to(cls, container) -> 'PageBreak': 

+

1392 """ 

+

1393 Create a new PageBreak and add it to a container. 

+

1394 

+

1395 Args: 

+

1396 container: The container to add the page break to (must have add_block method) 

+

1397 

+

1398 Returns: 

+

1399 The newly created PageBreak object 

+

1400 

+

1401 Raises: 

+

1402 AttributeError: If the container doesn't have the required add_block method 

+

1403 """ 

+

1404 # Create the new page break 

+

1405 page_break = cls() 

+

1406 

+

1407 # Add the page break to the container 

+

1408 if hasattr(container, 'add_block'): 

+

1409 container.add_block(page_break) 

+

1410 else: 

+

1411 raise AttributeError( 

+

1412 f"Container {type(container).__name__} must have an 'add_block' method" 

+

1413 ) 

+

1414 

+

1415 return page_break 

+
+ + + diff --git a/cov_info/htmlcov/z_af715639580e2d86_document_py.html b/cov_info/htmlcov/z_af715639580e2d86_document_py.html new file mode 100644 index 0000000..cb13e94 --- /dev/null +++ b/cov_info/htmlcov/z_af715639580e2d86_document_py.html @@ -0,0 +1,694 @@ + + + + + Coverage for pyWebLayout/abstract/document.py: 78% + + + + + +
+
+

+ Coverage for pyWebLayout/abstract/document.py: + 78% +

+ +

+ 194 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from __future__ import annotations 

+

2from typing import List, Dict, Optional, Tuple, Union, Any 

+

3from enum import Enum 

+

4from .block import Block, BlockType, Heading, HeadingLevel, Paragraph 

+

5from ..style import Font, FontWeight, FontStyle, TextDecoration 

+

6from ..style.abstract_style import AbstractStyle, AbstractStyleRegistry, FontFamily, FontSize 

+

7from ..style.concrete_style import ConcreteStyleRegistry, RenderingContext, StyleResolver 

+

8from ..core import FontRegistry, MetadataContainer 

+

9 

+

10 

+

11class MetadataType(Enum): 

+

12 """Types of metadata that can be associated with a document""" 

+

13 TITLE = 1 

+

14 AUTHOR = 2 

+

15 DESCRIPTION = 3 

+

16 KEYWORDS = 4 

+

17 LANGUAGE = 5 

+

18 PUBLICATION_DATE = 6 

+

19 MODIFIED_DATE = 7 

+

20 PUBLISHER = 8 

+

21 IDENTIFIER = 9 

+

22 COVER_IMAGE = 10 

+

23 CUSTOM = 100 

+

24 

+

25 

+

26class Document(FontRegistry, MetadataContainer): 

+

27 """ 

+

28 Abstract representation of a complete document like an HTML page or an ebook. 

+

29 This class manages the logical structure of the document without rendering concerns. 

+

30 

+

31 Uses FontRegistry mixin for font caching. 

+

32 Uses MetadataContainer mixin for metadata management. 

+

33 """ 

+

34 

+

35 def __init__( 

+

36 self, 

+

37 title: Optional[str] = None, 

+

38 language: str = "en-US", 

+

39 default_style=None): 

+

40 """ 

+

41 Initialize a new document. 

+

42 

+

43 Args: 

+

44 title: The document title 

+

45 language: The document language code 

+

46 default_style: Optional default style for child blocks 

+

47 """ 

+

48 super().__init__() 

+

49 self._blocks: List[Block] = [] 

+

50 self._anchors: Dict[str, Block] = {} # Named anchors for navigation 

+

51 self._resources: Dict[str, Any] = {} # External resources like images 

+

52 self._stylesheets: List[Dict[str, Any]] = [] # CSS stylesheets 

+

53 self._scripts: List[str] = [] # JavaScript code 

+

54 

+

55 # Style management with new abstract/concrete system 

+

56 self._abstract_style_registry = AbstractStyleRegistry() 

+

57 self._rendering_context = RenderingContext(default_language=language) 

+

58 self._style_resolver = StyleResolver(self._rendering_context) 

+

59 self._concrete_style_registry = ConcreteStyleRegistry(self._style_resolver) 

+

60 

+

61 # Set default style 

+

62 if default_style is None: 62 ↛ 65line 62 didn't jump to line 65 because the condition on line 62 was always true

+

63 # Create a default abstract style 

+

64 default_style = self._abstract_style_registry.default_style 

+

65 elif isinstance(default_style, Font): 

+

66 # Convert Font to AbstractStyle for backward compatibility 

+

67 default_style = AbstractStyle( 

+

68 font_family=FontFamily.SERIF, # Default assumption 

+

69 font_size=default_style.font_size, 

+

70 color=default_style.colour, 

+

71 language=default_style.language 

+

72 ) 

+

73 style_id, default_style = self._abstract_style_registry.get_or_create_style( 

+

74 default_style) 

+

75 self._default_style = default_style 

+

76 

+

77 # Set basic metadata 

+

78 if title: 

+

79 self.set_metadata(MetadataType.TITLE, title) 

+

80 self.set_metadata(MetadataType.LANGUAGE, language) 

+

81 

+

82 @property 

+

83 def blocks(self) -> List[Block]: 

+

84 """Get the top-level blocks in this document""" 

+

85 return self._blocks 

+

86 

+

87 @property 

+

88 def default_style(self): 

+

89 """Get the default style for this document""" 

+

90 return self._default_style 

+

91 

+

92 @default_style.setter 

+

93 def default_style(self, style): 

+

94 """Set the default style for this document""" 

+

95 self._default_style = style 

+

96 

+

97 def add_block(self, block: Block): 

+

98 """ 

+

99 Add a block to this document. 

+

100 

+

101 Args: 

+

102 block: The block to add 

+

103 """ 

+

104 self._blocks.append(block) 

+

105 

+

106 def create_paragraph(self, style=None) -> Paragraph: 

+

107 """ 

+

108 Create a new paragraph and add it to this document. 

+

109 

+

110 Args: 

+

111 style: Optional style override. If None, inherits from document 

+

112 

+

113 Returns: 

+

114 The newly created Paragraph object 

+

115 """ 

+

116 if style is None: 

+

117 style = self._default_style 

+

118 paragraph = Paragraph(style) 

+

119 self.add_block(paragraph) 

+

120 return paragraph 

+

121 

+

122 def create_heading( 

+

123 self, 

+

124 level: HeadingLevel = HeadingLevel.H1, 

+

125 style=None) -> Heading: 

+

126 """ 

+

127 Create a new heading and add it to this document. 

+

128 

+

129 Args: 

+

130 level: The heading level 

+

131 style: Optional style override. If None, inherits from document 

+

132 

+

133 Returns: 

+

134 The newly created Heading object 

+

135 """ 

+

136 if style is None: 

+

137 style = self._default_style 

+

138 heading = Heading(level, style) 

+

139 self.add_block(heading) 

+

140 return heading 

+

141 

+

142 def create_chapter( 

+

143 self, 

+

144 title: Optional[str] = None, 

+

145 level: int = 1, 

+

146 style=None) -> 'Chapter': 

+

147 """ 

+

148 Create a new chapter with inherited style. 

+

149 

+

150 Args: 

+

151 title: The chapter title 

+

152 level: The chapter level 

+

153 style: Optional style override. If None, inherits from document 

+

154 

+

155 Returns: 

+

156 The newly created Chapter object 

+

157 """ 

+

158 if style is None: 

+

159 style = self._default_style 

+

160 return Chapter(title, level, style) 

+

161 

+

162 # set_metadata() and get_metadata() are provided by MetadataContainer mixin 

+

163 

+

164 def add_anchor(self, name: str, target: Block): 

+

165 """ 

+

166 Add a named anchor to this document. 

+

167 

+

168 Args: 

+

169 name: The anchor name 

+

170 target: The target block 

+

171 """ 

+

172 self._anchors[name] = target 

+

173 

+

174 def get_anchor(self, name: str) -> Optional[Block]: 

+

175 """ 

+

176 Get a named anchor from this document. 

+

177 

+

178 Args: 

+

179 name: The anchor name 

+

180 

+

181 Returns: 

+

182 The target block, or None if not found 

+

183 """ 

+

184 return self._anchors.get(name) 

+

185 

+

186 def add_resource(self, name: str, resource: Any): 

+

187 """ 

+

188 Add a resource to this document. 

+

189 

+

190 Args: 

+

191 name: The resource name 

+

192 resource: The resource data 

+

193 """ 

+

194 self._resources[name] = resource 

+

195 

+

196 def get_resource(self, name: str) -> Optional[Any]: 

+

197 """ 

+

198 Get a resource from this document. 

+

199 

+

200 Args: 

+

201 name: The resource name 

+

202 

+

203 Returns: 

+

204 The resource data, or None if not found 

+

205 """ 

+

206 return self._resources.get(name) 

+

207 

+

208 def add_stylesheet(self, stylesheet: Dict[str, Any]): 

+

209 """ 

+

210 Add a stylesheet to this document. 

+

211 

+

212 Args: 

+

213 stylesheet: The stylesheet data 

+

214 """ 

+

215 self._stylesheets.append(stylesheet) 

+

216 

+

217 def add_script(self, script: str): 

+

218 """ 

+

219 Add a script to this document. 

+

220 

+

221 Args: 

+

222 script: The script code 

+

223 """ 

+

224 self._scripts.append(script) 

+

225 

+

226 def get_title(self) -> Optional[str]: 

+

227 """ 

+

228 Get the document title. 

+

229 

+

230 Returns: 

+

231 The document title, or None if not set 

+

232 """ 

+

233 return self.get_metadata(MetadataType.TITLE) 

+

234 

+

235 def set_title(self, title: str): 

+

236 """ 

+

237 Set the document title. 

+

238 

+

239 Args: 

+

240 title: The document title 

+

241 """ 

+

242 self.set_metadata(MetadataType.TITLE, title) 

+

243 

+

244 @property 

+

245 def title(self) -> Optional[str]: 

+

246 """ 

+

247 Get the document title as a property. 

+

248 

+

249 Returns: 

+

250 The document title, or None if not set 

+

251 """ 

+

252 return self.get_title() 

+

253 

+

254 @title.setter 

+

255 def title(self, title: str): 

+

256 """ 

+

257 Set the document title as a property. 

+

258 

+

259 Args: 

+

260 title: The document title 

+

261 """ 

+

262 self.set_title(title) 

+

263 

+

264 def find_blocks_by_type(self, block_type: BlockType) -> List[Block]: 

+

265 """ 

+

266 Find all blocks of a specific type. 

+

267 

+

268 Args: 

+

269 block_type: The type of blocks to find 

+

270 

+

271 Returns: 

+

272 A list of matching blocks 

+

273 """ 

+

274 result = [] 

+

275 

+

276 def _find_recursive(blocks: List[Block]): 

+

277 for block in blocks: 

+

278 if block.block_type == block_type: 

+

279 result.append(block) 

+

280 

+

281 # Check for child blocks based on block type 

+

282 if hasattr(block, '_blocks'): 282 ↛ 283line 282 didn't jump to line 283 because the condition on line 282 was never true

+

283 _find_recursive(block._blocks) 

+

284 elif hasattr(block, '_items') and isinstance(block._items, list): 284 ↛ 285line 284 didn't jump to line 285 because the condition on line 284 was never true

+

285 _find_recursive(block._items) 

+

286 

+

287 _find_recursive(self._blocks) 

+

288 return result 

+

289 

+

290 def find_headings(self) -> List[Heading]: 

+

291 """ 

+

292 Find all headings in the document. 

+

293 

+

294 Returns: 

+

295 A list of heading blocks 

+

296 """ 

+

297 blocks = self.find_blocks_by_type(BlockType.HEADING) 

+

298 return [block for block in blocks if isinstance(block, Heading)] 

+

299 

+

300 def generate_table_of_contents(self) -> List[Tuple[int, str, Block]]: 

+

301 """ 

+

302 Generate a table of contents from headings. 

+

303 

+

304 Returns: 

+

305 A list of tuples containing (level, title, heading_block) 

+

306 """ 

+

307 headings = self.find_headings() 

+

308 

+

309 toc = [] 

+

310 for heading in headings: 

+

311 # Extract text from the heading 

+

312 title = "" 

+

313 for _, word in heading.words_iter(): 

+

314 title += word.text + " " 

+

315 title = title.strip() 

+

316 

+

317 # Add to TOC 

+

318 level = heading.level.value # Get numeric value from HeadingLevel enum 

+

319 toc.append((level, title, heading)) 

+

320 

+

321 return toc 

+

322 

+

323 def get_or_create_style(self, 

+

324 font_family: FontFamily = FontFamily.SERIF, 

+

325 font_size: Union[FontSize, int] = FontSize.MEDIUM, 

+

326 font_weight: FontWeight = FontWeight.NORMAL, 

+

327 font_style: FontStyle = FontStyle.NORMAL, 

+

328 text_decoration: TextDecoration = TextDecoration.NONE, 

+

329 color: Union[str, Tuple[int, int, int]] = "black", 

+

330 background_color: Optional[Union[str, Tuple[int, int, int, int]]] = None, 

+

331 language: str = "en-US", 

+

332 **kwargs) -> Tuple[str, AbstractStyle]: 

+

333 """ 

+

334 Get or create an abstract style with the specified properties. 

+

335 

+

336 Args: 

+

337 font_family: Semantic font family 

+

338 font_size: Font size (semantic or numeric) 

+

339 font_weight: Font weight 

+

340 font_style: Font style 

+

341 text_decoration: Text decoration 

+

342 color: Text color (name or RGB tuple) 

+

343 background_color: Background color 

+

344 language: Language code 

+

345 **kwargs: Additional style properties 

+

346 

+

347 Returns: 

+

348 Tuple of (style_id, AbstractStyle) 

+

349 """ 

+

350 abstract_style = AbstractStyle( 

+

351 font_family=font_family, 

+

352 font_size=font_size, 

+

353 font_weight=font_weight, 

+

354 font_style=font_style, 

+

355 text_decoration=text_decoration, 

+

356 color=color, 

+

357 background_color=background_color, 

+

358 language=language, 

+

359 **kwargs 

+

360 ) 

+

361 

+

362 return self._abstract_style_registry.get_or_create_style(abstract_style) 

+

363 

+

364 def get_font_for_style(self, abstract_style: AbstractStyle) -> Font: 

+

365 """ 

+

366 Get a Font object for an AbstractStyle (for rendering). 

+

367 

+

368 Args: 

+

369 abstract_style: The abstract style to get a font for 

+

370 

+

371 Returns: 

+

372 Font object ready for rendering 

+

373 """ 

+

374 return self._concrete_style_registry.get_font(abstract_style) 

+

375 

+

376 def update_rendering_context(self, **kwargs): 

+

377 """ 

+

378 Update the rendering context (user preferences, device settings, etc.). 

+

379 

+

380 Args: 

+

381 **kwargs: Context properties to update (base_font_size, font_scale_factor, etc.) 

+

382 """ 

+

383 self._style_resolver.update_context(**kwargs) 

+

384 

+

385 def get_style_registry(self) -> AbstractStyleRegistry: 

+

386 """Get the abstract style registry for this document.""" 

+

387 return self._abstract_style_registry 

+

388 

+

389 def get_concrete_style_registry(self) -> ConcreteStyleRegistry: 

+

390 """Get the concrete style registry for this document.""" 

+

391 return self._concrete_style_registry 

+

392 

+

393 # get_or_create_font() is provided by FontRegistry mixin 

+

394 

+

395 

+

396class Chapter(FontRegistry, MetadataContainer): 

+

397 """ 

+

398 Represents a chapter or section in a document. 

+

399 A chapter contains a sequence of blocks and has metadata. 

+

400 

+

401 Uses FontRegistry mixin for font caching with parent delegation. 

+

402 Uses MetadataContainer mixin for metadata management. 

+

403 """ 

+

404 

+

405 def __init__( 

+

406 self, 

+

407 title: Optional[str] = None, 

+

408 level: int = 1, 

+

409 style=None, 

+

410 parent=None): 

+

411 """ 

+

412 Initialize a new chapter. 

+

413 

+

414 Args: 

+

415 title: The chapter title 

+

416 level: The chapter level (1 = top level, 2 = subsection, etc.) 

+

417 style: Optional default style for child blocks 

+

418 parent: Parent container (e.g., Document or Book) 

+

419 """ 

+

420 super().__init__() 

+

421 self._title = title 

+

422 self._level = level 

+

423 self._blocks: List[Block] = [] 

+

424 self._style = style 

+

425 self._parent = parent 

+

426 

+

427 @property 

+

428 def title(self) -> Optional[str]: 

+

429 """Get the chapter title""" 

+

430 return self._title 

+

431 

+

432 @title.setter 

+

433 def title(self, title: str): 

+

434 """Set the chapter title""" 

+

435 self._title = title 

+

436 

+

437 @property 

+

438 def level(self) -> int: 

+

439 """Get the chapter level""" 

+

440 return self._level 

+

441 

+

442 @property 

+

443 def blocks(self) -> List[Block]: 

+

444 """Get the blocks in this chapter""" 

+

445 return self._blocks 

+

446 

+

447 @property 

+

448 def style(self): 

+

449 """Get the default style for this chapter""" 

+

450 return self._style 

+

451 

+

452 @style.setter 

+

453 def style(self, style): 

+

454 """Set the default style for this chapter""" 

+

455 self._style = style 

+

456 

+

457 def add_block(self, block: Block): 

+

458 """ 

+

459 Add a block to this chapter. 

+

460 

+

461 Args: 

+

462 block: The block to add 

+

463 """ 

+

464 self._blocks.append(block) 

+

465 

+

466 def create_paragraph(self, style=None) -> Paragraph: 

+

467 """ 

+

468 Create a new paragraph and add it to this chapter. 

+

469 

+

470 Args: 

+

471 style: Optional style override. If None, inherits from chapter 

+

472 

+

473 Returns: 

+

474 The newly created Paragraph object 

+

475 """ 

+

476 if style is None: 

+

477 style = self._style 

+

478 paragraph = Paragraph(style) 

+

479 self.add_block(paragraph) 

+

480 return paragraph 

+

481 

+

482 def create_heading( 

+

483 self, 

+

484 level: HeadingLevel = HeadingLevel.H1, 

+

485 style=None) -> Heading: 

+

486 """ 

+

487 Create a new heading and add it to this chapter. 

+

488 

+

489 Args: 

+

490 level: The heading level 

+

491 style: Optional style override. If None, inherits from chapter 

+

492 

+

493 Returns: 

+

494 The newly created Heading object 

+

495 """ 

+

496 if style is None: 

+

497 style = self._style 

+

498 heading = Heading(level, style) 

+

499 self.add_block(heading) 

+

500 return heading 

+

501 

+

502 # set_metadata() and get_metadata() are provided by MetadataContainer mixin 

+

503 # get_or_create_font() is provided by FontRegistry mixin 

+

504 

+

505 

+

506class Book(Document): 

+

507 """ 

+

508 Abstract representation of an ebook. 

+

509 A book is a document that contains chapters. 

+

510 """ 

+

511 

+

512 def __init__(self, title: Optional[str] = None, author: Optional[str] = None, 

+

513 language: str = "en-US", default_style=None): 

+

514 """ 

+

515 Initialize a new book. 

+

516 

+

517 Args: 

+

518 title: The book title 

+

519 author: The book author 

+

520 language: The book language code 

+

521 default_style: Optional default style for child chapters and blocks 

+

522 """ 

+

523 super().__init__(title, language, default_style) 

+

524 self._chapters: List[Chapter] = [] 

+

525 

+

526 if author: 

+

527 self.set_metadata(MetadataType.AUTHOR, author) 

+

528 

+

529 @property 

+

530 def chapters(self) -> List[Chapter]: 

+

531 """Get the chapters in this book""" 

+

532 return self._chapters 

+

533 

+

534 def add_chapter(self, chapter: Chapter): 

+

535 """ 

+

536 Add a chapter to this book. 

+

537 

+

538 Args: 

+

539 chapter: The chapter to add 

+

540 """ 

+

541 self._chapters.append(chapter) 

+

542 

+

543 def create_chapter( 

+

544 self, 

+

545 title: Optional[str] = None, 

+

546 level: int = 1, 

+

547 style=None) -> Chapter: 

+

548 """ 

+

549 Create and add a new chapter with inherited style. 

+

550 

+

551 Args: 

+

552 title: The chapter title 

+

553 level: The chapter level 

+

554 style: Optional style override. If None, inherits from book 

+

555 

+

556 Returns: 

+

557 The new chapter 

+

558 """ 

+

559 if style is None: 559 ↛ 561line 559 didn't jump to line 561 because the condition on line 559 was always true

+

560 style = self._default_style 

+

561 chapter = Chapter(title, level, style) 

+

562 self.add_chapter(chapter) 

+

563 return chapter 

+

564 

+

565 def get_author(self) -> Optional[str]: 

+

566 """ 

+

567 Get the book author. 

+

568 

+

569 Returns: 

+

570 The book author, or None if not set 

+

571 """ 

+

572 return self.get_metadata(MetadataType.AUTHOR) 

+

573 

+

574 def set_author(self, author: str): 

+

575 """ 

+

576 Set the book author. 

+

577 

+

578 Args: 

+

579 author: The book author 

+

580 """ 

+

581 self.set_metadata(MetadataType.AUTHOR, author) 

+

582 

+

583 def generate_table_of_contents(self) -> List[Tuple[int, str, Chapter]]: 

+

584 """ 

+

585 Generate a table of contents from chapters. 

+

586 

+

587 Returns: 

+

588 A list of tuples containing (level, title, chapter) 

+

589 """ 

+

590 toc = [] 

+

591 for chapter in self._chapters: 

+

592 if chapter.title: 

+

593 toc.append((chapter.level, chapter.title, chapter)) 

+

594 

+

595 return toc 

+
+ + + diff --git a/cov_info/htmlcov/z_af715639580e2d86_functional_py.html b/cov_info/htmlcov/z_af715639580e2d86_functional_py.html new file mode 100644 index 0000000..9134048 --- /dev/null +++ b/cov_info/htmlcov/z_af715639580e2d86_functional_py.html @@ -0,0 +1,444 @@ + + + + + Coverage for pyWebLayout/abstract/functional.py: 98% + + + + + +
+
+

+ Coverage for pyWebLayout/abstract/functional.py: + 98% +

+ +

+ 144 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from __future__ import annotations 

+

2from enum import Enum 

+

3from typing import Callable, Dict, Any, Optional, List, Tuple 

+

4from pyWebLayout.core.base import Interactable 

+

5 

+

6 

+

7class LinkType(Enum): 

+

8 """Enumeration of different types of links for classification purposes""" 

+

9 INTERNAL = 1 # Links within the same document (e.g., chapter references, bookmarks) 

+

10 EXTERNAL = 2 # Links to external resources (e.g., websites, other documents) 

+

11 API = 3 # Links that trigger API calls (e.g., for settings management) 

+

12 FUNCTION = 4 # Links that execute a specific function 

+

13 

+

14 

+

15class Link(Interactable): 

+

16 """ 

+

17 A link that can navigate to a location or execute a function. 

+

18 Links can be used for navigation within a document, to external resources, 

+

19 or to trigger API calls for functionality like settings management. 

+

20 """ 

+

21 

+

22 def __init__(self, 

+

23 location: str, 

+

24 link_type: LinkType = LinkType.INTERNAL, 

+

25 callback: Optional[Callable] = None, 

+

26 params: Optional[Dict[str, Any]] = None, 

+

27 title: Optional[str] = None, 

+

28 html_id: Optional[str] = None): 

+

29 """ 

+

30 Initialize a link. 

+

31 

+

32 Args: 

+

33 location: The target location or identifier for this link 

+

34 link_type: The type of link (internal, external, API, function) 

+

35 callback: Optional callback function to execute when the link is activated 

+

36 params: Optional parameters to pass to the callback or API 

+

37 title: Optional title/tooltip for the link 

+

38 html_id: Optional HTML id attribute (from <a id="...">) for callback binding 

+

39 """ 

+

40 super().__init__(callback) 

+

41 self._location = location 

+

42 self._link_type = link_type 

+

43 self._params = params or {} 

+

44 self._title = title 

+

45 self._html_id = html_id 

+

46 

+

47 @property 

+

48 def location(self) -> str: 

+

49 """Get the target location of this link""" 

+

50 return self._location 

+

51 

+

52 @property 

+

53 def link_type(self) -> LinkType: 

+

54 """Get the type of this link""" 

+

55 return self._link_type 

+

56 

+

57 @property 

+

58 def params(self) -> Dict[str, Any]: 

+

59 """Get the parameters for this link""" 

+

60 return self._params 

+

61 

+

62 @property 

+

63 def title(self) -> Optional[str]: 

+

64 """Get the title/tooltip for this link""" 

+

65 return self._title 

+

66 

+

67 @property 

+

68 def html_id(self) -> Optional[str]: 

+

69 """Get the HTML id attribute for callback binding""" 

+

70 return self._html_id 

+

71 

+

72 def execute(self, point=None) -> Any: 

+

73 """ 

+

74 Execute the link action based on its type. 

+

75 

+

76 For internal and external links, returns the location. 

+

77 For API and function links, executes the callback with the provided parameters. 

+

78 

+

79 Args: 

+

80 point: Optional interaction point passed from the interact() method 

+

81 

+

82 Returns: 

+

83 The result of the link execution, which depends on the link type. 

+

84 """ 

+

85 if self._link_type in (LinkType.API, LinkType.FUNCTION) and self._callback: 

+

86 return self._callback(self._location, point, **self._params) 

+

87 else: 

+

88 # For INTERNAL and EXTERNAL links, return the location 

+

89 # The renderer/browser will handle the navigation 

+

90 return self._location 

+

91 

+

92 

+

93class Button(Interactable): 

+

94 """ 

+

95 A button that can be clicked to execute an action. 

+

96 Buttons are similar to function links but are rendered differently. 

+

97 """ 

+

98 

+

99 def __init__(self, 

+

100 label: str, 

+

101 callback: Callable, 

+

102 params: Optional[Dict[str, Any]] = None, 

+

103 enabled: bool = True, 

+

104 html_id: Optional[str] = None): 

+

105 """ 

+

106 Initialize a button. 

+

107 

+

108 Args: 

+

109 label: The text label for the button 

+

110 callback: The function to execute when the button is clicked 

+

111 params: Optional parameters to pass to the callback 

+

112 enabled: Whether the button is initially enabled 

+

113 html_id: Optional HTML id attribute (from <button id="...">) for callback binding 

+

114 """ 

+

115 super().__init__(callback) 

+

116 self._label = label 

+

117 self._params = params or {} 

+

118 self._enabled = enabled 

+

119 self._html_id = html_id 

+

120 

+

121 @property 

+

122 def label(self) -> str: 

+

123 """Get the button label""" 

+

124 return self._label 

+

125 

+

126 @label.setter 

+

127 def label(self, label: str): 

+

128 """Set the button label""" 

+

129 self._label = label 

+

130 

+

131 @property 

+

132 def enabled(self) -> bool: 

+

133 """Check if the button is enabled""" 

+

134 return self._enabled 

+

135 

+

136 @enabled.setter 

+

137 def enabled(self, enabled: bool): 

+

138 """Enable or disable the button""" 

+

139 self._enabled = enabled 

+

140 

+

141 @property 

+

142 def params(self) -> Dict[str, Any]: 

+

143 """Get the button parameters""" 

+

144 return self._params 

+

145 

+

146 @property 

+

147 def html_id(self) -> Optional[str]: 

+

148 """Get the HTML id attribute for callback binding""" 

+

149 return self._html_id 

+

150 

+

151 def execute(self, point=None) -> Any: 

+

152 """ 

+

153 Execute the button's callback function if the button is enabled. 

+

154 

+

155 Args: 

+

156 point: Optional interaction point passed from the interact() method 

+

157 

+

158 Returns: 

+

159 The result of the callback function, or None if the button is disabled. 

+

160 """ 

+

161 if self._enabled and self._callback: 

+

162 return self._callback(point, **self._params) 

+

163 return None 

+

164 

+

165 

+

166class Form(Interactable): 

+

167 """ 

+

168 A form that can contain input fields and be submitted. 

+

169 Forms can be used for user input and settings configuration. 

+

170 """ 

+

171 

+

172 def __init__(self, 

+

173 form_id: str, 

+

174 action: Optional[str] = None, 

+

175 callback: Optional[Callable] = None, 

+

176 html_id: Optional[str] = None): 

+

177 """ 

+

178 Initialize a form. 

+

179 

+

180 Args: 

+

181 form_id: The unique identifier for this form 

+

182 action: The action URL or endpoint for form submission 

+

183 callback: Optional callback function to execute on form submission 

+

184 html_id: Optional HTML id attribute (from <form id="...">) for callback binding 

+

185 """ 

+

186 super().__init__(callback) 

+

187 self._form_id = form_id 

+

188 self._action = action 

+

189 self._fields: Dict[str, FormField] = {} 

+

190 self._html_id = html_id 

+

191 

+

192 @property 

+

193 def form_id(self) -> str: 

+

194 """Get the form ID""" 

+

195 return self._form_id 

+

196 

+

197 @property 

+

198 def action(self) -> Optional[str]: 

+

199 """Get the form action""" 

+

200 return self._action 

+

201 

+

202 @property 

+

203 def html_id(self) -> Optional[str]: 

+

204 """Get the HTML id attribute for callback binding""" 

+

205 return self._html_id 

+

206 

+

207 def add_field(self, field: FormField): 

+

208 """ 

+

209 Add a field to this form. 

+

210 

+

211 Args: 

+

212 field: The FormField to add 

+

213 """ 

+

214 self._fields[field.name] = field 

+

215 field.form = self 

+

216 

+

217 def get_field(self, name: str) -> Optional[FormField]: 

+

218 """ 

+

219 Get a field by name. 

+

220 

+

221 Args: 

+

222 name: The name of the field to get 

+

223 

+

224 Returns: 

+

225 The FormField with the specified name, or None if not found 

+

226 """ 

+

227 return self._fields.get(name) 

+

228 

+

229 def get_values(self) -> Dict[str, Any]: 

+

230 """ 

+

231 Get the current values of all fields in this form. 

+

232 

+

233 Returns: 

+

234 A dictionary mapping field names to their current values 

+

235 """ 

+

236 return {name: field.value for name, field in self._fields.items()} 

+

237 

+

238 def execute(self) -> Any: 

+

239 """ 

+

240 Submit the form, executing the callback with the form values. 

+

241 

+

242 Returns: 

+

243 The result of the callback function, or the form values if no callback is provided. 

+

244 """ 

+

245 values = self.get_values() 

+

246 

+

247 if self._callback: 

+

248 return self._callback(self._form_id, values) 

+

249 

+

250 return values 

+

251 

+

252 

+

253class FormFieldType(Enum): 

+

254 """Enumeration of different types of form fields""" 

+

255 TEXT = 1 

+

256 PASSWORD = 2 

+

257 CHECKBOX = 3 

+

258 RADIO = 4 

+

259 SELECT = 5 

+

260 TEXTAREA = 6 

+

261 NUMBER = 7 

+

262 DATE = 8 

+

263 TIME = 9 

+

264 EMAIL = 10 

+

265 URL = 11 

+

266 COLOR = 12 

+

267 RANGE = 13 

+

268 HIDDEN = 14 

+

269 

+

270 

+

271class FormField: 

+

272 """ 

+

273 A field in a form that can accept user input. 

+

274 """ 

+

275 

+

276 def __init__(self, 

+

277 name: str, 

+

278 field_type: FormFieldType, 

+

279 label: Optional[str] = None, 

+

280 value: Any = None, 

+

281 required: bool = False, 

+

282 options: Optional[List[Tuple[str, str]]] = None): 

+

283 """ 

+

284 Initialize a form field. 

+

285 

+

286 Args: 

+

287 name: The name of this field 

+

288 field_type: The type of this field 

+

289 label: Optional label for this field 

+

290 value: Initial value for this field 

+

291 required: Whether this field is required 

+

292 options: Options for select, radio, or checkbox fields (list of (value, label) tuples) 

+

293 """ 

+

294 self._name = name 

+

295 self._field_type = field_type 

+

296 self._label = label or name 

+

297 self._value = value 

+

298 self._required = required 

+

299 self._options = options or [] 

+

300 self._form: Optional[Form] = None 

+

301 

+

302 @property 

+

303 def name(self) -> str: 

+

304 """Get the field name""" 

+

305 return self._name 

+

306 

+

307 @property 

+

308 def field_type(self) -> FormFieldType: 

+

309 """Get the field type""" 

+

310 return self._field_type 

+

311 

+

312 @property 

+

313 def label(self) -> str: 

+

314 """Get the field label""" 

+

315 return self._label 

+

316 

+

317 @property 

+

318 def value(self) -> Any: 

+

319 """Get the current field value""" 

+

320 return self._value 

+

321 

+

322 @value.setter 

+

323 def value(self, value: Any): 

+

324 """Set the field value""" 

+

325 self._value = value 

+

326 

+

327 @property 

+

328 def required(self) -> bool: 

+

329 """Check if the field is required""" 

+

330 return self._required 

+

331 

+

332 @property 

+

333 def options(self) -> List[Tuple[str, str]]: 

+

334 """Get the field options""" 

+

335 return self._options 

+

336 

+

337 @property 

+

338 def form(self) -> Optional[Form]: 

+

339 """Get the form containing this field""" 

+

340 return self._form 

+

341 

+

342 @form.setter 

+

343 def form(self, form: Form): 

+

344 """Set the form containing this field""" 

+

345 self._form = form 

+
+ + + diff --git a/cov_info/htmlcov/z_af715639580e2d86_inline_py.html b/cov_info/htmlcov/z_af715639580e2d86_inline_py.html new file mode 100644 index 0000000..fd37963 --- /dev/null +++ b/cov_info/htmlcov/z_af715639580e2d86_inline_py.html @@ -0,0 +1,519 @@ + + + + + Coverage for pyWebLayout/abstract/inline.py: 99% + + + + + +
+
+

+ Coverage for pyWebLayout/abstract/inline.py: + 99% +

+ +

+ 157 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from __future__ import annotations 

+

2from pyWebLayout.core import Hierarchical 

+

3from pyWebLayout.style import Font 

+

4from pyWebLayout.style.abstract_style import AbstractStyle 

+

5from typing import Tuple, Union, List, Optional, Dict, Any, Callable 

+

6import pyphen 

+

7 

+

8# Import LinkType for type hints (imported at module level to avoid F821 linting error) 

+

9from pyWebLayout.abstract.functional import LinkType 

+

10 

+

11 

+

12class Word: 

+

13 """ 

+

14 An abstract representation of a word in a document. Words can be split across 

+

15 lines or pages during rendering. This class manages the logical representation 

+

16 of a word without any rendering specifics. 

+

17 

+

18 Now uses AbstractStyle objects for memory efficiency and proper style management. 

+

19 """ 

+

20 

+

21 def __init__(self, 

+

22 text: str, 

+

23 style: Union[Font, 

+

24 AbstractStyle], 

+

25 background=None, 

+

26 previous: Union['Word', 

+

27 None] = None): 

+

28 """ 

+

29 Initialize a new Word. 

+

30 

+

31 Args: 

+

32 text: The text content of the word 

+

33 style: AbstractStyle object or Font object (for backward compatibility) 

+

34 background: Optional background color override 

+

35 previous: Reference to the previous word in sequence 

+

36 """ 

+

37 self._text = text 

+

38 self._style = style 

+

39 self._background = background 

+

40 self._previous = previous 

+

41 self._next = None 

+

42 self.concrete = None 

+

43 if previous: 

+

44 previous.add_next(self) 

+

45 

+

46 @classmethod 

+

47 def create_and_add_to(cls, text: str, container, style: Optional[Font] = None, 

+

48 background=None) -> 'Word': 

+

49 """ 

+

50 Create a new Word and add it to a container, inheriting style and language 

+

51 from the container if not explicitly provided. 

+

52 

+

53 This method provides a convenient way to create words that automatically 

+

54 inherit styling from their container (Paragraph, FormattedSpan, etc.) 

+

55 without copying string values - using object references instead. 

+

56 

+

57 Args: 

+

58 text: The text content of the word 

+

59 container: The container to add the word to (must have add_word method and style property) 

+

60 style: Optional Font style override. If None, inherits from container 

+

61 background: Optional background color override. If None, inherits from container 

+

62 

+

63 Returns: 

+

64 The newly created Word object 

+

65 

+

66 Raises: 

+

67 AttributeError: If the container doesn't have the required add_word method or style property 

+

68 """ 

+

69 # Inherit style from container if not provided 

+

70 if style is None: 

+

71 if hasattr(container, 'style'): 

+

72 style = container.style 

+

73 else: 

+

74 raise AttributeError( 

+

75 f"Container {type(container).__name__} must have a 'style' property") 

+

76 

+

77 # Inherit background from container if not provided 

+

78 if background is None and hasattr(container, 'background'): 

+

79 background = container.background 

+

80 

+

81 # Determine the previous word for proper linking 

+

82 previous = None 

+

83 if hasattr(container, '_words') and container._words: 

+

84 # Container has a _words list (like FormattedSpan) 

+

85 previous = container._words[-1] 

+

86 elif hasattr(container, 'words'): 

+

87 # Container has a words() method (like Paragraph) 

+

88 try: 

+

89 # Get the last word from the iterator 

+

90 for _, word in container.words(): 

+

91 previous = word 

+

92 except (StopIteration, TypeError): 

+

93 previous = None 

+

94 

+

95 # Create the new word 

+

96 word = cls(text, style, background, previous) 

+

97 

+

98 # Link the previous word to this new one 

+

99 if previous: 

+

100 previous.add_next(word) 

+

101 

+

102 # Add the word to the container 

+

103 if hasattr(container, 'add_word'): 

+

104 # Check if add_word expects a Word object or text string 

+

105 import inspect 

+

106 sig = inspect.signature(container.add_word) 

+

107 params = list(sig.parameters.keys()) 

+

108 

+

109 if len(params) > 0: 

+

110 # Peek at the parameter name to guess the expected type 

+

111 param_name = params[0] 

+

112 if param_name in ['word', 'word_obj', 'word_object']: 

+

113 # Expects a Word object 

+

114 container.add_word(word) 

+

115 else: 

+

116 # Might expect text string (like FormattedSpan.add_word) 

+

117 # In this case, we can't use the container's add_word as it would create 

+

118 # a duplicate Word. We need to add directly to the container's word 

+

119 # list. 

+

120 if hasattr(container, '_words'): 

+

121 container._words.append(word) 

+

122 else: 

+

123 # Fallback: try calling with the Word object anyway 

+

124 container.add_word(word) 

+

125 else: 

+

126 # No parameters, shouldn't happen with add_word methods 

+

127 container.add_word(word) 

+

128 else: 

+

129 raise AttributeError( 

+

130 f"Container {type(container).__name__} must have an 'add_word' method") 

+

131 

+

132 return word 

+

133 

+

134 def add_concete(self, text: Union[Any, Tuple[Any, Any]]): 

+

135 self.concrete = text 

+

136 

+

137 @property 

+

138 def text(self) -> str: 

+

139 """Get the text content of the word""" 

+

140 return self._text 

+

141 

+

142 @property 

+

143 def style(self) -> Font: 

+

144 """Get the font style of the word""" 

+

145 return self._style 

+

146 

+

147 @property 

+

148 def background(self): 

+

149 """Get the background color of the word""" 

+

150 return self._background 

+

151 

+

152 @property 

+

153 def previous(self) -> Union['Word', None]: 

+

154 """Get the previous word in sequence""" 

+

155 return self._previous 

+

156 

+

157 @property 

+

158 def next(self) -> Union['Word', None]: 

+

159 """Get the next word in sequence""" 

+

160 return self._next 

+

161 

+

162 def add_next(self, next_word: 'Word'): 

+

163 """Set the next word in sequence""" 

+

164 self._next = next_word 

+

165 

+

166 def possible_hyphenation(self, language: str = None) -> bool: 

+

167 """ 

+

168 Hyphenate the word and store the parts. 

+

169 

+

170 Args: 

+

171 language: Language code for hyphenation. If None, uses the style's language. 

+

172 

+

173 Returns: 

+

174 bool: True if the word was hyphenated, False otherwise. 

+

175 """ 

+

176 

+

177 dic = pyphen.Pyphen(lang=self._style.language) 

+

178 return list(dic.iterate(self._text)) 

+

179 

+

180 

+

181... 

+

182 

+

183 

+

184class FormattedSpan: 

+

185 """ 

+

186 A run of words with consistent formatting. 

+

187 This represents a sequence of words that share the same style attributes. 

+

188 """ 

+

189 

+

190 def __init__(self, style: Font, background=None): 

+

191 """ 

+

192 Initialize a new formatted span. 

+

193 

+

194 Args: 

+

195 style: Font style information for all words in this span 

+

196 background: Optional background color override 

+

197 """ 

+

198 self._style = style 

+

199 self._background = background if background else style.background 

+

200 self._words: List[Word] = [] 

+

201 

+

202 @classmethod 

+

203 def create_and_add_to( 

+

204 cls, 

+

205 container, 

+

206 style: Optional[Font] = None, 

+

207 background=None) -> 'FormattedSpan': 

+

208 """ 

+

209 Create a new FormattedSpan and add it to a container, inheriting style from 

+

210 the container if not explicitly provided. 

+

211 

+

212 Args: 

+

213 container: The container to add the span to (must have add_span method and style property) 

+

214 style: Optional Font style override. If None, inherits from container 

+

215 background: Optional background color override 

+

216 

+

217 Returns: 

+

218 The newly created FormattedSpan object 

+

219 

+

220 Raises: 

+

221 AttributeError: If the container doesn't have the required add_span method or style property 

+

222 """ 

+

223 # Inherit style from container if not provided 

+

224 if style is None: 

+

225 if hasattr(container, 'style'): 

+

226 style = container.style 

+

227 else: 

+

228 raise AttributeError( 

+

229 f"Container {type(container).__name__} must have a 'style' property") 

+

230 

+

231 # Inherit background from container if not provided 

+

232 if background is None and hasattr(container, 'background'): 

+

233 background = container.background 

+

234 

+

235 # Create the new span 

+

236 span = cls(style, background) 

+

237 

+

238 # Add the span to the container 

+

239 if hasattr(container, 'add_span'): 

+

240 container.add_span(span) 

+

241 else: 

+

242 raise AttributeError( 

+

243 f"Container {type(container).__name__} must have an 'add_span' method") 

+

244 

+

245 return span 

+

246 

+

247 @property 

+

248 def style(self) -> Font: 

+

249 """Get the font style of this span""" 

+

250 return self._style 

+

251 

+

252 @property 

+

253 def background(self): 

+

254 """Get the background color of this span""" 

+

255 return self._background 

+

256 

+

257 @property 

+

258 def words(self) -> List[Word]: 

+

259 """Get the list of words in this span""" 

+

260 return self._words 

+

261 

+

262 def add_word(self, text: str) -> Word: 

+

263 """ 

+

264 Create and add a new word to this span. 

+

265 

+

266 Args: 

+

267 text: The text content of the word 

+

268 

+

269 Returns: 

+

270 The newly created Word object 

+

271 """ 

+

272 # Get the previous word if any 

+

273 previous = self._words[-1] if self._words else None 

+

274 

+

275 # Create the new word 

+

276 word = Word(text, self._style, self._background, previous) 

+

277 

+

278 # Link the previous word to this new one 

+

279 if previous: 

+

280 previous.add_next(word) 

+

281 

+

282 # Add the word to our list 

+

283 self._words.append(word) 

+

284 

+

285 return word 

+

286 

+

287 

+

288class LinkedWord(Word): 

+

289 """ 

+

290 A Word that is also a Link - combines text content with hyperlink functionality. 

+

291 

+

292 When a word is part of a hyperlink, it becomes clickable and can trigger 

+

293 navigation or callbacks. Multiple words can share the same link destination. 

+

294 """ 

+

295 

+

296 def __init__(self, text: str, style: Union[Font, 'AbstractStyle'], 

+

297 location: str, link_type: Optional['LinkType'] = None, 

+

298 callback: Optional[Callable] = None, 

+

299 background=None, previous: Optional[Word] = None, 

+

300 params: Optional[Dict[str, Any]] = None, 

+

301 title: Optional[str] = None): 

+

302 """ 

+

303 Initialize a linked word. 

+

304 

+

305 Args: 

+

306 text: The text content of the word 

+

307 style: The font style 

+

308 location: The link target (URL, bookmark, etc.) 

+

309 link_type: Type of link (INTERNAL, EXTERNAL, etc.) 

+

310 callback: Optional callback for link activation 

+

311 background: Optional background color 

+

312 previous: Previous word in sequence 

+

313 params: Parameters for the link 

+

314 title: Tooltip/title for the link 

+

315 """ 

+

316 # Initialize Word first 

+

317 super().__init__(text, style, background, previous) 

+

318 

+

319 # Store link properties 

+

320 self._location = location 

+

321 self._link_type = link_type or LinkType.EXTERNAL 

+

322 self._callback = callback 

+

323 self._params = params or {} 

+

324 self._title = title 

+

325 

+

326 @property 

+

327 def location(self) -> str: 

+

328 """Get the link target location""" 

+

329 return self._location 

+

330 

+

331 @property 

+

332 def link_type(self): 

+

333 """Get the type of link""" 

+

334 return self._link_type 

+

335 

+

336 @property 

+

337 def link_callback(self) -> Optional[Callable]: 

+

338 """Get the link callback (distinct from word callback)""" 

+

339 return self._callback 

+

340 

+

341 @property 

+

342 def params(self) -> Dict[str, Any]: 

+

343 """Get the link parameters""" 

+

344 return self._params 

+

345 

+

346 @property 

+

347 def link_title(self) -> Optional[str]: 

+

348 """Get the link title/tooltip""" 

+

349 return self._title 

+

350 

+

351 def execute_link(self, context: Optional[Dict[str, Any]] = None) -> Any: 

+

352 """ 

+

353 Execute the link action. 

+

354 

+

355 Args: 

+

356 context: Optional context dict (e.g., {'text': word.text}) 

+

357 

+

358 Returns: 

+

359 The result of the link execution 

+

360 """ 

+

361 # Add word text to context 

+

362 full_context = {**self._params, 'text': self._text} 

+

363 if context: 363 ↛ 364line 363 didn't jump to line 364 because the condition on line 363 was never true

+

364 full_context.update(context) 

+

365 

+

366 if self._link_type in (LinkType.API, LinkType.FUNCTION) and self._callback: 

+

367 return self._callback(self._location, **full_context) 

+

368 else: 

+

369 # For INTERNAL and EXTERNAL links, return the location 

+

370 return self._location 

+

371 

+

372 

+

373class LineBreak(Hierarchical): 

+

374 """ 

+

375 A line break element that forces a new line within text content. 

+

376 While this is an inline element that can occur within paragraphs, 

+

377 it has block-like properties for consistency with the abstract model. 

+

378 

+

379 Uses Hierarchical mixin for parent-child relationship management. 

+

380 """ 

+

381 

+

382 def __init__(self): 

+

383 """Initialize a line break element.""" 

+

384 super().__init__() 

+

385 # Import here to avoid circular imports 

+

386 from .block import BlockType 

+

387 self._block_type = BlockType.LINE_BREAK 

+

388 

+

389 @property 

+

390 def block_type(self): 

+

391 """Get the block type for this line break""" 

+

392 return self._block_type 

+

393 

+

394 @classmethod 

+

395 def create_and_add_to(cls, container) -> 'LineBreak': 

+

396 """ 

+

397 Create a new LineBreak and add it to a container. 

+

398 

+

399 Args: 

+

400 container: The container to add the line break to 

+

401 

+

402 Returns: 

+

403 The newly created LineBreak object 

+

404 """ 

+

405 # Create the new line break 

+

406 line_break = cls() 

+

407 

+

408 # Add the line break to the container if it has an appropriate method 

+

409 if hasattr(container, 'add_line_break'): 

+

410 container.add_line_break(line_break) 

+

411 elif hasattr(container, 'add_element'): 

+

412 container.add_element(line_break) 

+

413 elif hasattr(container, 'add_word'): 

+

414 # Some containers might treat line breaks like words 

+

415 container.add_word(line_break) 

+

416 else: 

+

417 # Set parent relationship manually 

+

418 line_break.parent = container 

+

419 

+

420 return line_break 

+
+ + + diff --git a/cov_info/htmlcov/z_af715639580e2d86_interactive_image_py.html b/cov_info/htmlcov/z_af715639580e2d86_interactive_image_py.html new file mode 100644 index 0000000..f2cdb64 --- /dev/null +++ b/cov_info/htmlcov/z_af715639580e2d86_interactive_image_py.html @@ -0,0 +1,262 @@ + + + + + Coverage for pyWebLayout/abstract/interactive_image.py: 80% + + + + + +
+
+

+ Coverage for pyWebLayout/abstract/interactive_image.py: + 80% +

+ +

+ 34 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Interactive and queryable image for pyWebLayout. 

+

3 

+

4Provides an InteractiveImage class that combines Image with Interactable 

+

5and Queriable capabilities, allowing images to respond to tap events with 

+

6proper bounding box detection. 

+

7""" 

+

8 

+

9from typing import Optional, Callable, Tuple 

+

10import numpy as np 

+

11 

+

12from .block import Image 

+

13from ..core.base import Interactable, Queriable 

+

14 

+

15 

+

16class InteractiveImage(Image, Interactable, Queriable): 

+

17 """ 

+

18 An image that can be interacted with and queried for hit detection. 

+

19 

+

20 This combines pyWebLayout's Image block with Interactable and Queriable 

+

21 capabilities, allowing the image to: 

+

22 - Have a callback that fires when tapped 

+

23 - Know its rendered position (origin) 

+

24 - Detect if a point is within its bounds 

+

25 

+

26 Example: 

+

27 >>> img = InteractiveImage( 

+

28 ... source="cover.png", 

+

29 ... alt_text="Book Title", 

+

30 ... callback=lambda point: "/path/to/book.epub" 

+

31 ... ) 

+

32 >>> # After rendering, origin is set automatically 

+

33 >>> # Check if tap is inside 

+

34 >>> result = img.interact((120, 250)) 

+

35 >>> # Returns "/path/to/book.epub" if inside, None if outside 

+

36 """ 

+

37 

+

38 def __init__( 

+

39 self, 

+

40 source: str = "", 

+

41 alt_text: str = "", 

+

42 width: Optional[int] = None, 

+

43 height: Optional[int] = None, 

+

44 callback: Optional[Callable] = None 

+

45 ): 

+

46 """ 

+

47 Initialize an interactive image. 

+

48 

+

49 Args: 

+

50 source: The image source URL or path 

+

51 alt_text: Alternative text for accessibility 

+

52 width: Optional image width in pixels 

+

53 height: Optional image height in pixels 

+

54 callback: Function to call when image is tapped (receives point coordinates) 

+

55 """ 

+

56 # Initialize Image 

+

57 Image.__init__( 

+

58 self, 

+

59 source=source, 

+

60 alt_text=alt_text, 

+

61 width=width, 

+

62 height=height) 

+

63 

+

64 # Initialize Interactable 

+

65 Interactable.__init__(self, callback=callback) 

+

66 

+

67 # Initialize position tracking 

+

68 self._origin = np.array([0, 0]) # Will be set during rendering 

+

69 self.size = (width or 0, height or 0) # Will be updated during rendering 

+

70 

+

71 def interact(self, point: np.generic) -> Optional[any]: 

+

72 """ 

+

73 Handle interaction at the given point. 

+

74 

+

75 Only triggers the callback if the point is within the image bounds. 

+

76 

+

77 Args: 

+

78 point: The coordinates of the interaction (x, y) 

+

79 

+

80 Returns: 

+

81 The result of the callback if point is inside, None otherwise 

+

82 """ 

+

83 # Check if point is inside this image 

+

84 if self.in_object(point): 

+

85 # Point is inside, trigger callback 

+

86 if self._callback is not None: 

+

87 return self._callback(point) 

+

88 

+

89 return None 

+

90 

+

91 def in_object(self, point: np.generic) -> bool: 

+

92 """ 

+

93 Check if a point is within the image bounds. 

+

94 

+

95 Args: 

+

96 point: The coordinates to check (x, y) 

+

97 

+

98 Returns: 

+

99 True if point is inside the image, False otherwise 

+

100 """ 

+

101 point_array = np.array(point) 

+

102 relative_point = point_array - self._origin 

+

103 return np.all((0 <= relative_point) & (relative_point < self.size)) 

+

104 

+

105 @classmethod 

+

106 def create_and_add_to( 

+

107 cls, 

+

108 parent, 

+

109 source: str, 

+

110 alt_text: str = "", 

+

111 width: Optional[int] = None, 

+

112 height: Optional[int] = None, 

+

113 callback: Optional[Callable] = None 

+

114 ) -> 'InteractiveImage': 

+

115 """ 

+

116 Create an interactive image and add it to a parent block. 

+

117 

+

118 This is a convenience method that mimics the Image.create_and_add_to API 

+

119 but creates an InteractiveImage instead. 

+

120 

+

121 Args: 

+

122 parent: Parent block to add this image to 

+

123 source: The image source URL or path 

+

124 alt_text: Alternative text for accessibility 

+

125 width: Optional image width in pixels 

+

126 height: Optional image height in pixels 

+

127 callback: Function to call when image is tapped 

+

128 

+

129 Returns: 

+

130 The created InteractiveImage instance 

+

131 """ 

+

132 img = cls( 

+

133 source=source, 

+

134 alt_text=alt_text, 

+

135 width=width, 

+

136 height=height, 

+

137 callback=callback 

+

138 ) 

+

139 

+

140 # Add to parent using its add_block method 

+

141 if hasattr(parent, 'add_block'): 141 ↛ 142line 141 didn't jump to line 142 because the condition on line 141 was never true

+

142 parent.add_block(img) 

+

143 elif hasattr(parent, 'add_child'): 143 ↛ 144line 143 didn't jump to line 144 because the condition on line 143 was never true

+

144 parent.add_child(img) 

+

145 elif hasattr(parent, '_children'): 145 ↛ 147line 145 didn't jump to line 147 because the condition on line 145 was always true

+

146 parent._children.append(img) 

+

147 elif hasattr(parent, '_blocks'): 

+

148 parent._blocks.append(img) 

+

149 

+

150 return img 

+

151 

+

152 def set_rendered_bounds(self, origin: Tuple[int, int], size: Tuple[int, int]): 

+

153 """ 

+

154 Set the rendered position and size of this image. 

+

155 

+

156 This should be called by the renderer after it places the image. 

+

157 

+

158 Args: 

+

159 origin: (x, y) coordinates of top-left corner 

+

160 size: (width, height) of the rendered image 

+

161 """ 

+

162 self._origin = np.array(origin) 

+

163 self.size = size 

+
+ + + diff --git a/cov_info/htmlcov/z_ba7f6bdeb0188088___init___py.html b/cov_info/htmlcov/z_ba7f6bdeb0188088___init___py.html new file mode 100644 index 0000000..6794f1a --- /dev/null +++ b/cov_info/htmlcov/z_ba7f6bdeb0188088___init___py.html @@ -0,0 +1,122 @@ + + + + + Coverage for pyWebLayout/style/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/style/__init__.py: + 100% +

+ +

+ 6 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Style system for the pyWebLayout library. 

+

3 

+

4This module provides the core styling components used throughout the library. 

+

5""" 

+

6 

+

7from .fonts import ( 

+

8 Font, FontWeight, FontStyle, TextDecoration, 

+

9 BundledFont, get_bundled_font_path, get_bundled_fonts_dir 

+

10) 

+

11from .abstract_style import ( 

+

12 AbstractStyle, AbstractStyleRegistry, FontFamily, FontSize 

+

13) 

+

14from .concrete_style import ConcreteStyle 

+

15from .page_style import PageStyle 

+

16from .alignment import Alignment 

+

17 

+

18__all__ = [ 

+

19 "Font", "FontWeight", "FontStyle", "TextDecoration", 

+

20 "BundledFont", "get_bundled_font_path", "get_bundled_fonts_dir", 

+

21 "AbstractStyle", "AbstractStyleRegistry", "FontFamily", "FontSize", 

+

22 "ConcreteStyle", "PageStyle", "Alignment" 

+

23] 

+
+ + + diff --git a/cov_info/htmlcov/z_ba7f6bdeb0188088_abstract_style_py.html b/cov_info/htmlcov/z_ba7f6bdeb0188088_abstract_style_py.html new file mode 100644 index 0000000..54bd8dc --- /dev/null +++ b/cov_info/htmlcov/z_ba7f6bdeb0188088_abstract_style_py.html @@ -0,0 +1,441 @@ + + + + + Coverage for pyWebLayout/style/abstract_style.py: 75% + + + + + +
+
+

+ Coverage for pyWebLayout/style/abstract_style.py: + 75% +

+ +

+ 130 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Abstract style system for storing document styling intent. 

+

3 

+

4This module defines styles in terms of semantic meaning rather than concrete 

+

5rendering parameters, allowing for flexible interpretation by different 

+

6rendering systems and user preferences. 

+

7""" 

+

8 

+

9from .alignment import Alignment 

+

10from typing import Dict, Optional, Tuple, Union 

+

11from dataclasses import dataclass 

+

12from enum import Enum 

+

13from .fonts import FontWeight, FontStyle, TextDecoration 

+

14 

+

15 

+

16class FontFamily(Enum): 

+

17 """Semantic font family categories""" 

+

18 SERIF = "serif" 

+

19 SANS_SERIF = "sans-serif" 

+

20 MONOSPACE = "monospace" 

+

21 CURSIVE = "cursive" 

+

22 FANTASY = "fantasy" 

+

23 

+

24 

+

25class FontSize(Enum): 

+

26 """Semantic font sizes""" 

+

27 XX_SMALL = "xx-small" 

+

28 X_SMALL = "x-small" 

+

29 SMALL = "small" 

+

30 MEDIUM = "medium" 

+

31 LARGE = "large" 

+

32 X_LARGE = "x-large" 

+

33 XX_LARGE = "xx-large" 

+

34 

+

35 # Allow numeric values as well 

+

36 @classmethod 

+

37 def from_value(cls, value: Union[str, int, float]) -> Union['FontSize', int]: 

+

38 """Convert a value to FontSize enum or return numeric value""" 

+

39 if isinstance(value, (int, float)): 

+

40 return int(value) 

+

41 if isinstance(value, str): 

+

42 try: 

+

43 return cls(value) 

+

44 except ValueError: 

+

45 # Try to parse as number 

+

46 try: 

+

47 return int(float(value)) 

+

48 except ValueError: 

+

49 return cls.MEDIUM 

+

50 return cls.MEDIUM 

+

51 

+

52 

+

53# Import Alignment from the centralized location 

+

54 

+

55# Use Alignment for text alignment 

+

56TextAlign = Alignment 

+

57 

+

58 

+

59@dataclass(frozen=True) 

+

60class AbstractStyle: 

+

61 """ 

+

62 Abstract representation of text styling that captures semantic intent 

+

63 rather than concrete rendering parameters. 

+

64 

+

65 This allows the same document to be rendered differently based on 

+

66 user preferences, device capabilities, or accessibility requirements. 

+

67 

+

68 Being frozen=True makes this class hashable and immutable, which is 

+

69 perfect for use as dictionary keys and preventing accidental modification. 

+

70 """ 

+

71 

+

72 # Font properties (semantic) 

+

73 font_family: FontFamily = FontFamily.SERIF 

+

74 font_size: Union[FontSize, int] = FontSize.MEDIUM 

+

75 font_weight: FontWeight = FontWeight.NORMAL 

+

76 font_style: FontStyle = FontStyle.NORMAL 

+

77 text_decoration: TextDecoration = TextDecoration.NONE 

+

78 

+

79 # Color (as semantic names or RGB) 

+

80 color: Union[str, Tuple[int, int, int]] = "black" 

+

81 background_color: Optional[Union[str, Tuple[int, int, int, int]]] = None 

+

82 

+

83 # Text properties 

+

84 text_align: TextAlign = TextAlign.LEFT 

+

85 line_height: Optional[Union[str, float]] = None # "normal", "1.2", 1.5, etc. 

+

86 letter_spacing: Optional[Union[str, float]] = None # "normal", "0.1em", etc. 

+

87 word_spacing: Optional[Union[str, float]] = None 

+

88 word_spacing_min: Optional[Union[str, float]] = None # Minimum allowed word spacing 

+

89 word_spacing_max: Optional[Union[str, float]] = None # Maximum allowed word spacing 

+

90 

+

91 # Language and locale 

+

92 language: str = "en-US" 

+

93 

+

94 # Hierarchy properties 

+

95 parent_style_id: Optional[str] = None 

+

96 

+

97 def __post_init__(self): 

+

98 """Validate and normalize values after creation""" 

+

99 # Normalize font_size if it's a string that could be a number 

+

100 if isinstance(self.font_size, str): 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true

+

101 try: 

+

102 object.__setattr__(self, 'font_size', int(float(self.font_size))) 

+

103 except ValueError: 

+

104 # Keep as is if it's a semantic size name 

+

105 pass 

+

106 

+

107 def __hash__(self) -> int: 

+

108 """ 

+

109 Custom hash implementation to ensure consistent hashing. 

+

110 

+

111 Since this is a frozen dataclass, it should be hashable by default, 

+

112 but we provide a custom implementation to ensure all fields are 

+

113 properly considered and to handle the Union types correctly. 

+

114 """ 

+

115 # Convert all values to hashable forms 

+

116 hashable_values = ( 

+

117 self.font_family, 

+

118 self.font_size if isinstance(self.font_size, int) else self.font_size, 

+

119 self.font_weight, 

+

120 self.font_style, 

+

121 self.text_decoration, 

+

122 self.color if isinstance(self.color, (str, tuple)) else str(self.color), 

+

123 self.background_color, 

+

124 self.text_align, 

+

125 self.line_height, 

+

126 self.letter_spacing, 

+

127 self.word_spacing, 

+

128 self.word_spacing_min, 

+

129 self.word_spacing_max, 

+

130 self.language, 

+

131 self.parent_style_id 

+

132 ) 

+

133 

+

134 return hash(hashable_values) 

+

135 

+

136 def merge_with(self, other: 'AbstractStyle') -> 'AbstractStyle': 

+

137 """ 

+

138 Create a new AbstractStyle by merging this one with another. 

+

139 The other style's properties take precedence. 

+

140 

+

141 Args: 

+

142 other: AbstractStyle to merge with this one 

+

143 

+

144 Returns: 

+

145 New AbstractStyle with merged values 

+

146 """ 

+

147 # Get all fields from both styles 

+

148 current_dict = { 

+

149 field.name: getattr(self, field.name) 

+

150 for field in self.__dataclass_fields__.values() 

+

151 } 

+

152 

+

153 other_dict = { 

+

154 field.name: getattr(other, field.name) 

+

155 for field in other.__dataclass_fields__.values() 

+

156 if getattr(other, field.name) != field.default 

+

157 } 

+

158 

+

159 # Merge dictionaries (other takes precedence) 

+

160 merged_dict = current_dict.copy() 

+

161 merged_dict.update(other_dict) 

+

162 

+

163 return AbstractStyle(**merged_dict) 

+

164 

+

165 def with_modifications(self, **kwargs) -> 'AbstractStyle': 

+

166 """ 

+

167 Create a new AbstractStyle with specified modifications. 

+

168 

+

169 Args: 

+

170 **kwargs: Properties to modify 

+

171 

+

172 Returns: 

+

173 New AbstractStyle with modifications applied 

+

174 """ 

+

175 current_dict = { 

+

176 field.name: getattr(self, field.name) 

+

177 for field in self.__dataclass_fields__.values() 

+

178 } 

+

179 

+

180 current_dict.update(kwargs) 

+

181 return AbstractStyle(**current_dict) 

+

182 

+

183 

+

184class AbstractStyleRegistry: 

+

185 """ 

+

186 Registry for managing abstract document styles. 

+

187 

+

188 This registry stores the semantic styling intent and provides 

+

189 deduplication and inheritance capabilities using hashable AbstractStyle objects. 

+

190 """ 

+

191 

+

192 def __init__(self): 

+

193 """Initialize an empty abstract style registry.""" 

+

194 self._styles: Dict[str, AbstractStyle] = {} 

+

195 # Reverse mapping using hashable styles 

+

196 self._style_to_id: Dict[AbstractStyle, str] = {} 

+

197 self._next_id = 1 

+

198 

+

199 # Create and register the default style 

+

200 self._default_style = self._create_default_style() 

+

201 

+

202 def _create_default_style(self) -> AbstractStyle: 

+

203 """Create the default document style.""" 

+

204 default_style = AbstractStyle() 

+

205 style_id = "default" 

+

206 self._styles[style_id] = default_style 

+

207 self._style_to_id[default_style] = style_id 

+

208 return default_style 

+

209 

+

210 @property 

+

211 def default_style(self) -> AbstractStyle: 

+

212 """Get the default style for the document.""" 

+

213 return self._default_style 

+

214 

+

215 def _generate_style_id(self) -> str: 

+

216 """Generate a unique style ID.""" 

+

217 style_id = f"abstract_style_{self._next_id}" 

+

218 self._next_id += 1 

+

219 return style_id 

+

220 

+

221 def get_style_id(self, style: AbstractStyle) -> Optional[str]: 

+

222 """ 

+

223 Get the ID for a given style if it exists in the registry. 

+

224 

+

225 Args: 

+

226 style: AbstractStyle to find 

+

227 

+

228 Returns: 

+

229 Style ID if found, None otherwise 

+

230 """ 

+

231 return self._style_to_id.get(style) 

+

232 

+

233 def register_style( 

+

234 self, 

+

235 style: AbstractStyle, 

+

236 style_id: Optional[str] = None) -> str: 

+

237 """ 

+

238 Register a style in the registry. 

+

239 

+

240 Args: 

+

241 style: AbstractStyle to register 

+

242 style_id: Optional style ID. If None, one will be generated 

+

243 

+

244 Returns: 

+

245 The style ID 

+

246 """ 

+

247 # Check if style already exists 

+

248 existing_id = self.get_style_id(style) 

+

249 if existing_id is not None: 249 ↛ 250line 249 didn't jump to line 250 because the condition on line 249 was never true

+

250 return existing_id 

+

251 

+

252 if style_id is None: 252 ↛ 255line 252 didn't jump to line 255 because the condition on line 252 was always true

+

253 style_id = self._generate_style_id() 

+

254 

+

255 self._styles[style_id] = style 

+

256 self._style_to_id[style] = style_id 

+

257 return style_id 

+

258 

+

259 def get_or_create_style(self, 

+

260 style: Optional[AbstractStyle] = None, 

+

261 parent_id: Optional[str] = None, 

+

262 **kwargs) -> Tuple[str, AbstractStyle]: 

+

263 """ 

+

264 Get an existing style or create a new one. 

+

265 

+

266 Args: 

+

267 style: AbstractStyle object. If None, created from kwargs 

+

268 parent_id: Optional parent style ID 

+

269 **kwargs: Individual style properties (used if style is None) 

+

270 

+

271 Returns: 

+

272 Tuple of (style_id, AbstractStyle) 

+

273 """ 

+

274 # Create style object if not provided 

+

275 if style is None: 

+

276 # Filter out None values from kwargs 

+

277 filtered_kwargs = {k: v for k, v in kwargs.items() if v is not None} 

+

278 if parent_id: 278 ↛ 279line 278 didn't jump to line 279 because the condition on line 278 was never true

+

279 filtered_kwargs['parent_style_id'] = parent_id 

+

280 style = AbstractStyle(**filtered_kwargs) 

+

281 

+

282 # Check if we already have this style (using hashable property) 

+

283 existing_id = self.get_style_id(style) 

+

284 if existing_id is not None: 

+

285 return existing_id, style 

+

286 

+

287 # Create new style 

+

288 style_id = self.register_style(style) 

+

289 return style_id, style 

+

290 

+

291 def get_style_by_id(self, style_id: str) -> Optional[AbstractStyle]: 

+

292 """Get a style by its ID.""" 

+

293 return self._styles.get(style_id) 

+

294 

+

295 def create_derived_style(self, base_style_id: str, ** 

+

296 modifications) -> Tuple[str, AbstractStyle]: 

+

297 """ 

+

298 Create a new style derived from a base style. 

+

299 

+

300 Args: 

+

301 base_style_id: ID of the base style 

+

302 **modifications: Properties to modify 

+

303 

+

304 Returns: 

+

305 Tuple of (new_style_id, new_AbstractStyle) 

+

306 """ 

+

307 base_style = self.get_style_by_id(base_style_id) 

+

308 if base_style is None: 308 ↛ 309line 308 didn't jump to line 309 because the condition on line 308 was never true

+

309 raise ValueError(f"Base style '{base_style_id}' not found") 

+

310 

+

311 # Create derived style 

+

312 derived_style = base_style.with_modifications(**modifications) 

+

313 return self.get_or_create_style(derived_style) 

+

314 

+

315 def resolve_effective_style(self, style_id: str) -> AbstractStyle: 

+

316 """ 

+

317 Resolve the effective style including inheritance. 

+

318 

+

319 Args: 

+

320 style_id: Style ID to resolve 

+

321 

+

322 Returns: 

+

323 Effective AbstractStyle with inheritance applied 

+

324 """ 

+

325 style = self.get_style_by_id(style_id) 

+

326 if style is None: 326 ↛ 327line 326 didn't jump to line 327 because the condition on line 326 was never true

+

327 return self._default_style 

+

328 

+

329 if style.parent_style_id is None: 329 ↛ 333line 329 didn't jump to line 333 because the condition on line 329 was always true

+

330 return style 

+

331 

+

332 # Recursively resolve parent styles 

+

333 parent_style = self.resolve_effective_style(style.parent_style_id) 

+

334 return parent_style.merge_with(style) 

+

335 

+

336 def get_all_styles(self) -> Dict[str, AbstractStyle]: 

+

337 """Get all registered styles.""" 

+

338 return self._styles.copy() 

+

339 

+

340 def get_style_count(self) -> int: 

+

341 """Get the number of registered styles.""" 

+

342 return len(self._styles) 

+
+ + + diff --git a/cov_info/htmlcov/z_ba7f6bdeb0188088_alignment_py.html b/cov_info/htmlcov/z_ba7f6bdeb0188088_alignment_py.html new file mode 100644 index 0000000..00aac07 --- /dev/null +++ b/cov_info/htmlcov/z_ba7f6bdeb0188088_alignment_py.html @@ -0,0 +1,124 @@ + + + + + Coverage for pyWebLayout/style/alignment.py: 91% + + + + + +
+
+

+ Coverage for pyWebLayout/style/alignment.py: + 91% +

+ +

+ 11 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Alignment options for the pyWebLayout library. 

+

3 

+

4This module provides alignment-related functionality. 

+

5""" 

+

6 

+

7from enum import Enum 

+

8 

+

9 

+

10class Alignment(Enum): 

+

11 """Text and box alignment options""" 

+

12 # Horizontal alignment 

+

13 LEFT = "left" 

+

14 RIGHT = "right" 

+

15 CENTER = "center" 

+

16 JUSTIFY = "justify" 

+

17 

+

18 # Vertical alignment 

+

19 TOP = "top" 

+

20 MIDDLE = "middle" 

+

21 BOTTOM = "bottom" 

+

22 

+

23 def __str__(self): 

+

24 """Return the string value of the alignment.""" 

+

25 return self.value 

+
+ + + diff --git a/cov_info/htmlcov/z_ba7f6bdeb0188088_concrete_style_py.html b/cov_info/htmlcov/z_ba7f6bdeb0188088_concrete_style_py.html new file mode 100644 index 0000000..ce37bbc --- /dev/null +++ b/cov_info/htmlcov/z_ba7f6bdeb0188088_concrete_style_py.html @@ -0,0 +1,583 @@ + + + + + Coverage for pyWebLayout/style/concrete_style.py: 63% + + + + + +
+
+

+ Coverage for pyWebLayout/style/concrete_style.py: + 63% +

+ +

+ 207 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Concrete style system for actual rendering parameters. 

+

3 

+

4This module converts abstract styles to concrete rendering parameters based on 

+

5user preferences, device capabilities, and rendering context. 

+

6""" 

+

7 

+

8from typing import Dict, Optional, Tuple, Union 

+

9from dataclasses import dataclass 

+

10from .abstract_style import AbstractStyle, FontFamily, FontSize 

+

11from pyWebLayout.style.alignment import Alignment as TextAlign 

+

12from .fonts import Font, FontWeight, FontStyle, TextDecoration 

+

13 

+

14 

+

15@dataclass(frozen=True) 

+

16class RenderingContext: 

+

17 """ 

+

18 Context information for style resolution. 

+

19 Contains user preferences and device capabilities. 

+

20 """ 

+

21 

+

22 # User preferences 

+

23 base_font_size: int = 16 # Base font size in points 

+

24 font_scale_factor: float = 1.0 # Global font scaling 

+

25 preferred_serif_font: Optional[str] = None 

+

26 preferred_sans_serif_font: Optional[str] = None 

+

27 preferred_monospace_font: Optional[str] = None 

+

28 

+

29 # Device/environment info 

+

30 dpi: int = 96 # Dots per inch 

+

31 available_width: Optional[int] = None # Available width in pixels 

+

32 available_height: Optional[int] = None # Available height in pixels 

+

33 

+

34 # Accessibility preferences 

+

35 high_contrast: bool = False 

+

36 large_text: bool = False 

+

37 reduce_motion: bool = False 

+

38 

+

39 # Language and locale 

+

40 default_language: str = "en-US" 

+

41 

+

42 

+

43@dataclass(frozen=True) 

+

44class ConcreteStyle: 

+

45 """ 

+

46 Concrete representation of text styling with actual rendering parameters. 

+

47 

+

48 This contains the resolved font files, pixel sizes, actual colors, etc. 

+

49 that will be used for rendering. This is also hashable for efficient caching. 

+

50 """ 

+

51 

+

52 # Concrete font properties 

+

53 font_path: Optional[str] = None 

+

54 font_size: int = 16 # Always in points/pixels 

+

55 color: Tuple[int, int, int] = (0, 0, 0) # Always RGB 

+

56 background_color: Optional[Tuple[int, int, int, int]] = None # Always RGBA or None 

+

57 

+

58 # Font attributes 

+

59 weight: FontWeight = FontWeight.NORMAL 

+

60 style: FontStyle = FontStyle.NORMAL 

+

61 decoration: TextDecoration = TextDecoration.NONE 

+

62 

+

63 # Layout properties 

+

64 text_align: TextAlign = TextAlign.LEFT 

+

65 line_height: float = 1.0 # Multiplier 

+

66 letter_spacing: float = 0.0 # In pixels 

+

67 word_spacing: float = 0.0 # In pixels 

+

68 word_spacing_min: float = 0.0 # Minimum word spacing in pixels 

+

69 word_spacing_max: float = 0.0 # Maximum word spacing in pixels 

+

70 

+

71 # Language and locale 

+

72 language: str = "en-US" 

+

73 min_hyphenation_width: int = 64 # In pixels 

+

74 

+

75 # Reference to source abstract style 

+

76 abstract_style: Optional[AbstractStyle] = None 

+

77 

+

78 def create_font(self) -> Font: 

+

79 """Create a Font object from this concrete style.""" 

+

80 return Font( 

+

81 font_path=self.font_path, 

+

82 font_size=self.font_size, 

+

83 colour=self.color, 

+

84 weight=self.weight, 

+

85 style=self.style, 

+

86 decoration=self.decoration, 

+

87 background=self.background_color, 

+

88 language=self.language, 

+

89 min_hyphenation_width=self.min_hyphenation_width 

+

90 ) 

+

91 

+

92 

+

93class StyleResolver: 

+

94 """ 

+

95 Resolves abstract styles to concrete styles based on rendering context. 

+

96 

+

97 This class handles the conversion from semantic styling intent to actual 

+

98 rendering parameters, applying user preferences and device capabilities. 

+

99 """ 

+

100 

+

101 def __init__(self, context: RenderingContext): 

+

102 """ 

+

103 Initialize the style resolver with a rendering context. 

+

104 

+

105 Args: 

+

106 context: RenderingContext with user preferences and device info 

+

107 """ 

+

108 self.context = context 

+

109 self._concrete_cache: Dict[AbstractStyle, ConcreteStyle] = {} 

+

110 

+

111 # Font size mapping for semantic sizes 

+

112 self._semantic_font_sizes = { 

+

113 FontSize.XX_SMALL: 0.6, 

+

114 FontSize.X_SMALL: 0.75, 

+

115 FontSize.SMALL: 0.89, 

+

116 FontSize.MEDIUM: 1.0, 

+

117 FontSize.LARGE: 1.2, 

+

118 FontSize.X_LARGE: 1.5, 

+

119 FontSize.XX_LARGE: 2.0, 

+

120 } 

+

121 

+

122 # Color name mapping 

+

123 self._color_names = { 

+

124 "black": (0, 0, 0), 

+

125 "white": (255, 255, 255), 

+

126 "red": (255, 0, 0), 

+

127 "green": (0, 128, 0), 

+

128 "blue": (0, 0, 255), 

+

129 "yellow": (255, 255, 0), 

+

130 "cyan": (0, 255, 255), 

+

131 "magenta": (255, 0, 255), 

+

132 "silver": (192, 192, 192), 

+

133 "gray": (128, 128, 128), 

+

134 "maroon": (128, 0, 0), 

+

135 "olive": (128, 128, 0), 

+

136 "lime": (0, 255, 0), 

+

137 "aqua": (0, 255, 255), 

+

138 "teal": (0, 128, 128), 

+

139 "navy": (0, 0, 128), 

+

140 "fuchsia": (255, 0, 255), 

+

141 "purple": (128, 0, 128), 

+

142 } 

+

143 

+

144 def resolve_style(self, abstract_style: AbstractStyle) -> ConcreteStyle: 

+

145 """ 

+

146 Resolve an abstract style to a concrete style. 

+

147 

+

148 Args: 

+

149 abstract_style: AbstractStyle to resolve 

+

150 

+

151 Returns: 

+

152 ConcreteStyle with concrete rendering parameters 

+

153 """ 

+

154 # Check cache first 

+

155 if abstract_style in self._concrete_cache: 

+

156 return self._concrete_cache[abstract_style] 

+

157 

+

158 # Resolve each property 

+

159 font_path = self._resolve_font_path(abstract_style.font_family) 

+

160 font_size = self._resolve_font_size(abstract_style.font_size) 

+

161 # Ensure font_size is always an int before using in arithmetic 

+

162 font_size = int(font_size) 

+

163 color = self._resolve_color(abstract_style.color) 

+

164 background_color = self._resolve_background_color( 

+

165 abstract_style.background_color) 

+

166 line_height = self._resolve_line_height(abstract_style.line_height) 

+

167 letter_spacing = self._resolve_letter_spacing( 

+

168 abstract_style.letter_spacing, font_size) 

+

169 word_spacing = self._resolve_word_spacing( 

+

170 abstract_style.word_spacing, font_size) 

+

171 word_spacing_min = self._resolve_word_spacing( 

+

172 abstract_style.word_spacing_min, font_size) 

+

173 word_spacing_max = self._resolve_word_spacing( 

+

174 abstract_style.word_spacing_max, font_size) 

+

175 min_hyphenation_width = max(int(font_size) * 4, 32) # At least 32 pixels 

+

176 

+

177 # Apply default logic for word spacing constraints 

+

178 if word_spacing_min == 0.0 and word_spacing_max == 0.0: 

+

179 # If no constraints specified, use base word_spacing as reference 

+

180 if word_spacing > 0.0: 

+

181 word_spacing_min = word_spacing 

+

182 word_spacing_max = word_spacing * 2 

+

183 else: 

+

184 # Default constraints when no word spacing is specified 

+

185 word_spacing_min = 2.0 # Minimum 2 pixels 

+

186 word_spacing_max = font_size * 0.5 # Maximum 50% of font size 

+

187 elif word_spacing_min == 0.0: 

+

188 # Only max specified, use base word_spacing or min default 

+

189 word_spacing_min = max(word_spacing, 2.0) 

+

190 elif word_spacing_max == 0.0: 

+

191 # Only min specified, use base word_spacing or reasonable multiple 

+

192 word_spacing_max = max(word_spacing, word_spacing_min * 2) 

+

193 

+

194 # Create concrete style 

+

195 concrete_style = ConcreteStyle( 

+

196 font_path=font_path, 

+

197 font_size=font_size, 

+

198 color=color, 

+

199 background_color=background_color, 

+

200 weight=abstract_style.font_weight, 

+

201 style=abstract_style.font_style, 

+

202 decoration=abstract_style.text_decoration, 

+

203 text_align=abstract_style.text_align, 

+

204 line_height=line_height, 

+

205 letter_spacing=letter_spacing, 

+

206 word_spacing=word_spacing, 

+

207 word_spacing_min=word_spacing_min, 

+

208 word_spacing_max=word_spacing_max, 

+

209 language=abstract_style.language, 

+

210 min_hyphenation_width=min_hyphenation_width, 

+

211 abstract_style=abstract_style 

+

212 ) 

+

213 

+

214 # Cache and return 

+

215 self._concrete_cache[abstract_style] = concrete_style 

+

216 return concrete_style 

+

217 

+

218 def _resolve_font_path(self, font_family: FontFamily) -> Optional[str]: 

+

219 """Resolve font family to actual font file path.""" 

+

220 if font_family == FontFamily.SERIF: 220 ↛ 222line 220 didn't jump to line 222 because the condition on line 220 was always true

+

221 return self.context.preferred_serif_font 

+

222 elif font_family == FontFamily.SANS_SERIF: 

+

223 return self.context.preferred_sans_serif_font 

+

224 elif font_family == FontFamily.MONOSPACE: 

+

225 return self.context.preferred_monospace_font 

+

226 else: 

+

227 # For cursive and fantasy, fall back to sans-serif 

+

228 return self.context.preferred_sans_serif_font 

+

229 

+

230 def _resolve_font_size(self, font_size: Union[FontSize, int]) -> int: 

+

231 """Resolve font size to actual pixel/point size.""" 

+

232 # Ensure we handle FontSize enums properly 

+

233 if isinstance(font_size, FontSize): 

+

234 # Semantic size, convert to multiplier 

+

235 multiplier = self._semantic_font_sizes.get(font_size, 1.0) 

+

236 base_size = int(self.context.base_font_size * multiplier) 

+

237 elif isinstance(font_size, int): 237 ↛ 242line 237 didn't jump to line 242 because the condition on line 237 was always true

+

238 # Already a concrete size, apply scaling 

+

239 base_size = font_size 

+

240 else: 

+

241 # Fallback for any other type - try to convert to int 

+

242 try: 

+

243 base_size = int(font_size) 

+

244 except (ValueError, TypeError): 

+

245 # If conversion fails, use default 

+

246 base_size = self.context.base_font_size 

+

247 

+

248 # Apply global font scaling 

+

249 final_size = int(base_size * self.context.font_scale_factor) 

+

250 

+

251 # Apply accessibility adjustments 

+

252 if self.context.large_text: 

+

253 final_size = int(final_size * 1.2) 

+

254 

+

255 # Ensure we always return an int, minimum 8pt font 

+

256 return max(int(final_size), 8) 

+

257 

+

258 def _resolve_color( 

+

259 self, color: Union[str, Tuple[int, int, int]]) -> Tuple[int, int, int]: 

+

260 """Resolve color to RGB tuple.""" 

+

261 if isinstance(color, tuple): 

+

262 return color 

+

263 

+

264 if isinstance(color, str): 264 ↛ 299line 264 didn't jump to line 299 because the condition on line 264 was always true

+

265 # Check if it's a named color 

+

266 if color.lower() in self._color_names: 

+

267 base_color = self._color_names[color.lower()] 

+

268 elif color.startswith('#'): 268 ↛ 285line 268 didn't jump to line 285 because the condition on line 268 was always true

+

269 # Parse hex color 

+

270 try: 

+

271 hex_color = color[1:] 

+

272 if len(hex_color) == 3: 272 ↛ 274line 272 didn't jump to line 274 because the condition on line 272 was never true

+

273 # Short hex format #RGB -> #RRGGBB 

+

274 hex_color = ''.join(c * 2 for c in hex_color) 

+

275 if len(hex_color) == 6: 275 ↛ 281line 275 didn't jump to line 281 because the condition on line 275 was always true

+

276 r = int(hex_color[0:2], 16) 

+

277 g = int(hex_color[2:4], 16) 

+

278 b = int(hex_color[4:6], 16) 

+

279 base_color = (r, g, b) 

+

280 else: 

+

281 base_color = (0, 0, 0) # Fallback to black 

+

282 except ValueError: 

+

283 base_color = (0, 0, 0) # Fallback to black 

+

284 else: 

+

285 base_color = (0, 0, 0) # Fallback to black 

+

286 

+

287 # Apply high contrast if needed 

+

288 if self.context.high_contrast: 288 ↛ 290line 288 didn't jump to line 290 because the condition on line 288 was never true

+

289 # Simple high contrast: make dark colors black, light colors white 

+

290 r, g, b = base_color 

+

291 brightness = (r + g + b) / 3 

+

292 if brightness < 128: 

+

293 base_color = (0, 0, 0) # Black 

+

294 else: 

+

295 base_color = (255, 255, 255) # White 

+

296 

+

297 return base_color 

+

298 

+

299 return (0, 0, 0) # Fallback to black 

+

300 

+

301 def _resolve_background_color(self, 

+

302 bg_color: Optional[Union[str, 

+

303 Tuple[int, 

+

304 int, 

+

305 int, 

+

306 int]]]) -> Optional[Tuple[int, 

+

307 int, 

+

308 int, 

+

309 int]]: 

+

310 """Resolve background color to RGBA tuple or None.""" 

+

311 if bg_color is None: 311 ↛ 314line 311 didn't jump to line 314 because the condition on line 311 was always true

+

312 return None 

+

313 

+

314 if isinstance(bg_color, tuple): 

+

315 if len(bg_color) == 3: 

+

316 # RGB -> RGBA 

+

317 return bg_color + (255,) 

+

318 return bg_color 

+

319 

+

320 if isinstance(bg_color, str): 

+

321 if bg_color.lower() == "transparent": 

+

322 return None 

+

323 

+

324 # Resolve as RGB then add alpha 

+

325 rgb = self._resolve_color(bg_color) 

+

326 return rgb + (255,) 

+

327 

+

328 return None 

+

329 

+

330 def _resolve_line_height(self, line_height: Optional[Union[str, float]]) -> float: 

+

331 """Resolve line height to multiplier.""" 

+

332 if line_height is None or line_height == "normal": 332 ↛ 335line 332 didn't jump to line 335 because the condition on line 332 was always true

+

333 return 1.2 # Default line height 

+

334 

+

335 if isinstance(line_height, (int, float)): 

+

336 return float(line_height) 

+

337 

+

338 if isinstance(line_height, str): 

+

339 try: 

+

340 return float(line_height) 

+

341 except ValueError: 

+

342 return 1.2 # Fallback 

+

343 

+

344 return 1.2 

+

345 

+

346 def _resolve_letter_spacing( 

+

347 self, letter_spacing: Optional[Union[str, float]], font_size: int) -> float: 

+

348 """Resolve letter spacing to pixels.""" 

+

349 if letter_spacing is None or letter_spacing == "normal": 349 ↛ 352line 349 didn't jump to line 352 because the condition on line 349 was always true

+

350 return 0.0 

+

351 

+

352 if isinstance(letter_spacing, (int, float)): 

+

353 return float(letter_spacing) 

+

354 

+

355 if isinstance(letter_spacing, str): 

+

356 if letter_spacing.endswith("em"): 

+

357 try: 

+

358 em_value = float(letter_spacing[:-2]) 

+

359 return em_value * font_size 

+

360 except ValueError: 

+

361 return 0.0 

+

362 else: 

+

363 try: 

+

364 return float(letter_spacing) 

+

365 except ValueError: 

+

366 return 0.0 

+

367 

+

368 return 0.0 

+

369 

+

370 def _resolve_word_spacing( 

+

371 self, word_spacing: Optional[Union[str, float]], font_size: int) -> float: 

+

372 """Resolve word spacing to pixels.""" 

+

373 if word_spacing is None or word_spacing == "normal": 

+

374 return 0.0 

+

375 

+

376 if isinstance(word_spacing, (int, float)): 

+

377 return float(word_spacing) 

+

378 

+

379 if isinstance(word_spacing, str): 379 ↛ 392line 379 didn't jump to line 392 because the condition on line 379 was always true

+

380 if word_spacing.endswith("em"): 380 ↛ 387line 380 didn't jump to line 387 because the condition on line 380 was always true

+

381 try: 

+

382 em_value = float(word_spacing[:-2]) 

+

383 return em_value * font_size 

+

384 except ValueError: 

+

385 return 0.0 

+

386 else: 

+

387 try: 

+

388 return float(word_spacing) 

+

389 except ValueError: 

+

390 return 0.0 

+

391 

+

392 return 0.0 

+

393 

+

394 def update_context(self, **kwargs): 

+

395 """ 

+

396 Update the rendering context and clear cache. 

+

397 

+

398 Args: 

+

399 **kwargs: Context properties to update 

+

400 """ 

+

401 # Create new context with updates 

+

402 context_dict = { 

+

403 field.name: getattr(self.context, field.name) 

+

404 for field in self.context.__dataclass_fields__.values() 

+

405 } 

+

406 context_dict.update(kwargs) 

+

407 

+

408 self.context = RenderingContext(**context_dict) 

+

409 

+

410 # Clear cache since context changed 

+

411 self._concrete_cache.clear() 

+

412 

+

413 def clear_cache(self): 

+

414 """Clear the concrete style cache.""" 

+

415 self._concrete_cache.clear() 

+

416 

+

417 def get_cache_size(self) -> int: 

+

418 """Get the number of cached concrete styles.""" 

+

419 return len(self._concrete_cache) 

+

420 

+

421 

+

422class ConcreteStyleRegistry: 

+

423 """ 

+

424 Registry for managing concrete styles with efficient caching. 

+

425 

+

426 This registry manages the mapping between abstract and concrete styles, 

+

427 and provides efficient access to Font objects for rendering. 

+

428 """ 

+

429 

+

430 def __init__(self, resolver: StyleResolver): 

+

431 """ 

+

432 Initialize the concrete style registry. 

+

433 

+

434 Args: 

+

435 resolver: StyleResolver for converting abstract to concrete styles 

+

436 """ 

+

437 self.resolver = resolver 

+

438 self._font_cache: Dict[ConcreteStyle, Font] = {} 

+

439 

+

440 def get_concrete_style(self, abstract_style: AbstractStyle) -> ConcreteStyle: 

+

441 """ 

+

442 Get a concrete style for an abstract style. 

+

443 

+

444 Args: 

+

445 abstract_style: AbstractStyle to resolve 

+

446 

+

447 Returns: 

+

448 ConcreteStyle with rendering parameters 

+

449 """ 

+

450 return self.resolver.resolve_style(abstract_style) 

+

451 

+

452 def get_font(self, abstract_style: AbstractStyle) -> Font: 

+

453 """ 

+

454 Get a Font object for an abstract style. 

+

455 

+

456 Args: 

+

457 abstract_style: AbstractStyle to get font for 

+

458 

+

459 Returns: 

+

460 Font object ready for rendering 

+

461 """ 

+

462 concrete_style = self.get_concrete_style(abstract_style) 

+

463 

+

464 # Check font cache 

+

465 if concrete_style in self._font_cache: 

+

466 return self._font_cache[concrete_style] 

+

467 

+

468 # Create and cache font 

+

469 font = concrete_style.create_font() 

+

470 self._font_cache[concrete_style] = font 

+

471 

+

472 return font 

+

473 

+

474 def clear_caches(self): 

+

475 """Clear all caches.""" 

+

476 self.resolver.clear_cache() 

+

477 self._font_cache.clear() 

+

478 

+

479 def get_cache_stats(self) -> Dict[str, int]: 

+

480 """Get cache statistics.""" 

+

481 return { 

+

482 "concrete_styles": self.resolver.get_cache_size(), 

+

483 "fonts": len(self._font_cache) 

+

484 } 

+
+ + + diff --git a/cov_info/htmlcov/z_ba7f6bdeb0188088_fonts_py.html b/cov_info/htmlcov/z_ba7f6bdeb0188088_fonts_py.html new file mode 100644 index 0000000..ae58db3 --- /dev/null +++ b/cov_info/htmlcov/z_ba7f6bdeb0188088_fonts_py.html @@ -0,0 +1,505 @@ + + + + + Coverage for pyWebLayout/style/fonts.py: 66% + + + + + +
+
+

+ Coverage for pyWebLayout/style/fonts.py: + 66% +

+ +

+ 161 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1# this should contain classes for how different object can be rendered, 

+

2# e.g. bold, italic, regular 

+

3from PIL import ImageFont 

+

4from enum import Enum 

+

5from typing import Tuple, Optional, Dict 

+

6import os 

+

7import logging 

+

8 

+

9# Set up logging for font loading 

+

10logger = logging.getLogger(__name__) 

+

11 

+

12# Global cache for PIL ImageFont objects to avoid reloading fonts from disk 

+

13# Key: (font_path, font_size), Value: PIL ImageFont object 

+

14_FONT_CACHE: Dict[Tuple[Optional[str], int], ImageFont.FreeTypeFont] = {} 

+

15 

+

16# Cache for bundled font path to avoid repeated filesystem lookups 

+

17_BUNDLED_FONT_PATH: Optional[str] = None 

+

18 

+

19# Cache for bundled fonts directory 

+

20_BUNDLED_FONTS_DIR: Optional[str] = None 

+

21 

+

22 

+

23class FontWeight(Enum): 

+

24 NORMAL = "normal" 

+

25 BOLD = "bold" 

+

26 

+

27 

+

28class FontStyle(Enum): 

+

29 NORMAL = "normal" 

+

30 ITALIC = "italic" 

+

31 

+

32 

+

33class TextDecoration(Enum): 

+

34 NONE = "none" 

+

35 UNDERLINE = "underline" 

+

36 STRIKETHROUGH = "strikethrough" 

+

37 

+

38 

+

39class BundledFont(Enum): 

+

40 """Bundled font families available in pyWebLayout""" 

+

41 SANS = "sans" # DejaVu Sans - modern sans-serif 

+

42 SERIF = "serif" # DejaVu Serif - classic serif 

+

43 MONOSPACE = "monospace" # DejaVu Sans Mono - fixed-width 

+

44 

+

45 

+

46def get_bundled_fonts_dir(): 

+

47 """ 

+

48 Get the directory containing bundled fonts (cached). 

+

49 

+

50 Returns: 

+

51 str: Path to the fonts directory, or None if not found 

+

52 """ 

+

53 global _BUNDLED_FONTS_DIR 

+

54 

+

55 # Return cached path if available 

+

56 if _BUNDLED_FONTS_DIR is not None: 

+

57 return _BUNDLED_FONTS_DIR 

+

58 

+

59 # First time - determine the path and cache it 

+

60 current_dir = os.path.dirname(os.path.abspath(__file__)) 

+

61 fonts_dir = os.path.join(os.path.dirname(current_dir), 'assets', 'fonts') 

+

62 

+

63 if os.path.exists(fonts_dir) and os.path.isdir(fonts_dir): 

+

64 _BUNDLED_FONTS_DIR = fonts_dir 

+

65 logger.debug(f"Found bundled fonts directory at: {fonts_dir}") 

+

66 return fonts_dir 

+

67 else: 

+

68 logger.warning(f"Bundled fonts directory not found at: {fonts_dir}") 

+

69 _BUNDLED_FONTS_DIR = "" # Empty string to indicate "checked but not found" 

+

70 return None 

+

71 

+

72 

+

73def get_bundled_font_path( 

+

74 family: BundledFont = BundledFont.SANS, 

+

75 weight: FontWeight = FontWeight.NORMAL, 

+

76 style: FontStyle = FontStyle.NORMAL 

+

77) -> Optional[str]: 

+

78 """ 

+

79 Get the path to a specific bundled font file. 

+

80 

+

81 Args: 

+

82 family: The font family (SANS, SERIF, or MONOSPACE) 

+

83 weight: The font weight (NORMAL or BOLD) 

+

84 style: The font style (NORMAL or ITALIC) 

+

85 

+

86 Returns: 

+

87 str: Full path to the font file, or None if not found 

+

88 

+

89 Example: 

+

90 >>> # Get bold italic sans font 

+

91 >>> path = get_bundled_font_path(BundledFont.SANS, FontWeight.BOLD, FontStyle.ITALIC) 

+

92 >>> font = Font(font_path=path, font_size=16) 

+

93 """ 

+

94 fonts_dir = get_bundled_fonts_dir() 

+

95 if not fonts_dir: 

+

96 return None 

+

97 

+

98 # Map font parameters to filename 

+

99 family_map = { 

+

100 BundledFont.SANS: "DejaVuSans", 

+

101 BundledFont.SERIF: "DejaVuSerif", 

+

102 BundledFont.MONOSPACE: "DejaVuSansMono" 

+

103 } 

+

104 

+

105 base_name = family_map.get(family, "DejaVuSans") 

+

106 

+

107 # Build the font file name 

+

108 parts = [base_name] 

+

109 

+

110 if weight == FontWeight.BOLD and style == FontStyle.ITALIC: 

+

111 # Special case: both bold and italic 

+

112 if family == BundledFont.MONOSPACE: 

+

113 parts.append("BoldOblique") 

+

114 elif family == BundledFont.SERIF: 

+

115 parts.append("BoldItalic") 

+

116 else: # SANS 

+

117 parts.append("BoldOblique") 

+

118 elif weight == FontWeight.BOLD: 

+

119 parts.append("Bold") 

+

120 elif style == FontStyle.ITALIC: 

+

121 # Italic naming differs by family 

+

122 if family == BundledFont.MONOSPACE or family == BundledFont.SANS: 

+

123 parts.append("Oblique") 

+

124 else: # SERIF 

+

125 parts.append("Italic") 

+

126 

+

127 filename = "-".join(parts) + ".ttf" 

+

128 font_path = os.path.join(fonts_dir, filename) 

+

129 

+

130 if os.path.exists(font_path): 

+

131 logger.debug(f"Found bundled font: {filename}") 

+

132 return font_path 

+

133 else: 

+

134 logger.warning(f"Bundled font not found: {filename}") 

+

135 return None 

+

136 

+

137 

+

138class Font: 

+

139 """ 

+

140 Font class to manage text rendering properties including font face, size, color, and styling. 

+

141 This class is used by the text renderer to determine how to render text. 

+

142 """ 

+

143 

+

144 def __init__(self, 

+

145 font_path: Optional[str] = None, 

+

146 font_size: int = 16, 

+

147 colour: Tuple[int, int, int] = (0, 0, 0), 

+

148 weight: FontWeight = FontWeight.NORMAL, 

+

149 style: FontStyle = FontStyle.NORMAL, 

+

150 decoration: TextDecoration = TextDecoration.NONE, 

+

151 background: Optional[Tuple[int, int, int, int]] = None, 

+

152 language="en_EN", 

+

153 min_hyphenation_width: Optional[int] = None): 

+

154 """ 

+

155 Initialize a Font object with the specified properties. 

+

156 

+

157 Args: 

+

158 font_path: Path to the font file (.ttf, .otf). If None, uses default bundled font. 

+

159 font_size: Size of the font in points. 

+

160 colour: RGB color tuple for the text. 

+

161 weight: Font weight (normal or bold). 

+

162 style: Font style (normal or italic). 

+

163 decoration: Text decoration (none, underline, or strikethrough). 

+

164 background: RGBA background color for the text. If None, transparent background. 

+

165 language: Language code for hyphenation and text processing. 

+

166 min_hyphenation_width: Minimum width in pixels required for hyphenation to be considered. 

+

167 If None, defaults to 4 times the font size. 

+

168 """ 

+

169 self._font_path = font_path 

+

170 self._font_size = font_size 

+

171 self._colour = colour 

+

172 self._weight = weight 

+

173 self._style = style 

+

174 self._decoration = decoration 

+

175 self._background = background if background else (255, 255, 255, 0) 

+

176 self.language = language 

+

177 self._min_hyphenation_width = min_hyphenation_width if min_hyphenation_width is not None else font_size * 4 

+

178 # Load the font file or use default 

+

179 self._load_font() 

+

180 

+

181 @classmethod 

+

182 def from_family(cls, 

+

183 family: BundledFont = BundledFont.SANS, 

+

184 font_size: int = 16, 

+

185 colour: Tuple[int, int, int] = (0, 0, 0), 

+

186 weight: FontWeight = FontWeight.NORMAL, 

+

187 style: FontStyle = FontStyle.NORMAL, 

+

188 decoration: TextDecoration = TextDecoration.NONE, 

+

189 background: Optional[Tuple[int, int, int, int]] = None, 

+

190 language: str = "en_EN", 

+

191 min_hyphenation_width: Optional[int] = None) -> 'Font': 

+

192 """ 

+

193 Create a Font using a bundled font family. 

+

194 

+

195 This is a convenient way to use the bundled DejaVu fonts without needing to 

+

196 specify paths manually. 

+

197 

+

198 Args: 

+

199 family: The font family to use (SANS, SERIF, or MONOSPACE) 

+

200 font_size: Size of the font in points. 

+

201 colour: RGB color tuple for the text. 

+

202 weight: Font weight (normal or bold). 

+

203 style: Font style (normal or italic). 

+

204 decoration: Text decoration (none, underline, or strikethrough). 

+

205 background: RGBA background color for the text. If None, transparent background. 

+

206 language: Language code for hyphenation and text processing. 

+

207 min_hyphenation_width: Minimum width in pixels required for hyphenation. 

+

208 

+

209 Returns: 

+

210 Font object configured with the bundled font 

+

211 

+

212 Example: 

+

213 >>> # Create a bold serif font 

+

214 >>> font = Font.from_family(BundledFont.SERIF, font_size=18, weight=FontWeight.BOLD) 

+

215 >>> 

+

216 >>> # Create an italic monospace font 

+

217 >>> code_font = Font.from_family(BundledFont.MONOSPACE, style=FontStyle.ITALIC) 

+

218 """ 

+

219 font_path = get_bundled_font_path(family, weight, style) 

+

220 return cls( 

+

221 font_path=font_path, 

+

222 font_size=font_size, 

+

223 colour=colour, 

+

224 weight=weight, 

+

225 style=style, 

+

226 decoration=decoration, 

+

227 background=background, 

+

228 language=language, 

+

229 min_hyphenation_width=min_hyphenation_width 

+

230 ) 

+

231 

+

232 def _get_bundled_font_path(self): 

+

233 """Get the path to the bundled font (cached)""" 

+

234 global _BUNDLED_FONT_PATH 

+

235 

+

236 # Return cached path if available 

+

237 if _BUNDLED_FONT_PATH is not None: 

+

238 return _BUNDLED_FONT_PATH 

+

239 

+

240 # First time - determine the path and cache it 

+

241 # Get the directory containing this module 

+

242 current_dir = os.path.dirname(os.path.abspath(__file__)) 

+

243 # Navigate to the assets/fonts directory 

+

244 assets_dir = os.path.join(os.path.dirname(current_dir), 'assets', 'fonts') 

+

245 bundled_font_path = os.path.join(assets_dir, 'DejaVuSans.ttf') 

+

246 

+

247 logger.debug(f"Font loading: current_dir = {current_dir}") 

+

248 logger.debug(f"Font loading: assets_dir = {assets_dir}") 

+

249 logger.debug(f"Font loading: bundled_font_path = {bundled_font_path}") 

+

250 logger.debug( 

+

251 f"Font loading: bundled font exists = {os.path.exists(bundled_font_path)}" 

+

252 ) 

+

253 

+

254 if os.path.exists(bundled_font_path): 254 ↛ 259line 254 didn't jump to line 259 because the condition on line 254 was always true

+

255 logger.info(f"Found bundled font at: {bundled_font_path}") 

+

256 _BUNDLED_FONT_PATH = bundled_font_path 

+

257 return bundled_font_path 

+

258 else: 

+

259 logger.warning(f"Bundled font not found at: {bundled_font_path}") 

+

260 # Cache None to indicate bundled font is not available 

+

261 _BUNDLED_FONT_PATH = "" # Use empty string instead of None to differentiate from "not checked yet" 

+

262 return None 

+

263 

+

264 def _load_font(self): 

+

265 """Load the font using PIL's ImageFont with consistent bundled font and caching""" 

+

266 # Determine the actual font path to use 

+

267 font_path_to_use = self._font_path 

+

268 if not font_path_to_use: 

+

269 font_path_to_use = self._get_bundled_font_path() 

+

270 

+

271 # Create cache key 

+

272 cache_key = (font_path_to_use, self._font_size) 

+

273 

+

274 # Check if font is already cached 

+

275 if cache_key in _FONT_CACHE: 

+

276 self._font = _FONT_CACHE[cache_key] 

+

277 logger.debug(f"Reusing cached font: {font_path_to_use} at size {self._font_size}") 

+

278 return 

+

279 

+

280 # Font not cached, need to load it 

+

281 try: 

+

282 if self._font_path: 

+

283 # Use specified font path 

+

284 logger.info(f"Loading font from specified path: {self._font_path}") 

+

285 self._font = ImageFont.truetype( 

+

286 self._font_path, 

+

287 self._font_size 

+

288 ) 

+

289 logger.info(f"Successfully loaded font from: {self._font_path}") 

+

290 else: 

+

291 # Use bundled font for consistency across environments 

+

292 bundled_font_path = self._get_bundled_font_path() 

+

293 

+

294 if bundled_font_path: 294 ↛ 303line 294 didn't jump to line 303 because the condition on line 294 was always true

+

295 logger.info(f"Loading bundled font from: {bundled_font_path}") 

+

296 self._font = ImageFont.truetype(bundled_font_path, self._font_size) 

+

297 logger.info( 

+

298 f"Successfully loaded bundled font at size {self._font_size}" 

+

299 ) 

+

300 else: 

+

301 # Only fall back to PIL's default font if bundled font is not 

+

302 # available 

+

303 logger.warning( 

+

304 "Bundled font not available, falling back to PIL default font") 

+

305 self._font = ImageFont.load_default() 

+

306 

+

307 # Cache the loaded font 

+

308 _FONT_CACHE[cache_key] = self._font 

+

309 logger.debug(f"Cached font: {font_path_to_use} at size {self._font_size}") 

+

310 

+

311 except Exception as e: 

+

312 # Ultimate fallback to default font 

+

313 logger.error(f"Failed to load font: {e}, falling back to PIL default font") 

+

314 self._font = ImageFont.load_default() 

+

315 # Don't cache the default font as it doesn't have a path 

+

316 

+

317 @property 

+

318 def font(self): 

+

319 """Get the PIL ImageFont object""" 

+

320 return self._font 

+

321 

+

322 @property 

+

323 def font_size(self): 

+

324 """Get the font size""" 

+

325 return self._font_size 

+

326 

+

327 @property 

+

328 def colour(self): 

+

329 """Get the text color""" 

+

330 return self._colour 

+

331 

+

332 @property 

+

333 def color(self): 

+

334 """Alias for colour (American spelling)""" 

+

335 return self._colour 

+

336 

+

337 @property 

+

338 def background(self): 

+

339 """Get the background color""" 

+

340 return self._background 

+

341 

+

342 @property 

+

343 def weight(self): 

+

344 """Get the font weight""" 

+

345 return self._weight 

+

346 

+

347 @property 

+

348 def style(self): 

+

349 """Get the font style""" 

+

350 return self._style 

+

351 

+

352 @property 

+

353 def decoration(self): 

+

354 """Get the text decoration""" 

+

355 return self._decoration 

+

356 

+

357 @property 

+

358 def min_hyphenation_width(self): 

+

359 """Get the minimum width required for hyphenation to be considered""" 

+

360 return self._min_hyphenation_width 

+

361 

+

362 def _with_modified(self, **kwargs): 

+

363 """ 

+

364 Internal helper to create a new Font with modified parameters. 

+

365 

+

366 This consolidates the duplication across all with_* methods. 

+

367 

+

368 Args: 

+

369 **kwargs: Parameters to override (e.g., font_size=20, colour=(255,0,0)) 

+

370 

+

371 Returns: 

+

372 New Font object with modified parameters 

+

373 """ 

+

374 params = { 

+

375 'font_path': self._font_path, 

+

376 'font_size': self._font_size, 

+

377 'colour': self._colour, 

+

378 'weight': self._weight, 

+

379 'style': self._style, 

+

380 'decoration': self._decoration, 

+

381 'background': self._background, 

+

382 'language': self.language, 

+

383 'min_hyphenation_width': self._min_hyphenation_width 

+

384 } 

+

385 params.update(kwargs) 

+

386 return Font(**params) 

+

387 

+

388 def with_size(self, size: int): 

+

389 """Create a new Font object with modified size""" 

+

390 return self._with_modified(font_size=size) 

+

391 

+

392 def with_colour(self, colour: Tuple[int, int, int]): 

+

393 """Create a new Font object with modified colour""" 

+

394 return self._with_modified(colour=colour) 

+

395 

+

396 def with_weight(self, weight: FontWeight): 

+

397 """Create a new Font object with modified weight""" 

+

398 return self._with_modified(weight=weight) 

+

399 

+

400 def with_style(self, style: FontStyle): 

+

401 """Create a new Font object with modified style""" 

+

402 return self._with_modified(style=style) 

+

403 

+

404 def with_decoration(self, decoration: TextDecoration): 

+

405 """Create a new Font object with modified decoration""" 

+

406 return self._with_modified(decoration=decoration) 

+
+ + + diff --git a/cov_info/htmlcov/z_ba7f6bdeb0188088_page_style_py.html b/cov_info/htmlcov/z_ba7f6bdeb0188088_page_style_py.html new file mode 100644 index 0000000..3f357a9 --- /dev/null +++ b/cov_info/htmlcov/z_ba7f6bdeb0188088_page_style_py.html @@ -0,0 +1,157 @@ + + + + + Coverage for pyWebLayout/style/page_style.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/style/page_style.py: + 100% +

+ +

+ 33 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1from typing import Tuple 

+

2from dataclasses import dataclass 

+

3 

+

4 

+

5@dataclass 

+

6class PageStyle: 

+

7 """ 

+

8 Defines the styling properties for a page including borders, spacing, and layout. 

+

9 """ 

+

10 

+

11 # Border properties 

+

12 border_width: int = 0 

+

13 border_color: Tuple[int, int, int] = (0, 0, 0) 

+

14 

+

15 # Spacing properties 

+

16 line_spacing: int = 5 # Additional pixels between lines (added to font size) 

+

17 inter_block_spacing: int = 15 # Pixels between blocks (paragraphs, headings, etc.) 

+

18 word_spacing: int = 0 # Additional pixels between words (0 = use font defaults) 

+

19 

+

20 # Padding (top, right, bottom, left) 

+

21 padding: Tuple[int, int, int, int] = (20, 20, 20, 20) 

+

22 

+

23 # Background color 

+

24 background_color: Tuple[int, int, int] = (255, 255, 255) 

+

25 

+

26 # Typography properties 

+

27 max_font_size: int = 72 # Maximum font size allowed on a page 

+

28 

+

29 @property 

+

30 def padding_top(self) -> int: 

+

31 return self.padding[0] 

+

32 

+

33 @property 

+

34 def padding_right(self) -> int: 

+

35 return self.padding[1] 

+

36 

+

37 @property 

+

38 def padding_bottom(self) -> int: 

+

39 return self.padding[2] 

+

40 

+

41 @property 

+

42 def padding_left(self) -> int: 

+

43 return self.padding[3] 

+

44 

+

45 @property 

+

46 def total_horizontal_padding(self) -> int: 

+

47 """Get total horizontal padding (left + right)""" 

+

48 return self.padding_left + self.padding_right 

+

49 

+

50 @property 

+

51 def total_vertical_padding(self) -> int: 

+

52 """Get total vertical padding (top + bottom)""" 

+

53 return self.padding_top + self.padding_bottom 

+

54 

+

55 @property 

+

56 def total_border_width(self) -> int: 

+

57 """Get total border width (both sides)""" 

+

58 return self.border_width * 2 

+
+ + + diff --git a/cov_info/htmlcov/z_fc521de9aff00981___init___py.html b/cov_info/htmlcov/z_fc521de9aff00981___init___py.html new file mode 100644 index 0000000..54a5928 --- /dev/null +++ b/cov_info/htmlcov/z_fc521de9aff00981___init___py.html @@ -0,0 +1,107 @@ + + + + + Coverage for pyWebLayout/io/__init__.py: 100% + + + + + +
+
+

+ Coverage for pyWebLayout/io/__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.11.2, + created at 2025-11-12 12:02 +0000 +

+ +
+
+
+

1""" 

+

2Input/Output module for pyWebLayout. 

+

3 

+

4This package provides functionality for reading and writing various file formats, 

+

5including HTML, EPUB, and other document formats. 

+

6""" 

+

7 

+

8# Readers 

+
+ + + diff --git a/docs/images/README.md b/docs/images/README.md new file mode 100644 index 0000000..c79db16 --- /dev/null +++ b/docs/images/README.md @@ -0,0 +1,213 @@ +# pyWebLayout Visual Documentation + +This directory contains visual documentation for pyWebLayout, including animated GIF demonstrations of the EbookReader functionality and static example outputs showcasing various features. + +## Generated GIFs + +### 1. Page Navigation (`ereader_page_navigation.gif`) +Demonstrates forward and backward page navigation through an EPUB book. Shows smooth transitions between pages using `next_page()` and `previous_page()` methods. + +**Features shown:** +- Sequential page advancement +- Page-by-page content rendering +- Natural reading flow + +### 2. Font Size Adjustment (`ereader_font_size.gif`) +Shows dynamic font size scaling from 0.8x to 1.4x and back. The reader maintains the current reading position even as the layout changes with different font sizes. + +**Features shown:** +- `increase_font_size()` / `decrease_font_size()` +- `set_font_size(scale)` with specific values +- Position preservation across layout changes +- Text reflow with different sizes + +### 3. Chapter Navigation (`ereader_chapter_navigation.gif`) +Demonstrates jumping between chapters in a book. Each chapter's first page is displayed, showing the ability to navigate non-linearly through the content. + +**Features shown:** +- `jump_to_chapter(index)` for index-based navigation +- `jump_to_chapter(title)` for title-based navigation +- `get_chapters()` to list available chapters +- Quick access to any part of the book + +### 4. Bookmarks & Positions (`ereader_bookmarks.gif`) +Illustrates the bookmark system: navigating to a position, saving it, navigating away, and then returning to the saved position. + +**Features shown:** +- `save_position(name)` to bookmark current location +- `load_position(name)` to return to saved bookmark +- Position stability across navigation +- Multiple bookmark support + +## Generating Your Own GIFs + +To generate these animations with your own EPUB file: + +```bash +cd examples +python generate_ereader_gifs.py path/to/your/book.epub ../docs/images/ +``` + +This will create all four GIF animations in the specified output directory. + +### Script Options + +```python +python generate_ereader_gifs.py [output_dir] +``` + +- `epub_path`: Path to your EPUB file (required) +- `output_dir`: Directory to save GIFs (default: current directory) + +### Customization + +You can modify `generate_ereader_gifs.py` to adjust: +- Frame duration (`duration` parameter in `create_gif()`) +- Page dimensions (change `page_size` in `EbookReader`) +- Number of frames for each animation +- Font scale ranges +- Animation sequences + +## Technical Details + +- **Format**: Animated GIF +- **Page Size**: 600x800 pixels +- **Frame Rate**: Variable (500-1000ms per frame) +- **Loop**: Infinite +- **Book Used**: Alice's Adventures in Wonderland (test.epub) + +## File Sizes + +| GIF | Size | Frames | Duration per Frame | +|-----|------|--------|-------------------| +| `ereader_page_navigation.gif` | ~500 KB | 10 | 600ms | +| `ereader_font_size.gif` | ~680 KB | 13 | 500ms | +| `ereader_chapter_navigation.gif` | ~290 KB | 11 | 1000ms | +| `ereader_bookmarks.gif` | ~500 KB | 17 | 600ms | + +--- + +## Example Outputs + +Static PNG images generated by the example scripts, demonstrating various pyWebLayout features. + +### Example 01: Simple Page Rendering +**File:** `example_01_page_rendering.png` +**Source:** [examples/01_simple_page_rendering.py](../../examples/01_simple_page_rendering.py) +**Demonstrates:** Page styles, borders, padding, background colors + +### Example 06: Functional Elements +**File:** `example_06_functional_elements.png` +**Source:** [examples/06_functional_elements_demo.py](../../examples/06_functional_elements_demo.py) +**Demonstrates:** Buttons, form fields, interactive elements + +### Example 08: Pagination (NEW) +**Files:** +- `example_08_pagination_explicit.png` (109 KB) - 5 pages with explicit PageBreaks +- `example_08_pagination_auto.png` (87 KB) - 2 pages with automatic pagination + +**Source:** [examples/08_pagination_demo.py](../../examples/08_pagination_demo.py) +**Test:** [tests/examples/test_08_pagination_demo.py](../../tests/examples/test_08_pagination_demo.py) + +**Demonstrates:** +- Using `PageBreak` to force content onto new pages +- Multi-page document layout with explicit breaks +- Automatic pagination when content overflows +- Page numbering functionality +- Document flow control + +**Coverage:** ✅ Fills critical gap - PageBreak had NO examples before this + +### Example 09: Link Navigation (NEW) +**File:** `example_09_link_navigation.png` (60 KB) +**Source:** [examples/09_link_navigation_demo.py](../../examples/09_link_navigation_demo.py) +**Test:** [tests/examples/test_09_link_navigation_demo.py](../../tests/examples/test_09_link_navigation_demo.py) + +**Demonstrates:** +- **Internal links** - Document navigation (`#section1`, `#section2`) +- **External links** - Web URLs (`https://example.com`) +- **API links** - API endpoints (`/api/settings`, `/api/save`) +- **Function links** - Direct function calls (`calculate()`, `process()`) +- Link styling (underlined, color-coded by type) +- Link callbacks and interactivity + +**Coverage:** ✅ Comprehensive - All 4 LinkType variations demonstrated + +### Example 10: Comprehensive Forms (NEW) +**File:** `example_10_forms.png` (31 KB) +**Source:** [examples/10_forms_demo.py](../../examples/10_forms_demo.py) +**Test:** [tests/examples/test_10_forms_demo.py](../../tests/examples/test_10_forms_demo.py) + +**Demonstrates all 14 FormFieldType variations:** + +**Text-Based Fields:** +- `TEXT` - Standard text input +- `EMAIL` - Email validation field +- `PASSWORD` - Password masking +- `URL` - URL validation +- `TEXTAREA` - Multi-line text + +**Number/Date/Time Fields:** +- `NUMBER` - Numeric input +- `DATE` - Date picker +- `TIME` - Time selector +- `RANGE` - Slider control +- `COLOR` - Color picker + +**Selection Fields:** +- `CHECKBOX` - Boolean selection +- `RADIO` - Single choice from options +- `SELECT` - Dropdown menu +- `HIDDEN` - Hidden form data + +**Coverage:** ✅ Complete - All 14 field types across 4 practical examples + +--- + +## Generating New Examples + +### Run Individual Examples +```bash +# Navigate to project root +cd /path/to/pyWebLayout + +# Run specific example +python examples/08_pagination_demo.py +python examples/09_link_navigation_demo.py +python examples/10_forms_demo.py +``` + +### Run All Example Tests +```bash +# Run all example tests with pytest +python -m pytest tests/examples/ -v + +# Run specific test file +python -m pytest tests/examples/test_08_pagination_demo.py -v +``` + +All new examples (08, 09, 10) include: +- ✅ Comprehensive documentation +- ✅ Full test coverage (30 tests total) +- ✅ Visual output verification +- ✅ Working code examples + +See the main [README.md](../../README.md) and [examples/README.md](../../examples/README.md) for detailed information. + +--- + +## Usage in Documentation + +These visual assets are used throughout the pyWebLayout documentation to showcase capabilities. + +To embed in Markdown: +```markdown +![Page Navigation](docs/images/ereader_page_navigation.gif) +![Pagination Example](docs/images/example_08_pagination_explicit.png) +``` + +To embed in HTML with size control: +```html +Page Navigation +Pagination +``` diff --git a/docs/images/demo_08_bundled_fonts.png b/docs/images/demo_08_bundled_fonts.png new file mode 100644 index 0000000000000000000000000000000000000000..0397e4b5d6104f0e23f931e21071da44534ef0cb GIT binary patch literal 97208 zcmd?QWmKF^+btMegEbm7I5bWYTmp>*2$G<|J-7yUcM0wm+zIX)9D=*ML*qU-@AJIh zulYA;t(iGzEm(9HGrax9FRxy~Q2^h&$cVrQ?O<}{ zE0|ZWK0!VzI;9?_A;sc~%>N@!BCM&m@_`4@(63 ztsfRr(02FdmlrSZQ2%|=txym5pWlig+n_W4_fLB>VgElo7)IzV%SKI2{r3~jKeOya z`8+{c3yTvb^Y1_^9bpREyAH~K*2;~D_m2fcg7oEK{x!h=?t{UrZqJXr*f`LS1*6-; z&jGxSW7(SVJ1p9*+Rf-&RNW|)8+H)j5I|`4;K6I^b!0)mJ~g=ruo?~RQhGehQmR$m zP`X~lW=lqgWxHI$W@|JYb_~1;_L+^7kULzs_U}tz)oh1CV0-T_d%IcN%n26iGH~eR zv_N%MLb#*p0t7Z2e{ep<`~jIv&|<6o&ckZ3%g6RdHN>KkHo|83LTvzg*%Q_nSiOXN zxs1X7w}Q`4u1X~u&cf)#wKB3;d9oHXbm)Fj?FuEcI+gmpCJFjtp+-0hb=Sf6Q*Jk> zG-HjI@6JYr=$Jg)T(6@aQhDsCCd=l%;ou z59}9(-1qdjBi3NswZuri>~toA=9K>83pl}eSk~v;aAUfD?wz8bh8P@ME zUDwj^UJe?>9>jFxy4b`e`~0*!_SI&!9pS8>t}Ud-EYgqHv;U{S`q_ptrNg0h9X=K7 zTO()cXkVdcetL%!LE?R<4j&>&hTxP~62}~5AU3#rqgSqHs`$FQOw+hK;B{~J!!2LW z-o&C34KVG_80(Cw-eAzz`3zRo(=5rjqNWQJcIfHaSDUp?=4hHhFiUUSjb0@oo6e_# z3)yrMS`K4bviTB+B@?RcDLpEw-P?C*5Fq?c{6fJ$kzUf;=p zLE1=fG`h_A!JL~Vg0zXraJH5DC{Iz z>|_2ypnWWgcG%52jRulz-LCalIDayxRbex(^%J?fD3w>LJ zwC8p^mY|dmkKhx`=TbVk^x$8gHbPT*zV`QfE;Wil3csj{RjAhN%NOWJ<=2=AVSoDB zJrUUxgssk)v@$DiW76RqeFX@Et@trOL^&_n3ZQR-p zme(>uY*`_`OqOi{9&9V-T zM$!VaU9Ytb=7!NESI2V~pm2t0psQx*iIB&8BtKrduX$RXAhx4 zrzp9TAk|v)HhRVHUb7A7GWC5KZT83f3rCQVkQ@ot&{_-6pL_`3+qdTgSrNK=E@$f& z*i5iW&))Gbiw@*<*c#AmfHQ#yKPT?0Gf+50;4vj?9UBqy*sK>Fiss48=@h!bgAU8w zZq`QiZceu%`u|;(sb3Q;>@$Xp$id9>vh6Fg`RZS8DnXhRQUpw`PcHr%3g?Kl%c6l# zy04)!{>O3zjYBfC=*6ebZns`d>aq8eyt8i=>=*Nb$6J4 z5B_3Wd9q9Nl&H#~ILLMCjTZ^)3s%p#y%CxyHv(`9&mB=jeoTQ$Np(vY;9OXWkVLht z-}5lekCz%OE0rm?+bKDn0$drSCi>(6Uiw&I0Ly2Sa<0Egtk=Mrp5fxkKDr8IU6*>BO z+HEULDzRssO)DM;)CVWWOEyr?-5 zIM`SElJQnYa9gi7r>+uux53q(hhr2H&JwUJ5;1CWGn8pPR!U|F7Cw-%YB7y9JDWtm z73Da)JJ3ezS+sy|US>sXw0b_>R@&pLv^x+L7ymeDcE=$8s5}}DtaLJ}#%gjgbahdWb=ZCIf@ypz z_<%od^R6`t5yWE+}tmKd0*XeF`tXE53^oZj8-)9xfkA)}Rp_bae-?T^**7 z+ma!L5`~L=nMNVAD78d4lm`!Sx~L?`McJaMZg&~;KAQtCP_ph zexdg0*8-mPD+Ig4*+1D@0tW@3bhi?;=lg?mH2-Q9E0e|=$xIc6$Hz!%Bbz-UeKi_t z0=+W&G(d;g#BvJ5F8h=Hw)Tr`Nh~itowz@eRN9m*okQNxKp-}xl0!o2TnU9qfig6A z&zQ~dgQ`0%P>m>2mcaVpK_F6{1~49!*d6d_YgDC9TX-tHGWtYAvQ^6UDebRHponiA zF{%=E8mP2e?Xw~*=IuKTbKhmhj-p(hU<$0kD1#LJoBE{huhJYLPo1!x?VXT6$mY*> zn>QMp{S$I!SCly8-N6UHDE9|B4_J}68cinh_5NngTOPE>e0B6i1i3fTmRyp{B(Z~$ zjSM?W6j#-_lGlkm>#m-irnc_W1*YARMGylK=#hfN5rmQ!8}364`Wr)LEBWdZMJfCI ziTY9+THzS}J^n>F$E=jyA*;9NTs;GENMad+Xt_BV*d^8BBn{)Gr8O}IBP8>*#N833 z6VQ6+OdDh0blT6mi8s%W+pY!5R`u#c<==k9%I)d22dV`_w}R-m?AFEnj`y)ne60hU zRt}F9{gi-JDyOg2oo}bCG#-+ylF9QA575cD6)6p}HadEC<-opCUjGx$;Ev{U<@U8i z)kgS=AW>#R09U!`wRi=w&$;gW2IOV`VuL5gb6rLKH!S{Us?sCCpAd93X|ugm-Xj%s z`*f`MO)QV|q@o@yeRas-FQk)Cj9t9?&T@-SKM)C%OxhVaUpe)-7tEv?+L4xfPI2|7x*X@!k7C+3rb6Ol@5m{jkeYO65L^c_m^gFjwkD+xE7 z@O*`Oi{uD@mPUn6KD~-%Jt3#jkH6=Lc9gT%kz`Afhl_J6KwyTSqL3gC;vgMl zAo|atI3jd64_o2M;?xeQzOMpV&ZLd@0Yx>$Nk1a1{Pg13CzL?6jhLpSJ&RMGkPeKU z>n6KehJl^iM|!;fHtGo5yn<55R8iFCY<*_^Z`Q|gO#QwnUg+@E)_`FhdeTx(FMs68 z2r{BPeiZp(>hb*4Xz2#)vc!2+UsxPDvP|Hrs>QpvUqXB}s(YhI=ok$59&`c;0Q|L#(vCyBG)qGcEBx(fEB@%vm=*eOEdh?=)` zvk^IlXIyHB7Xqz3rbhRV`OFSOmUeD%8JQMv55`OXtl4IlHRYLuK4G{N$6_~hlKRO~loV_k)o(S)Zy2+sAq*KH#WHivz=?dTDkiYS97mv4fkI@3Mz>*oTK?+ETF%u`XTs0Tq2pkVsBsD z$H*zqR7x^Gyrw^x@O(m&LxfLKU8$<`XJ2bqG)$oy*Qx2g+9XT#HF!1DdkKN+SnJTZ zrqA!GGp!RxCh4_u#wX;r3USsd!aC=n-uPDCmh<*iTRhItU#|^eS4YZ-S?`hsT#MiJ z_}q4`cC>|RwQPq5y&}Opm_#A{i3_%h1A9^-Dc=+=wm!auZ1!L1}5@AbNE_bx1qJITNQk9i5# zF1JmzD?-59_15Bh%@M&d*!HK^-@^(n4pc-Rt`?5v*)Ptx zJ(s7R+zzH=)?2PMw4r!JgFEnWNYg(-BR%imGT&J?azou;_V?!92*u~!;2Wp^9W7JmHO(Kpgttp zRXVxGyP1wK{3aG<*5j|Nr0;f!2KqCHjjgJn62gsI6{2KMPDr&t5q{bkCD=qzgEbLCOV6;r)6%arHv zzT)9h-1t0blSN8>&@G!a6y?)=InNaZax6Ln#HOBf53XeG!iPln?ZacNw?bfLH-lRo zG3}LdJuXGgPWYzt1eDtUnUbH9FdMsYb&TO)dP}*psW8Y9@m`MayiRl^4lhcV}PzR2FP)a$kE0O9lv+HC^p+ zd1>@WdcUN-p&pSBdkw~XSt6|9B!-jM;iGctKFf-|#Wim#Qjg~}(~1NCkrxVBdB;6k zT@2`md+sg;3>R9$IKEb9-xCmjJ92WGN2o+SGK1gNaJ{-nh}b#4GvTwX(&-4)T7Vmv z8i*qgw%_~dm-62QiH8Mcn*-gudTT6; zrHtphgEt1^oFH1AI_oFxgfdw8xNsY@mBm}qgl8P$v+DZqt7OwU*Sj#a6BOEXa!6z< zk^{eUR%i#Dpr^`Xk~P!oPj$r;HQ^S1rNaTBg9H#4dS+s~cs!H`#$h&|^@J9HOTam= z*0!vIN^(Rba#HEJL_GAaJ0@HiQej!uyHHsl1W%8VW_y~@US)YT;0PiUqk&i;Y`)=7 zxzdiCHOTIvN@oCE@CgBiwcU<@h`FTsOq}c%@H_xz=>fo?9svF^thb0o$V+GIg{WFx zAoYP_HT$&o#?|2PQJ5$P(6g0?lYTQ#+VBr5j}>QnwR`?Szcb+DQ#p-HDX;JW1jvpGzQ#aZ=jxJRsggjW6ft0|SQ!Hz4;v;?V( zVnX&`jC`m5)A>_E&`uXN*ecHb?f=0jAs!+=Y;}=N>Tv(Ok4nJ*{|OrV{|0ItWQ!^p zh(X#tTd6OmNZmZRoG(Yt{{1h-<9#o=CsP0>Iho->mr7?-Yl+j@y1)6vjJBsHF7r1< z5`HH(sm-26{I#hM4kyIuYLR`Ta*&_W>&_%W`xb=Q_m`&>l-G3Bs0rLFDzLTzc#uKm%xlH23u%vxHw4J3|H3DV?n^earr^NIia_0e=`%HiKqZXnMMjm9&pce-V?JwK^GT+V3g z9WOTM^+u3V%BAyDHqV1qor7iIrAMgLwL#IF992`A5bsgD+04c0AX#*57zVB8dZ&xs zXrLYRqE{@4;;;35_K2qV2wP~q*zEk3`g5`(S??+iSeZ$oLZ`!e1aT41R_iG`QY+%||ljL+PMZC_Xce$@s76 zQ8iT_*Q*j4A;W=KU0E>4R!>505bABvcL;R@^%J(zeAFmga1p#1OveD2H{$;KB=l&Z zZZK|{_s4#aY#MLy_p$8Y6_5MR8D2d>XWIBK*T>B10MnpCcrKga?wqqNzda7f#HDKUJgz z8cyMwiFCi)%cs<8cFML|X|*+U?+SPgy*Zrwy`{a$V7EO)c79xXxO{A8z5>9ILBIzF zgD=&sT4?HiD)HXDUKjwYl;NIj^b%|h#DOr$1#tnC+98F@>ZlHYyxA=mxorS6l7YOB z54u;nv^W)4gc&=i$#XamOB!Va-R$pKuxevQ#igg%pDAZOpU4(NQ@{S$%0_s-KQ&lP z63?owMJ*jK!=%-$T5YwWB?rfW+|VUU3DwVUSsMFoELZ7rX{y;Tl_A&?>uCi&`t0!# zZQkNCY2#MG;&HVDE=e3XpFQ2dpq2p_ z)%2zy>ICH(0MrVEddfl9EYNGzC7!O5meYy(F1Jp&vEn~m{6Oa%purDb^lrR)x<4^Y zS!+;qd%E4J;-`{`sGgcpU7ASnSh8GdikS)}V7;TkbTAlJZ;k#_qV{|9_&HZP!C>LX z$+Bj~hJS7(Wi&2xJ4OH1(Sn&+2<}+k=ajsq20Mv`R`(|5!Y?!yz?MT(fv)O1uoDx? zqVYbTDZu5Y8IMjPQHhyqA)p1@i9!;NPT&P>| zC~kSYT9{vMaow^49@X;jxY%HK_dfj1`*DxEJ-P(f%i|?=Vs7iqrxxi1mU$IzcSCT7 z!=U9(t7JZaw$HEkgw_DwGI$5@GR?K$$0#3D%iEs5x$dMnR(Azrn$=k@{k-jN3E#5^ zY(f0NJn?Di>;0M_V4msip(H9#0IqJ1MfhcfRNPqg6@!ms3q`KV56We=ym;k4bh-5j zqa%@BzuI)F$YeA_XlB$`BYOyNgQ}(F^hDktMbp(LUla6~Hy31E?im-3q3fj)idr7e zkB&(Rmj^RuVxfSiC>Cays#c1Ssn?js@$Ud@{IP)l=fvUemE)>MPbfjc4!; zz|(hkfG0_%@HV5d4;O~$oP)V^*5yBGq)myrEbUEtfsotx)FGSJ(3D^P*w|L}2H*Mg(z$xz z`Xy$em#h62Z*n|Iml@4&m~Dn1y`oQwDx=;p!i z)l0t9S7gJK619?N0+!c1S#J5#)oZ-c>JB9w?+n56!uLFyf8TKDZI>sP!Sdd6kxw?2 z8>&(A?aj#svsJ5)(l(iZOJDQD{YjhFV1V055~qay;jBU#)+)CfY?VQOcDW*ajBwmz z&5Rc4z1ehZv6LhTd<=yCpKSsj52|IhIyJ{rD%yC#AUb?<_lxM>QhTc-qzRf|q4(=t zB~JwxT|W7?H*y*; zWa{;V*!JK$cc5BKm#F2o$pTSri~X=RyY=dDPX9pA;f@Q~q6XWo@q6wEe-fQ79QQPi z&tlrU;En=2J`=s2My(bXC<$K`S=bH>Wq+>b?dhb-LSc^ZWzunaicT0@I`YM=NvyP0 zL9JS?d6I|uaPoUwu3sYlsI><}39c?uz2s__Dr67NWW%@OZSVE}pf}tHKH)d%N=hH? z->K8$G@vA7BG(dxx1(UGJI;OyIMqGCAZ0|HQqgi2OAeVVP!L@){VIiki%GA2dfWbi z37)t%Yo=T~!IzUP!0i?a7)rLURGwcRRU;6y7R|jsh0B`NT%Xcvmg6DAmj1#*OwN$c z@i>)<0BR0TTGZf+f~d>f)LBnj!u$OfoFY+pM8c3TIz%3FUINw?OwV@}0KVaH*7 zD4sdxsf|<(^N$_D*yebIo6~-=0-4nh+<3u>5j#+rO*_b&j4#~Q1SxR@gOE*FHjn+p zi^Hb}Wvk0Lf8=nH6YGbSe;ZWi+W8{ZDiKTMw!>)?B|lvV`V&l%sOk(0_Z`^ip7C61 zvAyx!o@gqG?gVX*!e@+*fE*@7O=OODmglHhQ0xvbbk~nw1$3$%=+evl-p%dXxZvEs zySfs=AK?t;6~g)BJ`2Pu@3{;nFzTzG<{v6wzm_I-X$;K2A{KC|PL1kdA$MpO4M2}} zI9;A!0HqJCh05H*DF?FA-`J7!*%r|9`XXV*UyV!0Ges9isT!ijr_RNl2oK7s?)NdW z>JDerJq?3)4?J$S2zUxR4L~4;nsYP+co?p*RyYR~@wA0coJK>5ch3$60)(TQKrz+? z#hz|OnYo`Zo_Duw-eUg??{fHVAeXda!rR5m6QB0D;Ok^A>yPcD^o|l&&#jJ#66e7> zQ$8zahcD@pH=uVQCc7a^w?O$fDs${SodcN4-?JnQ24eedX|Tw1ybNwA_AHiFwtjG85C{W1Zx6E}h6Y7IeHv3zsYa3wCs@+O5 zp!kRMX)=zL!w{*2`lC@azIj4r)oZoW9YKdJEDIP{I|!K%iZ4%yJjMJ%kmfChxa+OD zL`SFk^EO7!AX=`%rBzTeqO(e?bw^2^PM(>>G~DB&f1O5S)1Q|FuZgV%vOegmI z10x4-dw4;RsM`^SHhGn?hfz#+W0}6Gp9>2{J(w;9SpmIg!8^7a%0n^|;mYLfc2n#_wn`j> z>X@Ei7=n~v^JK8K-Ol_ckJq{<^F9-2oEz1Q2OUA|^D43=3c8axjR%yl>^+bZOzO;4 z-P;XtSwltXP@6{B_5ak*eqHwt^yN5wzg8Vo*O8pIWUcL2B6?SV$*1eo61Ozse)PA& z&Umgr^El3qHFcDKt?=*Qa;))v~)rDZw^jB4yNPC`BbOkNP(i1E|y8RLWJ%$ z-|F?UtK7Eck@$@ZxrVU>28;-^UWM0QgmVp-{LxXdn`;;O9ELP00-V)j3fvkE2J3Z_C4ABoKxv|a>10t@qU4^~l9nKs}(7YB8!I(wD^4x?wajxJC07&+V zjzz)0+bwH%4g@zgs~dy(P6lg+EW|csiI%$WCkR}B;Ivj?Qp92c`lng=!3bs=a}@0P z*JHw3wEz5D6aohUED>ynUh4HOK`(hhi^eCtGbbm)`=smu=G_T@lu|as99DTn6d*Q1i|aHIOWt%Vg_R;dZvI2#j;l~B zosoI+S=+s%2DK?(5zVuZ+g?TA5?9?|1ABH4R;B(Q{7)M+ao0@og^AzQYIEshv!bl4 zFb==`P*keiUuC@Y9NJ~?$+cn$HaLq>OJDdQtHN${N%(j_(#j{c7UnOe7~550ySx52 zy3SK8%OsgTrKM!{ubHTmaqx@O3HuYw?bWE2*tK@!V82gvpd9%?|6gx3r0((W(o|FM zyN1lyn4ZrRyrah?6&F{jOH>}q55FITpN>V>w@Q4TMkR= z7eUG#5*I4RuJ8wGmAU&(F3##z2SNCvnHe=ho1>05h%s$h5q1qk5VM29+#c{Z1YW7Y_~V z=Lnf)*9%Q8v<(?8a;KF=7g*z%b0Z2W*ecVkuT9-7j-0#L9lGSadsfYoSbp3Z3g^_@ zTh4_(tFr3_--#V8G-5Q7p1J~h$e(FDfh`o?46IFc=+#No?ugv}fZwP`;%@5(87 zI5}gt>AOY$lEzW6Gylw^TiIAUziUc)*;^=9a#h(4CU2n(dCfvA_;4#)dH2`Uokl2e z>qFIEH|?04lwEYLBR+P%C`okYS6GGBP}ubegp3fdW4RMEmyAaUCIid5JE2^B*OGJQ z)?_^NzVP7r$X;fSO-!hK_~JvSN}gqE(TrN$qSC_`qr2%{a8+Jkk!MDQgHOH9bHK{w zW+BEy9ABa?gZy&C!gk}s)8-tgQfCMD(e;*OE7voWBa&nJV7|qSWky76%4%ub{=DI- zlQQsWQcN{nNIT;ry{&kz12mvta0c@wf@p~#z!#T>q+nV~?P$-MtAF-u_U`X3MXKAB z;+An`V`rBWAC|c{PPWGl0I`r1SqU{Wt8D3x=%@0BYx8MsTg*Kfj1}T(ePM8VIIO$~optocAgnb{>{}6Pn{_tIUpVM3C8B#H zYffLAG0$T0=&q0s+Y}hkfg)bgqb3h?6_s zHagESxaHD^YtSyG%MPMkOeyfcKHnMT{C<6VeogdlU@GMCxu5uDf!P(XP>0q!vD8GH z3qX+ds2NMFB};u6DVz#BsCu#!mWS>=HAYQWw>Eq!QfQtpaX^ zIz6AvxU+hgT!%J0D0V5_*~nO9?R{c5o$_o8FAx%0ByyGTxVl)OqJ5V_!MIsMLD$v6 zQU`Tx-{&~IvXNUPzZK$JFR?uKjeDG5nTj9qZ(2f1T+kqMR+}cJWO3H}R%`<0ui?P_ zmS9#o^SFuMTFOGDE$@L#Qx%K#1RH2U6_k!w;DYM6xVHQ{lxwn0zcb7ZAYOIuE-c3! zmzGyzBEy9I#4zB_(C6Ks9^4V74W8u6{!UAM_>Zgl<6$L^>QI`ebL-H!p0}lJZRYM~ zW@n#ahUXTB(CIMxpeTjIzQ521W0qVveNGtA{FQ`Q<`1eKLr2cy$NQtSxOo;6dQ-U( z>Su!!0=tJ6bX8tMaNtq`>ft1C{I#gspwh1L>RRGyg6=ab?Yp$hXNRjlYL(VIB7^si zZY?=Xt^TMJ-=eR;%=`JP^Pr{ zNVHG-M!8*fWgv?vru%erf2o*dWvPX@3!?x;l{kc0lwNWwaU5dELc5ZirMy~s0E2Y5 z;_#f;Cf$>As|0aBz(?Cztlm=bfW_0^La9xyt={IOW#J{%<23Z9C&veh{8f^L%3L(t8al zcW!~O4O-NE{d<43oOO5_PComGZ*AxhJRl*wR@|BC^t9d}4bO1H+5EJB08>myr5ne0 zXi4bKeJbvpL-o|uvmCb;B%o`L5ttOK^sBByPXYt(68#PcZv4L1A%Xf$d<|6pIUb6_ zSO`c~GqoWAX=ty1k@+roVmC{aeNMhv&EtZsXLVIT&HgB1*7zd$f7N&_^k_JL!Kc&zO5wZCTWO(KfWZ%{%r2evxN8eiUH01eK=Lc)vn4Ge3(+plC$sNq zSs#n@h5OR@$T1-VoGJVa6ZBMF39l0bae^qO4IK!ul^^1|`k?4<@ZN6DX=DJ_Mh?!F z-F*e>Dzj^Bd?bw-Ot-p>eeMk>QV}0gP>7f&S7#l+mSlaF@XK*TZKc9LQ6u^LpxYUe zkm=sCZZ^2Gb~vB!l93s&4r6Rma)EKHA5?A9i-vH9k{2n=R*+m*H0xlR^6;Fo@$4kv zx!xEI)klyC@NhaoO88`xdBA{WizKnfV%O*wLhsKNuI@i8rkFp_03J*@EF?v9i#_K~ zy$z=oe_Agl5sx>&+a?`|-47{{H+U$2iQ3 zm3Qt7B@ap*)8C)b&E<0)IMh4T*6*nt?A;8xOL1Po?K*g!V8r8XWRu^+p@*&Y)8PWD zU^0KE;mR`nMar0&nOEhQX}|ysy08T!PLWZY;+HUJw+AShZX*5q^i#YnZ)14<<}nVw zJSYftnTq2gGV2SE7b`sNc#zH($|O-NdLnIBYGP&=dHZ)uyj>R#Mnu39w!XJoCz#D@ z%Z-(9*QSA8hEHe=F*$th9SbMLs$EMp3+jsQrBpZBvW0x3z&RWIGIjq6Ee*(Hup}ag zAU^Tj;Q2{Hbg!4v1aY#$J3FYi@M+6*Tg+N4xMto6jE!+bnASxafNKzJ`d!1 zDo99(%S>S}2J1*$;KN4**7ItT+SP%Yns+-v0oEItwBNHOIslQniefR8Z zhKVuL{PK}*p#rZcFE|N%Lyzj`2aigF6HhAy$K*TEeyHNo*S#nFOsnwL_*NonJ-8L} zL^GH~!zt!3T+P2)NV2r1zs<& zfVWIpnE&|+41W zsqp0j!+*xRU$aB7b^)65h0=Qv`sf2ssZyqq{V9f)Qn%Buqy9hf6_|R_iC4Ad(l?pp zcb_hf7F1p+L?+b=3e~!Quh2lYNU^B;f6{>bQSos291j0Rz7Q}&=>qB`y!MiS98Vn} zTf~~r{t!d!fJ!LTWFjwkFo8AHe6FfrGX`LdLR~KR`&Slf%;;Xos7P<1uMgt3Sr3}8 zF$-+GUhVJ&E(GNInQa>TFq&dp=Yae5)w}q_v&~9^&C|@D7Xqxuirr z0J2AbZz2Z}b{qgGiC=mD83aI41W6o*IHG|VaWT&T9Tx)3Dj2|$`&Xpm7-`~u0@PCw z3W!!xtb_dSVvp)s(EU!)e6e0ZCY4(O{mnZ`Na$5tIQeEVz|~MXLpP!KLrI)HITDeO zln)lL${zphcn}6|d=u#l zD>zlGlF+8RL+$zWpgSTxl*lgD6M_ePfxBNQO<=RO&fdH?-5o&sXUC{t>(XRQ3o^Sq3=w_>kp0S4Ph4n$nef9u>lZ_sMcF&?E(}wpfhf1#?W<% z{_xMFH)%XDC4r-{Ino7CVY!^#BokRti!H7yj936^g=IEf67BI;p7w=WG6|_M9wn=^ zS`kdB2C!?oQBUY*@C%DQ3tSM|5;$w(IP)2YIS12orh8%bZT82&eRKHN?!G{%k1kvF zt#rG!1z7EmW0m?6XM?P5eb@f4NJL}7^yqBE0HK@lYY#VD2an+kM7;9LJIh5?nKWLd z60MeR$rgCb-zK5yK@LiJGDPeKeOTB4KfWjT^Ml1Vx)AAqT*^i?D36347JjG*Hi^1( z10Sp`6U(YCsAKZqt7RDrd`3Q7XfNJDwZ*LZ}WpW0LV>foNs0 zUPvy$J8^-7iw#g{xB!=amAC>Bi+!$j=5ym)HyNH!COq3M&;hXJ#N7+EZ&uzydy?^1 z6!FFLYye*7@frvyO{CHQrF{HzTHEuHhEb`A`XunKIMqc@<^Tv+Nr;TCSHtFWL%VhBbbUT2wBHBH89UyVA0gxT{MZZuF_}pxT zu5S=2Uu}VWmd|XhMT+5kz0p$R4_k$9()+6;JEHS4%_bw~Wgy0iZTty06mooeaIOSg ztWc6wqh4MJX^Sn-9}}1K^lHJXX>#~QzrbNU!qlruk1HRWIs14qA$R8t_&WiBRR?Gw z`$22B&?{Eexo^g2`R(GeU8AxtAT`I+B#lkDD9j9d6mBF z$NOu|k>NOSf^v7(Z#c0^nZe`Z^@`w%8NfDE0(RnfZQgV;UjvR+6M4y9 zfbWQChT>702fA{032OndqS(q;{|9EV%yIiZuJS}wdbviu^~W-wjv)$w1keK(AD_pd zx+MCjo6`0*Ulz)h$Po)xk+tlilTB4^J|E#*KV9p(t68YCN?3SM#ZwF;lB!+@ zbPP~J8I-W=>WQ*rFB3Rro{!O#Vg`4>37PP`j=g@7;%GG30#YQYz9}U@Qx6scY`JNg zPg*iRQ)XVk+s=YvfVQ_eoT5@D=mXIEnq|Tw7`hzZQ*u=ME>=gON+YzNlO-pxtC)C>+g^EJh@)h00>7`1@5f`u6gS7y_O4AI=2$~Ad5rK8pvILr{>q;LJw8Hi>0k6cIu zM6V;_)j_0F72MpJN8s>B8K_=FUaF`k%hbt*(x#C#zQGRs>yFF%86eUyjdpdP{0S%4 zTdcRS9f#pN$QBC?@qB(%nWjRltuh>xx;J@$7TAC$Lx6ij4#GNTN1vC{tym~aPyAq! ztoqw1{DB-q`s@0rZe`$P8H&N<-H)(|c!Z`C;N4F_D^1GhfGg|8pEGiQc|f=II)-1y zE&3S&4L=AEtlZbnMw_P$vgSKNa~7Y=q+UO{Uh!n5vygW?)RDkod)w1JX)Yb%+KvlP zOV%owugtOcx>{%-)>$EszM$!JSGvTDD12ZB@S|K23=n1CA`mV&sYlJF;CR}9DSU2S z^L%B9;~s-8lRTzO?soc7_@0;V2=kQb1Hc4cw45nR1L$}E?<=h>gC~G!0|Yn`4)DYTG@^7TKvpLPlud@}o!ULk z?XOOU^sr&|Uk&ADhUu)q@43R#s-0r9-)FQd;)QDMN}rl%TeLnvrPXLJi2v9&epM`a zumlQRdcQgp{E=rMX=p?k_x}_#17v$oqzkb@-}V7R8*WMco?(NPPKZdAh}p!#vJ1`)8PPXl$!<1F7vbB0Lr!>t`N zkn_G>A1`e=2nNVw>bMimDT*1~>n{=*Mhx35dE;n9s$GbEjOU26_-Ea1Tl9nTdo(gV5mm19{^1|a; zGV>xC7EE=bKJU>)wE0T41<`QP#cktS71`W*s5<$_&TE`PDW}s>K1}F?P zH(?BAfO_ESbWQY8waw#kvw;m0)j-?0?omMq5MJ5woB%oKi)EMTcpOmklmfEhtO(EB zt%R!MShrg}A49}Zvh);mB98AszDK@vovSi5{B{FeR{pk7Z<8~4QsAg@zZOWo-&-P6 z@$__WRSGCC=K&?w)_b7Xh#hqW3icY{RekFHesTH04$=m~<_1uL%mY>jpPvHKng(WR zps+Q#SMBcv#tnIt)U20*Tj3fb41l)EjRnvf=8?Y$Mh+)z!j&EYZ1%&t;k%B`0Ul-Y zaGm$^VjqBf9jxKr#h7g~NJwEDG{)ZtDiDHvVCOyZCgHK|lcQ)aqLbm#@h}`Ijs^sZ z(!YryPCjZ_%88FK#j4v-MIgf^9}!Y@5q{=x2bzZN7ZWDQwx5B(89`Bc8vR|U9A$GY zu66eCy{2!J4`O++M*r8TXByw%H<{q% z@-&9eR^zKiG!S$JGpotw^Ic~4br%Me!^beFmnH@`T-Oky@P`VjAX4OC#09M*miJTw zTdU^qd??C%1g+4v%Hby)ZEqr42g}v(;exOD>W%(h&F1>>Fx+wUy?2Pl-97)?$_8@* z@8FH}`VI90sRe>F!*|R+Y!5D9P1y^>%l#=d8noT~ zXsbTFAY0lh_NU@0FiqP`$|y;>Zx!eCeyBRgTvGN1}1=WzzBR%G%cZ9{G%m|e}UiRtDY z=BKpX`uPmtLB^Zpw`Cd)Rmk|tRo=Yqzh;rm7oz3P*>%c@fnaKfE(5Ep6>#6t{Cy;q z2c}$FrbLGpHW%)tBTgn}JO^uMeElsWp4Zw<%0_n&IIY1Q+iTG!Mi6@rE6!bzr4d_9sF_#988$qx`eVZ#1@CeOdZFA(2x>Ke4$ZiXD>)*#rIF`F5ayC}fzL<4~^F zWXa8y7`^@iXclzQ1deZb6K z{>R+&FijMvhTccdKv3#(^VBxz(j=WN1?(IeHw~oZf&TaAlQtx}0&AOOeT|UxHLu7i z4L^2&O4uG0jFl(;e50f=8TVkm%$trj05C9xYV}1rtDp(fvcC5NX*}ORp636NeJ%3RQo+1kQI7 z?9;@p`gr)LF$yr=cHGM2^5x3d`aa%E&BbwY^b&o1wC*h~msMe#n`lo10YW$9tp%PX z$y8skH~%)zzftCSHGAX*MYsi=AtRS4$n;uRAb+|2aY!mj(P^SS(Chjx)|tqmuoPDro z(SU>gwG=jouO>ddg^}2s5CnYLQyZi^u?r$=13Et}iJ+hp7S7)7Duczx1 zhOCFRh@WV+=F{B)>z*G3qE=3Q}$>N^Jnkh{6~G zs@nKlAVZzBOtYs_t-V5#xB*JWPk~>!cROz{Ckwx_>~wy5a};t8h{~BGJBWyO*17j5 zG8`^vP?ltu+n(;s_9y_<>f0@;Y^y&S!8D)+*I(!#FLFN`2x#I3prx4U8Z(syeE@U% zxz+88>SnS6^yJdF{yBUkfp5*6gkTIvsw|PTH(zBwu7*WCh?IrTy32M!M37SFCP&QKopnx6|$^d{V)ui8Km}{cUe^B1IH3Umkni_5UW_4G7&D0Nkg-f8fp{GS!+l*oFd~hnSJ~n{Np!S8ZiPIDVgerI;)^X;L1pt zmQp;|c=6j^D!!{7jV6)SH}+YvD5)sjEL(A?1SjN2Tdl>CHI_}}!w9j{ZP@yWI45-$ z_5C{V+2>QG({P4B!z$@GEt|6~-X$1x|DBsFhD~KlQ&0ep4*3I(w zg=FcA)p~wOH8!e;z98TYBMmN&?15aM_#jhq-}OoNX0)Yu(8GlS((rlI`|E{c+hKiQ zh2ASu?9h&-W2csQS7EF|kBDnFXzvPMzlg zpK&aKC9c>%Cfh|R-`UHYzUxVWgoKgwN6s(zBvya zRbQX?&v8ePm<Joh2Q_T60@Rm@_l!} zQvkRc3S9~Ck#<*#%+!_?!so%IG=Vn==WuUBP{8mD>scf> z>kwJrZK3njYeT3 zek_O@N@`CO&H&IQ(0ZgOO-l$W1LewtBuVdM_vq^m4~0GV9X0DbJ>JGsaEpu^{R^vx zZ1}3Z6;bk*J>K6^0w`hrOyi0@lvse!IS>O2zM? zgDaYuDij{l|9+11d5%PBR-v!Y4|hI|ls<~21DP`&STvAvN#)&@Pg>Gcm+xhgLx)cm z_ez2m^%?>41QxG6`N5~B^bh$EsCbPs52_Z5iBz#!!gV!q=irOSc9#3;ag}#B0RA7- zP4O1x3H>g|LwwZl#%_^+e{=~CD@0>a!W2T~wlPT!2yDES4q!9q5iAfP3-C!9$-$^X zdFggMiHlv9+FL~^3W&ygz6v2a0L^g|e=YsrMKJrXW1r`^Nn&F*1UMMvec&A!WZ(Ue zIn$e=Ojfn+H`R5@f{~>Q$xms|?wxJ-$b96eiA(=yr4R%&23o(%!o32LN{Z==$jbqL z@k>@{;g8()|B}OU}T`0sBxnOFpvhgAQ}W#hMnp> zcz0aH)d#K=BYUs`Z1THk&_eTTy-N@@rpdS&ejf32AvO)f+XeUl4Pa@M@b(b7oPs6i z8(@!rap!wkYM0Gg!!Fr+8+;Z;dBUPrE)HLi9ZWM#^Ts|^P67ASJ#Kd{*@9tTrQ(z9OF1HeydFjrTd z^haRe%Z?bXP%`O#*;5(QCY-)HuNOMjRC8btb2jswZooXDqr%|^kSLEFX*Vc6ivT%> z(3rw8)k=^D&%hSsChoewX?pNh(yd^m{7FL6G;gZsGa$UQd@3Ig@f`S__rF#3wnzF% z4QB0|I7G2Ea5HD1V>lEZJSQ!N*rg1XWji!HkJc(@G9smkK8mf46f1bAF8yNkEdjHZ zkra?{GPEK4sd51=(6F^2sC}F$=xlz1n~+R;DUVw3twtNnAGbX86Gb0IJiNP&B6W%Y z0CEM@h7@{y#j;y~4m5wH=cWx*v=>U#LA7zEc(Q|loedB-u6pBIcTG|>AZg&}FcR`Z?bQO~`*lW*jv z9F0CHTyArisJ8TAoWGi|3_nm{Zyas`IE+O3f$VsYr&m08a5GLL|_cmQCkJ(tKa#zlpiyQ~cMMV8wP+?3ga=E%DvKoY2xkJ;wOFi&=8qY<_f z3g)}o3Lupaw*W*~wsHOaGq-s2$&E?zsd4U4pA4olCQOQBK` z#}i+iI0juHW#VHp{#+lad23i-pY1OMW7WGfoV@UwrlXk zJoLz6-~jxi&j)Fl+m2npp(f)7aN;|`)b&kU?w?vGGE0+RYm(Q&W|?UShR27*goM#; zz4-o57rU1isRbVz)-(51N<94HXqtjnA}Ho0Sn)Ru4iX{?8p01K!wR?|;hnn;D1Elw zWbIE^X)d+I>8-kCDsnaSv8F8I8u{J0@(|kh@>-f01$?{ zb!mpVv%nkm70Nix1bWwkrBQkhe{xPTiYO@NB+&@BWxr9>{p8SPjELZn(IpRx@8ONq z_MBP72AzLwrr9)bayA11h^ zEuQ0}fxoIkW$5$?n|^CyWP)wcV5h?buv)u$id?xREvIuj+K5e5SL?fKuq3YH9lQyq z%6C+B+mU2CCdEz9U=&*eTh1WNZi^bjX4JRmMPaF>x!$ieRm%nGg!6PG{@Htch1sf{ zDQFi_QrLqsu`JrgdU5jluo>kSs+yWbZp%VZoi7HZMCXRg$xh610Tm?)Ic$9W8^f^M zq|7&xwl-1)R5cBwrwc!et}2?6>&7U>eoOh|nC?WTL_Tg10(!cb#{ifG2;>DOUONI52*F z1CjuD57(w2Ck~FTxp_r1cn+@PNP{IU$|jQehJ*If+zMPN`GS61d^J=8}dQXD) z#?br4=Ct1rU?x8u(+w!RuLo-H(4M{3$&%i8@d;GNd(uM5bcG5mw+ILs+~eVi<5)52 z@T%&0h`64vOE0Ca=G`<;y?S>hPsar--xr=FwUrxPp*E8zROoq&UfnR8PjL@?sYSL| zVz$Pi!dGRQcS)k2Ahyjz2k0zg+ENWanU8SlX6ZaehiUghjZu=pKReEW{TP2)jgm5uWP2X4!WgmqI%OFtB7^$hY zZ0i1}3fVFG6ir@M-W=?YUuFk@|2H~NG-9B}QYRwd+#2!nJ>WQGY>#zf!`>|rT2~t? zFf#ZIn`A3;DfJBalIJo))4(5Xy4_e%C$DR*W*>YZ`_OAw`{WD|yrnZ4D7iQHH}O^o`b27-pm>~|0j=@ncp*2SMELL(SJ**M z3|D0S6D_*NT955lKb=PP?wUVvB)wwZog%!kaEO9kIcFOol2{Dty}ur(-q5z76jR~h zx+O+a9ZEHOi@C|aa2`{?)QTF}3F|=(V0gu0(h!Hzq2yzd7RBK`KLRO7@2kr3PP&hh z=dijz10@%~C-;JF^bUw{6v2($0!RrY0)D*^duR{C-{d3^YHuQ{5aK<6FmYg~T&C#g zSZT+*iwQ=v`0k#darjA>J1ed;D~yZ*Mxe0355?T9(H}Sc*>bRt074BYA3bB}Dxj8de%YA0EoF_d?qZN8=KhNCI>f)|GG^s~sK z$`DXJ1zo)LCI}eQl{BwDpT=90zz3S|oKbX-bU}kKKLtD+>KVnG{SR)F(Z2>UlP0u% z--9e7B*pvi78ReK21PodmD}%xmSCj`eSky?q zny!j{FCLrtmU?~R&SkuyQ@1zh(aAg+s7J~tQCZl+}xu4&lh zUteOWC6pKOGn&@H*>6u2F_k;Bhejp9P8~x;%HGWi0f{RNl=Rf{ROpP!_r5CEfm3u8 zA5J-KNGVFHYfpifN|Kz2h6u}--5K;AZx;mXkI%0ds*MXTD@vvsvCV1-e4nm_f`d9c zJKk3JDhc(xz(I#w^3T(HKR~24qR)K;f)AwVnqTnZ()Soc`*ot~124Jr7G8Au<|KRI zuF}}ZH|{5@jfyyzF+6o3yKL^=@$|<7Sv_xbfYt*nP|FKqi)9 zN6;z^l_VKP#7g7ux-rR5?Vz~6GEq}`oZH$f8^HwP3N6en90`}aAYUX7#5Hj#r&r%x z8!txgs_b*@n1@%tqzx!1x#Wql-)S+bZ2B^D&Fk>lPfSuqF^xJ|6e*JapnFi{3}d^x z2R|o<-X|D3ta6@^%aij)Vo(M^)Ci%E+(Y4@S@W>(_T%}Rv2NOX8jM9e5r^`6`rMQl z^|U-%DyNauxzNJYvXZ+WkUdGE;}>pM8g-i``V-6Gmt8W9l7zjp)`CEBX@2ZBtfN2 zKZb^p$|pDa_%#;qDPYC9d@RX*KqkIYWjM)yPB;lkHvFrIvc=0kk_mU`9D#UlY!dJt zRqY{0n{`C(*OR?gh2(`BZWB)U(UPVYHq2nelBhL=v-uW}%#s1D7vt=IN*~Imha*?R z+sne;`-iBO0bh{0g_M*^>toV|6dT`1`d>wH1ogFQ%5Q4rOaaE&{){4?PBnDYOYUTP zJ`zKuuhPNzXR8>QGv+sDk&p_d91D3BoJ+mej7+yMg-3-L>?VesMs{jKgUm>J9=%2u zOYT}u-$OczyxI>N{6s&X1^0^Z$9-K3vWJy6azs9)YWk>e(*_E0SOKmbo zQr$N4;#;QA)~_D&kHit1SL>ue%Eu^AW#R6*={ElyiPr6|I)=I(eAlG%8-6PGItP1y zy6|gQN~;AANpSKv2VH|6;7nmTEGF6j9@;5hhhw_=x5Rk{`{Q!CnD^q$9 zQ^Ks$lA|Y|!>D7@0e!73nTW}*oCzlH6yNrv{mow$qp{>OOj7s#IQv(9F>IkD%LEqP zd7hZJSdXyrGw_W?56lu=*G7Y(U6ixvjK4E#(`D5O6)#E^88ENj z{ba;A!oX3VBQwHezDrs$>iT^)Oq%IteYk9Ue7*Y{Vkuhp$?2nLUR{9$ri13s3)iPS zXJZK86`}*GwHI_SIIk69lj-lVb4PnSA-vxFAqrpzZX zxA14jDx;0tUkdJzZXB{-AGsP(kEQk^%!c7azv)nCHIR@JrR|zG*<6XU+_-^pvIoE= z_JV`iqc$9uPsz7e)#f2pNIGW3bZ__(xg^mDHNR$hpyz^ybi4E+#%c)_w?@OfXX0)n zpC~B0&{e*9jkY9`Vw>|K8xp43>Idi8dUQh2ax#m&jD~?G; zl$c`bOty{3yN%7&&_atX?rTHJX&dN{eIhZL6&}nQYm+E1XBy6JGIIhgi7Mqa?k%A^ z{_L+hM7s#nzD(!O{MsF-kXNy|bdlici$5)aIP_vl>^#J=WWG$4cl}i?uxezY^tt;q zq{(mPd7m7zvtv$St*Wt4`nq16f3?MCgm?avuSQ>ZXYwx= z%^2{Xz_msAf8_H2C;l+K;{PsuzbNu|Jfnq)@<;vNQeU&fOMldV5?eB1MVv>VtNir= z_a87+#X?h-Q!SAdkcdm5KWDGM4zgin@&DCvf|86KMul`}$k<^%vUigmt#lAXRFne< zDkkQ+Ulhb}y}%l*IM$M2hc60WrZg)cMAVRYbRNDRIUvz7Z3uu4faKbrcW|ddK&Pf! z%Fk=k`X3E2B9`t;cPCfCsQMz{o7{k2aen_)M2savw(9SWg~E>|=jL0F{YPOguhP;= zL74|{VF*|NZp%bK->UNRBE)OmTL#AQB_j46zA~Mc43O0p+}luq!Gqv}ZoPhn>nY2r zp3FG+*MyAF82%gvgh)ykG`2KAekMfcg6xhX1JtzCx2a$Y&%E>g$CC?eY;66}Ov^jJ zht*pmSyKK77Z;JgVtX4$1d#2j0l9y_{CFUasXa@9=6?%SllXrWd%iniAX@dfyaqZ8GX6B zg322{@Syd3AfwD_r>?~luC+NJr6*GrUW-m31n)DkV(}7 z_>ob!2e1_TaQ~G6@kM3@3vo~?K6*O|oMcNBn*t(URfMAp5Ceu1Kw;ki+%xQM-huJVwj&0ivNokvZZb%r5a?VO#*j zo8fRnxA0+{S4zzlz**MbFt=s^z0@Hs3MeA_45;N-)f<3IDZ1P?e8TKLDF}}*OZGxH7up_lK@H;6OpY$Z(nHqY6}3Gb z7u|G4!YvUbTTPtKyaZ9FBh-@eMm^}-QpOT-5`Z7L{nNO{HNVp-`-pOv&-^^?+mULQ zT!aT|2f=9&w;^E16=Q((e1n0a1yXi0vHIX5HDVSJj|<2HIv<@^RRQ>!e;^O?!x*0+ z`{_WN3nHTjw!vB40lvx%+<5eLZaGAnWQRzBP+KUXBuSSQtplGl`Iqt?Tw*{Xb$)rR zetLXh)P1s)5jodV57Yqq7#p8d1=kuVh(mFWi@keq2QCPi_#U5x3fA@ZoWLh3!V0oH zF^MJ$wa|E+Iz8=!d)Nn$;P#~z*!`USEc-KWA#&A+4?@W0v3zMvx^9uONEA4;tOqrkl3 z_ZzD2>QzAtOQt&XdzSiXqX;4r`%RY*ecr)VMa9E3kt5gjL@nSXmFn6K0|yy zk7X(r2EeHz;kM4F+v$m!YdG=-!D+``fn+U$lSb<2y_CV!)YC}w41vd0-%uoCnYg&X zoj5G^$0dIC;0`v@$-A$W56`(u>l7A+L{f{HzokYfz;hR_M2#)AY708#JOeKRr!(|# zzj3oL9&7(dY=c*(Gwxa*70?Jq?|e*^5a2MTe)d7$D`X)05shU8rKp-qW0%Nlg)W) zxWO4ealCap3rf~c6F>c`^6k>j(P-o-y0W&P5aQP zT*2Iu$ORCZDg}S38f=fNS@=;7j`Jw$NRK8`5?6MUcNRESPuzd7n!9u6yUG>?1bCBK z-Cy_pIShU?f$c;H#s0-RQsrw1#p!N6*(iP+tJ{K901Ta|dITaIu01G_p0luMpP*cP&)`3hIp+w^01J|JR4kK)f5wLWCvc5qmoP?*a z+`afgcrXf(Q5igrCrIZ4pmu6GSYNV_uY%946U+;LK%Zm{4Ss3muzC}IXgM>O0MZCpOWj#oCAyEFF9cY))M?jy>uUr@{2W4-G zAtbyrM1?{a+_(9}iP`|Tl1>Tv6JPKnb;dm%`Z3vnV%MR9kk`*F+uG6^WM2eVrojW@ zJX8T1lLx^3mH`}N4X@Bp*4+<0g&-0)o-fxc|E78o4b|k)8Vs2w%*Btxci`?Uyh%X_ zFJmtl=kay!N87_*XdBmO@8!BWBCKWL3sw9H!iMSIS(D0LpX(PWtULS+xtKMMYp}8Q zR_GuP_m8MMGuQ>zuQ8Rme2{P{Lo6duCSuzT|E%$jodL}%un1|C-|T!=3hEV%?7&ti zosGno#)Ggze*^aFtxqSKlQ64yoMM#tEOs-kWv6(*>8UF|+rb}v0UNP=9PRF-u3U;a ziuzwai;0sTwe!r4cnWIYT&+&O)Aw4?prUKuP0C=z^vOhZ`SB!h{D2Bsm)L)wWj?&M z!=#c8PRA1aaJ3X^BWY}zq)Z8LC|$aL+U8lo;sfj(>EV1G-B`hQnJ1=Wjk*h36vGG(L9qf(5uV7zAcg~Aq33-cT?=ofk>cbI zVN-bZMQsvDSWf)Qg8@7oqr?GxoX#fD;h>X+)VBr;Tyr4Lko!JGjR(F4hGtvhT$3S| zUBI=H(8q$aiY>YaOR!Jm5Ms@F08MfzY#7i%u)+XYugVWY2{WUiLFt%9uZ@pIU`7Bvg7I4q8{%9Rp7I_xUMBINFScCZtBuKGN==wd@ zJDK6n?;(Rds&Ffj3*;N@zn4MJD$uqi#kL0f(!trei?S381Nn`D=vD|mAZ8Zx^n<+nH)T~e^--sq(%-EZ`UQsKCz70H?e*{6v7a65-Fb$O%HxdJD{r-E zXAug%DN!_k6#*zcJb{OL&ivga5R(Mk7RX!qkCJ9B1RixI`E8jb2RH~YO$`#FejE6^ zH*Lk~)=4{ez?7Ti$3(Bb_85PTQd0GFZo+*E5f*j;y)x@reFjg@@*NOOXangkbU3xg zHqW(K`f+^JxqTpI`R%5J58Ad6lC7<%Gw>+Kh{kyHqjX%!7C>mri?FfZEI=I{<~@Mq z%h>a;s3$j9)dx%Ss6^B>9)XzN5<|7-9mb4#IRW-OwioVPe;(Bon4;rxDeqnV^l~cE zW4MiSlx10ejr;p~74x-gp?hiC% z#iM9O+f(?$D4$O(FZx=VVu-{)70_)^E%g4qiC~ z(U0t2u=Cow0V=Kjg@^OhuFXI)JHIE3rT=rfN$LCj?8U~{#j6|Z{eJvXt6|}Yd+qe) zof;H}?Jo-Ne(O@QvvYw~R{~~(A~I~>Z=FG<|LA~kTdR3(#Io6nK8=%tlyaBoy{Gxn zFUFBzXiOwo(bQ=3+SY_xURHC2gE$pNJWO>zL~R>PiPsA~6XzL*-+NsiEKNs+9HpP@ zb6tpPX-MQ`Uuw|Buan*6@c(cI!XLZ1o`gfi9cC$qO$CgoU(f}HLhVK^J{nRLSo5!z zf3RRJfAWn!~uku{?2ZNir zSew^nGt*%o8bdLl2SwW z@Hl6B3j2&wJ+*+Mn!(EsTF(}BnUjm1_PEDSHc6Ukait%8seiKz3JKLI>7mM|GaQOi zkZ0+nvNYUqs>3>KuJ7Vw*qr+EbTPtG0vs|?bu63nvV(lvbbH{6K?Uj28IUuPNHcBj z`5L#8$sDJq5VOQX6C8?&YCfV^p8e`UXN@lfmCz(<#GiD88jEsQm>O$d^nsK_N*r`=r1ZzQw z+xt+>8lFr}d^C4SK@fW$MIM2#9{2LhP}oD=HtBMq8T%DQ@V8s`(oFe3@HnYqchtuuh5{;#Vi;v1)i|>2}PW96_(ILP>I(OJgHDr6; z*n%*zgb>kH1v(35im7Lb=yW{$a)s1}6@EO1s~s)AA;C!= z&N7b&xes*bi7Bbo0>ch;hrfifrlZ2H6x|zw^A_l5y@3IiPQa(sS{tOn)fyb{fUa$K+euwbr7OnuY0W5R zjD(tM5o%d|a*L^WQ#h3!O&+VR_Rg!3FT*sNOq+Aoqjgq8TH~Q%=p+1HOHX{v^p9y? z#i0R&FX`1($Zvq;Wbj}T$}uKqf8xIBRXR$=*^t_aM?)VBM{zd-gf~dUCsJ~bE{p5- zur5@oXt-l{cy&Bx?S)&!hEFA*XLV)u_se zKf01|G|(v<7UditpCqf^$JgU2Vopu-V(ALM#GA4F=?AYLN~-TEou*mK{U{>$dtNzK z0c$s7^=R zcv&z!u1I|51^gv->{s&!_VA39;*|VRdLBp%WB>fR$&!Fb5RYa;1?R3hcE4h5Xv5jS zEBdWwr%Qv8emgBM$KcqZWxaIemcZI(x8z3qVYSoj%`dx9xWQTxH??)lUVY)1*W`ZW zAKzvXc15=8_~Ngd;|)G74Ax}x3jVc+Scj5jmp%KVNRvV+^3|2{NVut6<+l>Ror zu%QRf+b#-soJE>)lm5Ql@8t*PfwJ?Z{eZq*c5r8A;|fZAS(`Y|XNO3{xZeDnUzd?iJV-E(EpD z1x73}?z6;j@b(_O<2uDiQ|#-wV9fk7n3-MBRHNBpY3G-(`&>Ik3i{PudJ2PSt?*lz z>6B~|=Oyeo5~Z3osVPHX7wk+>WwYgy=Y97f=KH~G1xx7vEDq^pe`SU?_8YwwRzWE6 zEW2k`dpJ~>nE~W+K?MO4exer8I$1nVX3Pn92_Y0LxPNd(y=*A;)fJ7s)`a_MVXZ~Q zlPljo8-4YAUhMpQH{NZW;lK+Z3`0h(xbu&7-i>2&y@>tB$i&Tu#(den>RTY+A93L- z>RSg@_ary=$~nqYU}Imtq2Olxaveonb+ay&yfOHvfU9LAcXNBBb|#}_&u6ZxGVF9B zM0J)`xYDtFsyNX@_mZGZ>zzgtMLJDPHF!G>P9ttzVhjxLUT%#PPsN#|rZnYMc9xXY zY#_!DKVh=K?u!W%Y%d)s~qo;p%+tFCt|9Z(Ti`1Uq&OD4+=#u({?9^E{r-AK>c-&U?s>AB;ka@F`(6$-(@ zz%G+5EFTJBzAyQ?u(tP|C#2@BZ8wdymK*7 z(MXY;Dk|zsUCgB6+O>vTc+_H3Y(0*x94+lI^6?<&7u3u4U*0sU14_Z;>Tg!T>q6N6 z;+MLLx*~QdFcS-!aEM5EHZ?($jdnhNKCsHGcPfy%&o86kr?DTom_NG&1$(pDIosi_ zSN7+x%5g%(Cg|pp!q4OC*LhYwvjydB@zPjjA>fYXK?GVk8gdfK z&BDxmDdYs0rlAz$jXbPaaB(xi+z$U`-xmLm7a~gKPVBY+6y##W|4RyTMLXO~Km!oP zSbY5n%#LPIn7pYz*<2-|2pcK#Z+33r7Jw=2Y z_btOW5rRZ+Q}OC_p$}oe3_?(2sA<7~3k4%o3H-+wUpL@aEugSN@b|er zfc-+@rv@X@Eb#C`0ci*Vfl-jv_jkeIHhi$|i<}5z(Ve94t^5FVo7At#SZDx>qXt;x zJT4Ixfx`EHzx6IbH5M9#r^S|?G@qn=;TZ?Pzw79DfgGL#t!N7XID#N#r&pzDsDeSB z7GQf?RGdT4KKE@PS1T#2U)NH!JuRr!O7y<4bXk@BH zH}&5#`tW4@g9^xU6Vc`12Pg(W4erpnt<|5R9wbE3T)1tk7*+hP!d31ojymdb zJaPNr?feEB<`!rASv!% zRmp`zQ~I-C{>RGg-wn#Dz{kSj{Ed0{U!LzgDUP{43!JL{&y6L}{GYTlvH$;arT^bQ zQ_&vxBItwJ;T(wHTwtg`A0vuJ8K5SvZ;5OoP=VzN2&UDCFZ|AcF5MKR%sLQ~mn7r! z5Mos1Gu2ZbA{zD|-@tEhetFJc(Y7g2#a~Ab`BNSIll++`U=%u;%S127t|43xJX z?n5S5Q!JVJ1AxqX(=Q+=(LnMTgX$mnlFV?^0tJCI92hHr(wOE=R_NX3J_WzDiH?J^ zyEG3+5`Lo56o-T`1Sszm&IbwfD3qYxr@)IkcuWFJEC10TRTi_-+!6}9rLol<-1lcM zy!~+q%fQ+3x*ES9jtZ|d>h^*XWz-EMmBV-zB&r!EpppCjd%UW2Y`Y%z0}p^yBg=rQ z_DCY5f}TXZ5~McFJtuIAui=~wtPpf9So9Ajx&x!m2>h#4aV_z2JhW2)a8Q+rh+~{} z(EEh}oc3VP-^01+_d!6g>ujckqQ%l@5j3AT#L{U6-iGze;YZ$ELsNzKCglPp9k_(X zFklv*MaGQ;q4VfBz~HY*q5l((Hahh|h)~5jC;>1|3GH*PoCdrv9WD*McjLvM zAejGG7E>f#RM*2&Sx`}I0a7%gR%%t)rgI1ZxHkhG$9X9C=aG~xp#I;~V41+7H)b%< z2@J!UQdmz+l61Sf{%UoiMmJOK?Y&db?cHz!5xK^}#?R7hI2`?L5c7>{?>t#QEP+K< zrp+G)4pm{p`>e(zN;*3$L$?<&O%m|E*8ZhPEqn|Ba6Djey`(jjE1lB`Al+}9Pez4s zYvFKunvS#LC*jU;0S5mVa$xKF@tdJ7n%UJk%B}1UJiz%4Acy0Y!TQ4L_rtpD)-_PR z8i)Ud^a=rFk37Bsbg{GL5~B6w6$7VURIZs7k}Q`G@qp+(Q0Q7ieAytN^0^Q(HJn|G z9C?wI0+}ao@4|{MgX{dwdsWZnp2-jw;QwfMaI)^q0tEE~adSl?0zsK%NQCoPjnjQd z=rV=qFrcm{OX1rrfO~}jgg$DWdMprBHmLg*n7MrTR(F`;tcqG!AeIa)wQRt7t6~70 zvJTvcs*qiXD|w8d@tsvXJ$e((RR|GEBjfFv4`kZm<6$6^%7D{)0!U?CeOd&d@>w@L z_cheB0WTI|n$z{8zH zy-PZ7v9w2;xSV4b_AswY!a#&o=-Lhd&|D}t*~DXz6RDl6V*{%u9{rx*;9Awp^X-K@ z(0kaUAG$|?XD9>AC(R-(QR1J6=%qLXON;mvxh;42AERQ8h+1D4pNcr3}`zX zkx!$TxdnB9z)>;A1PiPee3K z>7&O1dLAuRMp;J-nS6Hd2lMm?3cYF9eCt@9W?Dp;+TY4JV;8NQb#SG8SsMuNNsu7{ z+FDR>MN`-VZE@lbieY&px8FbbPuy0YjeHwwfht~!l^l2WOf|U9xz|`pAa6} zOC{4uhS{ZMtf!$>~xH=Lw=mfT-Iwry~eM*1!FB{fj;OghPZH zws;E;&(A3X6?O(|u+A$`)@sBt?794#u)bcRo&rB)$Z43EY4TqU@TTtpjA*_ejip zA+Z7a3;{}s*v^qNqc+UOOI`j`$>pn&B|+ejwKYlC!Ku&~-In#~Af zZ<_nq^1XHv#L`a8`pTD-rr^JJc!xZ(PQK=1IQbTl!PFvZJ z)CSCS&QXs+V(;w<6sN$SZ52u;D$Zw>4pZ?l;x6kTKDC`M1=Wi{OdV{oollA{e2OsF z&}_&WOJdnZ(Yju4fOnrCpJSF4w>}V-MrVoHCBocgI+L(nKRM%{P3fnwbuZpGpc@E* zTEK@pd6@gJFVe3GS)!=)D|Kox0Q2ofEQ-3Hh1hi4go`i-BPCvS2;ec0WIirA z1}Yn+tx-M`ET?4J{hG_8D4=8rM)UGcCu0VCB6rd`f=R3#;J^JU#W5=!F z2>BIb0%0EU zx_JDCL4~(?pU5nlXieUw6&TgJJ0)5YOWq;MYssBWtWDHiM^pwUhVA}FRT@@SdOuqN z%^<)qO^5gEuP0s#(qTQKI$wT`hi)Xg&v@*X%WPhpauxbv2@i-v5iwOcvEzSZamJ0| z;C{QX1AqGa4JqfR$BQJAJaAUqvziyL7a2_UFsHz4asfrf!*^03LWoA(A?D6@hN~h1 zbS8am#$lxMOPygzxZ&A3&Ty+R$%m6@-#WQ)>Ji*)cMtO$%t*V1*Xh7tsm9_qC=g7P zgz=Pr*6bw(_n~`trQI0kz)NRqE!8v)gEgTHPZiqP*wJ#i_xhOkh227gEacbBOc#kl zXsJ9hsD1CHcX_?6iCiHgWIGLbbh#I2_XevY`P0QEt9jXi2uG6s;W}X67*m0{UXO-P0w@G#4h;5 zfME1ji|B<&1WxnseGI8>1ryd#zJc!98R!ZQ&!9sghNr#*sFnN+fp~CH5Dysvwm6&^df)@ih((UlOJSDexkggQz_RMrVKhddAKZ_;(;JY# zY6dd#CSxvxz)X@{+sj<8vGY(#51(c=88kZ$@faD;nJx9Ev4hy4{}VOlCC45}@m1kE zg%SBDqJg)2Cj09CGISg}5*NgsGd&-At&vG}R5FFxX~8M61bvZJPi5OgUwbe9F07B= zdp%o3Z|*~qy$JO(2t;41kA5M66J>T|KjuBfR1`=DZ(O$>VP(oi4w;#L)+05Z zYfZe?tt3tbpvq3wMp2Wydf;2_RPUkE4`3S81k<&~Fi)Gkx`%B02;v$?(2)pgBybic zBMOJQgRC~sZvYM?4rG^`M&h8jy=#Gy+nn27#3(|yh6m*t0_?ebG>vWhA%s= zzGArPZrYA9ia8s*A{n+@AX}yNJnAjr8*o4gA~Z$V{WF*`cp-?C@An{gWDX^6(%G*s zWZB;B_(`MSzs#5XnIWik!p3&dj6w^yWu#C4+-NK270|9ai#&$&c>4(?s1++TOiZpS zFKVO!s^k7W!Kvu}hmR{|+F?)C68kXUcl!J3P0YdzHiQo$EhRyU!RM>yn1*w^H=14& zKYy^KF_E@{y@=?s&}UFvRsvoN(s6Mm3h@H4lyeqII+^&0g#B^4>D5hkni{8RBLL!# zS+Gwb;x>y+g-B6FE|#t~1;dVz{1;9Plds;^O)cqKc=^L(?#`xeJn_JYJy)A8i|Ns# z_?tXqBLKLWgd%RVXPl0fAn}vR4M*w3urh%8yOQLr)DK&+E{arys_K&8b{2WzswyIgJUxRVjXG3**UBxw z;xSU|QOd1bkR%2t1jh=ODFcYWx1n9<>G4$!8;>OFjDwvjmQZqJ*^MpIozydqVb)^! z3x>b9?j382m#F>sg>-QjX3s#lIzWAiJEheLd)h-YggaeAe;(7>?lac9Ld<8Y1;Ky^Bo45Em4w3lQCk7T-LiP*Lv7c zF2sujuN;%!$H^VpS{KUtXq!?TckYFr^+qRYrq@}EJr0GiyhS;YVMI99) zlEM<&&GyW7%p=`xf21PlE0@Bv&bNBH$KXrSeb9vrkySSj@~_{mQJJgRU zv#{T{>Xx2mr=ASlEYOWY9`(*F>RHp$ZPP3Do1C;lJ@RFjan8wF@Q<@Z`*dZAO>UOg zg|dhn!cA?nvqB>l$kbtWRA&ZbiQZMKZdG%GB1vMY4S zN)9K)Y9F)=a&inn7+1I^Q8uvAG}psYC46(XsY-~#|A1>{f=4xBsPtDjNXk}h((3wT znhS4=_tuE!E79jOu7)>qhTYvKpfjC!EPzTDu*1Wy$abJx>X^cm<3f1A{}nCiYV zQgfkXw!{GX4`-&Fc&x)Y>WQkeK7-io8K0!Ft(+FzvH=c@Ak@*ICeJWuTkZVL`tIf7 z#8PiH$Wl+ll8!JWEM_yijvHnDZ@pnMDJ0>Ne|q6&wnS}MhYECo3LhMv*=xDZ7fn<+ z6DghMvzJW=6un+J1YfmCI;Z+lps9jAzWIZj_?EkV<{9^h!aZK)=hKf&Gh~D$$|No^ z;8T(`wN44=V5nJyw}qSXeLvXbBy(+O2zespdjX#vTD2Fp+Go%qCahU|Y9@$VQH1tcXKxVSfVM6 zk*3`aUr|70N}D8$c#b(lgs{T^=6fZ=H@gn#&pdu0hWbFc(Rx~JI9A(8=ncD zE|6E$wV$KGN_9`{5v~)`!1b18$hlR%^|J{35F@vCT3=KmghMYNjKDba)1T7??EkjV$nUgu`}oFan#k})K| z=2>{1ZEL&P0$G#bm{$Y=+hpTYY8xu@429gG8leV2s|!;^ZTc_XZd0A*;nh(PT53#V zVv^?k;TW%!dD%_4f5MO2DX{(9XHiZwy0Y+SwJ?gPK?|(nMd2Kid4{|BUkW|6Tfbbe zXTNmjJJv&CovCh;20(6C>(ye^ZC8lHQl z!K|%fdaTVGnn*I@)Nu8c@iob`SK1X?diP^#dHpcDGg^aWbsW@hdU`6-%j@UOVez)> znZEK515vNVub)Q%AbGhe?kRa=MC5vYE~>Fw{e$lct+n34xAkBrTDkJuf1lq4YSVVE zL+U|>Q4-#c)<EUn#h=x$-+V42&EN`Wf6L+a+;C0Vq>V9qN??}ZY`rUYR){WS|6X;J=ys$hTRAZw##czWKY z@qGngf`V&nrpEOa9p{&e7&Q}byr|f5^DPzev;#Xd4`Qb|A&D~HUu$r~^^$}*vCWjR z*FAjPSpFf}q!~eu^78>W{)jnLvHpUHh02%9EDNmo1ZA>8*MX6lpJ5z_b4m$5dip}h zO@}XSP^$%=91HoaaOCs`MaCRoI-O4x%s&|MVxx+<%L$x92!shf{RAu-0`YSlb2SvJ z#UVaRU^d8GA?QOLMU0zbjz8K@j-V%{f~@@L=$up&4tYo={9wqw4TKb- zkM(~%n^PA~=hs3-FOt<v@Y;LTZE81sF@ z!-Y30{}*p>9n|ICy^Yc--O`AFqI7pkh=d{`QX&dS2}rjzNC;BWAt|9K(hU;QA|ff> zDII6s?)^OPJ9B2v@64R|YwvNp;rqSsPpoyVD=4Lt=Bo@BohZz7q6Qi^*J|#O_v-11 zG5w=la5W z;#0Rt{Pn;%bj+2OYxA?_u`T144y$1|9zUKb37m<${m&=N@lE;SF1L61DfnS7W-8|k z%Dmuo$|yDoQY9=#68V)&B%NfeR3xv`D^E2{Q55+oNrZ1r5s$HcE1ih)Zuk>RX4n5- zRhWd7^!N6ubLpqpgKF`OlF_|owtet zmullSncVWMHTPG--sU&e^Smz@u?hDHYZ?X&FX}e4>Stan{qy5G^xX{9 z+gM*`4K!>i(r=!sMOgFP#rR0*3AAnZkDq&9PhI?zLRqVvz?$0acB>7;p+Dy_*$f7E z5nX(@k@{ohc-f656DsUKM$AtGGZsKF1w16VP2Y#ryHi%enz1rhLTA}1ho;hP-Ok>T zy;az~_DwNLa){Twh5W2mXz16W_IUwq-bxMQeFxobgP3K5?9#8vEJf#D z&11N~+s|lGOF7#%KGqm|x3IhaT4J&Mk&`vo|N2q?pzQD77IE8eWtt0r<0J`V^DxD! zmZrKFIk>%O;@sl4jecwxI#vm(B(NU;8z*U`BN{+zu5HC`m$>FQfqcn;T5UY#_Qy1T zF6^YfT%)%&EwvRyt>81fE?!%VBt)fzNP6$<9V@PhH)vck5K^1lOgCPW_&8*Cd3Gd% zjDTH;{FjDJ!v47`jeQsew$9Q;i`J-dmQpy^3PnMlPcZI?aqtD6g`pez3zyZVn^ov9 zPfST|wEYVg3a;P1s+#MgTf-b?ev+UZ~Z zpK>7Q2`rOUOeVIDZgb|AEB+z3 z(-KFlk|i|W_q-gLf6#4qsAg;G6G(H^9B@yIOMabr?8a`tNzfdY7j9>s0jiQ+oDjO$vGRRcAv`8 z8a~!?_K4mYo>)GmaXbv}aJuHqv;CG|w)2sH4;|HYUz4n`N>KyFGe60CUaPywtrz^3 zD%9+m9MSc@-gNco&RGhAHGmE>C?vH(EYW zsCPPgtluALpkedJ)oWocN%5!kh2j(c&iDbU_O8<|iJ!tjGlq_jU*T|af22!maGT71 z`19^#{2w5~f>#nygyvuNe*H;BEN2f~k1-_fWm&Iq*(T4*W)88=#vcS)EnghC7bS!* zYYnN~$rUA%%Xq{RwFe{ERTMoM*gyowiT*MqW&2%ojme?@P-3Z6l}yQ>kjTiAmzc`c`fb8i_0k61Ye)gseOk0wSi?tsrh%u9kV0 zFg+$C!b9Y2U{?5*wEC9@!K#{UVmdeiY0{MqW_Krc-=}er1QQxcum+v-DkSerU$s5N z7?Nh9*8XO2-&*|Bo%4&nZ69kx{^t`4N9Aa2^3t^jFT?$bm#V{P`Ad-OWd>BMv+_SI zUQv5HJ_xWCQo45vYRH@!L>QWXFy&rM85tl@j z(lOI-C&0gxkf24lEk!n4qxaA0Re^Rl&z_zp7dzIz_RIR%#4`tTTC$;>?O4?zhT6^} zh${@(c}lJA5uLAJ_Re!vSSQ)k3T=>8<%;g$_LJwQ8>Pm9WYZG_@)y+C*-N)xHnLtG zD5~`nd-)o5h&7IaY){ABpehjqv$`ZA4Aof=DXrSi1$F3ZJk>T9YLgD`@YYV+TM{l1 z{F6dx@!)?;A&lQJyl#$huz9y2+~VLtji%lGgEw$tGwXI)GE`iVf7(ZcQ@ukNSiR0|OBz=XCwVSxajiugiq5z7cuNS87 zFYGg1c14h%5go!;Nn}MmnctJ~;N{*#pq5W$7_lnNspu2iv}IB@%7y!^4!jQEmCr7k zK3yD~{|yX_{)$Es&4daugzo>1A;cgeTG7bozWYKW_r$=rUhw$A@ssEfe9wDZ{z{r` z1~gig$`f0?q{y)rqS24R8HR%S_`LUs#W`Y>1>(R5XXrAW|M6>mK8O+iUy}vW+qX~% z7YYIL8`iAzKYuMczaD+C`o%L|=<*StnddN^E(gYk$pq+^k;O>*#ziKA^>(}(Vgmru zYI$jAkkTQHq~}Zh%p~H@)>`1a3PFT+U#EK0rMZD@$;M?|4_Ih7o68+icJ&*GcHPGg z>|GETSpo|&$U~ftt$viyuS^2WO$qX4N^o8!rr~=`430Tu;MEz|;05E_zX@LuLKbsG zBWR^vR^#pj-G>t< zq}r;8TPWfco$pbj?`;C>S|z@Lm6oApEvi z&LW7XDuaY86L9aDT;x&Co3+k>X2JrLjAKGfoN)iLe@S$1eK2$AG=n+l2t_oI-l|iG z$Zr09fxtr*Y{J9EB>@XV62#*H(MEp-Bl46z`A#rZ@fLR2nNukUK%#YYTs1tPiG z4{tN$aP5JavW)&{0sN2=yU;8kEg+P`9kGCA#j$@sbi$z|6Qub>ixUB?|E71^UM*P8 z0qj!?WWZ8Y7UYk&jss32du;?VVOqgm@Zq3p0#~X8qT&2Ywgad+{-AZUs}&!i z(Pi|wqDe7<>p!8z&;LAku}pM@YJZ}3c%zD7lBNi6xD710Sf;4Z>@R7RWbiZ!tRm{p zclSd4?}Pa?O`GZfjGTBu@sH%=S$@6`G@ot{ma+RFaQ^GM0F1^QX%(>fNaf)AXYaNW zFef{-;t=x`;XQth6J-Ew3^2t$^37F2_JJJEb8p+tMBI4pVn9gfq`e;owX}(g0Y`)~ zTKQjB^5`x0=5=2twGCi)<;c5(7@-vuYG(a8L0ZZo3d*DODP6fK|qc*QQJ^SyE$b12Ab>yiX55jDygX zC}BU6?}LQkrzd;qb0qgz(C88LXI*jTD0~f#eM$k%o}7 zX73^8&3W%##8+@kpWg(7Q3_zw#W!>SvlmJFMEDvzQn!%BzlwFH2FtS*9Bo;UkrM2H zMc#(5j4L~!Q0D5;NEMrRrmF?@bG{Q;Yd!@oC+^P<=2GDMPyiL+g=ij7Pr^*ZX(Rj%nd{j`qhC=Ia+4K1t$Ehz$K9QYl9JohU^fpyXO#k z6etEcTO4kPap`ZMg|YD6gSiXBU`2e?fBm>bD|Ca2-Xmi$R87EUOdH8#0kVVshM1}U zG%FBGC=iouh>dUp8xi4@4Fi_hk0G6Bpx#qdzzFjOYl$}ap>3eeoeBUzlpg3EPaZt_lI0^KOj%RG5H0 ztcAsicrYtE4P?_}9o$+Pxbhr`AEFtLra@_2NoRt~%uNOKj0DadO;RA*u(&Y8y~Il4 z4|X5bDIQ=Xk8E0mM2)bMqn5$>#3=10uBgrlGE`(=0Gb4TZi?yaNW&1=X<-OoVP9IO z#sFb~h?{6WXhw5ZM}jqwl&0Mv!(@2Z%DK&#c7oh_{SBjF*@lC~;Zle9$-1zJ0jFSz})FKd|#<~#76>gDSvGl$7T4SG*Qq}57<{t2 zx=JL4m){M3MZsO{=N%}4j$8BwM>=jnKjmSd*9JGyyj|iR?QWjVrE~C-P$c~sC8>LsqK0*U;?AT>f#X6GTgZUv_-_}GuiaeKkqo8n8sXI zQuxU|GV#9mhCiLKR~B9gPexw=Mr>^Kbi*fQ#i00$^LjE6NP>|>QnebOFgx6}n1lPp z_Ks!0bN%qAiE|<{`{GkZap#^L3A?^n?z4ghJRG$kVB?f9z65IA=@Sf9v^4y=Au?B~ zZ{i6+zmN7Q=g(;5mjt_%gk7FcZ1CT%h-*TW2cMtoA$fRll52&uerxc$E4jdW8GPKn z%~SUjYHlKKpHYgCLCid`NWM)?arj~hm{B5Lyx$b7p3#Y}w%ER$l}3?4X6sL zV_+U-J#Pck4893)3}3c{q+26~x8M!eU4>9ZI}X-=LGlA6%}F31L-QzI!1A@XH5kPJ zEn=Vo(vvNKf<6RgR;i%nph7q#yL$J+F*WxBc0}wHqojMzcW{vdkbcwpOi&cNv-cFVk`sAHZ7CpV3v9T>H z?R$#oK!BS4`LoZ3_gM-&ZY%KWa#mS|yXglMo!RR^y7d4*rg-o$>8|dDDro9#3SQu+ z%1L)l?Te=eZacZsf8Kz`>lrXM318I0+=0&yro0ukCBQ}gP_pL#l<)`?C%N;RU;5yj z{sdeN3na>QA1?WXDe(Eqhf`4*((?*#@DIv)(KL;IKIHZk1XwhSzeux@Rih?U`c`+AQ z!yd4h<#R)yVw*SftKfuIhcp)9584XIaWH}ZW4-q!bSt1^&o#dL3hsXKaEK;6cnrk;rEyMa}53Qi*Xh%-d+yf(`LsmlYfJ^KXz ztePW3|3dNgW0g0r%uF&!J~}rMtBuoh_-!j>c~Itcb^d};&E6`{bKk-Ff>*qLZF4_I z+t05vIq#Ir;gQurh4Bo^fWb+RO-X$=W1Ijqr0qDP29KAB*xPF4_Ls;c9K{jmI5Tlb zg4$i^O)W})*-64i?Obk>7qX#(2R;x!mT*3-3*i9FCkp&X%()t4pE479G~5Ih zV961C7DIFYi6`_bd2o4l&bx42(cDK8BpN*K$_&1{!}*53VhTLMaxcj=jeal>_h#53 zXv=_FeGF7XxgU5O=9H5bLP)Mi#Logj=px$n91{lJJdntAUT*uNbz|h#4)EU52*X9uAj3y_~4!tnyDn zUrRRiMti#f7cx#B?^uC+@3#(~1yW+X;IA+`?(U(bFVGVED;Q;B~~^q%KM$;oU?9u`1#PD3QBU0#ZwBIPzS-*7Rbx7D4Pj7>T3E{y%3`0qg=%Ay!FzlgJ#sid9Utt#~xf8s*BGc5fZfxqnWsQF&Nv6wHZa;(vL5q7Rx<_6=q@CzOJ+pGH?all&4{_3K8xO-idG;-bCqY2j2c19u1ahF=;(e zyc~!XSZO%eMLMoQlBA4OIX{Xbo~h7#x@TT(#`@dXyRF{~Q+YdOhG{EECJlXYZ+5LN z)8n0wush36H-?1@v01{=;VqN?%Y>{)LXLsBb_Qe=kDyN45g%+NjQOrAMNYP6B?C^# z&KslU*|!aS4rhWt@BW5|sj&k#2Bl{Or6#12=WUlFWw+bZ(;G|@a5vW)BT?CwoEy1V ze-F)Ty9fg#)3Ncic^tR!9M zx6PZ)b!8T&<8OkqA_8eJKFLh^1zz5x(bRz{lj?OQ*PnD^JwUVzJSHG>ximJ}s7h}n z5!m^Wq)6b^J6@h^e6~-2b*@6ua(N~9{?Zz+Me?^YU~*l@CQ@@MYYq&JmZsJnYy>5f zd9|xu;Dm(P>&F-~*LY*vSyfSib?lmmmA@pf*C{VbJVG+y=IO?+s<(Z0^LnooIa})w zrIZtBLV`t!&bz{-P0&^fQe&{QJ4t%DiR;Ok#lG>8h8!yhFeorL6Z((2;m$ce!!{FO zG{$|xaB+5ETre$UJ*qh=pLCR%9gnKES4JBaqST%$nr+Fz<_~vW{EJulcW?%*s9es{ ze<}>wo}7%OALFsL@W={u-H5YWJB6s7zD+07M_3;f)ka7nNAM*0aZs&wC{4n0$$8dN z65rfUR7b5P!kT*HM(0eD@i3V}ngjb*tl%2mte7Kx7NJRefivZ|>AI^tMIZRCw7iJE zIqRXW{ndC)q%f6$uu)UI%$g5=I7t74zqbGWYq|U>o z;g<-pR++@S7o1uTKvUxsd~e>uI%0HQgQ1y))1J74*D;5dj$e$r$oZCbGCaa+o2Me- zza>OUv)1e%D{9|V(Du(Rr1y-$^u;lX{snF(Ji~U*X$E#5mI*}Gejg(&({~_gvidoq zzJGIrX;KdVfm315Q8MYI0W?t- zfNSkhV|L#=`L(VAy$N>|bh6#fMkGi0F?qyZ_-5EUS`FS_G;RbJH7&7ZV*cdQl~ARB zp;K~u<;UDlX_1c5J5ZPrDuyiju;V%Uy$uRv@o~TfI{QD@2WVpA zIKG8cM^R?^ugXpp8(E*3stMVcmlBPkC#H$in#ae(WP8b_Gu{_ zl@$tj+y~j$Zzn$Y|4n#lX~i?(01@q)2c!c&XN4#~(L?V`&b|ySpfO8}!5V9^GFb&i zemtuRsp#{&OvMGFwFS~PYlM!5vmb#H_sgxrN2kwiXwN- zfwvJc&O9o-4EJXAzt2&|t|4qo&9nt)XxOW@gD%|x`g1&3)Y71KBv`0BuME+B!gXuO znZkoY%JAnNRvhD=FbDU!OSK)ZRQQTF3d!sKzUit~R`PJ-MCGF044$kz-(F-$g(#b2 z49pw3&A-vK*?4vl?2uI(5|pzM7+P7nZnvQUVkHu*=3;WMK(!*o3n1;Eli9sF>=j4T+OZWFTR1{Ky}rh{59@U6t%KR!sP~nW``N9 zWm$ESneKc~4q{9EK4BI%7FEjTgWe?m4jXzzN>cRNH}KjX>Jm}E9TTGknb{mS$E}bG zEI0YLv)n9cNiBqAa#?IFIC#omFWBjLO!6*Iv%bVzn=Pk!weFaGkAWFa#HQJZj?w&O zq=igciY_{V8k>m$p)~C*KDEj)nq6TKO^o<%<`&kw2RvjiaSKIBnP49ARYjS2VLzrB z8)!^e>J4RnN?+A$@6-SwHj{dlR8c19YJg`Tzc@LuaZ2AyT)Bp3JJcipC=yRb;&uwZ zYfE3$AefO%>j7#LN}-$&i$87(5iajb6^g8gCRr-Znvgw;cn*pBK`8V=kUgDoDNl#= zlZ-u{LJ%zDSR|)31ZSFF45uZ6r!S;7C{;9|_zZjo_YRa0SX@WJ?LWHS5W)Y3Cn{KF zRp3(oi*M@xzvK$NY)6bTQ@XZ7f-05?NGC1xyS@Ojc}k|9mDD+^noQ zrVJ)DmV6E-eN@@Fh`AKU-pq#G@eAFvkskG>q}0?2^YkfEbaBav?{7{f3q9;&%VaVC z0hK}i^WTr!{9k3V@~Mst)ET*i3=&J*mCzLy0gF9AQsZABIzLuf5gj0x`)~jyG{my1 zWWm1AGjd;sa5GSxeF5Wb@=*mZ5T~ba z{|+jMNvXVKpn=PUyOO;N{fo~Px z+d2o0&kE=uR+65kY2lKxYQp!6-TfCve;%tau#Ap2R0OKoe2|e9kzBcz50d{PXp{>P zFYKMnP5@)UuR$+IV6RG3N&rSNPNI2Gst?@9_;;G{kiSVaMWlSqp6T*mJb!T(227yY z_ShGN^P5Dmh5jEoeloO;iZ1d0`PKJPt2_=$#=yX;8j>x$Ah|M8KUXF87JLj1P(aEf zGBYF}3#@HL2S34)a37`vx(G<04Z(hDaGqlUrMX=%;fX@sBrv zDT3m21Y24I!!ef&B9x&bK&4D${a>s;fZ+f34;|N$VPRpR^RgN# zEl64BoKX9ol@R&)a6(VNi+c_# zqk%_+_u{^UMj9NI7r0kOi7HnfGq@PXF(FQO#( z6H^oX(6OseKRYg}r}r&_8S5=oJD54aUU0t-@|x?Q#NMw0gud2G9qMwgcLqpQk$}(f zGJ6#pU}e_<%D>yeu?jufl?flf^Z62xEVQv~fEZUmx~xUT^>9m{C?MK3%lC$H{X_lu ztLe~j{gOHd#p`GXtR2pdtu4@6l)LvM8}&@qE!DqMK>H5@gZ=?OhoZwv43kjwR6IcJ zun(bZ&jK+r{S~(XuNheUsDE%t#X}3KIO7}@)cW(auDpQqiGpe&64qxSqF9$aS^KyG zlf@kg#E)7VTn2`bNm#qk7$!a~;9xsRT!Y7uIxq~Mst7Eji=i~n6S5i0nFI+LBJOyx z{aZ0%cglVBrWV6ilO)iJnZoce_P{Ej01RPWYl+AgkmwYJPuo6yc0IY8Q0yn?!Zp-nXe#Jj7vdA9+6XZ9>RmYbiNKq1nW=p zZACcm))i$Bh|&g1sT~;5+={zTaLf#&kYe^`Q5QFKN1U-zi-9Z_nXKY`?x9QMMeFrykNeZdqn3Gh z+o+63JIWmB*CbXgH;5jqiKW)O#9V&s?*%(-&c}uYqbwXX7hT3a4S@W_iDPteoWzSe zCjPn4h5G`C3C)`^YVt4l$ZvQ;L(X?zzC?o84*f@sMB{`fMOO^ju2BWm0#SrK!OA!K ziqw-$p1`4+C(80aKHB0>caFazCJir_i|}}jgtODNEXHa`ZM%L0Hu}0Fj3k~SD(De_ z_uj)g?Jfs>`j-@G7&4rAKCFS2!FeZHV8?4XUn_EV3`#cfROo2*;2ik~l6E`BXYe?^ zHsF!jGNuD1-m4Y~61)NORHN=;pjn(8LJzPu3yFmE z7@$yn1fYL|WX5@cckl?j?e}mPbRGbxar<~09Gmvt;K%^25VRX~;UOVqa8Eu-5C2^M zVpT`;tchxJ3{~VNz6I1LslBjB=0^c9HH99pry4?+vuhEHN_8_R)0vkI@?+un(1R=C zBPpAjMfwF`NDsj0?89YYTkFbs7`35qP{Gk##`mO$qr-iF%@VNlHZjPCj3A(M2^*@u zrSDYVKXpgya2h>H11$;t^v_eD!~;YUXZCG;{=Ko;d2_A5Gm@?;c>$2*@EBmPDIa09 z?NNdX&0ht;@2rcNulkAl8LFl47wZkoPr@duM`A*ix+PXC(lK5?;eqenq0R{dU#^BX zH}nV3ZQy>0DX{^wt+NvtAks~2!kntCbQx-gAG-(x;y}T;r3wZ-Wk}|N1L2r3j3`EY zF3#Bd9%?ue%1m)WrM4FXu9^BUH!KHp70y|?KCn%KFty8xj7Z9lBi6Atj9;!AN~C-G z?L~XTc;{gxOr%GkKzyI`>cptV`P*fHvOSe(7W&fd4zvsj_lV9eNjNSu>|^IDi8V=BU{?Bu!z=@Kx#n%Dfn}LxROabfTwf_Fvdw z2)n-x2*9nkX#T;$lxmPQVlz?i^-Bs8Ear_UqM`EBJ3oSHqJZ{=Db&d7$* z7y|k_JmCVF<+4N!DJZppz_YMJJn*9e{LrP0z=cQ@OBO4Pk^_{nUK>zm&5weUreVN0 znPd4f{|6c7O)t6Np8m{(En5Y9o$Cb^8Qxcrwc0WU6K6)rAE6j~ZU=g;!J$p?#TewZ zJ?H1}i+?W4PRo0VF!xsr1=1T5;?QwSLGOy5jPCj8DLY1%Z@{ySojkpoAChriDcHITG#;v-@kwI3*L8 zlqXJyg5O3p&Mz2$P+e`tSby18>@w-5RDW_$CgbZV;PZUF@i+7zaSH*S-v*-02CtHh z%0rV#-$x;C28_E0*6yqCD|4*j+3o55fQRdRjW+I%W4&Az57Ek0ISyKe_S+v&zz=JA zk;y$WLJfUk>bg7EX?8&nOKJ~fDe*4MEbqp83go9t>DqPv7Pg)_L4R^ptV;=i@b%e+ zsZ1=lAM>uT!yk(qzqGN@@W$mhfy9ZwvuK#iQRkj`aOPDWI|!TVL4cukAbO&rs}+CZ z)kLAXv4_(TbVlMxo~YHhrl8a^aN)tfxb=h4|7!n-#yHI0{AuVuR={V1g6wVL{-$i4 z;K8wH;TVJ5J-k6n(oj;}0~94;(;n?rWVJWA5n0!`i`1Zr3^n%@gH5z- zAF38_XXqVAUGSm2y@>fvdP6NuLjMe&-Hr>4$hDyeG+bK~M92siGfoeoA)y*d(ERpM z{X|7%VZ$CP)K`Ve^M;9gfN1+$ALBP;Z9F1LYlkb+mzXzdIGy%M) zJ^X!NJyAdMOe~1lzB9emY6Uy>uw~k(N6-Z3Q7bT=W7vENgJQ14mosT>Jd&*+iRy*6 z!mD6#dKfh=>gVYNeo0`miN*Hj@-SI@cuhlu_h(|cN7Ci$sM`yTC5$-7sF=iO^1n`? zei@Lmy~VyAbgi*@-_Q)tI)Iwhdvh3O{$sPg>h(^#VP!{Z5 znY!=PqnmoPLPZ>8q7fPWHbfI$7VTV#ScasIU2NsD6wVh$UqeZmIp+6?7~+N zxg4RX62`f4ywjVOIUY;L_8OTmRycVw^KiIaA4}x&GgE3+_5kA)#KNTG2={T?i!qHJ zQJ(4r+k3JB*_F6)sv3)!EwG>3>~Os4w!#P0zfU&m1-6KJW;wleUK#Ezp~YDyo||FX zAlf5vB!S7+51FO`nWG;T=>k3c)u;JaH1N1&j0Ui07A^drMIS&)!Y}VzTSvZ86oY|% zxSqy)&A7+cn;%he>Zp0nlx3hO;+#vPnD5UKLF8jU@XY*qDjsE1cO}Xg|STxs6vyujl>^{wekBm?R>DUH5~E ziY**$t>QwoIMgCTO_@DgY=$}4C8p|dBNcoqzj#cO`9F|uxz9T{Vc>%spt<)xf4bDz z4sytTqrHYBQ|5Ccjp^q6iU z%thgx;)!Zp$!_mFq4QrBcCVD*3^7?$DXk5%BD9y|z)W8170w#BG`++fH1y=|f>+Od z%UTcF#qN6?_a_M5|Ilt$`O>^9hzoW=7BgCE%^&==XrTqh9>sI}D1rPAOmY(XSGOCY zxiwA1Fxm)XwxhY%xRyB#LR@pRYb}XM`V0q@lnn@U&bONg-`B{6ySOB3p*&X$9%>tp z*j}r@GJ7rg>fp%Ij#;qW=7UVho(ZzusvxQNo69B9&vG$U(+Vt-Yj)hn&0IRXf^q(2 z?9SR9gbupcU_%yr3i#3os~FZUD|}Bm*8#hs>djZz^UjIbLkq=Uo@< z@`*oq1UkFh867&6RcMPuqj|qC`>sxT9T@6YHREy1ktcK8IAN2CIOFZa-@Z#jkphZFkDXrooSe&m=ETT~2}vvb@$48A_Gb#}kriLoD;?+o&(*lV0o#67n@p-@NZy;m~g;Tllq zi6X$_@Z2(NtVI~Y3H?o_b^FO3&zK(0iuwuZRobTB=6HTXz3S~Ex&H;#p{r}rnmBVq(hvuXoRfwa;8WY%7MNR z6M1DP{0R_oNY~Lb1-B6cIfYZ1R*pQT4_GC#PV@>zvaEyw)O(aFmn3G`hDRlPEaKPQFm!(D=dfI+5m8hb{&sS%8E%C-zl9DFTHRWFs#JShNnJruFd(699FP_ii@@NM@Ep29g3Xwf4 z7+TGj1uRU?z^IX7w+?0+rOOv^^E(i-++y~B0FcOIwQSEs4@gJh%*>gPtplK6BkO{h zlGb<3$gPK+|u*>D7TD2~mBBi)!I5PFS<*aHHB zWYSaBLk=%+gD(~T4&e7!&TqQTzLxiCIXJJfhm`bE$aMCdS1?0f_%KLk3x+;-I*CmQ zIA1{)BY95C)XkCplUIXUK5`pl1J1|G`Kv<|fcD>{uB!rSl_rH5 zcM^D_KV>K=TVr@psJHNtw9u}Y){~RHdtKl7w$GFSXA1Su%rcf)G_(KS{BJlz)Z$O;bVT&wo(}~ zS%ly};OYNkaPogbJiR%Yz#H~1c<4neTmOTE%l-C88-%_Wfh|uwfLHIY!;v5H&wtyQ zj#h4S04^onkp92C=vE5M$4I3ItvH|^vO9Hv2V21;a0Sq9%f21|P%K&0sDx8(bGAi)Uy zZ}d|E_oW*Y+(J!)+$@2Ff4&|uDMj;#H=3nJl7eRKmjr=PANuedR$Hi0^sJ33xtnqL+hvQISqi%3cSS~;jV}e*e$@#8!k8N$VyRvI0wMW z3LpsAA!+M6ux@x$=}EhB4#r6fdFj=W#Q^gt4j-628=1ojefJTpmsuHzZ{mH$X7tQ} zxWwgyTHp!;ZCbSeu%se5M^zBmeg&{zo#(G0ZZIFvmqK7Z7eaQOSoH!B0;6{UYjF7k z@KX`2III^4*Y2Jdq>WesA`W9COJr58$S#7g1Q~zq__kxcV}nEGoi@CAmUjl6OOy#MEd;6(h%VO|vhr2kHziga%N0oaRv{Ct}JsSg$hFVzN#GE z_@KCWw{C&18jm;eZ*_{&!SG>f6-Gytj`!BRECmFTJiu?@%HF~jR# zcvVl^u#H@2FcBKVkjO3ilcBbaw@qtcpiO-03Ps)KJezkow&SdVJvt*Qx208T9T3M zD5(|RI}&e`g>0-KQs`DaPPBi9?`c$}93U7Rk>p-9&`pGnB9G<)3#&wMAn>tk zG8+S7P5dI=%HUZFm`rzEhsx(W5HS-p0KwQrDXQ5ENd(52hqJL`j}p#pRZPZ7KlnGN2ShH=rGH+<%*2m$E>fE;E( zh?yw7mlt^-v>8=F#H2o2nYvWP7Fseqrb#Bh+jA`L0#jxc<~wl>c@X3E1CGSWFpW!e zBK@jBn|uNA0!J>t>z0X|eQR(q#xV{<3gnlCEf+`YwZd9`573Gt=>j~}04ufNSU7`j z)5Ag>47(#^(wo4Y&cTul5T@1+UR#slHVk6=is8(EfM9pk&g+-2)yW4F>Q_0;e}Ht} zb%$DDYQ1I)aCFp?mrfVZvu|{RneI+L3V5DRBBR;+88Uv#4ZuU09Pn?0X$_}at<&=B z?roUAHR(W{!1=tqMK+?L)kvTV*%6*+0-T0O+-Jlx75|~KRlv4nd z&_ZyPj2Ud#5&1k@em`P;9=U+vk1^l$0YSY82&7*>djjeCeoGJmB73qPdIx*Hlk$we zPr}UD@p#Yjnla!$Fy<~8h6i7~G1b6r-lS9SVYK+|ZQecvdLq7^&{Ix-n1AJv07E@+ zNB%k@G1z=DTHP0OC3x0>i9Qjtk#P%bO!H7595FZ^)18A=qz!kDF3gs7 zkU=zDf?Dt#LgN+za#g(ykNfo$l35D3nB<>Pv#8LzFUYR@{Zb30T1WLD0C@pc^&qBD z{8{bNAbgCjMr-2S4-|5$A#CHu9xE85Jc>u%#hoLYzcsa;;8H=dLL~wC8tLE2ikf_IZDHg z9_++jUTh5Xc51a)C}7#nbhGc20_dL$5(!(zj=C+`vzBq(-9qst#>Arjr*}+#2kazy zKL*`msM8r-Bv%fEH}U(0V4|dpPkBQVVL3!e9b4o))9Rp}t%oS64~b6A5x1pj1(!I} z(v01``@zXyzULlzlv*;%r#C-*w68ME=&SP9n<0Aw(FG;!IUQ3A!$Gy19X>0w6WN8@^ zw+b=rx4Dj5`W?d-CNm{+3yQ{)W*8P;K_mKuobiH?NFMN*U@bHq#w>!6okt}LYA&uv zQSSryx$6(*HY7nc3V%t2q6;vD9H zOldJxuqyj@OVYx_mffqZjzE*BZ01(FJUx=7ZT$l%E2ZF#ooVEKWNt}I&=q*@ckX@-t^Uup@j*twILwJXo)|?+x z#AkLyWoi%WSgf6C2eZR&57(2ar>%87i;7}=H>95M#ES!8Q&Uq2CpsCf^xG4%zJU#@ z5Q?m@6!0jtJ~EKBLh_VK;OmuRS*U^M?0ifv$?FID;xRwuEY*B8PI1#`eUr=A%oe$k zZiq}v^yKA-nXuz{EIc9&Ou2+hwM_sg}VsOHIi;-Qagbdxr7Dz1eU^LA0!F2)c%Z z*`?c>EZ-KVpk_0l9P)N_Kesmy80b@Q$$bT0A8PrP+T*dUBjwz**4FqfC6orOkh~FR zHj1YKf{X7TpK4GUY(Q~h0CoS1s(P4B>%jA(TO>00hOq44KdJK~@S)I9w?tA9 z3Bn}W0%`XaORo}WKX&+i&OH-$8N3foX%74%42->o7%i0cx}o@OUmDNqs7NC0@v<7u zXRgP$;PjGCae?S`@pRZupGuoVy!U@THGc@L$PX!~y_{EpCgD=WzJh;_s_u$ROr%W0 z=4`X}!O$B^Fl|~MB{m)InQ^Jl;8&~S2(m_Tby(OYPcpB~FgL7CnA3%uH|UWz_@2rD9!|ifjBgnx^OeL}bQ~{>AK*~_0Gd~- zU+Xe{slsucGACiS=m+kN<;aM6UJsZbia+`MW;y7rCjvQfg6tE&%UGyGSAB6@GStG(Sn&`b2SIfD*!&Fm{tq#OGA zoqS!J^io+KqS3Rrjl48r$>8 zKvEO5K(fV^)#uzCO%_*NrpP$1Z|uGl*NfZAxiMRxXqV=;K!dB}zla%U+Eeo+Jq3D= zxu-~qE`1oz0&N94rM17!ZMm;6{ldV_f#CshdmV`LBa{iFU43Uqkx|6dHZ6mgD-&dV zs6{q_Sop&O4AU*>0U~#tp#jprFAzwCf_k|~o^7;EhN0(;m0Fj*y?_f!hU>IMU19$2 zSh<-}x|?5F*e;C0*Z!EUViZgKHjN6o$_9m5f+s?w>J{%jfy(eo?hI!i@ufTSXan~v z8V7(!K=K-yg%{{bC@^n5y4vd*vf}R8Agt3>es7k%Zty+hYrh4eDHX3f?fy`8pj2Rj#4vF8m-<#`whhlN2=G&qR^1M`aB%DMIEb&@xm+7Y{(E-kiI#FQKRPm(v^kY(8vG* z#|5!Jr|-l(#}-HVIs{s=wPOWiVG2(L=T=uov}3G6Bm?CkIqAV7abu)p z*LqQX26Z*3s2)b+i&1^v-B6+SIv$_55p<$=7i{a8&=jfhE|XKrDWBd_e2GT7ZYLLf zR>-ZSKDyRyJL=@?R5=?sA!(gw`s>x`aak-V#6OK*YjOU*jNdTLic|AwRPrs8;emuz zjlp_c$u6e2)$T*Om*`i1SG;u-Ohu`CL;Iu2E(5l+75I7nW}@Lx#;#}Eh#W6l4YIop zM7eqKlo(U?jn08FgEUh+0@`WzG}mUSpo5ggG-~H%fbBh|saGhx>5-Jfxi%-5_GsBH z)S3E3x)5Xhp2IgLjX>W`mf0bM^6+Yt58643AKF`25#P7Y=YLItX zsUnS*&MpIr6n>d_a7zVNWLH^ta3A3*waHr|?7KNLQ8KG!=cuqND=Y9%wc@!9uSb^s z`)c!jM24yxBKT4q!_{ep{)`PUN{MWd*z--HM9?2TZA`x;#k|odtq^5{l8!b$bO~*m zU_xo)B}HQ%y7};~0@FHCI?mxq(?v^btcD6xV-&%chE?=gv{_8T?`e&8TPnMAzP9fO z(g}K`sTntVCnsgnQ7km}UL2Osc;{;jQ%K zR4EC+MeLLxyYt>ty`)o0mfM~s4 zHYbrncgL>XxeyzMZcvfqERDGlH;Ph37B*#N2QB5RjG12UP2X^9tg=@V!X#EmL|Z`b zmAn|!?=xg>%!%xLs6^BB!oDm((=WImiT}c-=`Hi~b=qsudKsd^k#KaEHzhbYIxqNR zrGz|FGdi(c3>IOXX8iHNW+|d>`cq(BkK@Nc7#)%_o2GQ&A>Z%UZ;3al-W3KrON+@Z zc-prS-Cp_D+dqJJ2Rz*kG*#b0=@BBzDgE-}rF!2G?=FMpNJjn_@wBX){aDACaZ67{ zu)S23uvP%Xtt}?~v(AmD!}Ow$KX=$OdeV-yL>4Jig;=sJ zD`KlC`B1aGGW)6;8cCa3j<2Gz%9!k7#Hmy;rfiTrMpN%TG{%psm-&^Mp1Am%+DOJ? z#Di%)YR8mIkUow?69p`jTsDgR6*(g`N!4I^Uukf=+j}aSfv$iZu;aJ->(jHReYiMi znW-n+q_Q{*Pf&>7tDz}4%Nq-#$6H{cX|>tY(`^ZAcFiIoJrEfX;|WhW|Bp)BYiI6E*d4@c;8EwONxbv8N=os9A+(+VT0>W zUT@vrI`HSLW$JXA#^UjUgy+mN`g)SGB%#U?)AUQUN1*N>dp^1KTb@6%#=9!+|6uN| zqq^L_wqLrtRT=~YrMpu=Nl6u?3_uW&?v{{JL69!#Qb7f25G14#M7p~>&%FKZ-*e6z z<2>g)?|a5L|Li@+9&Fu7o8)&lM0mo_3QNs+G$qY1_2q=EG1Lk1UuTxaH_7GbCdW1WE2){_sgJ#yKWUd?1nmzej!zcLoLQz6EeeC;}c0I{vc7MvpCVhL8aP@g>Ocfwz2 zeAtyQ;LNy(JJ}TE)u2JkqbJ}X{`bSpz7^Ll9EOA<*|t-5RVqbWVc_x7HEWA(687di z-rRfctIedApyg|MyZqCBjjR@dYaP|DZ^CrL6x}&v&Q2B2WP0?8^^?cD_=gJ(*`#MT z684o-8g$i94yJrNrPgyEaICOIOPXVk+M}uA{dBnpIXQ<*Q;$B6*N+B@{TQRwGl}Lq zf_|aOi0W(5n+%&{(n zcjzrG5=DRhsJW?amy2ucE3{^$a#y~vb7W?C1-V9qSrkF&Y+JHvP3@mwu zAb!!)DchyX)dOu(w+JwpvI9f!ou4XqhH&=z+xt^XHB!ld5|8=eJD2HWf1;_@E00sn z;v=xHFdJ1$WLoFy^-rLtr!p>jgiFH`u(uB?Ls4p5!tWu8`*EgpS7RqC#2fliQa19T2(>hI6(_5Ns=@TzT=cK>iKt;veAF3i)G_faW!9OrHA--aZGfqPPGk!x(C5T)U z!!m-LBiw$UxH-PT$^=F9#a8%p_v5j)uX{rxXBm{w4(TUv{MI^bLK=jTdC%^l%72q} zQmx8d@|`K zObmJKbb5rf{0-$Y%QUYF7^}^>gXdR zZY}!rcX^a^VNCZicuOAYDIj9;Vg+L@nx*X;=_%~V;F&50wW~O zSwD5m(aY9aI;L($(OM;GHY<*A$*WfPqq13MEOTk(K|8N{0d1qy?=DNHYES8?y0DXp zpDO0K4m$az+zrvNl4FAJU-vN80yozwBKQ&{+&yqj)#gbz1pUK^)UeOq)h`{a)r&NY zO<+x)aq`Xtb>lyXcv73)jhnF2!*g1Z-@hMHhtt=*!8_$2My~c^^P_^mjR?ZCzHhQ4 zl#dlH4XVp}&)$3XVm`Qsrz2L~iZesUW72$^DGTj^Q@EfR_sfGqH!l442oGiY$pg}G zpHS)>)9*s!Ga5BvBD;7YiftS0{ypE0fQi+Igp}~3ck&(COms<#DPF={c`{G`NP!1k z*k)a4kw1HJAC%#k(1{8tOa{_PpA$E`UM(<{lsjs?eOT4EE@ zL;pj3C(@Mqu*)w|-AFUBZefkfUVwVM*A9SZ^o-1za-XC1*>{JD=vVC%g6zXYMyn)- zUk{^ZiZ=8f!99+sa2pj#?v%qLA;q%ed!gVe8b^%6uA?{EuV*GY<`|zG2mA^rg2g%|D|MA+96+EN=QkhHD(*6UAPx#r zbnBOu&wPOwM4{xwv->ot%9;4hB_dP201ior_=}|2ijW*;e8|)Itx*u$alo-#iXESg zkC2(2X_lW^(~Mq;#zfJmUqT4JKRqq;e~_NTI`CB7s*nHWH|LEt*&h4fld%7( zE$KfShyG^UjPc6TanSL`+Rk2*qha^J@U2Q2$P* zX7pdJ!z=$kOjF4`$nq_bq{9LLXfK2K%NpA6*3bsdWNfDVzYbYv{aHpRNYnwpqeu$P z@AuxTAah2BOUsadiOi7-KqpZkAB;y|D&uqdHSvEmIu%cxL_sqIVsEqwFAJ*F!Wh5^ z+JLtSX+^c@#GO=-T(&ugA*-pFj)3J!KrB`0or@(qQfdTf%E=#5e$5CeFJbvsZm&RUHgu%XAzd7HtnijG+Nq z;ARq&pS$~=P z&SRwWI0%3MzDKtpx`Klt5ezP#PIrbD62Bpwyofus&`-_)U`r7~g#ncpS8F=S(H&WC zqpyrdJ?$a5YG40Z&>vU{cw8=*HNAqVZN-yr`mG~BSzqYApCrRTEYxptQ=zevJZJ9>O#Gll&hkvMTGfc~TGEvgEQaFg(R2l|7&%J)a9CrXl%JO#@5Fr8V8z1Xf82o4F zc$W%bgdibKO`HN7O+I+awP2WZJE{g~4A#SCW}GDm`>ilH)#H{iWU-z> zZxC4uQ6=|*a^->0Ywj>WKB*Rr-t-_Y^YsP@rSuRP@>V27R~A6ZXaO=Rr{vJ8mww@a z&`XB59RLo5jEeH1;g%0`a>E)Z+s(d-;7UQ#bHuBEnXml3>Q1*i_=!;F+d!Y0H(X_j zBpB|2b*J6y6^tZdV18o|8ZB{vQ&L%gT*G^N8xtl-f_3}H>q7REvV$;VigyH6ekr1p zK%(hDE0GIx&a3!ju&>6sb0SWH1rYP=Bj!b0D50o;>s7^i>j`-Cvl%3JdlVk*Oh5|3 zy8~Gm9S(pHU^4T^XY0ZCl?t~9UwI7IKwuRKOtWI3BO;oMZS68sImFC~45!2vfEQx` z^U3{0?)gzb&it7G<3<*|g6R-kYR5wYMhvx7J@|xOD)*6Le%8%MWS@La4WKlSc?cu?Vt7u&mtm*yLV|-^x2)hs?R^TL#uvmITS+vrCkEQYls(2xXyA-I zx(Bc#*`XI7rC|#8ICl>|!uwm1(C)np?5Ozdcuu|cDi8}eLl>-29^Oj@W*r4vDnRT%(H`S z>(w@&*P%TZto_&V@Q2-paef7~POQO@+5+dLdT)ZG${5ru#IA>Tb=eJ6Wt>`l6M=w|v}vfex;qj{49Km^%Ph zoFT$ZR6T%si$08p72SqlZyD3)*hD-R9YQdZ{sQmC@^b=wj^k6%qA0arK+C-t!9Z1{ zrq)e@Ot3uFb6xDOP*GauZ1+t^CBpO(jB&*|XG5GIDcF)0>8#Z-#QOEeu5N8RABNr8 zsB@Q0`BWBoU;>cY>L+zvrVq-0C+5&$ zN1S}a_p66LV!}@G##T=qp5J#O1_VKR-n#DsCC309oP(gWd3e7He&JW2vr#{reE1o; z`(f`M;+}2TpUbd>(e#03&Pxh;)ph{!{P72B%RW zqM%F?XsBKSFz$zbcaRdTtvMXZ)MultfhnCy3~L8J8<0!tfbG%{Q`V{RaIOFtS_Mq1 ztnf!$t&ZT}$j{M8vG!H2HEZAe!4jPZAKOT)R~)9a;_oU;x5pa?mrfPe1;?!BeJ!tFxakv!3F;d(~&I@_JpdcKppnIQwg11H9^V} zIb6ZWdOb(VG8Y+{$6~w7UCFT3Vt~4Z;uCT9N*lrVE9c9XXXKJ7Gm2jk6Gk=Ey%`iT)9HP!TJ!We=&%P~GztP#Tk>#;Oq@ppP9vP;<6l;CU1c>+qQ z4pFzWy`e#fZ)0=+E|~!6!wPVTN!%C^8^JU6Q7}PFy#wfK_Q{i^NBXon6x&TO8{~s| z?XU@} zA3&_2lG*^ef>p;os$D1via~)k(#qhy+pFTPiKM5!D4qw1t09!KF&i)_uej~4Td4oD zmgxS4p>~plYst(G2kkC2~3O3iSwrf-}l2SgkZ*={VpTY9~#n}A{1X_%ed=%QLSMdTvXOlqnMndu+BO$K%}AVqxJg-!Z`1%o3JDahid_LCBkIA8 z$pF^F1=>oD(5wC_^gxX+VJnWTn`nwEC2{PHLx9;C1aAW65%+?Yuu#VCKA|?`2Ogf6ITLyq}fb_xyC* z6PAy$%b#yfOXflPBO!yYo^aA#HDCq)kyuX0ztr&Qo9qqgGQ-sJmiG&Q& zt}=~LkD$s}ZOZ`jHGL_Bf@XOW1)&d*p(XGv*+OPEJ~5Ld>(&)E85A?sT+}~WxdfL5 ze^8w=3Zf6UEtjX$qKum0*Sqyp+TET(pHdCH;iO3{pKy7x*rj-Mad)f77%>%iFFyTH z5rI7oqh=dSv>>4rsvwpdqk!P-lCFI^#2x>%Gei_aRMN=wCW=4yQijy#mCs{B_kRC? zk-`~oz-D^C|3)BHf%6elbHe^439ICsT+$PmI`v6|8DFr~IUSQ1XNYZ4K%)cgzMW1? z;A9otrAURh>U5kcH~j`NM!t-$D0AVt)vs8Ty}X%fwr5_0M!O_zwtSFs5MZOjZLI1BVN0VLVHs&B3&>pBAHhtqxG*gA?}A_>is! z)9I3@f+YsLi?6&w8nf<#RPyWq%9pj;$Pf8v80pz7o{Z{mpy)M3Jc_Xf5st+J@@CkE zmc3RAy|_7P=_U3A;bp1Rd_0Rzd?pz97#97kcoRarcl%nM>5@$SWiQrCXx-4}dgTS@ zQH_g@go z$;jgt<{;@8{3r0*Q}&f;)sFd_FjFuMh|JSuM<0>m#V%P`9KT*Z$A6@B5^qp{SjJ6Wu)5me zH1dn<07`>2Z-UQ5mvOEhiw2=Z9{CLhVCf$_&b2?1S)A&z3~0MVjQL60{zhLT#0*ZO zzlSo<bFuwZLO=HEBF;4khKTTcP8- z-ji@{foN-s8c)!DaKvptbtiG1&uirM`)or|^&2lBpT<1ocG`lfsB8Grg1_Xu#gr=t zDWvM4ep#KE@|`ChJBQV>Ti}IqoxXt><0Yh~zL>U@rsjQ+e%;h20Mx_%D?3oyf8!31 z8;=}ZXySXob?!m%?$L|Q(rB~z-&eGfV>DOaehqZLwLxR^Tk~TxWq-4Um?dKIow01 zBSs_C=aEvf;!~doIp^~0x9@!WaY>vpuwBoRs8&1mMxcdpw^O|Ab%DW&))NwYpzN~G zGgPH^O3J49)EhzYu?XpA3*0bQpPGu3+T2Ciw%)%~Q#eB*Fx?5{=BEtA<#zA(ED~E5 zcbJkbzt`)cF@3dEQ)<%fWJFn-(Rw>(AhU-ZP|<%*V`-d(cge*H1p_}pmZ?mE(b2E; zd=(@6ed$5AP0Ikh#Rkz=B3N#4B+k1G3)ydQiKmGNkVp?-bHILlFI7Ljmpz>W>yEGe z^F+Q1;l3u?`jGv|z{%lE~v3`?K7D)aOjK5yuI*pe;!D06K&GqThZxS4dQ9tb;1P0P2zajlYJLCmM+^B6V zY<1CaYjzFMDeG396oy$P2|kbK35w^!cH`H5o0fW=7==1?Aw2u&{si&j$x#7u_7F&t z9dIy1C87^E{L^Hp~uu=9LU>M1O(s_FLaI=Y6xjpcmh%t|fi3HP$f(bet?bJ?pcZKYy zhwAf%Nvh|r_SDWTd5L1J?C^DwC^OXa4V@VsR42@dv<^Y{fcv895)Dzu;-@O9USc@< zS-Tp~amq`kMoUf#x`+eB(s?FRQ2aYlYmYyXJ4u<*_c-dPbsLY*Q?JnwIn|?$D%X|u ztU8JkZ{+lBj4zJYd@KJTffJF@yWuQqKj~Lahj#n0smRyyy!C`$P~1f01oy!?&Jzoi z+(#I}D5A;-uDeW{T^Aa?2mJ>F^>QXiT5}xSaN}1yPC8a^}wNiW`!la3kX)c2JvllY33x z$std&={!&ZxdI&+3qB@vZ5_!8ObJVE6BL44Y?_TMnK725>=xCAxTG&TkLwj^daZWx zTX-*dl95&Wb6oR46UllzdDT#Sx*d8Y8?SkEc%7rw0KDjI)PZ4y7kUsBttuypE zfcoBGo_5n!bK*=4IoZahejR$JE9t4uPOJxBm@K>4_ox&<%Qdqq_Ot``iZ-y!^yX_k zVIKe88+J>Ji@NJpD*$IzXKJ7|c|74)pYHD$PJK7i8{sikVm0PUGg$OSq|Zmnu3UB> zd?d@za(j%?ZIL=2lOOwVx%<4Hrgz4W=ca6LzvDf)kk5PnJo@e>P^I5PYSpcC1Q+#E zDC4gBvK+^WovV=ubs8VG7zL}?deDke&h8y)b(L>NiOJU~c3a8tEj&}q>fZ|weIlNi ze0Kvy`vOS=*~uq=|J#d8BJ-VTDXu4RJdyzw+l|Ac?%M-SR1dgbcj0Z^Pfs z0uW+tP1|n2F|=@_?R;#sO2i_wY0AK^H7SQ>nCRxiD+j}KhL+Zvf9AH@s5{8z$1d%9u~PmAJ#KW^}sW4F~0t+O=k>r2KM zsk|xorv^f{ne0YhTw)W>NfGkv4j#95ohVR#NM4U%LGyf+Yx#-vfZ7YQ?@Q+~ z-^3RS@ubAhom;C7waJNQ$wQwPTyr$vr%bq5+TlG|a%E2^ zoIH%IO>f{EbK%|apSWBr;k>VuPS}ru&vyH98OCm@>oR=;CN+{mU8wKvV4Q1`u}$#4*#HJu`t$6|{YAv~bn zud(wIV&1f%{8EvA ze#d0X3(oRlA0C`#6J_ck9#-0!3e_v~n#Mz4v6+97%1^(=)i38@B+1^Y??fhlJzwMGS6-u>fMZ+8DOfjPpdEE+2>C zP=E1Dh4nYjH3I9mxO2jEqf?rsCbPuw23LR4H1696K6`s z4z8&|{ocHf{8NON+X=jF@YqLC`_Cd9)+bR*CnoW~*}RcGe*5K^k4~_nloEr@o8XZk z-LDE_lJOrlwLL`cL3CesAdA+6P&)2tw>(Wb?LmwS7Zg@Dfmt)&>XVuF~QK-nu13$~~vMV?! zGcW%xC0pb%}YrJ6nbPDI$Rat9xt2}*oeb@3 zE_H|e5d=+t=cC&qdC5W3k!LdSDYZMj+5LGD90@*=K0!zL_~MqgF;IBD@rAT#dR`bB z?O>HOp%HDrz(nKVUkr3(YhoHBKtT&8+hIILTSoQ9*bAuLXd@0GAv>Xr3B5?O|KX>4 zGxW`FRR!@Ua+_QW{6C{?L>LL^^%z)x4&A0mSD%>0M{W#viL8a1c@n(2l0Y^KJ@&=w zET0&n)kU*R3Y&3s%=@b_LLw)>+~3TUaM~1tvD6Stz^136=FmvkNwL|-)sw5qfb>gg zxbSWID~iP*jO+nS>gy?eLG2vIDE{Xa4hc1@&5z<^4C)v7&|IRx%L(Jn3jBSWm&Xm? z**L_SX!FTB#y7WwAEu*=@mos4i(LoMb9}TOa5o|MSV>}Wh8H(c(*0wn|K`%>!xl7h zQClnwa@vGTOYywoN(Vd%G>JL-+Dq&S-(GS1G>sApBtx>&?Rgucr-^SfyUfT4bReFQ zdpjVW*t|s8MD{Yb={|NHS%M(b#*dIAvZg#gr(SG(;$2(c$Ifm{%=2i~9Hfp)x({~q z8ma@tUvzV{(6r~Csi~PBht?7@-0U=Q3oEwzrGgVCovHLW8vouM(Oxz#H!Fs)Z>Gxf zNff4odq7wKr-QO|MJ3_dZ1R8EK73rlG@Arw#gk?MadKGX5 z4xO=v0H&tY5X4ZGTtymY3Ho)HY$`fq&YaB!j@#2B!-Vc|DS60_s+~+ii!Q+c^SjS1 zgWp+^8AWw{FFnM~M{GirPFn#??54hg5)612Pt?mY(QO=9UgNd<5r3{Etq5zDG8NBa zN%~H~`z^qoTul=?@XWVfm2P%Sq4hp-+U1^YyPu~;CgWd8nwe%Mhc)k}B@R-$6~1sY zwe&2Z20@~E(S%%5PU=^0O)n#!%yPvL8_oZ7Scl2_I6m|rIOP`J|6?%Y|LU;+{}Tnl z|Bt>YYj~8RYwBozxVIsw%)@A}4t)0c!<^!|FisbDGB&zAFsH-a$`kQ5z3;sWHQjHP z4-p2nK1$vvLtnNJsx@|WoA?Q9_Oldm$?~(q-W2R!>np6jv{QiR&Xs3SSX0nG%T50A zwDXUsrV#zu@OkZCR&l|@uGez!^%i!-t+#w%&TTZ~k{7;d(kvjjCg3Ld<Y2x z@;bXi`#{d5+#)>e!u^svCO6J*omE`$TzZMiLRKKy)}N=A&z~=U7cW2acX&aAI#wZe z_p}} z?DgaPzi@{med*g%F&3&W=HWwcYjt=LSmx~KI zKlgsTb(

Kg8E}({0>)r8aS|`}&E*GP6;8EA{PKEuj~`Von&ud=&z#nq1U)m>-fa&!;4D3U3)zF6f4{ zUR&J|I8UA0s-BaGFMJcPF*0#q|2%D)EM-;VljdH+n1X(zT>L%bfefaQfuDRLgC+jo z2GUysbz5{V>3Ok8ck0>F9;&}pN7!xl)s3Vmn>$bUi*cV& zsa&7lJZiE&e|xm;`dU$SU}rAP)ycRZJ6|^rO>dxFxLtTm z+WWNdk;!jggD#WbuTqY0lS4$fCxB_iS#|QobIBy;*C$*Ju)F9tITRJkBg+_TfFwG8@T@&XrBo!<+JPwf8;rj%M1e>rYXa zw<9~~cS4>{9L^K!?KWlJNi8?-F9;9hEYPI(F5n*fO0jfWw}1BjLF(?u_6e>!W9;`b zoW)2Ey7O_n6(uEGX{ybT@|t;t5(-f)@fEK;yocPTz6_ttBgB2zykgs@@{Kd^9OPW0 zYL{?4Q;VN4+uogd!LYV5h;KlBL1jz-XnbXL@Duk?Ch^Kd4-fj>?A*m#6-seLLbiIU zzPh)!b@oidfRb+OL&g~{n(2NcOr;^+WA>87#VD$kzmpIpC!QTlR-=hiXr(*Uh`?oi?fyoOPlhmHXw z&d=jxvFFYgCS?j*iKPb0l$wmXl*I>M=<4+U($K-~rYm1DUHMS_T0g(`{lJT+E4pRZ zM{0kZUx_Mfh~br!AIvr)Kao%?d%I>t_3Qaz;feV7mAoRWk0TRx_YYD!_7-_)h*K%3 zT$y8C$tz6jpM_w#%U%R)`mrlD<7yn6m09o?%yi&xJu2B2T&J=rhb##aIM79ijSPUVsTNZW70!Yrc>=^z`OmW#Sf6}o&NFV!nk6^RjR=gx1 zCt;lC+w-Et!Dnx)4(F8i-21!NW=I~_J~(rZ-8eX!zIZRTdz7)fZ4f{AsdD47P|h5U zUX}_WX+jO(qdkf}i&loKzK-IZ+SlIX#LjFITFMmHc+)dWzhjd>oJ^dkI?LhD?Yh3O zDpu2~l;O7{pntM4R>wYJ)>!`e;GVPdtX_pC!W=(6`Gj(Wm8xDBv~%Lw!X?x zwt2(6N-JyDw?>2W6)QaYq}`FX-t3RmM{TD#mV~NS?<9R2Yu}Li2uI0xCJnnw!P`7; zHFZ1;?@ZmQX8ZZ2h3cLi=}C#tYR6SMTJQZ}kIr-TbsH*tPNimyA;G4=SDQ>?GS8|0 z-s|FuD-HJiFrwy}t|nFqNu<*a2*vm;)|3!uF>5EfSgXG#lr}_UEIM$0g)1hl=I~wn zYw?$y$?wY&w?`->jB#*rt}aKGXgX28K~IRCl(S7dWxW1KcjHOP?qjug+7zJ!ZFdU$ zDsL6;M^_kdc^2tX?&$3%)^Te(X-9Slzddbmztk9om0{$)7BG1k=MCX4k+SHs+B4Ua z4Y@j^j~6#-hB3#eLa*U&A8LIL(fz_+)utSnf=YNw8sj_4VsLC^t@iTbsf1sis-p-7 zz1%`irNYO6HLI)AG&1<)j=`c;9aGus4IO$9T#iaytiqLfsKM z^!|#!KCF80n{P1(@f>z+Jck7@CBBj|hQQy4M8*IQscTov_;{eU|KxOl2%ZyJ2t~~4 zyY0f<+fp}tn|^EK_mkjx;~J5$eW4vQ_bt^cf7`*yi@~Ae7JL*clFD%$)Zx10O~5-B zx_rinV|7o{w)l?ot@rJ_JS>fWemr71J64-~z$U~{u(W-arf~6x&+k$@zJ-Yv<~SGQ zIrE4MtqC+X;Nygl|^KYmNL%UO#RFzEW~b+G@8 zfSs|9jbtSA6RrP1O^$pm-ZnSkfidynX?=4`*pZ&o*3C$fS!4DT`ir|LRgebt{<%Y-7L5@R48-!E%k9jw(csIfZS0rZELkZ6d^-LmTOFUED z@X%LQP9JC|6&jP1_rQfBqE^i|w zD%Ifus{bbDiFGblIHUTuV1aJe9c~^Q0fVKoZ9LzAyqh|BgJ+Bs<@w_`z0a11to6F} zmOGz3(T%Q>svfCGDjKe6TyP!!!eUc!)Ushf@2)hi^NK@`nUw8ZnnnzdWOY`3YUxXH zooY6-o#i(VLNAJd$naUhgcpq6Im9=4che>&u6A-9$5WNd8%LQgkF%Pzcop1hxY(mk zxc1J6H?^~LH29`Yx>4=Z%5cum&q0=&sf|O8Jk1f~aZp$SP z8jaMN&If3>lOu1_3I)m8zH!ZTgq}Wo0vcB4a<#w--4@fZr$?!W?e^1sbc(`>l&4C< zDLApF8^BJ*d4m7VA*iXDlW%pxA%*S!h|PnIles>g@9ZdrBcZhd9!HiZU)aN?n1sg+ zGX%4!%=Q>0E|>XkY#USK)+~h`tki9M{G+we;X(G1;vD-212e%p?H3->gZc6TngOMru!~PFmWZgw464H z$OfWw%-+3VDt;*&wtR>OT9A^ygmC7Oftor}__TZdT?1DPn>(VbuW#7&Oi9YzKYiz= zMv{T$vSQB|nSUae9z5+Af9#G%l*TY{QNQASU(fdHlqvhu{SHafld(G&yK^}-Z>P;M?S}+9W9;O2FSoi-k*ke-y=ksBJ?^r1n^H=OSo+{$IncH8FPsr807 zzTFJx2o{O-#QX5ln1>_Htu1x@L%p&@w8HT2E?S$c|D9iJCl^X`j@a#E-!yBk^i|=+ z!XmdPF_`Dnj9PLtAw*UJ(R2`6=FB;Z;o6HY6kSzHbaAc7vC!@81npF*sC#)6ZQ4{e)KblOkg$8 zjPtrhXf3JI=_+VA7ve20AG9u3q3l9#`bj#wjf78i8_SC^W;|~$>mlV4TuRIzqf7W! z=c1t%xB1;f9}pmxVR810vya7os2OjpzxvAHbbbB$M(nTX&u>4<^ZD^$U|7*Lv|l0N z&v;lbi0kg>N92prKhU0%_qIuw02YR;@=oOyV}SfU_`N@}>f`^|olge{Z`y^$6F!$FFVhZ0nT*4>|mxDL?6dfM_JqJ^x`s z$ju=BM|}KW%a-_GZeh@KU}l3pa2XUed?|#;10j(Qr3TwUI)I3lVFfNT%m z=BddB^bj507Z&&;_{!!3x){Jt6oP~(577(*lSu9ivWC@^h*Vg@WmOAeP!Q>>zNYk1_9-YQ`Gg>!0n~Zd^gu%UTSyfg>`1)9>3E5; zgpkvsmKav;#knU?zxiPhl%;Y&56o?ZErqaG+ClOG7@7A-Aj|1KAjbTyflkm!+v+@ziI?RyU?SQC4 z`u-v0ms$hDE|UYJ34~3I7Za6^%EjP_gVCx60_DG|vgvz8=7#}%ogSjPJORwq0O*PJ zKrt+@@AJ>Yp!vsl0OH5LQoHQ~5Cji~k+TmV!Q$bv5HbGn09}H0cnuP0Ulahg^%Mf< zfiR)9TAkqTi`}{wVC$9z+_nBZ2dX zjR5ekCCG^B8xzV!llB=16%lvFJ`rYUctDx?cf1O>E%V9+@UqVhLmrL|CMzg*xfhxg z|IMjWRxg9}5`=%Q2Oz)!fT!sJ-a>IPF5ak|jK88jzLi!YVC8C=v*IIN#Usr8y%penk!~?W)#fMI#sMp8Mv8Q;0OC0if*~icrlvJtgXhDZBL44-Kr$VS zb6dhZ2q!@=m|r!hwOA`700}$!(T5ar@M{E_d?rr9)n_a+s=R~5);3N})6oHg4p)`+ z`ahpvC;5ttED75Ih&?Dnc0u4;Nd3S~j*{nrWbEC4zDsitkxG%yzhh}W1iG31JL1OO zKI4B^a1H%+%!eTF-X?nkCMGPeHIN_a!UwMl3ec;OfLJ-KnTMIO0fHRTW&+c}a~Us? zbd&9hX&|lVLGY(eZ;4c2ItMm|&WCsKe-{9CDR}Nd?J}o0u4`IoF@{b{9!%Cl@ht+8 zXX9Xl{SZ>@ml4Jy#2If>wE&GZWUdwh0b)<);5pn{5S>Dd4q#f-J3BczIP`>q1Q*GD>q^ukV3|o98vX_j zefRUp2MGc~03i7sr*zi9_Xd#yZ@-gte~xg}DuAPM@&%j%H>)1_LZY)(bTPu5q38$g zvFbgQehlXwAkP)R^XL!*MxHJJ?3{HWHA9|W(!2)kquqqGUX_y-!pPi9=YRU^Is)@6 zZP$a~U|s0d55nUqG3U~h+KYwQhsO~^dS3GxA@p}Zpcd|Q3Ko*guTuxuky;UvAjZj8 zX7EPBL2k#QovuX#5+s}~f{u{`Z}CKnL=Yi>L68%q6z#=n&J}`r!y3SH)?oJF9|D5X z@VPgP5Hg416IB36_T4dXdr6(`_S5&@tM#h9SL0D}3&LlzCjh>~jF)weXz|Vo+p%I9 zIk2^6eINkn$TIzU!q7DOBfMMjUAIB6bTtJ5CQ{_Vb{-mlsx+uevE>afeV;OT2Z|A0 zdfgsGvCTHH-lHHXN<=VV=E89juFv?o9Tot)uLa2uG1zxuFPZbV4SFDCWPCJRSEUE!+}M@gfh2-Odmh{VF3}?gCIUJ?pgveN`ZcHBH+i0i%gl*hQ(00^oz>=v?SqDjgZ++ z}g+B$B9M1&&cgx(6xIxopPR{1H(xkUA6%XD)wv1?3V5ufB<*;TG;6Ptv^EZejGH(P$=o;WQ{&6(U?65wspqdDNMp zjU+uG&Yfj&+mi)rPs{{UKZu$5pb+qR81lQ_ftura zr#bKhlc<#~zZjH~q+oKxd>x{N^9czqh||IuA*oLD-oMM&ZExZqT(m3VwalETsNCRdVn!^H3=hT56E zNdldjD^9y1ay*GNjzzSkfF@ZrMfUD5(|D?O+GD%2bf4bGsTA%B8O)k6sG|(-$p-t= zM|?o!^F=RlgE^4GdKZ<0(<7=r3XcMvLr%jVEoi^_Ob`yT_^oKVO#>kK!Y9%fk03JBK{2ya&4a2u2s2IaSy4;->bsW{C89}r;koePca$?Tssaa-UD@mWCm38~vfNED0q_o3m#Ub z0wTJKAo}zkVtd%22?RRO2VdnsJU|Ua((4e4aUq19=);aT_XW1L+f|l<2-FMo@?XFW z_Xw$IAM7Al1w#aRI4}+xw2GZ~P%u7)ot1k5QiGb1JhWo4y4{245+^1F1;Q^7ig^9P zhdW8azpO(pvS5RoH@74DME~=hz>|lR)v#(lj$nKG6h$<|vd5A>2Tz{eMGEM^j!a5V zgIS0T#agV(@4AToX{>HC(jMaS*EYs%DX95kjP9Gjl?#6x6*=pHo^KJq=SLo5B~Q_- z6wX4P`!>{`E^8`uu+Ga&JLzP6jM|ZOK?G0P#*MXZm1rXNQ=!Z@>f|I5UCq=3n|(C_ zjq8AyLEhW!01YPcCrmUznnxV4RbaX3aj)0F=p_Gen-8q3@e^fFLnnx7MHww{+y^D} zhWY$=P65UCzRm;fu_jo)DEpNY0-3y5LrvMymzL@H670+fOrM}j#3D(-+2!^ot8Wv( zF`#Iy@tXVrxA4FWS7~)7EUvj}bhJ9sPIZ8WzxMI3MXLXJW2BR?6=Iomc7_{P_WK1d zh9j0F&X(4};~Y|gRQew|lO$1+ZfFO*D_A)!hY7?_?S>i@IY}f=^U{OK)pcWubJwPb zbDb$E%@xvcs1j-xRZQ%&U#Py?kKA1FTfau!@VQc>RrMVLCfkFJt00f0bJ*{7*l7bc zYS#cs)YKQ0V4SF_+WN}8E4Ddb0KUqX1gMo{#X`JTAa$xrrxVC&+hZREy`%NwG5wf* zHfc{7i^U;35=$7h%iFd+uTn~VB|XTT31tr@7*)H)G;;_%gxWY#N74xGS?#JrE!IKP zpnWpDcavztIW~zdR`0dG_|lJvUVAf-VSP34w*0gV8P28e7q{B&WgW z(*CckQ@QycPdS=0EmFb3L)6)L*&Y*NT zlW|cVWzl6CBOMf&O*VcILDu|s~x@ml5*Z|T-N==(;%N) z(5gKry@7}GdC@Epa}Xu zt5eWJG=r*x>Is;L3xf>b2df6*CX0g1%pgqy4JR@TC>?GD73AW~8}*84^W=HtI24an zdhJonsL*I=v!*Ck3E7?Z7NJ6`MD`T!3^Aj425FMVtVkUy7T{sRgetNKW{m@v`HDa)W926(`0eggk zv1*#ZlS#kzTrYuu**u=S>}80cDLv*JQN=|MFj6QvG(5W`%ZtN7rIlk(q*iWk=c*$( z%5dZ~LDyJ<+gU7qwyUF!QEF7lY=P6Y`1psZ;;B52Z7k-I+}$4K#)_=l@YLRtTIZ6n z;)b+>{4h+1CD|Ovt(V$R_d0m$D4yIDWd<+#=rPDeCB>_;R)UawG&TGEQD3Gqgufaj+?!3M4LPT4I6SvFl{7IA{*^!{ICWb^!pMue;%T=Xt|X zH@OGuKRpLECHkpsaL(U}cbtr|5$8+NEiw9?W$|T+WVKvs@)S7Cat@*U`c%HobKSUO zHAGXQMJYWNI%z0yZhQu-r40lpw>yY|N6wZDO6k5QvY_^D=DX|bnuu*;yPi#-+=+^E z#tJ*OewpZ{E8~7MLdU&O@4rtaon<7M{1IJ`SOh<%`W#!WAh|Ji&{fzJ<;EhQF-=2P z_SOWb_CnhB37j}ranMEt;MCCv3n{Cy$ybSt3lI_OgU5IFf&KmlV4L8axk`<_?Q?c~ zSM2$0%x9!!F*=k2e|3l0$nUXP*yw)2e#PwUf1+EUs~T`8Aw7yOIv0pPQ2xb>USu&q zr#mc7W0CYiW-7*iuzS%k#ivIu;ht!qxkzIQ98&uF*GTyV1{MdkMo;AO3bAolA0Sa+ z)?nk}ONleQ?G*L5|B`3O4(jf|luYR2sg8Xe$vf=IR*uQ7(nCthY^Wvypt z7CXq!{~wb5|BHaq|K%pqe|Y4lzo7LM8t^!8PRd2s{Y;g* z4@VvQD&jLoZn=snikz1h|9mMG2L{Dy3a|^{YAwBoIO^akce8cv?}Z(sN~T zx`_T9%*qwd#>BPh>*0Pm2n$IsRnq-0HXRCFr3O*37iT|p_{*k%)(4_S*M&^F^vB3_a!H5{_AfKOJWVzU?53&06`qe$OS5rhEE>xv?E{+D8C-W601YqH3-ZM z=Jy%h!4UGBz?an_29X94CCvY7M#xVmf`@4MZy6|uS)~7h1K=+8sT(1db6j4Z zG~!_5{oV(~6nE7pxIQ5dBdieb2eg3(p)MPMPK_>b=$ro&3IxLYNCe7qGN9RSR=^<{ z@3{C6BSB@93Ah(zS8uVh0Ytsx4uUtDfD`n3xB^_WA1s;BmHx=_p*j4}e{ZKO%K1aL!I~W_M_rH?G`(Ik>*tb!wCTeTKLqoN| zQAl_=0`{-F2on+7A)OAF;h(XNP$E$2`mk#xa}2F$LK<#>I6KKW>}!x2isuXwFDi94 zKr-M2>(w%BN1UIL*Cam#B#O!>HC}^yPNd}C%)(?gm$E=`&deS0cPD86Ko{dW;cbAh z7?Au*U>cA<%!Hoe$d8)Zv;99DDKoLqhe48`RT7~i_;S}15C{WMT@C;uyOdrRE^i!Y z%Q~7;l<%uN&W{$ZrhtC0inb0C7-=;T>+}1ie|JZq;?0FOg?#A{+7377lA#QZ-5_1& z7ly{hwhO$^r3-FP`?Vq?!SFwP#*H*q;tU>ac@i!ojpDy~3hvvp%VF%PTF}?};41^g z(rzKp!9CV20Q_M9ZC%nw;&41YpO|Tl{!?c+Qm%?%nUAmcxg+tk(Dad-UjTa&g1ULm zGZ3ExXgO{?7HQa`pI3q*@qNL~$ji^us@-=Apx-=Pm(ev=I+O-Eoj=6jm(hl2^rlMo za*aLEf%pz=c4C~4o;HxVo%+B_)jq<+^R zp-9KT_t%9x)*$RWa|fU;&->f;DS|_;5QwiY8>g+lECdouYr$1x$1M%Evi9MDG$4G8 z)SW@QI@3$;TEiC7AnB`gSxY=ky{5w!{}c{U&Lvb1B@gB2r-xhH{&yTs_HFa=XYZrW zS;OY>7a{~jROap^ZNwv_%#H?jKP(T1Jvb9P+Q(r{6u4}RuWd`vKZAA^RX=V~0_+GH z^crl2ZQs#p3!kDCKK4(vm*sfT1LO@4pfYzOt&?Y?Uo)T@U_S7}y|j!bA;#ur2)&{E z+hO86EuXrjkw}I2J`f!dGq0WW83{bm=hZea{5>d}-p7>+kf9jLKG-V5%L;=z9Q6j? z;Jy_OgXmSI?0qCD_jDf+s~rb$+5K%*89!)cjCtE;iM-*pjIR&raXHDVI_g$Dwy!OH zxEN!s;v*ZI(XZOb9tsWoHmb=K4o0}0y}jUj22J)%1SxxM2KxIp!6-8v3NHvA^!t&3 zYd`w-mGlb*-1-u17q13bx~#^lW$8Syed*0NfpPw8cAzIPi~f)DzC0T1{r~ncKd2~m zD6?cpp-v?kGL|M&LR3ftm59hZm!U$UR1y+0geIYkg^(yBDP*4KdAj@E>74t$cYW9W zt#$AH{nlOUp1;nj)bo6X*ZcL_d%yP9Q<$hXr@8HcHD}vJJf!b~gl4D}ea}N%U<6=y zBGOb;b#P704;#f1*dm=egeU}Arkrq!+Xxnl55n(557_q2r+P4Oz$@)>1}6zkCkYd3 z6otLS?PJb+=A@eQXlPa?pA)fuJ$ywzzvMc%s6-HP-K-B?gnz|^tRuVmy8}C^!_7-; z=vlob^+i6%?ODrak#dnS)Gv`%(7LVk#?v!wqt^hYTwQ%ROZVy=A{&lYD7kw1$>g&< zk{RWuUIY5*rtS1DX}!KJFg;0$96YHmadz4suXgq%0|k5;ec9oH!H~R{gOXFs{#XTg zM653qe`11cjmU$U=a0I)sNJ%TIqr$~x|d@Cre5Bj&Z=}6d@&n;ryeGFI=jt${ng_~fD-F>z`)k`S$0}UHo3>I4>b}e3aio#W& zTMOKfx)sYyx5Lvx3j|s{Wi%c${OVIhnQa4&L(FT+tl8&On0*CCm0zLOrMJ^8`y|a; zkR(#U21Fwj21SW*Md=&ytSj^s^?#UJol&8>a>hRDkEQ-|~Wn7hrrikejXwr%v*4!%!EU2Mpt zmK~8+>MoI7=5Wwd|I{sB7UmA=YrK8JT`qzTv{?kliuhF}=tac%|KKn^#`r7)4le;u z16<6i;idPAPfkgDW!0Ci$pJ;4W^_ZgJmv|&JS20(3^l6c1nR$f28ZZZUd*{8a(KpC z%Uywh(5|r`nCK-e*L?P%`!?ceQy3+SWG5lVu0tBB0=_IM&*|n(zf2Ujl+y7PvY**+ zMq9{`UzW!>ZR7oU)LT8Rx##6b+sc$t{tfjs!!>-G%<8-m6GI(O4xH=Z(BZYyrU*1E zC;HOT3ToXmVYusO+AxQl5XtG`_9BKyoQ?-2W1pEs2bop^9o=Z@QNt1TNNKer8K+VBfBFlb<+m-|D5UrvG==Q-EiWew8l=KUgc_}})vk%I(2L(GfI#W18m zlFi|r&HX#pVlhMARe=l+Pa3^+z0QV2MM;gFFNICQx{jOL`O%GU5QcsR=8+?EGkha{ z83e)C2^v2iK6{>x%#03gksD7#Uo6qorog)P~a_JaKOq>i~;Jv=cS{1D!a> zNWdz$DOvifc%7KTSj*T(SZ;3VJ5H%tE#x+>B5^?aUf~PvV?&uacW!XlY;=97Z6Iy! zZS5;JwKQjsjfS5m+%h+OyS;lbs+ni%rp3;s)~5oEhOuSGu{o8}zOleJ?o~{-nig>xkt&Qy73Tw9vziUI4!0;a zKX;C))QIIt>|Zz$#PYV6tGb_ZawG7%)MdMdYgG&t5>kURJ!P{FfRFgv!`C^ z;lv_&l_&76+UoRuVO<8r^&KA z(g9VSyO@A`-=XrCuJz2?wY{2lvo8FP$|_|NUd0zI58EDn34xndOT-){FX1fffN#Z~2K0@iMja^h$bqVeFk_${x!jAz z^YzpF;g3=a3~F`~#%ZO4QQ3jh_hy;889(tggNb~B4VL3|4`&B8d&;|KtuElLS{VC?)N)u(~?&pjC=3x1rHe0jJ{@ zeB9fu@aahRE_h!*^tPo46ezJT*>o}fVEX6FhuD3C7y0mC+Bc* zYb;VlM&x?FeXBo=6s$OatxfyBKf;Q28AikLw;WoAxUge)6Uc(@IG4#?z%3uZPG@5P zSWlc|^f6JSAW14a?4e1X`=`#E>-L=r-)Q^=@Qv5LY&bVQ-F%`{VEi?lhl1URw%{es zZIM>u2J;03MvrYJO6a=9SG!?>l7j!;myHwQ_|EERNyFb*NJW=Jggbjd`Rnu**Tc0Y z#k35s`}&AR?sj`)Ai!n%$=83o;f?E-{K48awLY{zjs@M`DCBA-5U)Oby^DvN%%(ck zLf;P31a!Vt|Go6{_9%nzCidSeKW091|43{8yT4~b4D%nqhx7l)we$bp&dpS+=U7ke zKBNLj$3Oea0g@WP%A(Pf>qbVL{y{v}RHK;a&3_HLr9P@ctIAi)JKF3&?e*96zQSN- z2oeku(Uc`A`!K#IQ$#YPABkX>9;4VjhP78RJb04iokkqL{~Aookct)eC+HDk6BQ0Z znOM((4({%fr2Tybo`?yc!Di=30eBs-f(9?&x__f$Y5p!$0X@u_8Zjc(z&_qiV&n$P z&&|d(ceYvG1$P{O2AIf2&Vb(^mh*lUIQJBAZjYe>Nz;6w*gyJ@8zwCG5>aIniI@S- zLsvGYk$Z`Pu)T-w>2K)?v3awf960*Ra|Tb{wzRqSXP?9<)pU(P|Rg7O-B|6>l;9q~3DwuIEJ;D^PfDuW3nA?shu#GHx(E6)& z8$P$VL4f?GEzK!WThHo~06cqG5!z4m9loGZm(O&9AWgqitN5@Qdem}#6{35=I8Xyl zf1T|i)U9>vNtx0^Y0(Sfk)4ZQzI?f0P@e{bIg0Cn|BU?x9c0&k7k={dHMYH2$}6ivJ*PvDD|WCji>ROIL3eZtg{28;^cB z9yog%1U@y;9L*j36LxopUYHoC+~u9WU!aIiKo1D9^?NbJkb0QluQA|I7Iv(FgYc!5cY4}0L7Cw z*mG@oh8bjpqkumq*cvSZ3%&h3#8m3h0I;m9?6+c@oh0l9*O3t`bOD`rzd5m>#X+xw z2F^aEm&40;e#TzB33L70d55;r<dObuk zNw7lf!D%!xNP5nujHysOdbRLDBaeRYwAbc^LRP8LH@9G0$nSw59;=f&9_dQvoAFNT zEIasU?;}XuV#tI(n+d@jk%&mg=L4uC%A7bp?LWjGp;fcZD#25#2dLx_PA9z^Lh3bW zP$uO)4F!vhL-A2=G-*c3ueP}hT;(aiNzSURz#1|Uti>vkM2rE6nC=BiJHVx@ zh!8Xay7in;5f3^c#`!!$$W*3q&;Z6z-~{U)JrE&XKoYG=nzS!jB*5XWp=1G?h3?xp zR00n{c7vgK?a8mNo$TV8NE)LAsM{|9J5(L=l*+rTmWcsouYGaW;O~n~It0QFg9Ec( zrx{sKDB7m8+QB4ZiP(i6285IWaKZ}j*?U55^w}Y#$GjJob^f$23#7C$^dspgw<6|riymXuyA{h9V6$gP81g{`%{>E(Aky8O3-{(wm%COwkg6&H z9PA5h=N_dHuS7z%i*uvq)gRcB$g2NbTmWjsV9-UYoTLwdEg_lxyH_mdmaD3PN?9 z#1rCGeg5?H$MfR@-{n5Z;`zHVt6Dmw4hyGLV2O1s>(~QBCeb(_#;YCfiuR)F(FXJ# zA=!(d+ERhBy_dzqp6I5DhPi*D8wQ$n6Q1mSj}>s9{LDy&*9E`L(diZXIrhCGg-=|Z zyB?zT3LtlyWT%cZ7JRu67s?}LAnFzx8ckN;ub^p0>gp#MXg6oY#03h1u0snDpdIWw zhbV&3AP(Z3QHMHRp*Vq;zz|;dP=eH=vh1Xi1HNb4qZ_8tDezDR9Jjwox@aPe+YhyK&3ZrdH|@3zXqDL{w=2G zJvSFc3S*`{-r$MoD(guXj|ivyMsvVu#NvCz4Oi>C$Wtq=)-GEKMPcbt-)0{!qoJ3@f7{g0wN7MxzVOrD>fhZ^nX7GfqzEc(*=y@72vBPaW(O%a!)gWts~E?+VfPAlzo_?1n}cYdN4 zU6>oys>RD%;xts(vCvb{#AAB*bBwk8NaxzKeVMzR6H$9=72}+)K7P z?`xvZ;gXi1`VP02l(S4gq1b1xVUABTaVf#clld)V&TXT(ZBFntK3QAuc9MOvnz&^4 zoC8$nUQVuGn#2|S>me6SJ62HZ!!_=1c}&`eS+26j2Xd|270z6lsCRrUI6iL4xT*(9pk>0ZB(qI?7k9oJSW@wHzp2BOR_U`U{92HBT@CnxrsiEE^R((> zDY{5vbY3rab34CWh($7fr(Bc4f#FRxX0uIog)i2$amJt6@c_CN`>(4D2ZW0G9MnRz z#>D&lTN)9EJq1VDVa7ag&fraRB}W^dr;nKNe- z?AxvEr%{7QGU&uI)?D*;p5)INk8$aXxf+(ARG&XSNwe%jeM?nqwIyHV^Q=tge8Lv$qN%A`M)ft#Tb~HLbmAd6LRB>|;f|aXW3>G`?>9JWA)ViCut_V|uzW^2SJP z>IMF#4xG5(B62D9Uw1`SWlHDrxsB=Mjl+hatpLDg2ndZ=RLT0F61p{oOnlDJ486Rp zH0p!R4$~wKi!Bn3Mc3}q33g({aM+xmb?xMstQuW8B6^(>)LjcrPM7V*CtlNhJzVye zw<(F8j<6zRzlWMJ*D>nOH7V{bEsQP~s7~x^lE+?m$tz^*T7I?dwKvrp&ZCqARqp7}k=uBhYpXld11y>Bxnm$7fZAph&4+Ax`|lK9C^ zyQxDU(mdg@c8p5UD71BMwLTN?hPE;J-c>4L?4jf^8-B3HQpK3~*d^cicTK=7#+=?v zI91y}UL`)YJtD_BI~YKLR$*7R&rzaye_x+}{>l%*u|u8cYclerjGmXO>kL5sGUd^1 z9ahfE^8i}-z73nC(DV|3t3cyYCX_Br`*1E2dVYTVF= zyWS$AQM&z!R@osZ&{w2n{&3!R&S&{yMg*<}GY-*tZrt8iV)hY<$IKaY6DyfD{QA{| zITHkU;$3Pu+a6&q{#C5gXg(P|J9WKo)=91t5~1Th!PM>k={}?ER$a)dwv}b8YjOS@ z-*6wt;XC3AR%f9~wF=mhyD)QBg8kyWexkN=Y*5UAso(isPiJ5YC%pHD{t5mc1}>9M^f_p^oOn{*y4!&uFk6psznj&M%?pe1^OVHMcT?yP zOKo$3>JUnVL(j>%X^Q^qXT^1grHrDu{_MrWd@Q~<)fGRu53*4kY!v-|(!49{yQ#fK zdvKt&P_IvCFsgW218q<*anRp>`=OM|6!5yv`5^1pE=78LRg5;&-Fz{9&(GAZ*Y)q1 z9_=i5v9IE6J~FUJ&|BqduY$N!7ZY`ZrvN2UwULcgNJM*=_O`JDV-l{H*sO>xj=Q+P zFEotinJ>HObUxka^7?d_rwYiGf8jgx_FJ>+2eNu_ig!UJs5m^^SzF0MH=_Uf*}6)1 zRT&*K-d`|Sfa2FE77cmE8;_Ar8>a~IP(&rmrYXc7yh|_zE+P4H{1?K~a)pJ;Ebedg zCy<<4$hf|2_ys-$(Eo!kMCvQWZd~#^m&W(izYD(jPevm&=Jb`2#rFtPL)UO72Fhe; zQ+(%he?vsbBN^-eW&lx%z9Lj&x{&-IgnjPSAY=9H!GD9KX>{$0iwz*#Xt*?q#AzLh zlO!B?f!^!}T>k>Fg)jwK98}tZ4uM1c1lsqB40N=~XkQ-FF)d3(Nbh6J)k#n}IKE!M z`r8?ZB6^?*NHQ((UJnt&dg#>L2j#%#+YkR#$J&R$G(uABFl25dc@y@qS_&7_5h5E1 zBzuBr_k-mG$hHh1;G2UQX9)q1MXB|urb&3!lkhLg7M=E_Owuz$K8(am@BB_AJlO0->!>=w$2M>3D>c`XQb)GU;GKP}8iCRfK5) z{eFq%Xn7Sw76%)%9+Eb^`>$2C=I?7h{Er}?_-C|NaZ9x0(#rwv-4m;U?+gK=hJA3Q zNVCl!f~*Y!S4__$MW6>-k)#W)#UyToEcZN}rCImxC{2OpJB(B{2&7-jq5|Z*ZB9F6 z`-S6VoO)qVE%f!q`&%3D)!7B}$i|{od*nRPCoU%%jSDaM{x>!@9AGhtzov-jCLjdY z1Gn}rOdR`Yv91=IPgnL;?>YM5?11J8tm5sRCMpjj;N3es7G9IMY91ezwtX~NHZ9(5 zsuKJn(k*}EYoXGw>n-z_?FM33gM6O1opiMG5K#2l5oCfkS8hA!Uh4jgEx0FkuGW{H zt)IG)FUZ%SOGc=>(q`UbTTabHDg6jJotpj|oIC!QmNtg$p!5Jdcv?>RGns&#IL{w* z%bZUnf*gSP$@sn9*)TGXZL0D$fCUfygC!hif>(5Q~W8kQW_!us3Kz+Jo>tr*+t@V4#m^(i{FaU9rU53qE(iV}} zma8i+0$Lw0VB$~2SKYHo?=KvfXB1&J#s#EmT75uS(%F zhrhcGlFO-rttT2|jBOfD>~D(4u!+ifdHDkr)9fSIIoY43g`KZ9#g4+I#aCv>XP#m0 zRQ5DX0YgQ~0%x#)aXA~Rv~mR^S#w*5ew`Ftl06jE1942L&+htFAxaESq3KElE%q3a z<0>qRAV2M;%zK_*`K#{KM!9kwE_tm8z zPNIp}y)ltmG)U4D-)rB#bffFN{MpNOc5BzPY2bA64iwI0B42EYI_XFD*es;}N|-xKBxD7Y4A z+J??obY_e1MIYz^Z4VoKl~yVGsr!3Q=CuR!)7=RV`p(7(n;9hGx)4=NY$*idO7AzP z)yh?A>r3ljSQ?gyIZ%TaxbI=N$?unJwLYMNAA)hXnvNWz&&bxAGk%@yQ_@;&MB!&! zqVKO9O87?1>=p!R-3wr59PgSjsE!d7_rORQ6t!f8K{|1UVc`s|1x zUzW)}V3x#bn(bQ8IdkxU=Mp?Q0hEk&Mh^=iBPyg<{X+SwfxGOIN{vjOg$)n(`H{dgvmOGQ=7W7$O$*}kW_2Q2qhf2(Z z?R+2ZZoTP(rdG3(Th=zttrBgtI(0($+&!hK!WU#YyQHbfv32S68?+bt0L~*su3B)9R;T zpp1v07wg5R%!Dvl-D4#YqfZsZN7WXVqDjOwyXspxbN zR!1wb@v60UJ{OF6GRS$7#FUDbuA#)b@s*hZ&=Y-hmF>sGb>l)Dl$|M-f(OKw!hii~ zlyi^xS&TLl<5NMoNP6SdS-FYL;$0y}+Ut?PI0K-0E`l19IXHR(H;6<1w0bMci5kCIk{RQ4f7H^ zO)YOqMXOQDz>Y_F9&JCM-)(onzu!LV$L}uIbibnoYlOB(!Fs*^QT|PqJr+VwNlO(9 z9Ug&DJNyXm9*PSphubY?xfQwkyI%}_0{O3#95k6vQO@TP(zG@ zy6`$&P?d3Yu;yrU8UT$V^jB_`G$MHLZ?}SsTaw+lP<%xyy_`XvE60reQ_QPrRSEen zEiM^CUWM0oM!8hBH{uj?ksr`RQ`!C+ER%*wjp=EX$-%SM*2ZbEac|r2UJ}Vd4&1uCfkI&}Vyqp@5*c$iojH5)$({$R7F5aIoQnSb zp-G4Dh>v1SclATR$Zznx|Bb^P1mD2ahKl=>i={+2`o2huD~nt|-poXN^m?7t6(ai+ z!JSKOdnfX0*|rblzI5(Qu*g{;DQOOhj6`0(c)vsW+pD#-w`E@6e}+|sfGT?9K9bP2 zDUQulqTl_^%dZoi{5p6*g7b0#v&JlrMB zrrFSFppfs8U&mwW?GlqaZn%`u!@P(aReIIp<|&SXV6?d0Bbhrdey#S{B;y4p$9c@J zy7kld#Uhffc_@-Ypq1=)^!{=H`6V$0^uD7 zQp47J?reTEb*g}k+Pb{Gc+rBhk<(qEi%o;coGw>%pxK#srP?HGTR&R4+mw|z1ZlAaNBem<&r79h=WKyE2m{O@2A-uNah=arAE? zOHygeZ9*1tpsi03=wa{S!mwDfE;xJO`3OajQ6)>}IW5|;0MBnlk7&bw?{eiQKoFZLLwzH>%~I{=_zjR zfqcs~Whb$y+b-9Wgz0%?65{?xMw#dPN1b_Rh!DQ#gG^Hx)s0Avc#@4XpX_FMd(Rf! zz=m4nnC$5VlsUxpiOZfGs(XCJpTmseQv3Fm`#ighEkwiUmO$Q&bMJlYJd9p8E}9>! z)p^&f5i~==zRJzT@85WwY_5Rr8GDQ30^r1=V;DS7fl6ZH-jzSt6_{WA@btXwFU@{pxVe~x z*LPprZq`ho&;=@;a#eO@|F8RqZw>vah{JCh9vj3cT)-B>VcECKyc490{rA#&DYNQm z7MyvMn|AEIx*mVDYStOEnmU#zX}lq5qxPK;H0f{Aqq(4Kpt*{jYQpZ0*vs`!K?mGu zl{Tr}i>)|isBdE7WsMU_C`={4Xz#YLOB=c5-uv9#HhG9`N8}T77mw#yZPye&Qva_v{jW#Uq8aLc$yA9a7ny zXzc@?mi8xH3XdL(2;5xuD4eRbcjZdaF$u9D?QQlCWG)>^S7ofoqGt*IQSIsd53n}@9CzpBd>qpx&Xmf(xUSYS)SMH0P z_zaIp$42(`0vDC4>`e+Thqk6ERD54*McctFYTvF$Q%$=iS9Mb$F0us&S46gjavf&n zn_SdqVw^o|;V_4b~z*Vtcc zi|Jr;a&fNU=;1tdh`r0+cx{T*A1l)-lxMqrQ+Ju{I%-xxb4thJCg77&Nv;N(_!C^S z9g(bd;@6DD9I3TuV^vQTU0X6)kQI@6A!;Dg(S&V(>5$ya9}WstY!xBzFcSY<9pkWG zITT>fa^#_`>`fGhQtHJ=FY~jYl;4Uk*G%!ZeFnBp!;+|kZTmsJcTO!d6s*>~PiYYy zeuJ^;*9In!29`gkJyG^u!{cZ7+C@y0cSr3#^Q}A@En+iZ zL7g`K$+P>7`p4nRL6MiYd9Sqs#<}iV;_53at6gj=(bm4%k?S%eu)$KGpGMS3O+zJ) z$GWm_p{Fc>ZbxCPHx`9TFV!fY>*d(ax%@u6ZNoLgwqQc}&%@V5q6v`4$?2fEh^cb; zP+Zd8Rg;+~OX=z|_W!YRiDvN?8FXSolNg5wQ!#LUx8^!`2}q4->oT)z%PSpGlqReR zo3LG^S?&Clr4wV2wWX`wR#Bq4rmhyRqC`=%nFXOfcZ+G>;2AKi)ekLB$NBgh44hQ^ zzSj2k04taAsg*mf-G*B1Du=9)pA&6$HL9(Y=A8fCD;X!R?A`6jtX8Xfv_;<6ahKj! zf=|^kEBss(<=LDbyZWavzhkq1K z*yw`o4V~5~vvvb3= zk{qU!ZJ#cPUe!ch;5!m~fJPSj!X4N*BPJ^HV=6a}0Xu*b&v40^4T`t!glJczIoxVi zNWT?}r9@6Zae=_xL=K7; zwvM>2a}$cMha}V{9MCJmE4S#_lFhy~U^biRBwPVt-;gJXsL=lI zeS@)&Kt)gQ*#OGkWJL>%$l%49T3zguJtJ$iWHB!8Gdi3szh7%>5W)#ts_e1&%Vte0 zZWab=huuJuj4Tp)B<4~*)FTr7T#Im`)!KJ(V^)jzLy$AQU#4o7QFq&9HUp9FK?$e> ze~!>=>GOA%XrV+p2WOu z0ObB4itI>(R23(P@W&1VjgLb`wVn_$BVY<%y`1TCRlSBv6L2f$wO&AbhrpN;jh8q@#I^4Vy(+Lt;$zq51r?wDCVo$)ph0MKwb2b9 z1e9|K7+3~>AiIU>!sT#9c_lN(p{|F)Wz*6zo`_b>z)&KNxxlK+OpEC3)xAcF z|D8Ks{;|W22xshRc#%v5;VCNkK1S{E2y(-Ct;}wQIwA(Jb)YLTII^o1X8D;+Wq7HkGN5v|!NnEnHlM#X@D8pG%FNY?3(oLKwp+>fCx3q%W%Hq8QW zwCH2-#}M5c{#o9smoBPu>BS>ER-0?{2pnm8?R3zAbziR_0cb z=`c(6G+jdcwr1|-&1Gdi%A4DDIWdS%%Z+JoXr`>fN;g7p*7)CYbClnZ5ldDZlgZIy z!$>2b#{&f_T=0Hvpz4r z%@Z>qH89Ecyg#y|WP@?y$g1${2%tJso)-ri5Bq*1Q|$$J?>iikLHiQSm*i-%2ePhO zFPtO5o^*q+fpOC2_?62pDwY)OU3^Y$%S+6Q4;nj{TkHAvY#*w*Zqz=H&RdFY^-?~gc+)#r5tto>KGNT&eQC<;f-puAf%+#UW!xiy zy{{qg_~jn>b)rslf?ac1ny&N#V3u%`h`Y~iKKz66_a28$ z2I}{zaheUE_cH}jpIk61R+{lnTWA)3upmAA4oa-HoE%xh#M!HRq^&x)>^${VB|I&$ z29AEF&I~lJkl3ApOe@J_aX~0?W^IIQLHDaA7q2OIX<UWb|c+N&Nw=L`Mxjt z-8p!Pjw^Oac??)}OXcYZCbi}tY#Oe0ua?clN&Pm2*4vNzG6#r^U~=$)#8E>SBeDyi zr}j~2^+8>^uz}*VnHvsPiuDVNU|4H`dB1rQQMk(;)%>`~WhP#F+WIXnS2y^$3Vuxg z;K!_!ICL8?&C=)RL|$>yAC64!<=WN<*xcXNMTj?If7j4kBmZkpvd3%q(?|Lw9wbWq z`STlMf8Q$wKzGSuMWjg+nBhclno$ypq{&VJ2r*!Uc%(XX} zL_OSGOv^A`;>|t~z)R_qrZ-BczTecv#gyA1mEEy~+)oQ{20r3G|WM-zMN zkzOyfa`KAlRpFn%~Co zh7eRz^`n~{ug=jSPbZv24_(>hmzdRE7n8D~z6;dH$L5ivtxGo05{ec`S`wR@ujmK> zXvJ5?iuJ?O6@{*3bG_u)UGS-wKv@`KqhLYE6 z-i8(KY_EcE&Si1b&hrLt2f0&A5h6whn_wZGUD zP%YaTaF{}Aw*5wTBK3vK3JS&1mvh7a$;W}rkdp6m?GMyZU6g8%lY LYpOj_G4=j$84t&5 literal 0 HcmV?d00001 diff --git a/docs/images/example_01_page_rendering.png b/docs/images/example_01_page_rendering.png new file mode 100644 index 0000000000000000000000000000000000000000..1bf85a43519b15e78038829f33222341aaa9a961 GIT binary patch literal 8202 zcmeHMc{tSjzn@ke9g&JeGZlptr(_SmB1!Y<;%}6MwP}wwUzxv06@s;)~U!7?IX~i9sLa}&dSU@n|uBhVQFEfmakiP8IxH#6t8$e)AiG{W}U?Y($dm<_h!0v77RAVlkBS& z5}Z^0hf{E3eQJqn*xp;K)}*&7Q8<_LY>I>WCvGd8%RT?XJahlqmzJg1dweImR9rs| zJU@79ac&YmEw%PolBzrM=E&2GSyN?YWm(fi))!l>dw1C@{V>t?94l=5%d@l+cLl5b zz)EfIY%yU|)$}X;)L>cMWqEn|dtwI#(H^z59LY$dtzIk{(Xghcd->S7O* zJSU4z<_yG`A&>g~qT@`b;3Xs^a;!*bW8-?U;HE^Cd+6>Rf&}kP$fIS>&8a?5I}4rX zN87S(ORrZ)Svom6MIp6S-M)(8$pQ0IP>jI2PUqR-W|a0#R{ivV0NN@20jitbfziGA z`qmnOO;EJUEl-Y(caVgGF0w{iBX+20CMKoKxV*h9^fp8|D*QK#Y_wXhpF>UfHZ}Jy z)=&}_{rH#NaNp`&WLdNB3NJNaio3kXxU*}!hLi5=N%X@Q+%o_W&7rc1&(xi!NuO5P&N4$p3{Hr@v>&m%vUpg?v zojfD=&Vs#~XVUex zR(uUN+oXn@_TIw$U+t?HGAM*zg@w334=Dr{Dp8Md?F3V>(7}=WE>K+_@sV@Ze9-|`}j>e6&TpIiQ;+RPC&9N74*=A8#83l#$u^duw zRRB(8Z7H1)0B!ZMet1-IrP6;k%QUIPYp{VqU7-M`&*q%Vvn`z(jK>iAg2l6*@ffum zS!3PU%C8E@oUdO8g@;wA9TRZuscpL?KY%l&Puv=jUe_y;Z=&iJ&UvMkk<0X}4D+ z1-}@ytBmEay|E2_}$P zgX{J*gil5KWrt5^3Y}=2a)^96u!bGJO~H3$-6kpZ2MGz}qpYrLqzI(;f~+prn1`9~ z4Pa<7D!1UtP)H)L9|d6cpK*V007P;6@R&mt4SV;p<-UY+s^9mp8_nAD9wvTWuGuGK z4=XDR{Jg!TDq#L5du*GM{ZR~VHt|}1f~tGh?KJ|24<8mgaNdJE7;iYNWK$%!|IMRE zkFMpD_r=@R_0O0k93E^nL`9H_m5VOdguk%JhT0Xf$2)`t4|cJ+?cqKIO%w{;wvSFr)M@D0%t)t zacLY!$Cm>7IPq1cNvaW`Ng!(Vw*C&#tfZsJ$l=UpA@RskHLLoD> z^@e9>URi@yJBh6I8T%YIf2j)kabx)75{Ir%TXDwBy`}`^Ve)XQmf!TnlP6EEEH4dzOwqzGuo@LB4r~Yl?Bx?zmRs+J zNU30ZD?z|pM{&LW*?@1L6F0B4cLB&uvLhenJ_I==GOFgkD`BhHUpV%kNf;_nk9oN^ zKCJDfYg-oiRN)YwD|_m(NJ`mwens!k5hz^>93CO5rpPP#70x4D7djoZDJmO`v-O?o zmRMU2CZy)}^*2r&DEUtDOC9+aFaeXr^)a$)S3k>b2+|Ii>n5%&f!%1boSZ)fG4GdQqB;_Us z17$oOrSbeRy4)&{DSHBUFa$;Lo9ZUx7v~~A*4NcNKIC9Pz(o0T4VspTQAcNtcvMi; zrEOMrn|*q}Ro{UNlvKIQ%DqQq)-7roPfV85ks4kDKvM8gWXfzaarp*!X3$KV{kfZb zqd$K7nQ=@yeXt-wL`ttW3 z@C~s)psiiyKRd*fTv}vMQ7cxJRoPeDU(R>mFoUzNb>#O@i7R?ondMG80BF-r&1c!! zR{3@slmQUpUc%z!+k?)V> zYj06*Zf@{KaRX;4Y%$qRjnRk_BI;|ko&=@HI+f4`QV;4pP!|mvBNX0M?lb;S%MX;O zH4G$@@4Ux@1;H@3t9x85ngdZ)gUq3a9k?rbeFSPSlUl%u&O4Xa1uR_zMy28BIyKP9 zt>2=xS+v0sxIsn-iC*GPORov_6^B;L3KKbcM=orgoQ_ylQ!h7Lt|X=L?A388S~$x0 zaD`88Z56(gHu-kskq>^_1551le)Byz3}|Y`WOPpjE-G~nf{ndmns96*G^m5R>;q!j z7^{FAjJ102408SopgKA_%Dm?dWMDGZ*K6XYy5X)^EEbT7Z)uvdcZ799vpdY5#H;F0 z=nM(Wd&$ufK(kGb;<-~5jE?{lhy%1zY;#GbGgy9WnEoDSyA|d{c8u@~SCD%f8ym0Z zlf6}ojY`z}XsVc|cQl1J(+Jupb^SJckN*5j2I$8qm|)Y~vJOCdhsssHXNAr$q~B8n zt@#2!pvcn=3Z!mdnmeSXu0m%smLhR9Gafk=&b3*F#wgiWs&5G5WBBWDmz;V(!xSH#vUT*SkNaUs{rmzO!B^d?31MZgP_|xHI4TMb?7RO914daVyhrg$)C? zF)A1o*;Kzh%U>%uDwge15C411IH8K=Bk3HL8DSMnLRe~*ZUOj^fO3qgS} zo@k6a9+?W!sv%YZ<3AWHZE!!d;wm5#7}5;xP4?`6uQ$I`qk93O>o$~#*raIqR)up1 zXitwbv0uq`p3bKW3O{O&M}eI^lDDwuhp9UPvbfiL=O1ViQtK8;c?^UFn;QCaq5s2BhW=It?7_v_`(*U*1J7r7OYQu z`t=&-F*owsC1K`3^Ws81BRFd0vxQlNnXH-J;5hrd<&GmREzoLCqlqH<8CYz+cwN7i zapbs+fAil0aglx=jx*Y(_CJLLq$5L=-0R-Q9Vd8@-PRk)h=NJmd6%P8G=+6I-vBXZ zA(!sfyZa$gMS=V9N@JXAK^_7Dmk_3^TwprT1(aGt0PdOW$BV71(1D@_liKD{*0$;+9=auR#vD%73 z!mJDTbv=6ps-K-kxCz<<)jR$4_~@t1jEEzWYS%yDwN6%s&wLUH32+py>P*Kw3eUe) z?=ANQZvXz>eiY;LwNKBCLB}G8VsshfvQ%-!`;}DWy$053n=W@ChSLZwgk+Zmcf`fZ zYNYes`B9)*j1_jqq@P1{)jMn}s1a9!r6YX|j=Zvk5c$349!lNgTT8Pb+QdX_IQd3; zn@QY(ux3nwqj8BlqP1GBb(F7^D9y*XLZ_zi^&v6Mr=VD+iIy)de|mYvkJ_?>i(hPt{? z)QiJc8uYD2h-E!MS}E+g&N5F2@1L)*eo{BA90bgBmKA~YloYtDZIjYT6&y++r!S<{ zpat?1H1Qq%(1svLd*XeEZukez4C9vu=HCQI&v!UPa~8%)C?bzJ;(dfinEqA`pVqIv zH8h^`Yy+OhtYwb6SYZcvv(36!mE7fQ(jPdy0AXi7XV9?JDv*ADN_|y}Y-ILF^Dlqo z9kf0TG8pJ`J_J$_pb-0+tfcGV=v){|V5EX5Wh^6VTjFL7b3|dYPC%Qf+F(JujI#2L zk(Q@m+(}1VKP9SMD<~)cRX*hT{hi{;q+@77!C52uD8GsRaP*}&joVWwT|P#ek5j1q@s zPL7H0J^8Lpm3$4NmiJ|tsmyejSdlxmD9a1u1I_(%4-Hju5X|nQUcd;Q2hkf+G=Jz^ zrX3UzMt)$n6rmRC)8^SRbm)ICCMviH!XR{WSE3+w`pw4L`x0Ax`rtemv$kq?&alzu zmitT-x9)V&f>Z@cFRxi>9J7ynf6q=VpNtT^Gav3X;g8mdb+!PnB1|zz03<(~-!2|2 z*@(gQ_-qtA5bFCTa^s1xIo*|6-PY`j?<6O?id?yy(GH}kbrdKK&E2m_Asm;Z@OJ{B+{rv{JK2R!S>kbL zGsr{YdacqG5J}zAu;v`YIS)uGDv*T@k^VB#Mh9$5av^qv*gL@h1XY*R3G|z(J&g*M zCH8ih5tQt<^G#tQK@~;`ml#@qlr9Cn@6y=$G$FBAqa#Y zI<$<>a0iu$3GR)a!Ghbo(jIG|?YlhG0kIR2T2s$qy)jT91L@ZaqP|m8VvkS91sI!k zSPx4z$(x3v*p9~?soy)V? zi`tfI3Impfxp|xthJS?s70|hTqG8Z9h#uqN3i+IN5+QanbWsPt@GL7!@%(QWK@c?v zs$LNK`sFGo3K}=zD$l6;+bzQ&)s&`7?0XZ;&vKAFz-acL%l!)X4EeBD?@@4kV&V?c zZOG7y{zBbw`3G%Fl0g>=t~A2j6fUkr0JByBlY#I1uZ+Y2^$k&YR) z9#UIi=RuumP{>HZnCWl_U|0*xjo*Oo?QqyV$SGabXZSYmI9diw>mr{gMJ{LH7mRX* znR;2Kz-s8WJa>yr)E2n&6m`$bmcN34qhZ8~VvAPSFWy}1B`c^A?FIybDH|d~oi70_ zIYdZO%(Mon3pym0hBh^w>>y!!qY8rh>0#`fYxx_hJrXcb!h9boWcYoWu8nNr|dp`Z; zMS*Xt5bE_||GDt{ryU=E;rHW98zhdb`p1hOo5JJkO%eaJHavax^af~eFj@2UnHVD( z!N3a)@7H1J-x%JJ@_(FndZecoAsgRY1(iVzIKB&g(xSinbt_?bo%$vshAsZz9QrSJ zk^kOx{&gfQdH;OR`P<>mZMFYpRs8!xxbDeMzweEI9LYzH$YGb&zweEIz=)5WUw=+S z`~!$T7S6lx?EC9T{@VSoTK=fQ4_f~p0ny2b1@|o%J)l#OeuIsSMwUAFve?16+gtz^x`9DYGe?O*tqh9;Af~Y?g WLVLR{e+-`SBaBa=_4AKk4Ehh;qeB${ literal 0 HcmV?d00001 diff --git a/docs/images/example_02_text_and_layout.png b/docs/images/example_02_text_and_layout.png new file mode 100644 index 0000000000000000000000000000000000000000..2467e66ba55343bffc8481abb4a35e2a60fe606a GIT binary patch literal 23455 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 YCe+Xx>CBgbKTkSxNabMs{&ODxA1mxO{r~^~ literal 0 HcmV?d00001 diff --git a/docs/images/example_03_page_layouts.png b/docs/images/example_03_page_layouts.png new file mode 100644 index 0000000000000000000000000000000000000000..2b73c9a3e9daa04ceba92cb1d21916d20f3a7ce1 GIT binary patch literal 11923 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 literal 0 HcmV?d00001 diff --git a/docs/images/example_05_html_table_with_images.png b/docs/images/example_05_html_table_with_images.png new file mode 100644 index 0000000000000000000000000000000000000000..a11f45db9b9d3b2ab552af57d1cef3df36e5c49c GIT binary patch literal 120550 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 literal 0 HcmV?d00001 diff --git a/docs/images/example_06_functional_elements.png b/docs/images/example_06_functional_elements.png new file mode 100644 index 0000000000000000000000000000000000000000..22c5392588dceb8bcec6650660f8ae662722324d GIT binary patch literal 18076 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 CjhKM| literal 0 HcmV?d00001 diff --git a/docs/images/example_07_button_animation.gif b/docs/images/example_07_button_animation.gif new file mode 100644 index 0000000000000000000000000000000000000000..fb3deca4b1890b4459938926f65a7c04ab9a7b33 GIT binary patch literal 20864 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 E#j!RlMCIT>W)+^2?O@x197N`M@oOz~93X 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 F{}0fdR6_s& literal 0 HcmV?d00001 diff --git a/docs/images/example_08_pagination_auto.png b/docs/images/example_08_pagination_auto.png new file mode 100644 index 0000000000000000000000000000000000000000..4875be1f0d245971677d2402c01d10ec9d9b1908 GIT binary patch literal 88831 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%0Aj=8#m)mOBl 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%0D$jkjQixwt z-fuE?YqdG2^wU4_VA4Be*vW>i(H>VU8!Fc9Zw=friu`SN^ zF8CCxWgTgslarCneE1;7L^X1bg(?t~gzqpi;AB_7xH4u!zIN@$xw%HuJw~e>-|gqH ziwShPOI-H#;m1_~-n^j-_6?L6V3Kfz)q*jxvG(Odb91gYO(JCGum?b!_y}U`va$_0 zjc98Q*he;Ib`HKB#Brg(;bH^}3NdQeuts$q9dX#&h<^4*HRWk(X|=URZc|z8TZ~N(_-<08?VAup}1+ z7UdHb66k~^9{SMZUcIspR>0WUfG-w{#$#7K+}yBoeB(`2KfzUPxpv3 zQJH^wW-81Q23o01nqo%dxPohO$n(wx4R>=rXC45wv3bGQTPj&){tF)O=y5bOG`x3J z=H5*ayY6dv@;mHc$tBY{nAr6k^cX?IiZ?;quP~u|b8e!8&1-!9v)MEk$Mc%mT5wL9 z7MV2wS0lG?A370d29I2wH4P38J^d_z5!cra%-Fd#ucLb}`0iV5)^J;V3O3f(ZQD#w zPG-O@FgBfiar4gib>BY}!uX|>EY&+a{1tuj#x-t}DDgW>&h^Rjfx#uSVm(A_A=b|I zXsWIOE)RqtC}oZLQcEq^D(MgK(SUTQl(>T%aKZEkpoYzV{!B>QZg%lq&tdH1Lk?d= zr2EdDBMbAhC}Yluk3Nu|fRD`1j{B3HyZZ-Z_MqbGH%?jxRvmw#0#)uw8O%AJ^Uuyt z+8;V=d*%?s;W?TjJwBhsuTO|ROz#Cx^zp%Xgmo-FkyeVWK{~m+&wRmmsj}Gu-$Cq( z0hb!N-ic^v+{I;<+|zJKBUbiioj4A(2p zo$s^M(*cLZ25SLcDu?nRe5cbMAG+ke>y{2X0W^f^XmQMEVcqwb%qu4q=Qrv|mJ_PS z#{tX61gy5YGad{Lox~#sxUq>S;FomZ{l%@lu<$7EOQ%oQjwjKx?<0N@LrB211Qw#> zQL5d&7neWy(rUoh!vRX;s;IX2cp$r6eS9MVk+$UqAGKfLP2NB<`CsyCK2ub(|z z&&=Fbr(0ilDNH8vhVyDFCg0BQbz@J@-(J1Ns_B2R_vX=9uKoLebD>eDG-xoBp=eN4 zLYe2=EMrOLp%hZ0fl#R_pI*my3gx8-^X#hj_d0P$JA8&FldlJ;K>_q&b-#DHdqNeB3P0wZ{JS8*xP{` z@nXQ1v1epLXT5(X+Zj_wfe!hZ@{zn7W z9C>ZsB`lG=sQ-B4Ry#)7KpX@fsy&2XPHu*)5^oCZ6`&ZfSRk&^_JfPyHbmDwdv|8t z#ev?+gJfJbH;ac8bfLjwk%1yT1{VQt*MczN;@So8`1|Zk=zJbhjEX3}+%*SB9>WkZoeJ0M%`KvLUG%4gJdCM`-o?k47lVspB6(C0o?VzV zkUv2;DvGYzsJ>_lTjDo@tpY#uTGgXv>RXf6Oc+sBe%$VcR#yL0hVHi>zaxFHinmDx zbq;>gYBM3~jv4VIVR8PUeSGQ~71$$Sg+l4Ming}-u8_H}FWpMM)E=NacEQ>C(ey`e ztdzLe2Eh7x@d6t!=65#OOCZtFfyD){3ZBTRxI?qj!#C%!wRgkO4wDgufogzag5pH2J6 zuCZKvKg^2f$HD@yBtGHT%*-!fIf%KhM7C0`b!ZZ2$mkwP1ml_$1N04DdyMEDZ4Z$maCk4rZoJao-s=5vkAnK@)cb z*ht>OPJ_zXi}(l$NFO7k+gKGx261DAp#1i&=JQeQpo z7c$23W0b}2%JD6!jpm@Lpb&X_BSgJzCo`KXD~~yyqFbyNE@Ky)r=}OYQ08I@IF?Fq zKmo1$P%#gudVK9uH19JMzEdV7@ndcZcc$Clm8ceYyu)EP0p7YJ`3o-5`<5guS-!mK%^N<= zw8s{E@PTyVc1eKb-8h!!EdoFf?-K4OoQ^HrJ}p|1awXcD8~Y<&R~EJ-(6dYVc087} zX+2T9a6#cmU%yyF;~QM^^Xy{qQ}Vkx65wO{4i}JmA`TL`Y!KqwxYtUTB~MvT zCBHu{!0zpnHdeRCju&H?wszrWhaTvUjhnFsUVXiAc-b>oJrknzSnn6+nlp||=77vc z5y>j84wo-4@Srj=Dv}ZsBg;8Dmc#MJ#vihhJIRbqFy{Rc3gEuAQ6sUDBu@bDQ|TXi z_G$+`U=KDY0Qc8>egf__j^+94W`X8@0f1yo?##2l;XHu*V8rfItK-aS0317gx;fqW zn$j>2)x+*2TC6H$b;ZMuvI`l!CE*Rjn8nkRz)*sog;zz`60ltF{K1b6!8;t42&gH0 zK!p*UOLgYiU_K8G4NdRZ$skK2k&KLtU;~A3lQ^R=Fx&|buXUxOuiUeOy$a>rBO@ck zfDoqzoXo&`5q0RjrA{JxxFe3j79em9)`q8*l^cDY;zLDf1?24>ki!_fP4UaaS;W%9 zVz9UOy%aA$f37Ako)&XHVCjK;GPAUV8^hV&ez%xU_L(M(&TUw5>pXbiKr6SXcZGjM z#1A+kzC>WuaXhuDQ?Zm)QDuE=T70Hb%vwU)$4LYSGb|u`_Ut)%@}ylV2OFC!R-EsK zxL75VODuQae_HW;wJJ}QSWsgDt8l5>#@$Q(p8?4Cyt+E$f^x8%v-3D6XO^YOi(?mBcS1lwnu@`(2k6mObNzTf zHl14Th50<)gEk#|0|#j=h6%qL-(UT1F}FiP@?k}x|B1IB((-547MD)rBqj2HZ`cC7 zeMCSmpR_zHX!9S}_4w6MY7f59702(_VT@zg!|a2lZ3YL>)|Qs%C%<4PeEuu-w&^>a z>kTqghL_!Q=H}E&J@Xf&e#i=~D#BdnpDrppPq$C+-~J(P@>R80&MSP}T{%CW&&$2w z7u`ObR8t+@XznA`iEq$H%2-9kztlj0*#}wIFXQ7lUKb@T8QEFM@z^A$ZPlK8)YX^! zWuy$nU-F#tc(PA^^RWZfPj>O3p<3OJob20`?q5TFC>xdBcb+os=9=NM?v{Gm<@b92 ztx4>^g8BVwK>FYa#~2vrCgeu1l#aYQx@PI}<+A2iO|v(#tP1#q!69zkd478_A%xuU zOy;qt;g`vv_@pM&Uo`)7ce(3)Rjw;2$8oAHT7ObJ>YSvsJGk)Ooz-E1mNW=+DrfOY zTCGf!zu0(v-h@W_1n_rHyB{?b)#%8``#tnmN)H2tVZFb#T;faU9!u?aZi^34&&W%v zy&2dgWq5Nd1DkopK6!fcV_Xt(@v3y(m#KDxKOeXe=`X4|b$or51XW5m7qu)BuY zRjJI%^Bnztn_P8d=<@Yg_wAd;ffC0%JL~xOI==a#Z{HcrzYp@S_hEOJyTMnc#l$o$ zN`gu*ocJKs+`SnGT8{E~N8pVBcm67yk3APx@WIh1>v3q=$}ULCe0t)y*)6d#e82tG zR!N-W!!PtF;D%gX9O-UNG;*K20<#;uATKFqw{J9r+3k(If`WdI#fA#Sj!9Hx4@;HQ zP%5(b5f#}bZf2Bn@_Qd!&++Bd^TQ+CJl$5mNJcj@XR`8(gVQhJdy6C%Y$k}h4ns*9Ex$8$pk{TBK zcuAexE~WcoIbHU#y+T6B`JRGmg7*=xKTo*d@6q>lW`;$?;n^Ds;eMP#xObrPIOFkk znR~w7!ooIHz}`B;e?T4S#S93B<=RRZl$V7m;NbVfRaih^1RFm-F0QI)&-TGJiol3| z>Y)qcoRTJV@T%dMPXxEIwj)?vT~m|aq}*wz_`0Y4OSi?LPz{9`6=^ZWLQEX6UgPs? zZf=Gt@B8jKf&Fgdb;oc&fHJ|&$;pJ>hNb0B7I~6s*BwcRotQHMD-H9U9vYnL`$);! zj@{MtbJP9({mA#h(28RoDtP$8CdSzberN0!+p%ZDqH2;{C=dYg0%N3eU&p#J48Yz! zAtfa?Sk1yBslo^u!6j3t!TPhbv=kS2m)Q_M_dy^;4o5gDDk^n#b)0|lusOlj>^Lpy z&5aAi?FIrk8qw3yp}@$iU|$w zHhy%;%P!hl)*ry_ZYYnNOkIDGwHSbVw!^{;da9QWI!EiPeXJ?}GvIy=0QXuJw}r~E zse4*caaxEO<5SdxNC&WEs$wp57cFNn^nNax@#R}ZYbhWtbaRkIxC0-K#|3#J2~~e4xFqphZ~S zItZ(qGhuaO`E7Mevltz$zq{Jvw>GbO6PCuX&Ohw^ZzqTsig$c|h9*@t+wS@^6%QZ#3c7HOD6*7B?o#v-s$qu81$e2xnI7qpm32!3a^1` z-+@qVz5%mKgABd9-&d2D7R?eP`|ryrtk4C^7rFe@QvSaEWwpqdT)_#v{k@x+z}sI0 z-hPf7!QTh9f)`@Kl649Zn`48hf?_+xIq})FPbpUThb=t(o~$sJNMC>pQNF2 zqWA*YTY6ZUt?o@Y1bhS>h;1$gEAP~Vu(BF+?+9EjySmghp}5!;l5&o#xQa+_h3}M_lDlR%=^ZwWXv-qT65o2G?L-s& zTUt<%*Q7OARmpX`mI!%cxt(g?(2yl4*GWkYeGZ`Q z(z=$+bow0m49B6E|<%-hoCeU-KsrhRgz00)%mo0>bdmEZ61g%4Qp1ubQ;a zkb_fbx$((<+;lc?#_3lN9aVnNUo{d@Buj8?QyKx}Ak=IXB~ ze{#Fw4R9rQ3twLC(1>Sy!fK`Gd8<%cnd&|@XH8W<`yMlMuGFqU0S)T&mQ=I2^g@f) z)7`x?x*K&mQg|*~+ff8z^T5IA75UB;MO^jdjmG>D)KY6@EgQRs+c0*8XhY=0Pn`mw z)!WzC>u6Lm(a2(DHH>mK=-n{O%QP(MV|_xpUf@VRI&3b(bF9)s_4*v(7bWU|F>{`qq`|4y!}YlqhRzfNcizBrkXS9EW^&Ee3x zWveu(U+vu&qre1~{qEOOt~46d?{AKm$1VaFV?*Cei6_re8>xBA?|)wpH!=29tK=-4 zsiSZ(SP2fs&GOq35veNRU~HyvFedhraRkNTyy#nhqDLhnR5D^SHC#3!Hw=Wba4x`V zG*G#fWQ&~%yIk;nyVJqrWYN3zo*Ucqd#^oG?K6fj=Z$?PU^OsTT>dV7)|bMzzcl*` zgONRt!I-h)j-p^N;wcym4C{{hZfedtt%}X1v^3QZi$pYpV6Ha$@~zWAaxNr@Ec~uP zg~w_5p&EDX4p2;l&`TM3w<*P6n`efrI>+0~;=paBmBsEqu+~%?IO8cG3ZKx!p7?s@ z0jtEw3JEDg`Uh!gw?M&Y8W6NiT6s0f5iYkc-4#hIV$UZLF1NE~2R;`Ww`#5?7BYqwNNQ7Tj1g8PGu6a#s1aCCB!ao)L8URl!GrbRThO zC|GZ9HmbRF8>2vHr>3jA5COK&-rn`%%9vEm(2}~6GTq3ikj{Imc5k9Y6`548+i1b# zhLv*#ge{7L{>%ywWRw;eW5tuyE1*D zkg}3Yen6rtG6>cU=ibki&u(n~LZD~7go2h2@rX&=>YcLU{1=0wy6OW9AF04)=5NpC zQ7SGGL>7pps4pVcx3TNo0%-{YBiY+m2O`IZ_!Ss-aubYq`fLL9uMci2daXo7!<-*G zLMFj980CCbr#Lhy#B{6Hm|Jl&3V`YpT>Gw{Pp;(&1O|iNy0IScKSCT9U5p_R81`Hj z^yxlis62st?C-inZ{ZWG6<2V;Pv=~3oQU28JWsy!#_v4H!9e=WoV{F2y1>nKRP^ki zCVF!`y!6?>x`?Q8Oy%5|__ zJt`sRZtAN<`E~XXpfFZRj*o{VxesH!&khTjERs3X<8_S_t?$C$2{OoSX^m zpz>N5O?DA;%7@U$Zq=yapuzcYKY6^!Qm*VN{9`Zg3MWDbLOz|9l@)AkBN4tEujL^W zT?7b(>t=Ee#o>17&>?7QLh1MkL8_DsKd3cr1o`pw={{@;h>(=f@3!A55b< z{Wk2k>mtVKyFxfk;*(gjJ>JX5m)#H$Dryl2Vxm!Z+(GLO>AAW8wv;83C8+d>unueAgR|r@8Bk|`(7=UeHLN( zWoReA!FYC>uq|Rt52*)QqzzJg$g5(mio*~@$dO7EJI2JtRaREsd(i`FMr)TEzC7BI z+sW+Pzq>NAUDeQ8(VY>0H(G(?#JT6@v}HM4Sq%w;Amzmc=UJXm){PAAc`$swHQ2)k zc#bW*OpZG^I9OUH7du+kdKSBISNkL@T-z;UzA$Lv-MAB)=|&Y$Xhz-Wx^;=gcSd9DULvKT~#DE6M3k;+=kL+!E|Gq%aEuq5()?nFR z=e8e)?^Q#W#p<%z(hLBMAOJ8zx4MbwZSv0)s4T)(2PQ_lC|JDmV$8lB zJFcj1j&JNgw&i>mqH6WXY1_aoT_bLzPFMu+LNEpTmnn#W-Jh$MJ&`fW%pHkF8 z(&3qjAOC6Z%iSd28kNfx1Pw!lpkZwDkcE~6wfhQ2r|7Nga}Wur z@|dS#BpS>#@Ya1(TJgZLl6u98!xS3E`9+HsL4sJdY;o@WWmgQbYeJAL0CN786jK2v z_bXQrfgK4VI_a_()*zypI?pFk^^Y$&e=TQc-@0Rw|KtYTi0ogn^MuQR$nS`>FW29_WCz= zgVlclV5I7H_M!I(>Edk;Zpu6M&wuO*o=L3iE$~(NNJZE8p-x~oEC~>`e5BCF+WF}d zB>j%M81zP0`@||O_ulXkdjJIOT-oH2a@XB^7GX7rID?9mOk4hfKjOtx=#OB#d^FyOF-Rb+!^XTdb z#{J)gZc3}Qc7zzE)%wf-Qr12I#y#N}sM$DCMlkLpe>3iR9$=gfq5tBS7kkxR-2pqP z8qMee?8H>fXrWMD3a{*1Xe z`X993CUkTv+jy`~gTZzevk&VdX}K^;qqS_KdU{jre4{malY-i~>=E2s z@Wm$$e^ly-KV{laCfo;SX?JGYZHs=M=OCrYvpLD%Y}eM)&sN7oMiP-w|IYye9r?s${WLpNf)j@3``!aL))*>o_l~zd7P6H zeAS)6ddf?-(bJYaf;wBEan-x0msC=W5on7RR zw&1<*y=u${H9C$---6VaRTH1ohQ%krMuIj&X!kpK2mJi;3^Alg~vM9#bl%lTtn~P zEwF6e(`u{`e()>kY_5~dr{;I{>n~C{dWjFx<+$0tmELb*WaWS8y~rb#m22(rxS%$S z_WNR!aI|m1xy4;2d>ItU=2vz;u%0evGn;K#R9BL9$yb7M6{C`Xo$vIOfX`Q^@}vBN zEPvRrH^CJkeQw%LmV|>FNP-T=3^1oSj3yfxylV`u* zX7Fa?;o%V!Oh|@$fRGucn2#WJvis#hdzfLoD>87jg6jb48@69IJCOI#;k`3CIf=Cs zv2<)evT}03k1Ed3-w^Q>+6Nku^88?q2@eaiB!^cRp*YG7?w_xUSneM*4UEPa_;V!O zu+(hOidR_yvGW*iY9LZzoP+*S*dFtnP(?@huIJLAUqz8RayBS+8lAkBKsJe}G1v3I zYw*w~WRY|iM0|82X%%xme}w4|4*WQZeJFvIZSwi6}vDJC=rmi90SR!_1@@ zx({?0xZWU4W3Tf(T=d?|Yus^QmA4e;Z>aR&d5V0gjcj)|@AF2P$)$Uy)KGTL_!qWB z)az%@pX+9r-0%{1s^JZDYMkT+Vj_}NlP_yYY$TJ&30lwaC?*pV$-Ao3G=eYR0(tZ(#YQ=<^Fhs0;@{A$v%G0iGd&j^T9 zJhH|&Ljq?5bM1B*j~(lqe``^prC3vu$2kbw30OHwN=llV!KDUHy^+_jZD12F&(QYu zyIS=HGbxPk+bY2tb%Hz3gzbG9=CiVY7TylXlg<7I;SD|*PVJ!Wd#^UKKQfnatdZk= z=8*UWRxbO`#}JWsa&oe{=T&O3RZ!eNH+0A_>b%0eJ;}q5E^qdnjor>Y;J6`LKx(Zw zE%6qqv!m$%IN+itKlTU#wnN$9ph#2WYn~m4X)Au%-Y?}F6kepYbZPj+6G)N}E62Q6 zZm1M(e6*W35tdg(67u(|K_t~x7_|ENKA@3O1VqXGgn-CqnUSHO9JPe~CGkOWmsWT? z4n>kH6rms?94QD$JWvyD;duL%&o7!W}mHW69-W7mt@oi{`K&bVvz%qS_r+9X$BG)qfG59|& z;E#tn1M|n?TGgq&X~$L8RI}VOHi|{+L^o91WVqry24` zyw1uk&KB@CpWVFFCM(UYQeCh-IZlR)gGX+dcO#vmrzi>K# zet+3V?U?0x^a>;AxG!aPuJG6JkwVC-($(etVOf#O*Nj(Nh-DUN zL9w^WE7E0N8rpi)I*2k^aC3$|Og<{Kb?f2$UOGpCrXl)=g6~Uv(yC3sm zW;6HbKKW^2;LNTNY?hIv!IS|jP>w~j%83)7J3BMs3B(FsqdkQ^w*HuxU0${JnWDiv z%*sbnUn%kQUzU=LK7b1Ap2c1*(S+w~uz5E;;n?r1@!$5g>$_RAsciiF>@0XYCTCVW zbxM-Hux6>0*v^6_Wx{1Hi%WI$FX;^@Jcx<0ed)%>%RBz%i%#L$ou7>1fxn^3{3H4cxxV3zj!bPoS`){w^6R8*Vaf1M#-`9H;F+f3?eDspW}NNnnkL(}Oz* z>oq>V!NI}TuT|zYxCMt2gK^vs;4+4Gwvt&W9?T*?_QYLwHQNgqAp1Q`NT{lkgh{HqkQw6Npdfp{DjYcUMHTn zBue!TQDEIMS0?Lnp2xk1Sgm59rD>?gaEcq?E}Kun~E1|1+lbf*0U)= zd%wg6FN&(Epd@3c6dO=e%}ta4QvJqp&0VW*&X5{D$mG7k`wk=Cw+Mcz>>foyy%PC! z;%9W|a#*O&S_!TYE4aN;?7iH+&_p|-Vv$tcjLzxv{&{BbkgK)&* zlDsc*N$)a`1_LH1n-z8jISPS#F}?xQo<;3*L{jJC;(4(1;y=L7e^2bt?$L5y5*Qd< zE&o(&F!}JKD`V@YXk6QJ%f9+Q*Q;V1tQD9hf4q5frNY7cq(u1LZ(~9-OxDmJot!** zJNy}ETZGE;rAvYER2%!;{%%QH?xNZiu{(Sy!G5*W>Pa5#-3tsuT(u^5=0I&AW2mWSfu#-whxDeZ1!ZCT)!Hz0DKAhC+DVgsm_^HL{jJ0IG zwz6l2{DSPY87>7@Mpy2{xZi0^4CYxH5U2hfgSoZc91PpfRa6~V*yxkvD%&ne(9^I| zU_K4x$+@?1x5=~D1L7t~URQmE?b`d2hW7T7i?bJ6#kO&UwHj4oPD<2k4`b7a%Bu+GZq`y$BaH1JI0YTL^eVus?hSO?j-vDS$lm*R@KCWmaoWc7G&xb?Pp>LpOz&LU z5!K@vRL=C)=@m_P{D@NR!jD9KwN>B};d&r_aj9lD4-O6KsjJgtM$+9ac`E;DsC)nV zD(FIl8D4rxx?EC0o8Lif6M3r*;P#F}g%8(af1~HwE^%$m4``IPc-5eyU)5}wLO0fe zGG{JQXd0oo3GSX48}st@#b6X6*$sk5OQu+?yzoTZTR6(<>GxccHAJZhi(Rq!;f_m4D`Gpf#e+O%W$h^GlSL26j_)%j`f6yR*SOo<%v} z%;)ncv{QkQIvs+{X*8ZIFV6|1r#605Wrxjkbalj4U+t6r3v_RH&Pt7&jQJljuF!3f z?Ug)stODiL&=f`X?mY+B@#1AGHS_J*%-vC5%_H%%yqb3H&i9C!RD~rC&zDy}Uo9M! zM7&?qU-T&A1(3b+bzcg#6#@6)8$ls(pgZ}A1YL=MnSE~=qp?GS$Dj=BVk++6Z{J7GZ7L7lk4yt>w#kw&i&(yzc>~x^BfD;z4Xcyj)fY* zu^_ztJC6B+x|o2?CCs;OIgLnO!`d``%A#oEjn0*2tF&{i^k4+SX;__`{?j@TwNNWh zC-dTrhIBG{md>iwAGHq069d-_RHDnA`ZuXnS-TwThzEN~N&P|6{U?of2R(5A=Q&|?qKg(Ia?rNE$SDVT6ZNR%biI~5m63%i; zawnRrZIep4nF+|f_Pof|eb(LVgkg$O-Sg2GT>H<|g>&5GlE_Q4V|#*?l)<|j;e)m~ zq&%^b!ouZ$rC_nlUi!@Dn-sc+y)CLnhDkNL4j{d-+-KNz zV(UGQkU`Ax@(HDHlZ+hR4D9buFO9dSF>>d zuum}V?S4LaQW+Q)6m=2obUBfEb@6#VohZMTjTV= zdp$~Vz}F1Ck*ByHeVlFK#^o;>S#(r- z=Qf9GeNV_k&$=_BPG1jCF?pw)n4tUkIkyE1daIA^>hlnz^j7P<(CBT9y-twjV5cRi zsexG#df=Szxq<~Nb4Y=jYANZs0%R=*uvkO>!z?3d{aRXD6%y&`?ECuW<|-E6hB1WB z?V1RLg=LG77;kR0Kp=$>LCsP2YJ{5_SFWmkYrSdm0rp)~lbkEWD7DL92!QwIDin@3b^h(Sg*RDM*xH(9oFBabGM<98i7h0!qXZsu2B3L$V ziuYP9m^d;kWPK)PLOBuJnm z>}nq*n3(k~2}XoD--QBRS)P-^(gyl#tJ0Dj8axOMx?aBRVQQ~SL41$e>n}NiR2#2o zyKZJ=JcpDPlDXRj1=sx;dX|g}q^W#r9X)~ImmJD*$f*T|Ihi!j*}20-kek~apcfcQ z5Q9OnxD1jPxw+>c`@vs6ZH%AL>-u^O(O#UihknYtfg4{Q)$}vc`gL<+H)+cgcM`4I zFO=f~QV*OIrZUu7>z^j)&_EH$Rpr`pQaMwUWy_nFF5=ZbF>m+Xiqg zULCAGjsO7i%NQ#obK2UGzmMVX3$)uJB7*VvsJYFQyeT}IkBlD)ELfmjAT4o34J(cB zo-Exn2{k$j^a{5Qh4HPf(h4>P*kA~CMfq12`Pz5yA~d39_hh7`mc{kF>2rVzR`ueA z@Vyt@L7mV{VDtKbFL47737NV-TZDxHvj)4_|42tb)fw`q+SDg z4$Q8;H@VFQm3c*8y>q6Y?0hixy_`f1xZYF9Hiuw1Di4_uOQW>3v`&siLTc*N=%^MT zAYf()o_^~~24z)ot?APWIWO{RhTX7|@-oz5sH6l-5@3&E3a563D7kM4d=cbAZxL}9 z@gwTXSwosIJ#nVz(%K6s!T=4C5n>-hgMolt=)O^;&UPVFU7o#i*-?}i1qCtAjiEm} zN2YwwsjEQ`4;WPLN6|IuchNOl?kB>dhaW2Lm;=MZR%k#1QAEsDWGaS|eO}=e7caef zCyI}qigbzM;2HS$v4Y*gqGvQwxG`e zIa^7(BcdD2ZzQmvvY}Te^oxL{iAG^)ux^-qz_=4$Xzf`haU51|CGl=G4 z>qvK2-?3Sd7>nT2?A**}$;C$I$MdK?=3|cyM%MW~2E#ej=Z<Hk~7^WTkp>*@MfLaKuBheNp_(@FwE>+4H$iVi0yXuKJn)^3*0 zdS2~$a~IKBo$Qb%L))_fCE=m_?Z+>OHf6!I2r3YxC@qm(O_R(LYw%{<Kj>YcYdCLYms1(jU-q^p?oX;Fk2lm5gqfVNJ0vdd zEm(lDr}o$Mp%+Jm_D_iu722|Qe>Mkn9p%-@NlHxgwr&AZS4~cBRh35Ri^5A`3!paUb;j<(McX)~OlyLh)Znf~x=V6r zyEiRJ;~AbdW5&(tvBH1ip?E%(?Z4#y7MTL5LZVoVu*XH#RjJk!5 z+-$~NN5nML#{8@9mwAzBRjIcGvNEx~$<}kt*0yX^v&+ty){fM^p5iEN?w+s*JakX9 zIVVcG=%S;?z%$7iU_|hv)=*WAMw(2oK*(+GJAqdBLXx2fE@w1F;Tac2i#T5g8q+qGmtidP!()wKiNO{D) z9MnH8(+T{7zU&9Hg2o;slru1a*O?U|DzBgF))R6H2Qa)WFsAn7SAp5HukUKBCvcs6 zSG;a-7dt=J1wa6PCYVBD#|I8UXT4_OSqSW_r$({X;JlmsI#ZJuqA?QayMFXVNqXbg zGDjyj#wwh?%3Fj+oq8MMA&Bt7+MbUF92g97lAXGsqVk#}!ljHQ2l1JP{?1=S7hwuJ?SXH z-wzcOqjzQ>Dc}JxS4UD23$XnOZQPr&bxrQ^{HB?K6z8o%mD<%)u1Bj%9`}S(OT8Ib z^gC)M*rKe)jC{WA9bV=1W!51v>r-0@p^q(aiMLVqw=zXSG@9Cfe!#y$f|fdjZ;Rm^ z6XmK9Q>^p8o9Sq4ZWvv+5BDyN(1`Pf#hwUY(1TLfA`K?Cke#R7DlI?G-G8Fssxs4# z>ykIL1r=0pqRjdhc~g<;9li~%nXEX|NegUAg&0S0}aQ8kev#?je z-h{9+#{SeaG`pErt`Qwq!iI!EjLw42+8u;Pyzfl)UX_IcwQPR+0lpIj$dioc&FZ|-S)5Ejzw{ES4LEcIg zYd5RZ@v%FZqH;nnv8p%D?oEqEo<_p7nD3XJ;AXvABn8DULP7Db{{zL(qA~oyLI*$W z(fk`N2^&cFzCT>oNGa-0UVTm{CL_bYRT4^Rs$Sun*RQuvU8OGR4z9WJZq?8CQN?+4 z-IFMZWX`w*@uBz!@}xIhGJ%h~2-Q-OEf29o(X5PVypda&^yv*4*%jgn2D)SvkR2mh zW8pL3)vr*pnDx?ba28>_o2U1rre*+aBN6Axq_&OR(|3&dIL_{6>h%Bs>6_de8Y#p3 zkCZrL*sb3IwuF1N^7nDeHHUKtt-1<}6qs(7UHT%bgz6X*~UQc_^0t9CEYjES>TJG^_)<&g^fAm82^`z32&uob z;baVpTK$yCM){)(?GY}XE4z;7^PXH`7lA{0w1iJu+aMfu^6VH^sRF2v9U@jBv?2lp z0uK%V|HO<`3XyZj(q$qPBQ3lo>Z{))?gmSv>H(|Mi0YZU*d=XN&too-7YI3 zzeCEyNuo^y!yY2Zk5elQ>M$k6#>Ak$1b81usI6UnsB=vKQLxzX_H7=X5&s|ThmCLEjMbT_OIc}2G#SCojHCrZ zhR`ZS!mJ<-qxI#3Un=680MhgEDJUv-JaYSibsc~VeP!kJ3lseq+v?@n9C=efv-^L~ zGl~zq+|RN)pehh|$6L3Q)YY?LdnV9;z(TMZnuf(U4Xj#r6bk#1BbTw*r%5}tgV=)$ z9_*u0Km7;+Lvg@k^z_~VBQsL0rmg+3(Hs%|3EXXt*O|X(Y}!+d+g-D7qDq3m(6QIf zcH49psVz20(S2O9w_lR2y`ybls~f~Uplwa_~0w2XNqV`pt?Qyi;z#I5b{5RkRNo| zq1qn;+iWWc`TT4CPROU^6sdXkgUcE@>d7dm{NS=}UbW9v|99LAKQldbx<7g=f*{au zZUn9TxEd_D)apor`w?G4VREt-AhRjrEB!qb-n!JXG@T5#<(%kaL9%reKg9p-`d zzf*|b{4|j-)guYLZXS@Yb4Iy2S&snZhf@Ig?XnIOKt6qSD**XF5}GK0{AM*F=2ZaX z|Ml?*K)(7LjTLjXq~5%kX;EtKIL7DxBmp>Qy%u=k6(cN+(#oK>O9vt zkJcsdjJlHpoe?Lf&QPUUm2BeICL9bN$BzX2dWn*zuueyJX}rhWqQy~`$#Uhe+| z$X_T%zBmua7bm1bK|Z(co0nreb?*P{EP2e>jB3u>Z!PfBS(&a;X(dhcy8~)!Q@N37r74jr+qy@hGh}d)5-C1 zaI{KBpKd&vxgRBh(k89S`wo=BNRFScmcIV*&6lzUe1eX6_66g{9qtF;ew~}HtFPaE z@SqE9A(;DOKQ^!05il0a<6*RhtIbXjwa2V2EeI*2jeB&W>4cx3U+(5GU2)!rar74q zm96xi=jXg_(NRtzsYP0>KfKd}HzEazEYpPhrW4#fGNoOo>3@{3IVAD*hUYUq+cAa1 zj?tDK{^#mocPjyf2o;;z+AlWe*mg<B>)k3Ty%H}%A$_8i6IGM{qZ zj!KDA3bdWE(S?J+&Fu%4F|`y9brl^QkkcSBV5@v%m@|X*iIeEgr^&K*d{5iGG?vmp zzdM|D2~jvg>Ex+m|E&I;?4Ft|Aa;WY=+*QuQQPaKea&)R*l92_u%|^nAm12(8=UfW zbvK1sY_z9-sK_cCXJ`CZI@)Ts>*_|0$Pix7Q*?3p4pd;_>3a6auh-fF%TH*%E_Lzy#3rVNaYFkL2sj1{Z znv3|5ZVgJ=dF1h<~`XSUVXf zJlW&NHBlBKvlb+)BZ!%OPH;GT7A3>QlPzcPId`wwI{v^YD;ro>5JuWEPx*CC$%s=4 z>Ob>Bt@Q2RuSzLJv1zl#D>{FRGW`l2i_z@vPNLO2h0>qBk?qI}A>hGx>?pU`11_Hi z0*z0ez!6lZl_D5-|C>aH1NYvm-98s+kMOQf@5`zrr#z+o4qts{T*%CZh{0%8p z-$#+C&t864MOuR=VjK5DxrNZa@GZp#jL}YVONNt11q0MGK6ahe&;MvX7NQWcOEQ@R z%#8vB?vfHQ4vuRn10!*qgyft`NY1=GiF{3~qF!ssZ7RyyvvuoMoWNsaPa-uq9hvZ_ z7~dc1#J{L~Ui2J;OFVlFp3A{Z55@sR^)u$?NYj{^tBCa3Pc@IsP-lAtKS@qpYp21(y-;nT>3%KbXytcylS4Q6 z$ZSj9`70YLZZB{#7T%Tk#6%uGzHmX~WmB?RKNE__WpK$2cB_`IFiJ=+Xbam&mg(;7 zBua^35Q59Ru1?t&?b+B6)V_FuG<`EjWTX_`8+VEJY-;77?b%BtX!beuDj_8~GvA(_ zdDMq9kx(_Ycz(8LN1Vp-6E*E1NJfN&NZTE(So<@xSpQOtKIokNqSe=Tx7`0Av+&!p zxCW{#ND)eXE3w9Zhu*&UR_!rF#~rR6xi&EiYFALw)0a9Hgkc!H;QirC4+uW&-!mx- z?|F5W>1tK)F*wAmUL~9r%&L`d;c2@MICFKcg@(yjwrYs4%$tj~gPj&3C|G~VBQTz+ zRh@F%zm#wHx|>&XQc`eOww+~q!mWTE01QTIe5(RVGLG37*pwe&oP{mT^*P9!g3wy9 zy`W*_yLG6lcAbIMA=8a%EZs?RLRm?=IcM!Yy3a7!bbv3yD|5<%zHtD7vUW^eaHs^B zikp|0my7E|gk`otsXNg>w_)SIfp%7$_dr4d_$g17FD)p~Hl>tjBYj89#Pn==;P@(U zbJygxvB$O}*|f(UpEc@L9yg3N4T-*Zqr4mG_8Ex8AB2t zks|?U&bB9-v%`J0Db3l5TM07$ro>dQtBCDIlc4GWI-Vc4wM8SCM@`7y#a^aXJL?mb zG4(pWL|rP|e@BVvx<7pBwS0xep6H+AQnI+tDeQES+~Q~l{$=0A?|DhhRb&Es?&Z$7 zGN4A4qU#aG*`e)0cSv>%cL$^aj9*8;&6)zn=XkF{fbs440LH)Gn?d`u!AuAv+fO@p zVUfe$kC^Upy|xhzESasSijt&&@uSY_0E~~}{a#^V#J%j-)?EC z_dFQCO1esTLrvq#fL~9Zm}a;0Srm_WJ3%xLP>pTyL0}-6YBTp#JTObz`5TA3%|#09POs)xstB}RM1t>1?4-JJryP2L|7#^3oU^xB3QJJ20%52tE zQ)89AT8%Gnkyu8fgvX;b(N9LqX+Z5K+yq?%zFP9)Zy7vT|IlRV*5?lKc=6EHhc<{> zZwyEiL~Axt`~^cxa&k6Wv-8vDTeJIY(3<^z*WoYYH1n)D);YhZP#P@8i^UIbpix@< zqW)26!AscRzU^PLeDNOg#btr%3xxc3`v(WV1;*W>?=?YwKgZ|0^luCF_r3v|tBJVIBx3jl@aWb>Idm6h%*s8t(K>-*`$yhZ{hwIelIj8?S zIwIVlhzr2SwOQC)?fm(#fObz#J^Hcvi@@2xn3Ffz-SgHZC)FgL+Wa>%b-5N794LRm zQs1n93AE*6!nLx0W!muM?7tfEQAT`Q3VkF?$Y2e`BHN!{lUH2WZ6tq6K-l~J#b|IvI1g)Ry8~xg~muQ`N1ebro4-%!$%qy2!9a*)k9HtLs zayhP)J{HxjF%SSl(E0rN5Bx0o7mO2yEbLDbdC;?=yPv)~-F+%1hY*RBqfUrB^Tln+ zxR9QZaAW2^m=W8zyMN>|_di-AJVCpKUn5**n>HL$|7})qxrNYGs_)W;5yH-2{lkKH zPH1JAi?2&HbLCv}I>-1!MZ3)NQp`3 zHzPwOuKvTSFwR4Xx<9`XHo z%YqVzd2w95&xYM6QK<@Z46_eXK2Kl1eA(|ycb=JZcS@TXwId>&*qf;8>OS8)%B&{Km0vxHrpDiYDJO8r-kVnx;BWhm)rFrNre6swXT$-A4m zemlG|>D8v^K+PfHk5`M&2VNrqoU9OTKT+cH6-LcvS4xpK-$zsT$Lj?Vgyux)MiI|i zH8!GWJ)TyNkDy%G2iqb}vfXEa``@Qcn@83zAldrH&NyLzm;u-CR?wrbq8=$=!8TfjvjB>VpJIwK=)C zkXJvB@C;lo_mKK3?3lH}w@+9ngps+j;6&m3#W@c~<9)noGRV3@@E}|$`I|-C1 zlk-=#XRE^GEDHV0Z+Kla6-1RJA{K~-{kXh*%XMF0{Q?KB{Hdn)SbeR^&NHSQbs6G} z84orD)vn-?T`nv31V9m}X4A1gH*ZQ*NHl`uZs8UolO^WhZ7R$V_I6`tU=*}&xB?L6 zpOZoh@%3hl$y*{iO(z=Rr~uOkW~T|@W_##q;`gmvk&&6HUVD@!QKyuy)laxj*3AZD4S1C>I!v8D zcWw&(N*-=BE+7p-OhH9b!XVYS&W|5qG8n5P(TF-%=8YRWoH|!!Hp8L(&~6*3Hwf8W za~ycHQ#-de;#JrwBBmw=O{?VQxI` z5T%Yi?N&hEtIO5L?v!Y}DGmPSO|1oe-?A~;Nig|;29oBF`6u264fbnuq(pwOv|xI< z(MK9@>wlqg7{Vz1q@YFwOqymgI zQim0cYI0uOX;klHe`{OIbTp0}L2k*BO=@lu$O$$n=U|%0=)_<0h*BHeOtliFZx%(F zM!q`Uy4s`3nP+slJ&{K|@hgwWxnk{R-|a`MS~7~c^)}wQCu41Gjp&xCX^cW#`9=}t zi==!)@8XmiPkiFk(%5LaD-?Yx|gir^fng|8ryLXBmfm2TY$$K6J4BUqlc! zB1an-A`kEni`nfs-m^otL)I?M?ocAH;1Gfwo@Mmy4jVQi+V0R{)HOjPa@p*xOMCrsvaQfi1K>;c7&w)uWM>o zX@>4Yx^`1F(#l2xPnuC)?Zw~h+|{^4h|T*>g;rN7o(et?FrPrK>TK8S2|XzleY>#9 zeJ|gCK$CXYZxS$gj5hg-M;~fBj;n3a$}b5TF>vWq5-0IECfe=b4`bfIRvokciGQmh zRi}=y&3q%JwKG%f1&{VK{uxTCw{5nX7w7W237pgMEQ~BGre$C_+uy(yUB~J2S(dJU zWkP05AvbiozvzZ?(_bQfxPa7)wXC+b7EL>D|EIk(kEd$y_xP4NMWPgmq>LMpP(%{Z zV9G{jq9apOri4U;gi6KMfE1B=%8<;NH;O1Slt_jKDpQF2Sv;N7bIx<_{pbF3U$5Ig zGVHz9+H0-f`VHUD`#t+(+-JInhuRTu6FVfZo#UWf=+zf$orxXQ>JN7B4sae=n4y2d zaM0mbbm{SVbg3gtnO<;%m!=N;;1*V96(N?KdiGbjRrY~g5$Ufq z9X|CyVkh67F#I}E8zu&#S2vfeq@>P>gx+X%VT*pTj({=y<1Lw-7qx~i8!4G&89TbW z=K>($vk3_Ca)+%`6BAcWO+YEkg@^$3tz+*r|d`_ZSNK@!QN<4dD7idcLj z|Ds+0Gu3iAw3#k89Vp3q8qE{Q6+T|xr*{}0+cnKaeDh>O#vq+6kPV~TC~}R$36i8@;42(ER>fnN zVF;=nID7~)qe7dIkbu83GUEDKJzh3PNn&3dSS&_AHh;1RfT2$q)c;_i#oD zJfu@thDTXDw*AE}*(@K+>;6;Fi1i|}qCbF?Byt2m)ZdzQZH!j+EL`WqDClwhcIySz zNIwxjf{TlIWpi_LM}fz~jEv1vQXyUn!omZnig?RHfc@-89mDCih+ChyX}oVNFZrcF z0z+KeP2sC5=3sN7Pw*n>$uvp}AOm%NSH0r1I;zBEe0IpsypZ2of;SyiH>8SD@w6|EBD{@fG;8)n>vr{!>NwtPWbZ>?bZ2IzTZ0 zb*XTCB8yzEtE)rGD&Fwicx`58s@k9#LrO`sVM@`k?(`$qhh;yLhmFGIfQ@xLJqFgi zij{rBo)niv@uy~1(wu$u;p5;x=4NN#s(+90)z|lI%73~8A>zq2ZvjK~emYrIw9+oH z@k%Bem-^YC8~dVfv0dMce|nXoc;`ozUwEb$wa)NL4kjvnr{1a#{^DUQCK{}tw1x9B z3T@Fgqo@;gBev4akTEwk5kiAZ){@+{QKS&6vrH?nkE02qYy22SQzN3;dZgXfej=|d zPUc7QujW9G-1REjM$tJNqtWX_b@x9$D4=q!2~nh_)v27m^lYU76WF;Za_K_*-nr2f%yMsJJCnX zP92SAx1Z;x{-9~Rt8z^$>(oz3PJVadT31&Wa$3L=SN~}ctZzRUooQ|$`LFXvvrE*L z54=%LQ&0_{zquXJ#-#We_Ms+7AK4XgAYX@v?;bJJIEo;MM|XhGx>LhS8i$gtFBjWu zpFS-3^>?{}H1soE-ZSL}p^zUqQ(XkJsb0Gly>YJVE&u!*edKXu)?b9b$sZ`X{;}t-n^)3lILS5ITFI)O1f%NnW-(GWYB+bmY$G_l_ zgOnM{Ymc_PyYx<7;xQ9TI|_SE~4OHu|lqC)FM6Mu4`frb0gf=y`a~Y za?p#bnX2)U4T6*-dfCDX)S zWl^E>u9W!2oNSjr6D&s-vVAZ>9)#{0>=YecT?*lri`vd|VOAyp#a=WH zgj($NQ+H0F6@cUzpC6igR5yROnYMJEf%5^QFn6d4wY4Q|8}7EX8Sl8AOsf_P>Nf^u zCMhmXP+mSiI$HTb6Y`^jG^*(I7Zh_-C=^iqQC2xMK6Eh4wsB;j3B}BakQ5?%h(@Q{ z*xgbPYyl7?hH!#F+ipoV(ls;F+?)V80OH@>$ILeZ#lG;DYEG2J2X$n{e z2q=&sJE7K?$7lK)e{%B5e;+)sZ)JFvKU9t`B2pc-_qmR4#~lF38sWkomC z)YZWVz;ntxt%^t3c-eG!Tbz4;!-jSzAjjfWI&+B&|wFuweTKea#f zp2dNHyLzcguhuymJ;`k4wL6dctV=;bO}5gAUDn;)93rS0c=_@RTj|Z4H={5RoilDT zPX0EF>$V)PQdoztSYBGp$ujTPo3jey%*UilD)cD4d{?+<$sbxlpQ)vrKu{$U1l3sf zP}&y4o=Fo661%?J&{BXv{z(@N^9lw^OE4Sc5rRR1F77f87U^|gS8ndMSLXK$Fb%EM zKPo1@FvKgFGACbL9+`Q+ajivhLYF|%*z!4dUp8?lx2Ujz8ENk@$m6v%;xs$;H97j8 zRqoiiHhQsf6@lG0@G7q8ZKD_Umi3qtPWVmnDyzRt-UrSu?a4B3n_asG_WChXa-wye zhkNGDibgeuzx+)wC?=AhEs8MXleyii_6pGIR~O34QTCX+S2ChF+IEero^ES+;O>~x z{=dE-l6#pn<$jj~WY#bZ`rabdZ3qdr>7NLnLtG_gvfyp!j~Z`l9IC!y{%v=T785&) zBmOqF+Wdii@AAWBIqIU*rgBQceoD8r#6^=fzT06Sz-w6}E%DieLQq#@ta4AB#c_Z5 zgStxKQ5`9*r^&#hHoVoem0m1c^58j(wUL0i)y^eEp0Khvt>ct?14DkEDSJutHw^l5 zNuFb+JF$oe8pb_$m(4bP*+_aN)?iaY6%ai+e+80T>W(p9U8`HtWUTBH4q_FoF3j48fAE~gH31%QNOk^ zF^|>W=$WFW0NN6x+YUUm0Lx#gx0}oow)X6l4*G8x6**3wCy15YLjEQ+Mqf6uv$I2R;D#aElfEfq zCGicFOO3u9HM75!bYJA=CO}s8=n}jii#wc)(^U!?5xG~PpOtg!l(J&R+=o{aiiYiH?i znj2~aKOQKkU_ba%qgczz-;+;aZPozrSyEE6&iWy^Zm`$@^$hw|WH0BR8O_)6LMzH8 zGX|X%zpmYWG97#Y0Rg{`kieFgSGZ(SQKyrf>;SIH#6pPuyzdHAU9-QgiDoTXb+u#8 z>?bF@$udm`nN=PsBLPvZqkUA-RKchL0OXqO-5ec1p;wQ9C?pE~kC}Y}uRqTd8+-iV zENgjY=Ad)udXdPA^3o$m=oc(N1_J3p=yWge+%{I$=ze)%NnR2gl@#auPD@`VLrGWN zL%_yAPpA5bdcRPdq0En{xS{;3*77R4A1vnOj+*sLBzk~MIaHoMG8v*pxEF!P^_eU z^TkYi9B){5cjm_e6!lNsy5Q$`nPYP|SZScF@K@bpt# zO3B^LFJ5Tglu+t@E-@hLCH-KjOo2-yi_e4SRftcoMnChIon7AXw+p_9d|XTSeoPmZ zM9Nc|%C1`4;Uhvq;*XBejd$uHb>xcFQDG{ia>#LCS-UM02orQJ!8M$Dr4N$f#h{>` zsEjY5AuZ#Sw#9u7WhaP8dI@?Fdmt~fZH}jqx1^Z~Wb2RVhB~fhdv${M%$YN%cJ6zV z9{;k1(bAKD9%~r$vQvLoP{5@pB6}?@H{A0zN^AMO+i(q&>IV&5rT4W#Mk_SF8J=M^ zsQq-_{k2C`PMWr;AI&stn`)Y1qfyXUfvtwd?}lPWz)H=VyzO#?C|jpezl5EES!aA~ ztZ4kT*Ug6=3uBM@xVgK}XZ$GnDXbEj2Fhmhzr-ea(wrr-EyLgH4oo81J%{UBJ6WY&6!} zDALj@VxNl)AQAS#L(xF{~p;eZxFVH3J_)&A&GOkWsYd z?8e9Q>#0%$3#Zj6CK;RibVdruPY%ak+y*^2U@%xtIC$_4I<V!{6iGM zN_|8gu^pH3trG^YTcn!hTTjtPri|$6T<}C{kid{sAQ4(H{A4K+{c8~RVk20cH);dp zHh4b1?zSf-4&lqBbnoPt-P|?zS5CG*NK(;t+;Z@qK{)Swz+e$zgpv@^Lnm;fUJopg zXf=kiGH)(Tmz{#fIu}MgDXE8AI}tB1GFmf!ZoiF<5Ey#3Jp%(ZW|3}X3aBE93v z6C(Tg`n*WQl3isftS5;Nbw2H^cV&^tH zwHHM{Qr=v@uk$BtRl%zIYJYz}hGbqJ+9H-><~WqgB$C-Cw0VIzz4ZR#bSzO^M0$G7MHwT}-Zo|Je z(9;ZLbWuydK>g1hSK_CC4JQ6MrThOc#V7C|YO#q=?Vov0_~l;#vi~?~j=%kRa^_D_ z75&VA!khe`Jx3RW^0i4_Rs{fLplh>E!eS@ZelVA^fSx{K*N;A*nLeOsz0ngu*e;k$ z=8D~IQkO=_tR88+0@IoxBZ(TMPB%r(=m8JT-G@18EK&lQG7~&I5sUzsMEwd<1k*$m zBPkwu0QBj{Fca&8Oge50%(Ms!#zC-h_wHSACeYJEWE>-q&x3=LRf;o>xXs(!V`DhqO*Vr6to;T(3zIv8Hn33H7}QXf^R5BC*jiW9v~F%-u0EI4`=Ku zLZI0Jx<|k$IWNC|GyCHWfLNGf#R{#aV2E0*9Ke9Zvs1-MdWpbLumsP}Uhal?-n961 zr2IHm+A$5&Yu@+h^p8pGyg`cb3!jw>d}kJKWh?0%N*InhjjC_Q zr(-B7LLEfb*Bi&G7TZ086fWj}dv_eQvrB-GfpCZwB4CI?)>n5UQi)kOcznFYRvPbtp$Y8jLm2D;?vF`9_~yX$B9vJsCQ159#O{3a0~JIbtmL%f8Bfe> zV|ELg8zy_kM`8#?e8zywtPtXe&)}RB`Cx59(t5hBY^3Ev>(^Ugq@vADy1J+%elc#xf3`mn z_v*FNQ=*EBQL2LY#Ud4&5*c3NmzeGjt9h zFu=_3V)s7xbDrnkb6&qc&;GT`iyh|siR*gT^?mnPS(fwy%>^PNB2u|W57dZ=PQM@` z`s2^vPs2~1o@-|#Iz>bz_u!s}N5bm(c^!@28N$|?o7aDjc~4^fE+Fvr=w}RtGjEBa z(uQVVL7$5H9}mwC=ITZTzKbMDvq_%7DseVl?dkG6r@<8Q$Rp?sI9++qmf3lj&O z^cG}qP90U(8mp*u-gfSAms*%j7|!eI?PVb%+I*rbHHCw(aOxt_3o7w5=ZO9!I!y)t z`TG+hqTjB+I7Rfw3!?w~4{aZ4yq7Ps9WHjLKggInuJhhwkn%ff6j@~S-RYjF^JbBI z|M8=J*XNgKSr%QYjSYbgP z`)|mC>BW9c67Utwk4#LaH&6upcBfPb{H!UM_DkHU-<$cTnmn|vgV5xW7^cToI9NG4<8>N4^Kg5rN>fF`r1etr%LQKVJ3cl=lQnSOyxv$ zYwKui9+cDRJiCr`UcVQFu|4hTjsE_j(j~+hh4Vu0pFf8s4&otFj=60zS??RoYuwS2sKObL?Hgclcb){m`uf}n4o zlT*{t(aCh+k&&6XF#6siWJrL$?op5)KxUgVB4xe&N?^HZoL_c?hwgp?Bc; zXSw+vc$2X6$2~W-Z_#kz>quvrlNlDq6EQKm8gg+VB8qGUd ze;KA#iLbAlO5`t<52I3AA;v&ApWOP)tw1?yFlS**{Ew`l2E?v3= zJC0r3nr&_^$(Z+Y($X3jG)8bwy1Bbol$59^?R@$21=AYcI;sNacV!^|-o1O6SUyjW z!<{82snHeKJ~-6D6D5zQoDv zB;pH~cCE6ql9X1cElJ|=2c>aoIesNbuS1o>@9NacY3j`@Y4VXA1l%A>CWtCDGLps8 z?D69+_+)OWxTvUW^z^<*`&&@1?h{lTQ5O z>jvJIEVaq2iG<5i%v9pu-d!~HPm z$Qt(z`LH<*rax7dTrOPUPwG?*`-=R6r9@ zg?)nSD|NV90+m4?qYi%)4jyK1?g+Pngax{rc~0jD#>|imO+H7^RF5a_yg{sUK(|jU?p4R##W&q+sK0%*|Oc zBdQGN6vLS12szVm1my=SA9MEWilJz3;h$*%esy&W6N2Tx{cxc~=_oJQd1 z=x9x!++fAn?V@G)AgZtCac}~*x3}HbMg$S~qtOZn1a2v_HHw>mto8YC^4tdX=RRAE z&touhVSSa3rpL80sffDkQc}i;s;Vj$gZ%#e!9hfKv3V;LgTWll6)3v+%|>#iYhTFq!Vz&1QBkZY z!~=Y0-BE4ToP~XLE2F*yLx}1zVSd<6)fc%3Ro8hFBukqZV zSgVM6d3iZB8{Dh2Bo|0Xcnzx~qN6hj%?T#>Z1$tRY;}%7J1C+j)q8()y2^P$IhOCC z36TvvLvbcgT6hG@DnL z`T4iQRV2HYTy|Fmqm8`};l6)lkg%ru;^yWCpI75|jDthKQ3w@KJ9E6kqMha-2}*wS z*lN{6Qn(5Nb%bq&vhlg!nxCJ)Gf|9^f}-rnx6`dT%JTAI#$BZ34r`z0@T1` zC=Jw-$7Qx??DjU|XiN5B>2qRYV#9Wo8i_W_zB!nd4JB(bSYSv($)iDaEm(%BdHdxV z;*X3{N%8UCa4-Fd$$&YBn%IE(V^`fmpf-%TZmWqFOB>C_X>eSfua=H( zi{*#59P{Z@q35;*F)^{Tu~q-)d;X`+oCADvLs-~-=KH%qa#Fgxg|Oe)^eO&5>a|hW zcWg+`S=mzzs^Vw-)QHmX_Ai(vtH+M_E}JDG7;9Z-!Et zbw3>)-ARWpnye7^JKERE*Ao#F(}okP5Y00^Jw2baSL!e+dGqEMKtAx>#YIKgnz>O> zQJy@Rs&t%9d*ieiBsr8?pD6>MQe)N<2EeMax_V@2sIsbxlwNe_WCN-k0h37IzfUV< zn>Bw%d46bkI8t;8S@&xsO)i{9!0L&O&1jY?Q{zTqpSIxuJ2X!x7Z=-!+Jm7Y(=%tz zMABv(RL1d8{c?wL3I`6G*+fxa&T{?wlabPA&@OU}>Z;4iTz8lI^~N#`=A^S0%f#@&avKsz~{(LG%)4-s1Te49sKQB)vnC2l) zFGw{Yi0IrCEm+r+@&te(A|fIoAt6rC#>>+a?(9YYrO}fgjL`1*; zNpyzj`d=J@=(pQIUH~pQd;x#WOQ_cv-Di&Hc*yv zWAmd9vlu(r+&o?9?=#ia?u0h9_4k<{-W;ahzOp+65s|Z=p6zrJUTX*2**#1@vt%`M zKjs;F*S={+O?YuSt&dgO4C1eje`V8d`C+k>jC+Xxoqc7CM7)3lPyO7)WUFGbDc;9w zO|SKSg+_h7GyHijW53T03LR*)nD;LWv$^^xZmFaBc!WYoWkbV^n0qmgk>Y6S(p&^P z?f7^m8EyH@L>*oCn%9oTMA(^4=ECfundN?GIi)&a9NpciHB=Hi3&|sul@BZztOEib z4i6vnG_Lh)*Uii{$%Uo$F4>Pgnk-vcg^%m);RCvtj@)n#rsU=5Zg9#}|!F{;7}j5@Tc5bdo+IESQhoaLd0h zEKt_(YOO%&dqh5;G}ZS-SNnF#*3M4Q>ajkMx^`jNUo41O{0vRZQ1k?hmMC_GoUZbEwFuE>TGsyH3u1CV)vz?z z75qY3Pp&pW++*`;o!1GG<*(A-UB0KK#WXXk9<;6^SH*;)Wf89UY7h)8rKp%@tCg3M zMULjq(=t3MSErOzD&NG8iTQ3aEj6`>%^(B)?I{J7PU`+#VofB&P6G*)@E|3nI&CLp zUS93q~}iV2)tNfSZQ@xhsUNkn}fXhJ4z6 z-~1`zU)I+p)Ah=}j+8Z5*a#n{hLi)}>@0p~-)|ii+5a{s#@hHTqGHO&A~*_I>^<9?zjRKU^nX4WY(xbmQmjXOXags*N^?>x~#b0 zEESa`=B{uYmirv2@(;B2gM7W&0;A((;dTnjm1@^`2Xnz02LuTx7gzZcbFH7(S4c$M z*9YK+VWeq<67D|zz6`Z6XJh`1{`ObXeVRgMIhuCIIyx#7}Vjqk|a`$jO=lT zgng!5o54+fOZ%UQd5rnmSZt-U<;?z;gyKMY?^08UG3v=T`(ufNgMc^W#-cv^JtJkZ z?yFP_s|NX}YvyOv+6VWl=*5DPPg$B{c9%*#I+M0>-u4rUlZ7|@8UyzM1sx(0b@r8x zjz#uuF|MJZhdX<7^Vi0z!d+K&(A}xp<@WTZj@x7mwVUfT#tU;tY%xf~L+9=<5;gA0 zggsQ|!6Aa4*4qGunQn~s+A}IOY0RpauvSm{2Bfm*XJ|JyHNn$#1WJ(o&>tvM*%`QT zal!;bFYbCB!EeK#n0I}`*M912`r)2R59;Ig^c$(wVdr`arRJEJJzLwn)@nXZ>rdu~ zdu*ufjzJtkdb~2U-sj+@QoQo|ghYyYs|=dn=c^5a2bG3A9ez)4v-|R3GNBsPKuV7b zQ?AIn|7L+%POp45S#+H50JXg!#etZ{#uwJ(anbkuzc9-cmy}T7EzCdag6kGU)$O>@ zu@%kJ-qP}}*3opLDfkp@!n0?wgrf!Rz zDK%+c+#}*#&aG28+TCs66eQe{l$hQxGgf)WVRzY`Rv^o9?kDE!*D|$-;hiDu>F;P!Fsdbr92pHE+Vev`gK*;3bZ z)qMM;l;h0u)-39oK<7^+b*pZfSxZ;Nq&=_0QICR>+ge;#-mpt5=lc__z`P^rIF(tB zR`^^kE+^V2)8Bt>)S<#*9QQ1EeikaHgtr^YGAUd0<#E1lFBH?&)uY;4X|WoV){g*^ zXWiWhUt8Ptu~S!Aj}~IRTBCx69LT(O`#XW*jRjH1?{>a~wnNzIr4M61UNJ_(RvP=p z3&vT_-MdAut)1yl%+|^s!TjuKpJ8hYm9OuofZIedvRY5?xG|G9^2Bd$VHsIGy5`BPj%!?0b}efukk z#9_?+`71~5!c5T6S4%qG>W-XxDUDqi#XWRkuLX8A4~!pracEmSBrQ&Y?JT$ZhFbxO zU1mR!XaC_Eqfu>4x!YPd;ZVxhnd!P2?9c-Llx4E3?@~G|>ey`|+L%Z4SMMb8*Kgkj zdQ0$GP{b0w5Q|-E6A!d13QCY0#wtM{8R~9MZqJ5fV(#4WIulpt+YePfipyksUL8P% z-H3R;)Rm>WP3bei^VEsT6$nJJM#}pSAA*ghC#rX~6P2~$gSy$!dHMO0`%_ACVXzRM zOFbJnA!xe;1%|$GnK;T#fLR>c6Z_u&IhUCPtrUl#{+WcRPfCc>E;v$6`sTA|XzYta zSG#UU@V=j@4CQ&(Tjk8}xp{nyTAw7ej#luGR3HX!+4m#c-qV=X`5o)6^lA^(R*g#G zrfJf$Z3EhA?BA$GzRpnI(T~#fh4l??rNK~zKJHa5E_W2y2hpexjqVzq{D&Ggwe^LMUZO!Y9wCI{p2f z4d$?y(QR$~+8?t7$;Hc08DxG`-&=2=ow+O%Vw5|NE5V~PLmL|8MV2pLmgKTL4=^IE z(=k=X^#rALO0F!g8YuZWEJOPymFk?GGvfuhTII!9wfT%!Mr~cPagFPWB8o?6x~I1U zqtfTHx|o9M9X>!>jMYB-5)csZ11t6#W4AuJze1mb&D3JZdFQw3uR9DdD?^1~q&%~@ z$aLnMmDisAvte}Jb5_c#+SdsQcwEA0IKR>hn#lc>>mn|^29I*pe&`W8Oq_Ex4_EjG z*G8Ktn*j~0>n2w5d$WWlzj6Tp7;Q{uH`r09mA{Nr31nuQ6dJ=GED&%*J=yAm^7S@5 zb1gP33Q@)nM=N;c2t^&}UrU){r6c00(dXxE!ZKBiJ6rkjSLeG^qe(8jzsTE6I)8|WTni}!;DPEC-WO>4VbI;LH!*rvV-@XZBqVmE9p{)CGzYp1VK^r?ejuVTN zs^6U&6yJ;?em+^BkI@c+JC>(EsOxEZFq1hgtBeVW;xLfozln%^jsIe09*&%i^zpPk z{Po(j8NVRVmm6KdEEmml%y4B}%2+upTKlepkwHt?;jWFTrKR)AfW(1sN+dUjHDRo3 z6r+6;BfRu;0y-dcn6**rOP9P!D|mSZ+T+SJm5q;EFZkk0i_6Lmj`!N2*=qiJ5G|KZ zHh;#*X>XFF42zDGs;#5tGFTLbW4}HwK{!$udp@OhFnQnHFqXo;HzWM~dBQrPXcq)-fq7xKo4XrElNXeW2U``r*D|)ba8RB_KgU zugZdk)?n!~L%Fc}gZW8-pNwBoshGWZfWY&!A&6Q>CJEt+DX8%${d+(^R7T&+Yam*x zV0*&5TOsPmXF*xZ$Y>g1m67l6cGzSLx15^)Il;M&%Ka&S?)!mUQm{i77WPI)Ci3V+ zxVf1sgo)8Ze{AUyKIG0^f|$DvU}j)fvLAzZcm^c)3=#!x0%`3B;42-_G|jaXCHoYw z&A0Esj`n6EQM+1m9f|M4nA_v}n+w}hQjlY)ZDIrgIXkQFZlTcz?Fa>kVYh^!}SMR zT1Hx06Z7qvUcZjMWMx&($r3Sa#t|pw4Zdi#ULCSO@k9`ku5B?6O+g1T^}T4c^>Uw4 zvX2@%oMRaJaLC&sTE^raXx9fx7VXQL6O>FlD}&YQnHiwJiJLa<@mn12#wFx=I#;kN zSwq(zlnvR5;_(#|IdGG12!8QG@YE@roxKp+!Q&6Seqqcli>c|onW&`5NMVPGkFF~p zY5bDkwG}&D;omb+x3S5+ao&IJ72vtA*k`0|#`6mm%q=dVP; z(-I{XyC-MNyp=3Z&lLpP`+?+jc&`r^5*CYdg1+Sqv1D$FOqn#|%bhS{Va)VmQGCzT zR*zM}QTYU(a#7cWfqYsX1Yf1|f(5pujW56Ly`Z%QYzICaLEt6b@L6Vm18%^I@x?D0 z=RK?>xLQYATJTffTJGI5O!lj`0BmJFSTIrJ-g<_ZwQg^sl|#X5w0vtI-?YZf_XHY; zG45wShbHLgIBU_dI$rv09D2rGk>Lv4VXJ{W$>xv8xmw(_jJvspH8{ZAnJS3!#?+6A ziAwEpA0@m52wTlXP8$HOgL$a*bamSkM+y5|OH48@wgAHX{q11K;{_bodN2n7(KzoE z8EjlHDJt@VZw<&`ZM4EwM~4s*QP`h5$sJpdOr*EKc`(~}()=q? zJN`HvzMWmEVexeyETF`{A3pUMxkfdZtBlNGFv*mK_jX_*;(7xmyUr z!r=;%U&(K*<57Bn=hU=WWRm>>B}rxF(QdXss5%DaT%V=&chhw0aGQlDoJJlg-(vX< zOd6T;G;?)Pc(1j1hY5h_5f%EUL*85>kk)(etxI4#UnKuoRdsfH8sY0}T4CS$!JdR@ z>K@Yz#@B!*jJ)l>1(I%2@}yxf6d9S84wFfn*w$n~>x~oj%ZuHHjE^{ph{!|VV3c%1 zgcFr1DWm_OjDF&R3J%_dC*Ilg7^`QxNc5QF^!~jp?N0ONkB*$$45F^fBhYZ6U@s54 zFO5|V1JSFkBP{Hh5fQ2UetQ3uCx!y4ZFC2pPC2#pe7W!NOFWUdmj~&8v*A7H&fEnt zvHQzImJRar=VQg(?LFm_Bz*BR#`h~zh+eFUcM=8=eO(_tKLOUrsb%m%YzVF z4XuB|dt-QXR9lUE4}l#%00)o!!UZgLrtUSykb%7My%*KU3+%ZF;Kqca~_7Jog@J8M+YEVin*`n z>6JwoYY}Z0`dnm}kN|KyA3qeoy*fZfd{2l~z%@T?QU!e*A2 zN07!SkPEtj{D-QC@TgTicV%^mAVMzcuf?9ZRC-@N&BwA>C17qERz zn?Kr({z7h~^)I=Aar7ZLSV>7~cXJa=a%tcw%yM82fblm7em(bwr0cQOOs|Po>3pCVk{=_H7kZcG;zEK@cY@?R{`P83PbKsf43BSS3oqx~&9Hb23 zh9M2~{`~Vre7wG&U%hIyWk-T1#2XH!p6m@GiR{KIi@&JLy(Mytfv*45p=e?Qp|heXcuik$8g-wvws%Qm23)sB`% zAPz9m?CKe-@HfF_)J7Vu4I(FBoH~;MIG-SUnd$PCD@;s;1ZsXjhF<+j6L`mL{A zzAWmtx_`Vkx#CiN{tD~sfPf_z(R{5@u)bqrVnD^F6}IQnD_uM~*cMf9Xl$JH*>2xn z=ma0VGhP^}6fXqEV;CcD<^!*h_gYzRrpjcEy8|RFcBtSH8e9b1K$VS-uTGK z$WNbcTXxWS9>NB~*6JApSytb9*g}fmYGy3cTLVi-fJO;^*L`&;rQaHSLd@)}^8wZz z>H-CY;FGT}UcUyIn6SUU55WvrC3bh+%w9;FxJ}f0!g`N=Q_JNn`!*l=DCOIHMur(L zDD>&wR!>b$%k=W6kKNbD4!|KHzDV_0N5{(Px3*mcOwMB}Yv<#B>oPE!BmgxjXP)?L z)sSRB8cTUbyH9vp9$+ynHtfU30pA47>|2WyR`Xp-7%#uQW zAX(JTD~%Pux`(v zZ(^~_Lq)SK;cTRg@Dsp7MnHL1*Ve|z#vq6S_JdaOr`p=up&^|ERVb_A)emNGCp$cE z>+B49_l{b^tF)p5%=+!0p-kiB<0$y1V`F2Bi;G{se(gvU6B88$`=Uj~MZO;jfKi>7 z$lbg9IGhj^8}P>=)d^tHCOPD~i3c3%X6b9=s>qsBJO zi8wM>z4uA@{j{+sk?Ks|TR$id_ck`BHl?I&bl?QNyTX={mS#}zQ#HqP;aJVMtR;$b zXYkv%X{eAPAtBMs?2?jxaQ@+#*^NY$eFLwlXR-iHt?6WCW#!n|STKWIT3XIuxDd7xL7?yZeD6%8p^#jP zf4Y{owSPNlsFS~@a_}l7VEQNPU6y(v-T~F?TrXeT4z-|l zL{(@-c{$ImTbnyO5$>hs>ouus%2JTtc91 z_@JEDif3k?2AqrLx;Nnm$%pxo>h=bZ#)L+4so#G4jY_FnS_GYs-PsWb&lqf0R^c!eW>C<@<7B9%oa+9VFmifPjf}dv zina)Y;dHmIndO}}(ol2t)GiXau%4bCh_^rl3-E(4Ep7bV+}w$`of>$^v8T|UQ8v+At4qA`|OGx0F z#kRIKC=CaYhXXJI_vXz->dVyBRR{teRtK)IAGU=J%L9+}@xuoNIk^@zS`UJY zlFJ&!6%~v^wmMK4;OlGU*14`|+1jpRF|3jo?b6^B!M7M59yVY8{G)#O#(0^k=1q|%Cef`Wp;&w&Sm&{Nf1>IKX# zB(aD#;5oO|Arn4-&GN#dRP7tYWoTEM;rD#*(j}{lT9WU%Ij@9FAa8hjdK!XJ4FCdl z{7WAU@QLcqxhSY_Egc;Ul0KD37VlvzRv0f_yvW7F!ys&LU}2H*=1nuC_ArzVr_Y>8 zOh|whj)2w$K?%rS7)|&M6`BA6+}YbhLIeP?hGLR>L@=F5u|<2FvF~m_gd(Q^5JEu( zfEZmJot!)wM9uHDvlt4P3v2r50)1(o=>7S?pRcl2*8>3YK@`!$V;_1C5VN&~1^3-$ zO=qSj1;)$T0p`chVo#3tQN$oz&e2h^J9oOrNWQWh<~p4$opT-M0vBhH&-3Sn(>>Oc z^>z5CsZQxQk~1GaegyX)QV9?h70|7za*7!AZyO)?g-|Gy?8nmL;tS``tLhe;$7?R% zJ6@}p^z!nGs-NqDJIBoCN0XGQV~5(<#7ZDOQ}}HN3U;A1}c2ftsO=ae{<*$_!w z7N5MIIDi{Ss_vQa6!Jl#0oe7(i!W=**3ofG7NqcW1S}xd@^c)54#p0sR|V&B`aMJm75b-d#>7M2%#tXNumv+ZJ=@e*H3Lc{wgT+!=B%@Ik=cE;5Mk10;j3vVu*U z^jt^;P>DW?CjTRSc=Z=|z=tx)g3|8&YnA%)<)?Ob60nYtNRkiRX&0V+6sCdAZNbo& z>Dd8phGZF#GWc70`Go2hQb0NWA`fYQlLt&Ps-wLfQZ^9PtPp2)-jmFdMq?OVSuiYNB35bnk{ue{zpnYO$+FNcnDm>v~3dv&7QRj2i zsVtpazdmY@6P$!x52T_1rbry`^kV-sx3mQRqN}%;pMwK!AacR~$>P_SX8`lW^O>Uo zlNF$xd(z~9UPKY-yCCWbxo=LD1XXYOkA>2fwKeZcuSIBrqE@jQ7lF|s=9)85ck~t53 zkxMtUO4F{CY-JFM^yzrbv4r_t@4*HhSV=MYf<2>b2-Mj0@A0Z9)`UFNDHYh7G`D$M#)b2K{J9=9OVE!1!&mf2O5AT zAt9Y1QuwTXYjV1~pS!JqVAq>AljUpG*bMS8GA2W?wN+o{;X_iY+t(qj3VC8l$$D5k zM@L88<^RclT5I6sozMtTejp9~u!hiYEX>TlH#eWe6~T08YrOAXY3Z zE6dA^gd3ZaoeLQ>ck=; z^Pu<$+l>fVv{88~zVi1!iL(~Wsl}V=6gx4slX`gKcnH@Mle$M&f<`jLOU$kRR8{Q; zegFYRNT-W&Xjs}OL`J_fK|#XFChW!mF+iy!AsU1Oifef|y&1U68_q#A^j) zDjRupZaW69-4SWH5M28|IN986U*WQv@MFY-7*4doHWrI*OB9W0miLrFS9i2?k6aLm z<5+Sr)2WwcYUOWb(Jq@7{od$wUQ|koUrGuEcm$gaQNnJOHQ?0{qBZuIK6~6WF);z- z2g-nYT3b(&eYz`rN3JW71f|6<&CNx`#BucHJN(m>vC$=);S;*-MjbFkJ84*CIDk!Ajhik zYjg9lRa(_4UX@9u`g67gcIcCn{U6L>*?z3jQ+R>0*by; z3Oy9+Q-ML1no<(GN`i^EBGe`(V6dVti&GmLkeWf__V-I|29*iePkDW$eTQw?>f$W?@pCvx_Q&0Gl`y>`dOuJHZ4{E)5=tI zb(`QuPr5?-N5z&Ah5z|%X1bQTo|;lWD{e{N;F!8xnx7vI-xcp4^zh+B0JGRNC|$!N zBdn~f%fiZ!9z92Oc66-6SOSXz=xY$|>LPf;wJR={Ti`F0x&TF^>@!c%BhZo}$Q5O^qhgaTsAVw_y(n{-_?P+w){=q?e%t<$407%I?sWu5ZFmO8=Sy@p2c9(i($({g{g^3k_ z6eqJf1%{)Yof}Z-u)n^2`xa>01BxY|;aAmLx>ecp=f<95Ig4>pxth6dn^X6k?bKs* zKd@g6z~1HLJ>!d_qo)VK3plHOx$U#!Q>?A3_K``@yWrk|M*hs)oZ|B3FI`HzS1g@g zx4RCswypq$0y+N($OSyi?tpJ#I=k&>pWAWbf5W7|x+?eGuaV?*&L0L|rDmIPQ}~JU zd1)vywYNL9oGhte@N1fr9Clc`fq?-e-|oz`nz!P;Fspa2{qaW^6yQ6iO*-W@%9$&Z zDAdt#&3k(qs)goez(;GVt2>N>=NR;GQ&1|CegteP=|o~6A`NG6yvCiEhX=$tS8Ho) z7z*RaFK45iy*LH)R@zb5xb;A;b^t{jh%898hUK=p(0~8^J3szPBOAP$-}}GJB>I{C z%}Off*4}em3AuxL=NA+G4Zc>e->EC+nn?$~F@sH}FNm5NdG6lTY2Tn0pZda4)+WFE zQ*dAP`}O;z^>1h22_f)4-AXS!xcr-Gw-3P}2aDptgYN)zp)J7FLDNTipoaPfDKB0S z2@6*M&4+^m#Tr-%KQr_95ga1PvlGhc$rJ!@iI=zco4`OCymDERv?{n>P|~2OJW^1w z>H7Q)>KfcBPPrb4@dFBufCi(Pt%iYv0;&=1?I&LWO2GK!+qaJt6@iU&=>}-!28V=z zPP7bK-Q&lf3<9cT=I7@F0|M42>L8;ArUxL@8jx;3q1pxva<695y;mbPHgCQ!Ee))L z-xF(wnburPwFP+l;Tkv4h?1g%&i8SA=JF;cDMyN}W7y5VjE`viVG#(l%TrTNb|k*S zae@@;XJ$Eo$HCx@xYu*$3D-5gA?G!!z01M^p(aiIwe`Q;3RNd0bnCy}3Tg_9^e&G7 zVn=LfU`K_(Kf}y9BA#fB!x8S!`vl9Vtvv{e%}Fh}dKGrd5>5d)8%xl207m0G#5O>l zEfk-9R(Vlg%MSbYu}&67{3bXwAY0b}ee>K)NT54bY36M6ZpnVupA(6V$)eK?5Q-Nw zlPALh#|+X+?G|}F-KOsJF3u*b>fY^y$*|@C$lJ$xs!BH9z+QGSGXqau3>YB_KF`>A z5=4ZFO2_+zMbOLu2UGx}0WBQ=Jh^)vfQy*lBus|_LZbx+w!SdXPEiK4VZc~U_8qVv zJc%Lj6@X=P#sVNQha;ZvK%IeYx$CwX)53VreTYBYkdTl7z;~5_;UtC+V^HkEPTyXg zWm8Ey5s+Y}3fkM=+Gr*(`eYv&FJQh^e4f3~nA;^g%!#ZGi;k{<3DAR1I2SM}RHc$E zd4W5k?*K-Ie0_b7kB;U{LJ^MXeWzhJVDl%y3W56vVx@(J1xyfpGgHOJf~sl>*b!JF zI#?H2&3g(?0G=mq7U=XaTsUqJd*NcPI!e0SVR9l}AsQa=1Z_gLnjR1jXvfviUttOq zsvM_wL4>7vWUTnjtJkbWLf(Sj1fSkoGdem-yIyd6gC$d@-71S(6C)HaZWeT|3j8w` z?Nqd*P}PHW!R)?!w>=HA5-2gIbPcDWsoi2_wE%$?@M1?t2iz_fclT(*65Gl@AZzD` z{g8c!aT6#o04{~9UbcLJlcAljM?pigjKP>YI3T;N&XLjGB8-*#`!}9Q35<-qa%w(4 zN5DTF#;bRm84+{y^MFGDyhY?S05%1o2}Et0;HJq*1YFn)!ENpBV958z+%W~UXntnq zG94X^%o=*~2WVxhEy|O&Aeeg<|UO@9$IxO$Dy<%a<<`#oXOoT!1=+ zA#X)Q)>rf8e}YrGx7@FQZ?9)niUX**9;t2Q`26`IOg(^SfH>aO&hELzcu9t&EJ5}G zFcSy_h#Lb6oox<5!m-TWw%_WD5Y0(1*KKc6!dPoiSvIm*7sy$k`(~z~QE)%Nl4{ z=^r@>14-I*EdPe3o^!BPFYT)?8;1djy z!c&_u0Z1gdaABtg;$kc0+!(FcRKHthQ0|0<_s!QTtETH5X9KGwX_mp<1P22g6jd0u z1+}gAWJc+%K)^LU8Ksx@_V%#un|pf*7<~ov1@3SrDoD4dq*F3CW8&XNhR&E%>N2`i zdM;TdS`%qBzNrENSzDW8c2A7>O#qfkJQ@N0UoueGIU$D$1AGa%skm{9`SF=-c2^Ar zJLhaR;oaQsvDjv-lfn{r@AmNJ-IcDfJ7h)3Q!7D)fhE9ZI|!Nyp`sFHVYgKss4R<% zR)8RFY*t{JF1}{nx;P8G0=ck%BYbOtIEaz_!eUxsL11}OyMK!G6`Ly@f1pm+N~sUU zwq*5bMIvqm8OTtNDxk@3q8HPzLkqM~s&W+1xfXytS8`W_5DQdZXL&3RAr9!Nzj?NPDY7+ruQ z&C+Pa1{84EyD@l9%OuZ?zH6c)A|T!^ZlEAh-15}U*7l-G{e`nM#l^)vRswM6Kqde~ z2Aj^G`bCj%ki%12CE;$$nE38-@sj|Z0jwG=*9xG^qc{w3@7*OE?AuStr{WDx8WQk( zD@Y?Di?XtAv9o9Qc?t^)E;oOt`>^oS>XTA|jG!gMu96znZ}(aB)iNaG#G7vLy~xkc zy+|W)3y=l;U3gglV0AFkwzjv`tGxdKkVlrC0;vJ$#s`>f{P3aIx(u|4_rbx2;6@&0 z9a%2|N|_T@EVt^Of#U@b`_7)I)zhal@bvJ2prY41VbC}32i_9snV6_IM)|>PG;lG< zM8K?Lt?yxZoEC_iN~zs&x1*hJ!7NKVsh_S`!R^|4oK+eG3EV#a3<|1>$!~$1vt>m&iN;H z2Mjbr-vx~bO3R6}Bz5MNZU)+q9r*`|j!;rM2gpEN8XrP_k4;Q8B;b$1-r(8*$0d1% zcbTKDrYc)iigjK`TsF|()?P45w8Pvwtl|1%tgT!u&dMbSrd&_h$#T=>$X|ET{*wz7 z_ECxv5G1y?4e(E6<6xeydX1YMh!-cyUD>Js6Zpr;LRF8CiMb`UT~b!I`|FpoqM`sS za^`pX1pJz5-x-^#M{jY2`pGto81~AZ5I;1McwfUxSW+O?r+%V+W!Gz$j#F%;3P3 zKfOR06f4-gwgi!F$iV=K7|`E~<;pR0y^s&yiVGwH$;rV)nT!{*1G?uCZl1HMoxM_? z$Hkz6d8)v03EYhfB>lxq(hi_e2iAb%txNdSP#d2>vupb0Yq1C)-5k^-s6-`m)?BU$ zts~6N{*RX(Q|d$JKRW6(p%>+JVxeVI;d!=9uV26Z8Hzc`gGILPiOS%WAr5+7(faTd zA=LaCdD{Mr=g__vFJ8r66JU=(=N2>&`dOpe0(=e6DDDg1*Vc z!jcY703;`1OO%zAG`AfZp~uLF7&3;8YbS+JO}~@4x%p^{3aRtkbI&rX?x&zagA(4+ z(((+59gI=~kYc5PtpsQq9mX_=AMMsi zi8Fk)lKSj8qy157Ybaw8Fvc|d*ug(1M)0Bevl?RkVT91##rVeT}!?b{f5 zi%AmR&YImpbZ`zuF;nMD4V z6#%*tNVo7kZThlqAYK+KXX-bC%kVSEnuw)_HtYj+WT4ANZ7cBRjsNaXowJ@1^R)FegG<+BZS~wm-}MHJ&VPs z0E9z$D-!Y87esBSm|-ZTdZ<%42iRRq0% zPjmappVHFOfNjAXf>I9hhzm;)jbIM^4j}k8&~8FPHUKNRxRl~=Kmvf4Hn-ne^O{v( zt!k{c({qWuasK;0fz5H^ZtkAiDvkSxIaj8}J!S;eedAmVuV7}V$?Ew3@ z+@EHK;7)?Bl;juvcWA4nf{BRDlaYL%F`Udz{t|MaN~*d8pe(S*$OZLYFm zG)ufqPJeglfLz#-UGh3`p+GrwfByWj?q`$j9ruENM}WHaFt9>kxx<-<#_I0u40q8Q znoLy+bkurH^W|)5VG0@to#f4GPm9K!+?XHiHg2vU_nhU!QTsC6Vbp5u* zu&px-r*K){ZXTm$fa7htB0l=aYk7<$A1`4ZK6UCskC3zum}KYX_MQK6-|IXMxH3&} z27ql~#cREpo!}ZX%RzYKf2D?`45OIY1qJ&@Myfx5{t6&@q8p+is6LS9!@|R_T)oOn zU?&_ljH!^UD12hh{@?fo%z{DaO-tz|Yy86W^bNTlfJtyxYMe0gjg3&Z@*%2;$K#Rp zxSWs>6*#1TWP>jAMX3!NGr=9$-PO_77V6Ers`Ecmw^+Fvz2|ZT{vkzFh4gugPTu?by-DyI z9QHC9i1avxT&AY|_Us3BbJGa6zUSZNB{&qys}xsckMBG! z{m?CeyR3LkbHGTaoJ>JNQL)?(8v0+a?je`l{|wb)UN4|H=R4)l{}0ta9(+Jh{aY*G zl)@$g(@4wkX;d*g>976$Wpk)hiVFgR;<}*HL6QW74CPe!b1>ok-(-Fpf3$gyZA_ef z4)T$Osi`S;heLSw#G+sX+4|oWMc8OW8Fe7;*IEs1J!m8q*8R(|mPzdDXXHDiW3@iU z6~y+!n)aN`|WDeSn)%(3?m(_hp{r6NRV8b2_JTR%}!TB3I_BAXl#IZs(_Q9nAL}C zYiozQE0mWmg~BNf4J~>;-2l4k3L~(S{|pzkDn%v7#8iS#42AeaA~2|O0=I#sLiJXc z!S>GB_;~7Hai5gVDIomtDkCwd^w8qi*_YtrKn75)7sDs0s3hF4&72E#0xS)1S6eJD zQu8tM@i{@0fM|(jXHvfuHJlVzP?P25<>B>9pfrVrg~?yjAF!YTkOrl&q8_5G5Q&Hx z`)U9U`Csd1`hT)TkbU$Nfg@J@;q{H3AN_B!d zMia~`Az|45>q37l?Wuq}6;DEMCFm=KKGW)-wfg%|$KdOO%n3Dj zb9Wa`V;%lg3v~CBF>pnIs_Pnkd7U>tawIl5SQAVo+)+KKpwM{M&SK6pia9RM zDh#1Vp53mk6{s-Gd7dLybrpXZce@WdCHS0tdY-r}=R@KXZ79L3x^(Fh9B#tG!-rnJ z#NyZ;A|MvYWW9r3`j$+y;(N%B%|yr4CD!v<5aQE+4Ayv4|&Mbk5D?xyxt{Mh1r;)LKwqocJj`$KD28{?TxlsXynat+7Z7fCM!<<_HZ=8~mjj=b{1blr=8 zm?r$5_rvbenGT!78DgBAZ-AGWQSN=Y^M~jum8T?<%x`D|qSZ*>w>^x0K|+NW4%r3U zCgv~gz-_YIO6?CDGGtKg5k6NqNbn6_(o4%f-oV@#=b1b|U3jH4z8@dx;-)QIY^Qcm z-lB95Dq&Ae=}#$GsiatxOc`@#O}^pmL~jLl3F=%Hc^`~d26pdv`zUjI$WubiN=-u0 zupDj$H{xa~*CfT(&D!NX)dG-YaaGDDSk!$PO4NfnBs!A2u*pn43d2 zrLx?TgD%+B(eY}<2l1~;a{#|MUB8slOO0PmGWIUrn?bdJDHd7*5UfD~2gq1}+DXxb zouHFq(+)4A9Dj{|sBV)4xbkz4)?tMBrKN~T;MYI@GAx6Mj00DcgJTfrE!gD*Qy0So zl&~NwG-C6^0zjH#4bAo0poQBl)~{c`2DSn8n||X)b2~euLx+C+>8EUf5+Ib)(gtEF z0ODwMq+k3M6V2XaZPfq5?LK~{TH_JuQ-H|=@cc+M{4={M6kZ{$$^(r0iHBXJx=})j z+djV<8rlk?T?kBns(|Etxm;NlFT@Si=!=y^Fceso01YB7e?8j43s;bi5 z70Z=1x0;%fQy?{H<#;HyOd=o@zu|cQBp^2~h>fAH5)+NO>4H8N0|Nt| zoBZp!raBCBH`R+-vp?v&aB$IDuuZ@(RYk|LJxKG0Qb_zFD}(w^Cil194m&$ueyZ1q zXsf;vGDN{T^Anq>0H#`CJB5Sc>E#Li4o)^PF`cxQnprqPZR6!tT}}qa2yho#T@?8M z_R%qT&c)#c{zYqoub@7VfGG<`M(LBISZY(g+#8cmVUPlHlH1+WD}chWQweJ2kYPR- zuahS0_~Nu;{7_HX0)`oRhsQFFO3*s+o0O;b=6t7VfFR+Y)D?Bq*Nb#QY%6wWZ7Qbw z4r{8s*KkLzmI}}lv@#nuY`_f*i!lgy>FDV#PiTS!UG0~2=Tajl7j0J2WStEWaSsjFWWc1k)vAxRw_|@HFcs_?KyAR0(E)XYML?SRuU%)s0) zh?PrR!-5YaD1fPy)YQ|HlV}s#tVFQyn&thtZ7^jl1_MZ+-nd#_gP|uU)FhhC-2R8_ zYwB)l&}&z_>a}jT9^%S5l^gri#BboVd7y?j9J59zYc}oGM zWLm6~2}ORcGxXU1Dl(9kF436vm}$4%#4K9s)IU0U6Rvj3&(J+nQc}vu%Jwe+{BL0` z+x=S*t5LDbaU5yrudl^Cg+Zi=M@Y@?<)-!^*1rSwi!t*LRf3v7af0#ygY|tGTP)xZ zq7EyW{FSSq*D$LN_33BMQ9$D&%5hz*gqp+u(k#4IU#3d5uuElC#vA9RkFMn8TAx)j z$z#3_WkdfiVI}u+)yzGgNJq)#KLqnKQ!esyPU)ITY4I~Vmx;C8&XKnTTSf{UxY)Ro zr|I+K;ECbi&u4OA2cLwNdXYor;X6m}K|2CBJjS1X5(JF{R0+I%20KkdL)#BY)iF!0 zFcs6#%qo>=-l-q!rT-8LHcEUn|VP`2}qYksMY7e80W1N(Q{hBz&3x-k;*c_4rXcX`|ar(QVY@f^CMw`kK+j+>?& z(AINPEV+LC`sEo>{qk*ol_3V1POd2|B2_C@qOx#Wza7f55Vn73t<~GBk+dhg+BniC zzq0*dPKaT2%Pq?jEt_0*?(d<$d)Thy8=(5^cPiEz{c9j41LNMleLD(R!1kYcF?PWQ z_p^(MZFg4ni79TzgQuVDsn1UKLVSRW*bR7xgbnIMgdJnEeso8~QQ@MMcaX zF`C86`VovAP-6b}2+GP9uWsA3ea8;e2{F)gqQ+E{o7)?dkR$fYobMk80u~2CScgDJ zWv7pBKYn$!8|lT5%YkfnIfNQG6d=koDjX3ClDQA*gCDdYZ5ThMo|P_kb{+#6`J&Z z&0nzqaXq6`ZUX|2f8`Fj0;Ry@=KNt)43uNKu}{fB`h8)P3d3idaf#J}sp`p3~(Ricoo)>C=C3_19cyUbSMy z3Uq}m!pAPZIrrkijCp32)6D!e>CP9|K5ThgHF-df`#WIwSYy0D`1x+@Pvq~i@VvjW z5EFGw_LyYc90A{fgAfMKM3|p82CRI`@XGdV&CFU8g1X;W_QN8pk3R>ARQc0)O!@+C zNozi_^^?;*(TNaV(;I5;P}A=5WTp+x@TU~~!thV`6r3h8%S5k;qyy;{7Qv@EC`fPn zewJ$VOcHK zDq;;+%Vob}sz4BG3uSHfFd+C(fVY3jm+I6ct@RJ(Z;0<>k1ybHR$XyX(O*pUSLJ`F z`lIm7r`#&&o>YB~%cb2gGDOM1l({yK8F$wc*LJvF6S(t4$7kI6tYJwdWpzVC3|1gE zOuyJ*U*8+wq;5mCfMeUZx%bOYQ_5<~e;~yw`bF(3N1+CDb`(^8{{A>N+_5|-7YVOp zTx>TbooB|orBSBiYfk`L>>DSjU@-Yc{|Eom4^`fiiv&;8eXR(aFiafF!P#CRbN0 z$2WdhSjaLe(eXJ+cvOFbkj8g|q>XMwT0#QNwCm{ecTjjt{!Eml0T?uax(Lbx4k6%& z+E!I4)xltfror67VPar_ZsSI{XAAocXvc!h0oY5BjjbO~?pxYL1V#uxwSU!p-(``P ze7xCduC&p^QX|Dk%rCBXWNLn?O+)rLUcF#)1S(b0) z1;t%37svjE)$EBu3UFV@)#rS=z1e-WwR|^el^?nBw?b)(jc6}orqP^zLp=5seP;e1 zl)>8e5ge#|#-$%{0f5OT6?HHu0rxO~39!;3;k?n+4=rYh17V4dkp&_1l1x?Qu1 z`_Oob51p!xbxx86qZi4|F)S9vg^gP$$|uypKmoxn2H!3;q#X?)=HajhJh+rg1OrIw zyVL)x;4!sA)jG$WvLAYQPX97iWw2AAs6x$=fw?7wW9Uslmt$3S$|A@CrJk7JVN?#8 z@ANDyD{Em%36IO?4{G6Tr^9SVHPC22R$~OND}foxyMR)pVRIZt{-}1#Gx16i5?JT* zcdKpUoXS%@OG~2VKaHOS3KP_%uFx-F=t;9#Xm1cJgnBqlQ3nEWz<3Pp?t_O9afcaN zgrf?;%DDupAk+kpAL|u5zQoPBDPR8E8GJXuNWTHDYnYkCsG< zC4B738%#9p?K6u4w_!#KLjjz>S^Bw&P}Jkkah`_|0>y8K=;R+(uSQ=Q$Ry4$EZkI8 zWrjLrk}7RL>S*StC$JlrLrMC3EBm{3uKGazq{DS(|6}Hxx~ulPc!BsLTj^2aIRYTjJa=Ms^2KwF~ym zW|taQpI$SdUi8_dcx(I>XRsZuqkbgfqho`5+N&YLT}gq zi`~nAo4WE}|IGjV^Z#jz&3~Tff5EH&uLFSo^XUHb=>C6PA*T_1^`iPN;Uxd>C1L&B z8vzMh=%=CF|L@ba{_UUnKS=EQ&(HkNlmGv5!~Xi8OaK3=t$pVwbuLma*Z}CxF<=0t z9Xg%oU?5yt!-E(#^s|H*s?c%U4jLI-x7F)q3~g;~;b6qT$Oz!$DJBo0Ny)%PW#2y$ z-qa>S+H}G4?%h49z|#qt5-?XVSwSs_9?BCYFyO*?Oj~E@5M)+x4v_^UgI@xq4eWO` ze!j~Kz4#o{1Rt{I!2{3@E9g6DNr^w}H=vcSfTzOz28;uLpCO&z0JoLGjdXM%?WhGM0NEn& zuExf95J*Bo3JNaVHMqC{4Zs8h6TQH|H2BHKDfG=dQs~w z4+5KtgP8=#lmPrBqx%;??{Jlh@-o9*9xyuG4bl7l;{BV_s%4GU%+?T=M1t1_h5VK> zBF7FW8mqFD`(#4tJt$*%2!kL3s#hqWKYbXj4zo4TJsQOD-~$070KpVt4!-Gn*++q` z5ZqG4Cb24GhlCEBKUZ?g%67ksnoB)Jx*I$Sj1XZkgm#pftn##9 z*#}LW`Twkm``?uTceonrfkAF++kjs!i1s|!%_MpSF!W?uicsRyD&+k@I<53D$701R zio)F;D@b?aZ|#nDclty~|8pDvUuzEkci-w$An1#qXkJjPIcOuYV`fa6t9ToUbUi#k zYZutzx)~RxJ*I4}hhu1HW*THP3k(Xxkf3zylFIZX{Zf6dqQnuJ@)saJcQ zc2|Z3$}{t8U%9f?V2gaYs>dN5Hfz?qnRsjgFvB6?_OicLe0sL4*mbZqL;6u52jrV( z9eHuP4c3qXlP<1%v5AMU{3);*+|2NNw%w8mUhWAAT}wkrZbO1cE!c`m+F*p>8pfZI zbT9y)_fGnjHEX1+e8s(%7k>+sJ1lGVP#%aT=Y^{=CKC2tw`#*2fWf|5Udqx%OkY&g z)(L#h*AH3n9kX=mjQ6FHUS#VEu7JgM-+=$#>yx)hU`S60-O(bMGlYYvSvTiH=6(!ocE!Z<#Yk{ILT`F#nab){04rb_!zY z&L4Yxd9#Uv;z5{FPT_HM7ryh4G#H!h8R^colos`d+uoIcC^l_5jHJ1_nFU`p_4SR| zw5mFE6-JjXp6VTok(Ktif4(PPv263^xw7hPjb!`UP!n)3-&InL*M=U*BWG~;>KEA8 zLH3n@yh*DM0j~!(3vxpckebFj4nlFCaheHN?t+h`To_Vx|Y0cJUnn0lq>QO zJ+LIq$0sW`Gq^*kK2u+~yLxE3;$o5O2u}4Yj|+Sk_+00w`ZF^{MIx4CMEc)#*!&C~ z3=PZDvsW9guSta~e|xUdhq<~4DN&r|Df#)@P#wWuwyBu>2;)Ul{QzRovK;YkOPV;l z#XyuQKF5-p^n3CR60=zPV6-epuRF(Ri*9GW)jQj!L~2|=P`XVq-M>F7=_8Y}XU&9q ze2?w3`w&w&bms4%qPvf2Pxr^U-zLS7+T=3X=l3vRB!Ff4l*0tOftLrZi)Vxj;TATp<-rS)|6qxo^w+$l#cXT zSYDc#u8Z_7a(t=ix4v@GzMkjQy4$o`ZJBnf*S~>Ff@g40CY%AymOeIOT$ES%Fg?8s zw6tnA`48FJO}#bIq9nIG`E$()7drh}%6Z^2GaNHmzliDNJBRXi@1>6|$;lVs+pU|~ zRa47j)n;#Ay|t-{ENZhft?{ZQtw2_Wak!}<-M;HXyne2t)9%N>+M!Qs3>OZ=O&!wW z8-1m~e$2EmEYcu@N!%xlnbCUn{-A`bSADlg27MUG3d6^2E^a4MBJRWXD=2G_w+pjT|Jk9}o>&&a9KmeL&xW4Z!y|yR_t0}iHt!9bJ3Vg0 zsXg`Mw9YT06!>F5sy0o{V4DMJ+-e`+Cxn_i-I(6iJS{=`~g# zeXz~_MM`OhJ; zLs=tUF%zK@OY>3;}&kPLo)j*-jQ+{w$fz^v`k!FX0>ByGP|a$F^<9 zo`u1faQQ_Suhw+!NuC;Hy)5(&Iyt|uBgZ)pwiXr5#l@LKOV1kE+b_+%*WI&wJHGoA zi-rel^5*3B+<4+RdH1NQ*Si4muyQ;)AKgBFFbUgiZvC+4X{5tRt7rGeo`ly{o%5c= z#W&GY$IHtL7SDKfFg?5%5GAas5Rg%QE@dcd)q|CHgD2NZ@bP)KWquH~Imy`F8&^d& zX{o&Uv6w~D39?Deg_$U!u2>^!wZ*Y}XG?d=hmzHEP@s;&RbvgiRzqze9PsKZ3dmGCSE^@oE)v} z>$3^w5D@gp^Ibl;Ni?6GDe;)8-l6n)ofUWE7uUOucU{}Ijl$b28caGxn8o8V;xmww zKJhAc?!8iN7^j_WDzBl$$yfJxMMpP0@9dP4(gCfyv1DRq{so1v_qP0eF_qBYu{chg z%5t!=c|Cmib%<_<&)a(@9GXgxtepo+2VXYcvDVIAKALP?)^Tx@CiV$$!Mzr;;_^~d z`I3jVf_KQfvvD$qpv8SZ;N6tdpBt~ZI2ZvvY89Mk$GfVZ%3Ej&>nJjcID%5EX!Rym zCnMVVYh38R8w3QAtrMXDalKXN-WB>1>jQ= zri&<>83P7DQ=-ioRyyhxI}qzb^0#xZ=N{I!R??#5e|z=!Gm}1^CQzJS__c8=v1Umvf3rZsHCS!|?+m*B4 z3^l%$C-%uj6SSYFiFJ;1Ohq6#EyJY%RCm3)D+xW3EJ~Frt$Te=2ZO~K&1UUsikiNp3@H6GiWKHv9hvy16l;4K0p|R=8jOP z1%aewfLSo;`^hYH!LkvbC502qhp#7M1PBH_zbpORHfW5Y11(#e>70!D!p-j7G0Xp_ zVsbv|bj|DdLt)kHG_3Z3A%hVorm|?&Kr}&)t1v8)5j+m^bUZS237=$i&B+4oT~EU! z`-6D$VR$qU$dNnJ<(L)3Geh_t7L^r|O+{XSUM^6SHEH?*QUTR)Aq$9W;L%ou+XIBQ z)$18{!scE%2+X!m_V7EV*N}_KZ%ErxhmV}u=GLYAXpl|$r-OmtZBY{>YjgSDrRu>Nd1q*2XE=wQX z<9F8-FQR20tc%pOW^$1tB-h^|4QoXdF>aa(xO{oEgFJ#p5bR3uit!HPn}*P_f7jA+ zTEhUU(4~P$hiG|UVijX1j@Sf{u63&4!C_hpUr{I15b5PVv9R<4fqTm>w& z3_D}*+<`_CaKezv65+L|q9e>pDLW6rD@?XDoILw?F^E__MR_r8K!hy!MnhK#$q~Fm zGjwdUVkfBjTsD^ z!fg@~7|;2sai=B2blU#?wKfkUk}C$HLZ29ohAdnsL@Vy`(kRQL&FgQ`sFor z+*=|QU#AZg@&%q-fWu$be!l6u+UZ3xF6mir~K z1Vafn5%3uVdm3zU5Qd(;Z3OWM8QJ)w>Ykn+=z=*plfllRydC1|=_dxZ?zcYdPv2~g zBwide4gpuL(5zoS3cm#)Ij~Iu=^9P~W=D@I1~NnE1YH;M2=~EIVy<_AlJ7}vEg=QA zaXE2fGh`H%u15GE*msaD9T#DQw-;U)qursuGqENAvRzjJuN#WW%+l~0Q!cU+oBo|k zJ%3my`Y+F|*z5OBJ-N2;hmelb7}>*vr5606wzh(Qa)+#Y%Edyp>A@MZ`;K;;>kH?( z!J~`zU7Gvw!A&^`%yz`oy8-Y3XA(LeakvXVdZctfsYb+*t>hO?;cn~J(&|a={6jCF zHwWg{yskGF#_lPi*RAACh4b3peeL zi;M6r(}uGs&P-&2S-+d1v@3+n5$r)2M|o9zYzPM@FF8fswD(UrBLkfiKsOwS^u}(x zd;{UQ|8kpoj^Cs)u(Ys=EREY2E5nfk#Nk#&WGllNI(w=2nY)ci@1`! znct89akFYc<$bQOEXb9%MxJ|}Uh1|n0+_-do{e>yKZU>S<7Nr54wYpdCTL0;J}%!8 zK1PZh*HG`|$%`v2)bp9RF*(j|qxD=@PUf=p^$C&UM~+PW>Kaw%>n0z_e&yx@@3-)- z9Mvif>%f|#OI_>B2yd0^hKa?h2M?v#?d<5u*`&Rsud}CL@K&kV^)-eEHsL-;z(|?b zT#ioK^wdC;Kx~gMdkXRzjNZqS`bQ z!XkCz)$cE(fNLUr>wv0ib8qm~t1rgKGYuU;>xJ3-bWA4?n2P)N3l(I)e6#?23vjiE z-@HNL9U2iqAh00ac0Pa3%E3WTN4Gx+p8tOkt9r^m5vz(+tB&ppyngD$U(6@b{?(j; zfxC^^IUc_f)62ibYmhxWE98Ee!Op?nE;&7|x#|wOut|#++}`>I2DNJE16kxV8?Csl ze`t2uWm>EfRVYM#ZDsJI#2T^d9hX5^#0}&E_8|zrba{0-nm;oKlL#2|D?YcKTS!&T z7>b@4toEp=3_~(l)xaaczCwKkWee0yh~9vORA!>0pEnMd?>V>sBlqdLfz@}s;VJ3) zzDwjv_zpq**=*ToKNhFz&t5Di!_Pf9!_4)K(Ie+a%{_IW?+IS}v)e-U=ms@jB-9e{ zYpW^NO?h;_ab}?PTtsp6mUEM}cX0s(_iA*)0vtqYIHf-9qq@r-sK3LyICf|E?SoM2 z@B6LVOw)p?*`=wwTb7&K8hk1`^E(zv>@q@Ou_50va?iTi33b_D4v3SUvvr##oy;MV z0m_sHvF=?+Y{5i=bW&G8zOo#)IyiRlh5&|pg1h`EkVT+Z-_TG7ZVqr;g02hZYN1}H zxHghxxYoXSbRXYUztsqoIQAZ0NxqdNnXdoR-Tkx6$@ZZ* z`VAX|PK_HjX1Hm{+-UT9+-A&1z5Diz2Z~R~9Itsr?ipBTa@UQ4LPA2~dCtU&HOv!! zvu_K2JepKKsk7E*;^i#idIATETaj;nFwJ8*yUVS)lUByQchzDI&sh{pu@%nX`a{W# zBoY+iM1nma2s9;ds)JRzNB%!h$0@a8HD6d%1nwhJb+C0IcELSUtx!$-Cu!Rmp1|xX zFzSXMILA|Fyx?Gw+^LrJ@~?G%G{isuycP7H%CRqa|j>6&C(y^ zc&&o{lZA+Ya`Yz)kt!H>U`pck;Vq;bni?90Mn?St8ljy!iB|}_@1-SgcH|w zHE@n93*|ed5oc3uc%;6wA2hT$4MFm(kq?&p+|q>DzQkT`=9Tm_=1k8Nn(pXI4QAz% zm-oeU1=pNcl4_IOg&IDt>9^u@S63PF7a6v&bg*9%Es|(Se|b^LJ(RZy1G1Wty1pZ~ zG%BbL?-y9p?Oo-sgo=$IfKI|wgh&VbPGR2fk0*{k0sipN82^m=7iT8U`6zevJk~Hv zhj2w$NQhAFog8D_x;4XhUwq^JhYzjEhy8*G^A1P@mnLfkCXJ!0_Fd@gMP-7xYj)Ke zKhn{$_Y%z|AgM+s8cR{+c}u!_;H8b+%2HWt(Fy_6Lm0&ik7ymp%N_p z^_Fo$?CNoBHU1Sv{=9E;8lCu>;_Nb?2VH7PsF8MO3`sBT*)J=7@9`f>LyJYGBy@jY z$0POo>VI1j-|3rhV|w?kgZNV1U)*#`HrR5Udh^s2bB0NNEV~SpYmE(0`UeLe-M@dC zS+)aSjED+>H8|`C2qRCYs_(lI>bL%+kE0xh5I2H?oZv_XqWO1-^(_UhXH2z#yPK_Uvmz!7qj%&YSme=P+;C0?o^E{7lZju=%KNCFc~(Q4B!6^$-cHA>gG*I@TO=1-yQJn{MXW#+vF#q_G{l& zA07%yK0mxl^Rgp{((xI<_Hy_W7G6w*9A9l@nXbo+6XC>7sU#1kXg@aXqJ$kpp$d>1dCW zpvw884s;j(M;5<(7R!={aSkZxtujH2_@NuRK2%`-0W38zh(ZabGN29ckb?8K^Y_ z;2N6VNnRp2>9+AAe*-IbUP5zK@29QKI_;moJDZ!{vOh|OT7}=WESzZ&+wJ6VvvQH$ zk6!CKkWB{8JknCIJwtl0In@7?6^GNyuXO{es~uC=Y(yep17K3_rSE%8WCH_(-d7dq zu!+gn^& zicHiumR=tU+4iR?-!BAHLY8rvl&B~f=f>@EGEU#2bt6K|+^XASH>~*h z*1GZ518i9^Jzyc<1mGEdIDjbCJkn)pnSzSN z*DT9=|(d zD73oZF9-?6ZGUE^akE4xio)+DjOJ&2Y>5XzjDlwkEi(++V8Zrn$OR`_H+Vva4B(4~T28AS7er*J%DFDTm1hlpJ_Q0%N(o zJo8r;QPry%U~foEBb*-)Sq14HP_p^nSuydwbb$$tSJ`RE6{$v$S7IX>&kB&VAnk`=7G8qXLfdW_GDI++KruYILK-!IB5^!X8n6& z(XE{m10Bb6W>8Ut*p^mrP(7*X9>Db^rD*7o@iA`NHC1)UhO5?pI+46{0E#^1)Br*Z zjXd?GjyB?#Y#U&&JQN<9ZH%7`SIkLZ#JlJ=zEF@}RzD>n#9 zo*Jgra~{)@Uweq>3j{rfe-OwZxLdap)e~m=SyAxn0Y0n}%1Z##nI;vPO+ygl{29hs zYVlownZse|_3QV9>le8L=(m+f1oj97ntRe(#yiNhj~)#IVr3fv2McZ_YpJM$9Rj~^ zwRbQHMy^n6A?Ol5xJBpyP~k&VnbkCednykAy|?-o=Z|tus*az(15h{8TiKt#Tpa7e z3b;Pjv>L4=VYzqE4=eSr7$DxJLpLJ3gj$%t_1{QpUUA(3`GWuIh-a#|k$6`@0phy5 ztNB9OZdBfkh@|PaNE~q7+4ZFB$MDEii&q978h(bnBf*S`*(}y@zH%=E9F{CVdi#ia zA32XiRFHQWrJ2FDH6^RM#S4CaQWrH((P}8&LR%Spsa@U+Df?d**`@t})^_jNQ&w6EA5BrlIyiYxSDfztoc&oXZ`X>j z6m%D5lQlemb)o;vGAJ0wTp~45@LN|6{Aj$mZ{73sQxrnTviR><(@rlz=KAcReI;UB z&JorCz1CgMu-GH4t#LX+tD6Rk_G)b|fVprd-nsJ*`Vj0a{5n+m+QO$H3m)z+DZr%= z7N!e`1&PSV?TN>md2+|5XH6P|&0Q{>yuu;=XSX4G)M<AR$qR z?Zw`y^IMfw5Zs&rg%d{Yy2kDn&@MyOHjliA>m%Co`J0^Xgyk+!DwWkxw3$A){RN3MqVnn|~bwi)hpmRSGh zQDobDfnx(VC+2+QdndxhoeL$8<<7w?IA8W>I=W+?6XTFBkIms>gsUb31fPzZxAPk_ z4w)&awC~7ES=@uM7{Xj+qtpe&Q+Ds(jcMd%MYBVP4&kr{;*X8cj9%&OTSr-xo*gV? z8k~c}RoJHO^=rm96dkIzHe@+2lZccj~ z)U#YnZ(4cQi%7AOedQJ&m>#g5*e*3}6FkjPFZhtd<6 zYr@4AKl3PVBY^3m3$Z7&Oc&yl_DYF26?PWYudtroiEocd9U=sfXVTqW2C1U9ws!tZ z+SbftR2{)-6-+dpS>)66gGTW&^;ddM@5so1c(X2L#^sIfh~AdaeU@8YqJI({%v?dQ znz3@_%GJv?N3(I`&XU`VHGNxs#;&BjH$Ub6A)hVbMC*v8sq4(o)UOzg&)BzRMkOU# z8XHHCg`>fP;4;Es^_n%F?(Sn4HFvnO%ggsJji8K~Uhu|7ID79$dinwsGQ@w;2#kp_ zQC7ZSw?L%c%K5_{pU5@Mpuh(Qh~vtAZE1gNpX2EnG($JbHgR9nKt?Wfl{gN`_T3#- zRj)DeB);eHZcit2pmD}E2y#WWEUZHni0^2e@t%>dc}t>=jjEZHNND&{(=N3O)~&k& zL9`PA`KDx|T$AN6Zzzl-q`IDwPfoAsn5+lEipLZ;`C^q&5Fr{J9aC{1R9-cHL#sEYC z4hB9V2(NSZM9LVb^|i*H#v+v5qy7f8Y%n9>s~}uYW&xj#>JoZT(G2`Hye+V3)Bhhuq?X2pqM}r3)9}-KC6y zH?!BkaRC#lK(Ni( zI5;Zs)quVt=t#Rx-ik_s1i-1ljeH={7=I%$&@E49K-oOKtew1w4-3ux3aR%3W&io zqr2ou7#zZCnucNL-J{v1FlPjjoI%3zm;Q%imr1J~WnumahvQLj9WGa!gCPGN%y?Ij z5i~ry>g_L)eBRfI-!36_jVIy0Ynw8;uw`rEite0(9Y2s>C@K>1qMupZE7qe8_(YW{ zP$zM)vxBcycyBfe=DEmN)Jm$e&x`9)ReOq?D5X*WEgQD9~m7S z+i6h{eQwTW8! zO5+jl5$Clp9-fd+?~Am!TsqfP$@t4@-w!WyV|?YXtbi$QQfNo+0{S;J?;!C{U_Ai1 zIR<8ZpzO?z;u8=fz?!kzoS5#zKL8n0V(@0ijogpE0Oc)u_>XX|wKYH(A&v&%@TsY( z2&j7W=ut_@^1=>J3yXdjVZv5K2l-c6Bv2(lRPg*H7Y`3+H70;PT9~{#SlQXJ>zq&q ztlaUp@n8epQTyfhO3v5E}(wGFKpH(dg%!qRC}pWtcH1FIV>T_R52_ z#>G{=ls}9G2*re#H+!WuS;}C;%ocl{5l)f)W9j}gBKav|S5fT`ZT72I$W+I1*{QN?dmLu z%D@o}Q(_@;@iucI3}bi^IjFb18mxNz*Phy1*Y}qR#2=)Q(I*v;sc!s zjzf7UAna;!o@N-8C^E9-$h|Yx3qY+2 z-*~k7-U!SE)D4&)#ww)|{!gFVg4r`szL1$=h6i3c#ujiznYO84r{u@zK zui zOnV=YHgIfS$pv6=w5+Gky9e~cRS1;mg}D+k6CZ`!K6|svJS%x zmHqn(ueq5SF^2U3?{X@35HU;e;Amkw?E2>EHe|4&cA9zjZZmAjQNfna4KQ_vB2W`Y zHQX0gpWOyaEbtMm7eKlpT;r-LD?w{OiN~;IOS+FqI_G|jjFc`jynOvy(}v*JFFf86 zh`|sp0OI)rG8^UyMAMCUg2#{dVOo!@BJ{K!6WYKuKQnl+K2M)MjlD;#w_~qSVD)B? z5dJYuNooSTyfO&P+H+uUhRW{S&cchgTgT(-?QAJhG-sy-CL=^)?y~&iEGE^K~_&!Wn%>E%^Ae`t~U-co+p%qjoB7s#De@Kow3hZ!jsH@6UD z08%k<`1Uug81wmTgaI5ehmQaS-#G-1J6Nh7V`WT((oO zEclSA`I^zFn6CL^d-L*}7RK}t4{w2Qi(hKdpUC_dr0f!7PSU#Cn=$Q+D%Ks@o<$yD zjB*j%N*2>gI$K;iY$$yZ)qSH?o^K*5CzK6URq>|1j#IXpBmC6b)y2%Z%L{vq48Sv8 z13Er3-Oz3Opx}q+?AYXZdoG8dp!(uGT2ZgQYf4j7lr17p*sMTR>Ga%-EO1j@^SnPoJxrZDhRN+Elfg z#qS`ar~~Hf7_L?L6`Kj z_+^pSU#sdb9b`0Xo})Vv|88o^>rI?L25jYMVnMolp1D$r$HCm(yngu?Z$FAc6w9dC zQ8!(@1w(UqbP=#14^OR?2*H|zc{m?mo!>EB1mD!ZMx6NU$nvPGt>xk4JD@^%y&&>l z>+Jgbot>UIl+o{CF2jc(*3e*rDB;D`=%A5FjINH0lM}f*?r`|uV!ZNM&nT|shMF2~ zUS3#9*1!_RB|2tCAyYAaS3sFEj@pZYW(|P1?h3!3}ur-xtj8srkf*%<_J9{En zYdBdoecR(Q74nyb*Av%E&bzm7^$?MP!WH5bA~YTjUpNQFNB6!l+)yvp#O$Y-GCg(Y zo>H9u$vQ|gZRKxa8Me&lh8*Tl9~2Xnx^_75fXdW~f0-HyWm{M_%qt44^V z+%s5hzx{Lh3UW!o;)4vBo*x57lj1u;)s-K;6!W44bgJ5bF2&__RE7G4*B^R+q+s9C zHxh7weK)vRPD1IXq${Wks5ijpi9)gN8l1>?d}tc0>sZGGDtbF;?fJBk*WYR$3O0 z|5Y2qDTmf zaRRGsmAg?lK)B9z6*a+(bo=&KaA!Kzb;5t1#RkUkK(4J9bc-=xntJz6-6UtC2fY&R z5$Hs*mRo$wY)aV9M_&WHPJkKU)3C9z0S4Q@Zy&l}|6<*`P;_J^7~?CbgX5jdd2zm> zzvW?VO$`=I8yaT7L6+T_VtT>Vn3?w9;l+E$qJ;~(56qxY6W~CceQp*=aG$_fP?LFg zAB^mbp4qXKq-anFAe>=%;pn-L-B}DvenM)C6D)alTMQ5Z@7ZoklV|XR(qejzu5k(8-sMC>VCegs_AbybXmx z9l^QpjSjX$DeIHTArXs%rT^=%L?SEBb^W9zlVX-cFpA;KC`W zV^%YL|7W!)I5B~K0Ye3{*wj;gc($gt7PZ3bSFhj%3r+i;J$o?jN~!1&r9Umq=;vzs zvgjeXfB4Q`G7Z)%YG4>agQmB6{W*Aw1OFXKiOdbrY54gbIg#;d{cY{#Q|m2>b*Yy` z=W?Z^y*;&T-|$@Y<&k;eBMT}lFr22D*yRoA1?V==90c4NWaucUH;-()cni6c_4)cJ zG{#uw=jK8}L##0SJt)EHG+L?Rcy9y zo#klYNQXuW`NpT+pf%z++`BnyjZ^-!VfXEYty;2er8X*QOWsd&XNEm!h8=b-Cxgz~ zacOVE_TF4ewWCLG!3GeH1ZagMwyf-1UZ0Yj{02ZDP7ND`wSeUY%RAI-9ftx6K2@K> z^71C$dH`V$9XjIK0vpqWg!?mZL7{SaejZ_B_l}&ubP0-5O?dB;FUfRZf<8Dj6do34 z>B$a=7t1@|0wrk#$7xy1?pEgXGN}5&yT@y5^BO{n>N{6!f@~B*9e}$R(X+ddh|I?# z<+9IHctOxwfMnV7lt0}^s_j<_eqDbyCp(FbsO*d%rU?`oE!%u^h8)Lk*~n)%*aO}_ z@T-0%abZeelmDlM8=Olw6^NC4csZCyNJwLGn+rr{L!Nuq5b<4@Z3_zvA7Cf$G6+Qm zckjRjB+Rg0)FJLof4}I_`{%K+QFG%61u>{@>CAJ4+gv_kXC652#-!#s@gm z!x6=U8W<`fREKK@lDH26Z~}ePbN=&zaoR?`(Wjs;G29yZnEcY} z6uW=`^w8eR!(#D=uv$)v!pffLFV*Sffw$`lccsJKwwQ`U5+NH_tKWX9HiwK!B*(~AJ2xtP zt|o-rAORYp>1d9{YCZs0*-f_5wYIiCetZa_CL0x$Ra9!9JTZhtF0eve?BPK{IcAS& z$Xf%{CEiz32_&Ct0_8*nI2q)5Ee6&gZ=fLG4uOVu$keLS2C8RQIsO5!dHxf;Hcjyg zSY@>LOgo?=D5B7SVtfaShU{CnP*vZvg$#P*Q4?Sc*j=;VJC; zbqG+0$Hwe-G7#*Fo@0h)8$Cp9T5Z8A$9h0Rnck~~hAL@!m7Q3Elylf#2821-VpdK9siZL@Yi&nT|lQ5$G@$~S(AAsitgflBI zPvp}uP7^)Xcc7T&OLrMdB&2@_s)suV#Sbby109{vn3xJAj}?pEl7e3^%A!DKSyab} zCB%T|IY6g>nwxT6yd_Afpgvx)yaenYG7scZ;*3+Q8-V5X!omW!68cuOrwx(f4doQr zU;KcDczAMUbJ630eb#EWnnWrXSv-wG`75+UXnIXD=3(4CJdrz1&&=Bx0t6 z94w1%zKd-jMSaRS(ZrELK4pkD%IS5Hpl8bq`(V?H8v{Cw8>}yt=+9p;m}5CnSW*Jb zqZcCS2%H9VPo#`8Kv~eEOQ^rmNhTDx<77a`-QQ5(Vf|oS zjG3D};?Qyb3<}SmSw#h`{}$)PiV9zx7MQ#__%0X(%nTwS;BQcbhYCen3%J+wv%S6* zz`-!jLj~n|A)OdkEc5S|Nb?mnyJuC zJE2)BZB(`*OjFv3kbSEZ!a+EMI899{l9?8YERpOa`+78rQb-*8nr!DdmN=HfIp=!3 zYu3hmKiBpBe!kc5d;4AeF}GVI@jmb6^?tsd&&T8b@ZZ_DU4V4rFk>*WrjhZeBhR;Z zE!p*%O@@8_gRj2%rW3mev~NI+e%C%xX{oT*JoLXa0sXX1-lZ_0W6$IYM|5qx0a`=G ztQt@9ADR=S5ZF(@>e0tO`y|5cu%j8fFn}@??_tCr$9cxBB|$lft_d!sE7o!db8)G$ zhqNBwBrTnW_A#QZVwWDtuumK@z|gL;qC!JWttsCfw*7JR`s}0-JwHS9!!DyGBde-y z@`HDFe8ys7{%PbTpsZ#;nkx>wBNic?l1CTz+#&DJdr&d1S)hq4c%D&2?Z`eW8~p^a@+0rOmx3yReZ5b)3iqL*GUn;t&lDWitIn z`_#qxgSQuQS10BTC?t~k1_~a&GYBZ5xyK0@^+n2AwyPM0Idlm2W0xq z20O&Mo1*%$wvL76_tBB%@VQ%B-XAkV4{7C=V?PE21cdFXm#cLr5PisDBj^87AtYtZ zIGp?Jf%wm3a%{!68A^m&$qQqA=w86njbeSloL%bbFHroMT0cG-WV0!;xFDU8Sal@S zvaPET!QptHG>zF8;;Q0JuQbtq-lO)cm~eH+FYE3*lWZ51U8sJ!!oPN9VZydE+Ezg` z_ef-Z{H@comH7{y9_>L90Y$@XZ{GaI^74bck{P-eq#zwTq(%G;Qw&Tr=u;=q;w5}N zmh9tc;ZZ)Z^)}8h&8OC!=Hbwe(F$GVN0LQo>0f7VPsc)i*soy4CpB%`xG}L&LFeHU%kiZSw^S~&*_ z9J0s4KJVa@+_-VUym?L&*Pa}Uo4ofA+B68*9*|~im2?e#nO;1`G;hRn=k+|p1~q0$ z0!`4WsXoJ^L8on2f|3riDcC5hYT%41_2@%~gPv!FlW1u_8_hS48JxBu9iq&V?d^Mo z24FAZGZ6#?Sv+KM=v&fF_&oxn!%XQOzI}#Gf^&6wI=if_?i3`{5O+XLUo7L3>fTH* zeOa-2g$^?P$2&Ys&M9M^#AJ@Bij;|(a^1Sr0sxa#6q=udGjL?{<0nsmc~}S8W~1&^ zY(Zeb#TFzPgDzA?*a1PGWRt}7Jbk)eD&vm^hn0(hPt=G3mUFnbUsP7=i|gs?f;kdv zo`us|T`esoRhfATh z(Qo?6sk~L*CD$Hz@!PP=j4z6o`XPzQ`&n68@U~OOpd`U(NK(f3Y8*N>$a>6joK8Sf zm3iUVAl|A_t*Lm7#x~fYmdZXo9LZ5#uB!F-5!#L>HDM+|G4Ew(>R!D}rl_bWP7NKK zny4zm@ril>%@73vol3`1@#jdNL&Fo>JxmKxg#rZ_%w*sDCpFy(YF-#A`3vSO)W2AR z2e74rC>>gPqCGg@U#3Q~k?iWOd)U~NC4cG zXWB+e4b=eYvkHyWU}Jj9t>T@ujP}N8*V^DTwBg2ICN=?eNskrTMEUmnE&*$yvM|-0&1P~8&hp8<`7{b5J z-tuyn7Kck)UG3npVG4;gk&@^B*gBzav1o#)=Wws3CQQJC6G(uri{;d(BhGxnp zN0e0_^Oq@E#FZGz$7>1M$OwaLL})05#Kq8n7dsp^o1B8D1f?OcG3X0dUg*HU zr@A2`K4b9vLJ;%*JapsEoyN?Kjte8a`N&zC4PkGAr`m%$4)rfycV5D_&kQfG)Igym zWBH)=5%xW+R<9-mE1NevVgOH^4#-J3*PJt*Y)qYeBM;9!G&B;h(si21Pjj!^wo6p5 z>uq0WqD6*YYsc#>sT9;P`KO1`^`k9nJH8b&;gfex2ajU6~ z4^1#?D}80YfjRO#gEqfzN!@vt{-QPak}=H2iyfvDGzCCoy6YoWR#v8ME{f4V)WZqh zqzofu&P`$~XlQ#HQpb|3qPCu~sl=pQ%xkm?gGpd7CQxECYm-8l(fH)Jda*{&A=w%M za|x`PrwRHg@s;Q%Z8UuNnEqJ0az{1pNi254{w2^;L)Us{qQfU+tc?3oAy11Lfu1gK z#U#X6B32t|+L-^?k~$DBj=?#w#V8KCI&2rj8%+sw*>zN9&S`Dq#Vv?7BS(G$N2nrkZ~j0p!6j%%(zb3xgP z$CS8zX*S8JPU%5+Xf|HR<^vStEhcW_&5FTRwK!$F51ZJ)>W4p z+BV*FlS>=xI;f$M89D)lUaHD)C`&IYft{5|b_y*IEs>NJant+3=J~6J)X};FozqV4 ztk$qK>icx5v6>g@1Eq;K$}fJAISTn zZjL7gt0n@(L;_;oTTEEw^U$Cy{MoQ3S zwjtoK_@nPp=PY{CJHP1`qUg$kb{J(#JIt{bIOouE6lkPCq*!@fxxHZS!5T=Z63 zRVs$K`8|1^xT+%9iNvvXQ8!MKP6&KD#<)$8YE*_<xR)O zLP>Opaet}blMDRT{n25nvf=9c_F)O#r>QA~Kqoi1K{O?Fb#GP_<8h~@=}Wp4ckHw! z-G$kJwIg{fVr8beeotjMfWwjO+K580*fc%or}V1Oq-ce5zp&YHln4LN@vF5nw-T~7 zEk-ai3QNPAGGT7p28Cyzs=x7^QEFIflay~uPOx=IdD3()EvLF(mC{KIj2=rJwf<>j zzCi3ONleGuI^Yne@VtFeh~esu8}(wy)eP`xbjbuPm?SK3KaB-7P6&DrO!908%x1Pp zWE22{*ReU%bl|3RV@qraHzgeGEQ|p#I%d?Ivn_Tbof{?KOXl3SLAXz`g#l#s0@{Oz z7Ow`^1MN{lswt-z?v@$2GBCFLk_>dXM_QVjv13DsC)%?_j{E4YrOef+FBOqrh&FMP zG|uNE>Z@4o^7r@h%0CwAh8K;$>fJI8-}N$gCKV*r>T-m3yCnLT^<7#Rh#=^xJO>I2WoS$SgA26W7sC%#MG z7o3p2=%^U!RwLI8&2GF?v9cjg4GVY!wehz>-NW3g>B$yV1AAog%0OE*?;TX20&`AO zi^g?JO+*U0I|*qgM*Rmw3ow>}=kPhYPEf$0zhqe%MoKl9xwecSNl5+e+?^uHuJO;e5vMR>FV%_A{*w`L>H6 zAJvfzSFQjkvn#Yb5$LPBOJ970eS6I!9(+4#rmJYZq>N;Go3@ITvwoecP10Ge#mSlj zQ`1iOEl;7Su-v>H0C|Rjy2*8wUBtd%rmFLhx$o7h%D}&|s*-7l&tEzb-v;`t^8!@?4 zQYwg#w}JAAz5)saoMhWYZ~B8C*a?BZ^$@2To*77Y@fNGDtkl%Hf&R}`a8&R@MDI7i z&ky&qnuZ1d+~E*y?Z9x^sP0Hi`Q9$*TfgvEdxk@{!>(T&yp#8Nng(pI zf7Y*Mo4y|*W?c%AIy;E!5qk<4l}nCqB@Z-DCS|qej#MZ{E6(OCV!9Bn>yXheNBy1T zzr}=Q!;I6!v^gjWU!!g5P+(yi6=tw8Fv!d!4kI!Q7Z4GN*)*XS$J>MuFzV^yZh~Og z$zRa494|*ZxApm?hGo?ab#)yt>yp>~Y*@QJ9Ugg95#~W+v*eBWV0gsN$}o3XR`6fy zaA^1;;VXCW9tk1D?bvDs4m1k9DKYev*U+?IxcJkodm)43r()mL+ukN(tlw&Isd4jD{cT=&%F)pebBJQPose$F8#rC;ES1*qd6QXi3JKk#Z~W9BI8)_hk? zBWi7?mZB{%bxv5%pj|-Eq7MTrypu6Y3G*Dt!NRf5_ZW|Aj2ORG55}gZVl;CgJUUrZ z%6-XN5%=u)!0wbLU`mv3s40Y=+(*-S?t7eZ>p1-3g6!BdC_gLX5rQeVY#Oo1DuuSP#7-N{A|a z7BppvzA1`~+Ja2`7GW{5FeuG%vMJLC3XC<-{DnrMBE_ZkI@ZC8M3WpRG%zrD;DR#& z-wjBAuF+SOu>*(FjgW6c34uu%THJ@iDIFJ<)z~{=po#uGHc!IiY@&iiO${Xiss%Ji zq4bWD`4Nf^41Do06GSihRMq+0kyO5g+w>fcCX>f%@+nQOzW7qfyCRwLjh|GKT4mUGDE zW!obE%P-xvdskc%Bb!zV#=L6C&l}2&DA+9;_DkbPG=(gCMEBHl+p~Phi5nqT>ut(P z0iPB(G#-f{NmHAagc-ze{Z4a7<#l1?3=0zj*I~s9Vo-rx73>{z#Uy29WE2#@+o(im z?D6A7b82DZ9GvYCWiOATM?3Hy5Y0UF&O>o`>H1Zxj^I6&=~v7;`VaEAmk~Oq44v?w zgUh>Ts_AoxPIWnPKgQ!^L+d>>Je(tFE46j26E@29f>w3f0+*Q+lNkK zW`gGWsSV#l>XvD|2PkbFOrhVqgqQ3>WeqEp(y7<5W0t+A86@c? zpl;S}4&DTGSBae*kQK2)fGi*8$qO5&y;Hc|#`e~#${?IA^K>7SD1UT_}Q5=3%Y=>&I(}{q5tOx;*w}lKVVyMOSst z!qJrEi}CC2=L(ESxBr6_?GQ=C@5wRVOCXJQ3Yx~;uhG$}9HV&{GdgCRM$1UPpvP2Q z5B7?kR1c*m#wZKOYLiXSGD*%kj4i57vV=cV7u%Iu9n28*kqYmg*ZM{mD!55A$oQO}`l_G~N%%=PSkC$C84Z8#P^~m6NkGeD@;vl;!YY zSO(|<*X2Z}XQk{p!=yP&j*i~bSaj;($&{X8nftFg6!+sK)t1%lAlL~eF_TV{-_91^ zcGkZHK^cFk+_VbbIAMeQmxgL%&%J}FAu;Dh>)?iM(afd>_BC$nOij*6iN70p!}iEy zbhzira?BmH@lb1PzXh_Q!Tc+iBzxE|{%KS%pe-J9Q8*3@fs4y1Rve66uk7i>Cjg-h zinYb6@W}5Y9y+gsuWeJpobSf0B?n)!J>aQS_r`9$Xhq(99Ua>fHetVzL#2W=wL|d8 zv0zwA_Uu?%%@-8mb>@>i*8=B%?Xlgdl!S}+M;++FZH-7q`x4F9zsR(7z04>Muuwuc zfE*H-*=6nOH_M)dMggtL8+YzZW6jNg$8P<4`e z+wYVq4rCX1tt(;=!Rck{NU6ENl87EB?}iGp_spJ+6f{4zI{*8lMv2q zcyCCcO{ZD#6$w0DdIn6B z{-N&rYv=Rqdz0);h1AW3irSKr6?@L%Sx z|LN2G@jQM!kG}z4{^RQFtk2_}v$jD-unfDu?rsQa5DyUxHQV#9W7v2M!qWs9=5i2` ztgR(f=h{o)*|pz@9U~mdngp;zQ{BVZX^FCa?s2T0+7aTO%k`kpDQ2@YyPi_|XM~ zbzp-p!7C)_B!~~eAOXL+%$Cyy&qq9vf_tAGJ#gOV^G4Z*YiUD&x1fRn|9^#)!W z-~^EBL4zm8*vu?hf*?b=xTHr)pd*!%H$|oVcmuNosK^G4wX}|=8r1aYg3444F&gBi zZ{~bjwB?iK*BvFHiF!Z-#xuZvrVMLRHJ?9ljiU~9*TDDA$CdI}Y zWZ3Act6#ys?C~K!>yDrYXGI!?RHk2|i3{SctOsR0uZ6_CiDpy&<~r^QiqxfpS&sm)a+Ev<41h=-!b?BLx^uMiuDpH_v1^Vl2u;p`g zH<#SW&*zwCdM;V>E&ia`xrm^h{&&{JL$Q_b4x(-m4r1&3gCI6Sj%i+>=qYhVLj=UHCI+_P?`MJs`w%msfx*sYRG1( z+G`bf;|8-i=WtHy?ed$*itfpC?OLhgU3)wpO^HldL*3_slE@M4BGuf79 zH~C_0s6JI1^v-8x>=ZJ?QtqU7)&eoPjjyk3I%V2(jRdZ#D*&^6)os0i&C-hDhY*rWZ2 zmwHLTq&9EIvKv49i}1qN)#tM2%ig^ya;xDCx$H(4X7~^vw}f+L(v7n!xZFbG+=-PL6XN{fhaR;OGrxLcxwf-NhoZ) z&KoB4#$Wwfj)yCbY1tvydLI|ga z!UctG7$P0!jdjn1B>nxa1ur-MkI$*T_ia>hj=8sE7+HcS!fkQ-^iTTQZr? z8`9b|4u+1UP?n?^rZI86vu9^ajD(A?*s;x|a{1bW9?jgN;mYIcA!DVtrS-$NkLZ{_ z?;02wd-@GkQ*ElpD%?x@$k|6@`Bm2J(du3B9147%qsFvs|*2gzvFb15Cr3keRqa zVjI-iV@++D;9EURZc~WGjR2bxn#`iWRa7e}i6m%d+Rzep6D% zg}jNTP$j&Hzv$r7B2v?w?(Q&XX3BV;Q@D9ePT477SpB%~A~`E^-i z7FEaV;lsB!Cd~=(bLoiV$?GB>777<5#$BVM9c4?ZCx+*1hD&OTFI}?4>iC(luAs@m z0{Q2agA~fQ$p0Q_%Vest7~z>olP}-(%&S`2xW1f?n-<=L;p{|>yV-0lT4C%;qV^ha#PH6 zWi(m^H1-xABf9pYn3}uwy()j(al@di{0xZK2VjNT8dAmD9b9Q@;K7VT7w|v zP#m;#s9xjv^ryLWv(3?1k}xdV?zn0&Cft{wfACOvb1uuT#9!&6AhXXg2+`}gx(RI? zO(uFIO;HNiS0E3X%3Z|SYghA>)8XSuJg=o!cZ!MmJ2`7^G8k%Y%rJzRaCA7XNwu@H zwDPovT(awhs7F?h8}b>3YK@DMoKBNE)!fMkmSto8*_(X~Pds zT>s|lfzLm)ZcM!0hQ*P}l{K;wcO6s)v3Ii3w({b|R@%MG`um=mf=_05eT`Y3oA|>v zyZ-tgqV}rPB;KZCMGEJR`pA(!fep*JZW!!NDwkY7+-ViG@iOZzyG`?lrYxx{-p`|{ zrC)Dc`h2fAXFMhyYs=oNcdlH?uWT6fZ#%WOy1hNZbC467mDMBaCd1(j4w|JN^W`eL z^}TJnFrU|)WZZN1?ES*RgC1iGZY!`Zzjbo9A6OTCHQ8jHLu2}7%!{91=`~ankgei# zTr|Bx+edJxa}QTVRSVB^SKgp^eZ~f>&Jt5R&ydpe4Gh@h(qjm>Kq%b6x%Blhi!@6f z_ilS%^)f}WP1F5@^Xn8G$NH%dPuAtTpJm?eF6UQpvv+&*bbkVk-aXbPUP@At0S*<| z*-U$t$0)xrU82>Qha2Q;`qOXohkylX(Pco)ny1W7eG|O&f*_)vx6)%q(=gOp& zN*?x=VPAn_hq5k#=@X}b(rOAA%#lTFD@=2aJv2(9UsvHAHmM7fC(*sSe4ialwYD0P z4JMwLoon#KJfu861?3K#mB;G}TDw4G>8XjK)MB3iF1sg5BP%5_Ki>Ops=r3&xQhVI z+pZ6VdQ7KT?qPP1iqy06O~CcsOEzuFd!?2awm@tpw^(QysBS;9wX(4`b#!P@+P!$3 zQ0`=<>h(2B1I(6*a9%UdN9BdV!{gUC?WLvai>RqyXc%MV4Gx?he&SZhWuHEI@-|6o zip@ZknIRjpfr=mZ?uxL&t@*M~D$lCB$Njz*LV)9&T~4+Y-h6g=_|e=ECS|k9!Ktw% zRK8GWRPNkao=|XaKv$PVUAEj7Q}8EMQ7Ty1PBiUJHuqi!FMnx7M7PJW?*gQ)ynO>G zEp~;$fn&`uhEb_@{SQmU7$e*%To6v=y~h(Hl|jUc=o9A|m>2?nw z+3PSB(qlSCdUz)oSZGwR2*j!S@sdkW0JkRI=AE0ZN!)n%4;DaNpfc4jGG+e!)!<#9 zTsY&r#!Ez2BBC-SGpCK3G|a)#RXQl5=e9?e!pEK|NaAoMTJ!Um(_8ZI=wucf&IC zH-R^nQzoNFQB!o68;{!t@}`0;c?@dd1}R=!fD3K+o>p@+dyNAgBe=sQq~xIk?R)(A z@b$P~#`Ydy-3ZcOz9?;|PQ@dvYE@r?L>Xne)FIF1WR`P}KD6(2&<}f~3PMRI<;*!0 zos{#b=IOKE-V5Ppt~vK<_t6q`!M;N*Q~kBoA==u? z*0C#2Z5#Bcy=IH|UHSOu*|D*Z&84+@I#^}&c#anubBbTa#?HdPr6o+se6V38gEOb2 z!<1wmutNICU~39-7~lUQlDQW7$Oc+Ba2tEN%R)M&BU>ZNrCF-KXgAG!Nac(@&ORm8 z6lT{XH>62n?>c|wRC#cKr0Mzsm$*?O%B`ck>0+UAgY2P7G6tR@M~lA(p}X@3es^T7 zBu!SeU1fzhXdKR?GrDMC)OgZT26JL8tb0w}-DCD#xkd84Pimr2#-ku_Z)(hF3iq)2 zfqDDU`QplOo?)D#W@SkVRV@6c2mQTO;#MyV44JeqzG2JNT-kQVP099IMj&fYkWapB zZTk~Hpj5BYTRAF5nfXy#-?(AP@El}r!wV={EnHVh&f~O@s3x&ijE{|tvPiIP z=kdmQ7YiqjT}k>vQ#0V^hD8<@KC|a$jg5|ewjjR~@0I+C*VMM9US1&^4TgF&LL2p_ zs9Iw%1U|GZANgkQo_H^OpT*-#g@gIhX?M9&!?IhCKjp`ee6Ki-J*XcR=8qW1N#1m2 zTNX*Ax38}%p5`z|fS&5gRG*@rfy||qpdyED_B0{sgOb~?EC z)cG5|bNWg)B^L60QW6vUU*45D|5Z}A8gvh*Ayc6@a5q`N!$sgz4;;9LXp4xQzV3eI z`@AkXA5?pEd{*81v#`77l0zc6dy_FP2A`AxLDtK(PFz5Sd#|zXF5Qds&OhOAewtiL ziIkF*B!ppqrpCfg751A|7TINY(WU^02(EfC!BFZD=0g{^6mQaw_Y?jeMypWnp7HYf=IgHs6-}wA+lw&HV90F4q*^!^ zVd|d3G#E~+6=>I31SsjH_57}&g7UN%%x8$B0gW%j_#DJ(BsVAF*kH+q5(hH;2ZQ$- z?2_f`A!+Y#%7QEqn-H(LMG#d1Y4PZ2IB+wWW!r2IX;#H&ELy%C{!6?<82DN6#2}Wr z`=-9x_R~X0V0Qy0Tg9eE!v8=m^_JjRHw&D)W$@9sx0OoWDIb>NN6hY{k-zy7ZHbx)OK#OILJ{RH_iNq%ar?uyhM$gs}*?HNHxP2nJS1 zgi?6G7y$weDuC{k@%5%Z6IrLhMvVPdj2upjNq=5PzxvM5-`orF<^;$CaM}l4^Iw^_ zuxi29fj~N71qq-QD7o)F2~TY@U^sxV7FXig@vWg)n?QDL1C2IgPmL>tv6%o7pk)MP zF*-y9AEl|gC!B&&br2dm?jQ!Btz%skNvOX;F2_CQ4V_}%JBIBJ^7ENdys|=&w!)mi zLuG58FPI@r&41iF3qK0v@~SiufWqUV+ox|Xtm1tJ zR^_C%p;E%%TXU5pC?_7SEF~|O)+RL$Jx*!O{k=1Vw0Tvy1BD}|lt;P4DlbmeU zmJnxK$1CXcm{IjrBh=*^X_D;-Z(za0hp#gIusa~7!tGG@o8e;c7_448Ovm4aWNDPQ ze~?Sw1z}Jstc7Yzj)nt>buZ0>?PpmH@qAMq`qUYeUeI5SjN+>j&@Qfxd4B`r2r%$V+u@H#YAW-A}Nr4@}# zfa*@-ZIW5ocZW@&WpJ9rRk*lpKt4{=6H*?grBTq$28a;%S(vhy(bT!FcUioU^SI?lc&@&|(5ph8J9;d14w~EQGBW zs1;yPpelETo7Fc`2HYAT;H9Ojk8c&lN``b$dI=U0EN$kg$5b(UKq>{6Ll9l8J=ZBH zU;|7;OA!k*oR5C$v=IJyduqvvtQg!8N9zI$y2{uD0XsG zZ{Y>pkesZ3XIm;3a|diKFymUje0e<*P(NR^re?=6xD_Cf{7aBTye9e}>vZnlcxMke z2?KP6_wM6_wi}i`RB{`oq}pHyO;eUXT{T)Adz|Cf_u!gfA9w&7la?}znG3UXoK0mJMD7>Vb2_ll_7u8+g_I1RVTG-)puGqFfq~L z>|I%R_; zBj__nQVlS(Yk^UJ!OCria&_brj0|X~-PCzM=TkV6lUzPG;$}qO^akIE5+EC8o^i3aaW*?6;+_V2wEE5#5bd>VJ~U;N2=dDR;PKJZ5nSr#F7 zO;_K72Sn_V3aVpBrFZV!$$|qS06=b}lJq3%u@mC$Ps)WPkn_6vM7h$OTuDBwy?4N$ zFE7SUM1kCN^c&eJ6OuJjr{R%y$<(Of{~)0wp_-oPiG3Bp9R~;BO ziS?2IOqQ3+-d0Bm1NZ}0y};b)Gz)Nn;1J%;X|&1Efaec9M!kd#oae8)?3xt-go-hCTO86c5= z$)`wH2!gb>H!8pzb80N7sF3?=lYq(@u@UPEb!%OLp(rT@KPSZ`N1J05MV#0wrVYPK zZ1Vi?Ixooz^?aYj8qI06Y-GGaDNX!a$k+*MOIjKZ14`cHDkY57VS7e_gWxYbh}bMo zW7Ck5p@QKfY}fJTHEVZ?v>QA5b=i1$c)+5C041PVv5c^PuCkLu8@3O*asu<`kBpCp z!?gyO2K(eLtsB_a!dCSQ;#6v{GFPzdUwu&K(vxBUSP&Ic+XWvR0#oeroLN}kQ2+ua z!*sM@=>`8|;aDSLQ%e~BK4b{@+dY5Bz`25$iC;@?xN=>8HeBm()R$sonrG^tw!g}Z zPa)tmGuO0^{H3t4u&5}rrw0oHD3P@(<|$PLc#aXaF$&0aQ!@5BqztHHxR{B>lsqZI#s3c~zk2)FI3@xylbE&?3WddG3dXIT_ghRv zvO~3S#w&ALu4|sK)%C~%J{Pg!Msi*<;~tjV%oJp{6Q^WPCsv4+u2K>bu~^w)s|Hn( z4t(k9)7i|m^Z8yDQGe`0x0tyifo6mn&r)`5Cly#Hng zR>BbOFE-Xkk22&CH3HNgVaK(SqV1@qQg03o#ZF#z7y)=3S-Y~*(j!1#+O`!Iln?*_ zZ&vPWyp?(e29mv30WxvQHNlnHX=D2?>+BuI?*WQE#CB#!q5h zx7Fn)IcwY4-%r)B16ovc`SM-{BcV#^1JA_N^KQPEn`iY83~WJ_2EP)vpn*4U0%MvW zF8WUv(pV#r(XlZijS&7T_VbN!Hdg}Qs-~u6?sOWoGNa#0uI>lrI1>nQ;0|IXLGD0$ zKr(mvX_Q;^K}IXqR&-o}F}Rc_&SxvGAM(2c71b+HRLIxP$TizZi@o{WxR!jjV$G9` z*@u{p$zgz@kux9Rl?V+HssW*u+aBcS zH*+h0I*gPBg1Unl?)@I~y{z77sM)m@*k*e`_bMPP(~JEkI_=-$dj)1KXpX>?b0merf!5m@A)a8IU_(p{ z9}v%o1Y*p?G5r8^!5j?%AS4m30zkJ3K(UwChJ7X|sOLrjpG8SY3BM3d9PniB?d5;x z;@FAai5uFwQ=`?IKtc?oQG%W^8vH{5*bD-9hK@1_Uw4prBvFPYBJbrcq1kii@{r{L z$}A4ZoYn$$%wkn{goO)_xlm#EEF(0_s%DAcQNns#?(FB-+13qAQDmR&=t@LdJ%Kl`rgo;{L)O~O)hFjyuBqd4exOA%sp2E0>PTSni$ z)n0r8vwvJJ!aS3gH>IPgbfRgP2wBOp!(YJS)4Pgq8`d=WK#b-aR922{XeuTmE-@!Q zi4G9JFBtqRRy|pp*Z^?2Z6=X)2yqVM)d{|2oQRP9JT3=e)QEM3LWBm;%>lQ$XBnRz zX}5d@z8pTVM(#2`VLs}Qv;XD@vu`y2#e>}^FLF;5%Bw~8Xhlm7u3WhAWke|aHI`13 zZ%+h`cYTSR9Qmfzz3gvQVDXp!w_E)Gw%P`;`-y{aRKh{5aejXg#A+|!wDt%M(y@kx z#NXT>hJx(vtmai5J{)V+s_-4eDCvJ>VV1pwf18{fA@*T8Aj$S!5JY*@-8tUpZ!i>U zCQqzxFt-iX8iNrG^MH^iF8@5LP!K{mHFkaG? z`((ZQc^Cd7fyG502$#ciGX+L!y1_<(rVywJL}S)JAatXC29pTnwu)O@b`>1K!1Q0s zyvQktGzUsbRckSY!g~kGy+`N;d?R6)lR_*9rYi8(VHE=~DYx^gBB*{z5(i8byZaZ? zi^8GN`Y^pcm@;#EJKEWJdLUx5JaGaB#(ks?DaxeZv*R8RNSR93{W%~L?h1lk74PLJ z@te}Fwm&WGE36jpvnnAqxeMtVvvm(kq+!nv^ZF7w>PmDeT$cR(v!x^x%Q=N6@UbUJ$Gy zKQi&r5z6lSKU0v+Ebh|l`7Hxo?q4OXAJ1sP`RpOWEHPil+!13dye>ST=_2%jV8iH} zdu2ccfcr)#E77*}j_B)um@U~)!p10sv|S9p3NVR;Gf@0=J7mxm21&I@et@cI1T*zR z@*6g6nBt(NjnQeIiR8=!sk(Z#sLC;xo7@|8)d^!{YrzzLAb6HQEWeaINl3_+25vMw zrZQh}dqPObR_8TeeRcHNwbf(%Zkc(ccJliBB`?7~vVdA$F(IvZf82J1I)y`Zvz?VR zmD<^9v(3G>j(SWlbwMJ7xci$g#KpsbDFpHtw#j-sxfCSX;2kC-Q`6McbYwGOf0UNi zp@jVJh^=Uu4p@}&1}FTEVLK2&#zjyCk(Y~66I_Xq#n{v0Z{`|$DOgfIW8KE@?d`P& zgz@|%kO$MeIUcNqGsZY543Ccww&W3k-Lw{rbr4k^1)x6|IT#T3-UJB*^nAm_NS!0Zxbxlk?-_JM*2Or*KQp7Xja4DR8-!7gjdFf*DX=a zaqKvUz$Fwbc-U#A=9U)H;5|u~7add%UtLiiM+|hOO{|13}FluGVf+@+6D%< zm<3K4>LMIylav(H(ye%(ot?#xaSK3CCL8o2SNR)TY=dtFyPv@@X8Z)GHl%M*Rq7?} zv|5e0l3z2}!yRg3e;$*cOCPJCcRSu*N7iBtu>In;$oS8`pSFq^=rIyg1(`G|zrwF4 zp@sDsQP$a18f;w33d)2g>*Xp^?T#Nk1lJ;XbN6nb^&w=+&C45ytR+6atRpa)tUk4{ z!b6u(RS79iAaQC6V^ie!2#M{u1u<#36YlSV;Sv$d#}rNvwfipF&|MiG9vmFsa;aFE zvEreEB*&RJkqZ8*dG;P2$w{>>rV^G1VaCGIQR3>T{Iq?c9z~nJ>lQ!t>D;-V?(W1; zJXkDx=G4h}i37M?zcFk>3^xWsKayL!#meUpMp#Xh_hYZ7KL&AX1W)$SBYGWNx*wz? zoY(Lw!$=VvN~{@hb}wGO+<+mg~|6CpZH%rS(lW& zW5*7gH6FW`=vY__>%^t4!e=QEWX?6_3HBOpuIs4bx1Y6j7K}T{~grT z%Cj2ecG?v3cmdP{*R-z$e;)qqqR^(OMb*aTDR%X1N~U&A6h5CS*Y>kJYUkh(B0%c* ziTy(QN4JZle4qMK%jF|lcI+)DqHc#5thlMuS2t;48voiHgy-46*@eeGs7-B`rMAal(PYbf#0XEf`R~5Lafk$RA4;QKyQH$TN4u*6$Nc4P>uJv zB0k=;IyGe!QdT#7AfAibt~!_r}xowKpz8)EA5q{7E_&*mlwX> zd5^$@T$dnN%posCdgwS&n%L0jlWivlkHwYOu5^M%At8ZrZM7QAOcYlDSAo8NWj~FP zZ8>V- zk?pzSJlqnz0ngbR8MwT7TA<5ANGG7?RJ;-Ko1;Z$H%}RO6mi6gpm%M902>HUd|VtC zA?ETC^VpZGRa6iVJ$LyjaANs>Pv;P)uvC7cA{~zz@w(UDSuP73NW+^EtMa4X* z(YIKXpf`5+YUs_dFfN?WXnMTt`|kYreEb4_=g$|zlKHM5_zPy8#EdfchMuZm}v{ zTxtk{N6f|&7=W%_`;zrDa_S2ZGX&=XgwUx_Wx?0T>=o z-acE4?((a-pBC*PKJVbH1m%f#{!k5h$Mz!5)ObQ_yt0|0;mOU*YsBwshPMI>c>a+i z!pmj7mZ?P;&5Yv zDF))3Ft|)kwbA|Y3}2m^B0}ShYo2e=B#4FoR%-g+Myl@s`2SheUv|tV6YY<#E3~}f zOlis#z2f8B+!aw1{_533O%+$CD=~ zNBf>v+{vlWah{@1pSYFcU?ndfWLx)E$|O72!Q80&z6MKsKCb)~_UG-QiiMoXp7{Yv z-Eicp?=3B%pNHGQt|d1b|EzkGLvwb{u_t5vbG@@&lOj&2)vXObbEfP@`&=k}Q=n^GH(-|8UtOtU{<4sBzGN<(cgYF1)nta5?&9dg#52ssmUZiRqed+#IqnA|W0-b87&()~HPP1g=zaN&$4*K7X0LMoLI1gY5n=oj zG2s=83Uc1l!+X=^F%#=*&e`GMkfLw4qAt}UB+W_+u8#XxuB3^3rP~5uQ&K)Qpd{zq zT^|!hr)8%W<0psyG(7R0(sQaJ)z==XQq- z8fSSQsft{d>oS>j?b@rv#AfhBM#hNK;K5z~syqa=K-r9}l)LFEROOC0Zzi}?QXZae z){W`=v+PBYNbv-#2EdZ1s>@3P#CN#%M+9za!Nq}o#;P%s1>DX#_Td9%TDTWZovNBO zs|7JCwGX3h5B)UYK$)MB)Ue4&2L9zU_^hFKRvM>UkNK$VSSqf{~Y? zh^eNv|Cb91APd^k;&X<7289tB(cIThY>`wv5ix9Pa5Kg#;q~h;Sv}4@$u9JoWRmf? z$H}o5x4+uHoqAPts$^==VgLTg6h_^A2iwkBe6c-z-lJ8nNG$>!%-P?#v7v#MeekCI zHD-VRm04ETTzqembsM^Ble9Z4K?h;K_N$N633qd{nSQd&2WG;XPyDVQtWNh95#sw! zY3@+O&O+bFji$L7>FMegX_=%JXD9Ch*G+8`PaUXB-n^Nn_cfFS+vzOs>hLj!6^A(R zTDwFp_&h{QlPrb$CI_ zqOPg3NI17wg(uBqvwnH|))zw$_yRqKU%%;kuA(E)z^%N0f8UiA;>b;uLdq=6a(hQr5WA;%64h zl8j6=DpFGNA4Zm?MeSkfP(GV4^3cJYj;6lmt)al-23O_|#4cJTCR#UVNAS-r|n>y?F>epNNg^p^<`RZ9(GguZo&dnKLc&N|CA;*=LEDWeh!iG-Mq?%Na;((O4#)tci*d9D@3LcDQCH=W*k znvd^FKf;gi*2lY#@VG(kI0q&f>woD6V<|gtIi?pp3U5nqVWH?(SfuCw>-R&u*@AYU zSD>=crR-RcI4bs5h{tyhR3L zt=bO{%v@Yta7$XfxOJ-3HUFS0&BAj)$wjchCr|2ZXed_YwQ+4vGhk@Mu(ch@>b3n7 zpcxsNKgWCu<6{55Jj-%nXKAlZ6W%QR^as=SApLo159z(o}DgM{m#jj*#tl5p|{n1+tESsVv${=3|+GHGf?b_@1Ar;%B@R>sciSc1o zZ^=(T{RDgihYIppjVa=|JNx?+1q*(Dbfg{z`r0I8WcM^fZUD7P4vtxPQ_b>V;pd0P zWd(th-LQ$}T(BU})pH{`n6=2jVvAl}i{1PbbE-1(=v27F0nlCOE$)IYgTk&3N5FY` zM8v9UmVK9Y{Het9v9P|$q_4tlHVOnr-@fSK8YQ@Z8X~i9CkIP*4iexm@@&g)tb;uf zp5K=Y7JwQ^=7SU|=h2bV05MruSP0BHZu0mopWW5+Mh9^vfiM*oD3#%VPP-9f&?|XRCe;VG&^lW{ z%rZ++IXx7i5d(QdW13ZfSqh=hM5-ACN>PE(eu^&;RTzbc5bPBoWqPY)Dv?Yj>!EJ3 zn2-^j9zIrm^;Mq>_9`?6tC6E~Vy}N^{3*f14(A0r%?gy}oWF}(b=9gPP}0y-@G}4y z*zVYV)>WR^0O5u$`JPB+U8xuaJqHw5-k_6XsQTjRQv)cfT<0fkmry&Qw_>sF{qXYh zx)#4*zF-p~oDUT+Q?%z5XEii+0OR$MBa`ORKqU_k(_LZsCL&;mpTG7KRxqbE4N z+l4^f`T{h02$rmubwO?*EKH}<`awV98*t~+&XYhdRFf(=o?nz);0bXi;q~>8z%tm!NeT5PU3+UH$zg z3CXqFBt^Qv^YZt^?u;;;_P63b!tj~M2rIF#!FKxEjT?(stZ1N8C_wr#p2HTd z4-#(|WZO#a3^AJ`J3C{eChb*QU7ZG#J}7hqIUcAKsBrv)_R_T{h+syHzeh)1ST~7$ z>x{+zk-4r+o9P_fRLF$G_8s@Ioz~dc7z9BjrdV$MZ=b@#04WLXEtWnjOm1&lqiF#2(PrnJc z#kD!1Z@V%`z&S#!)a&u3FL0Px)5vb`LLkAlPcRdz8Q4R@S;HOjmc>fq-q6kz zni~FBd+z}hWxA~kql_34$B2#MIF5=0QAq*<>If=PLX(pcnRYt+6M{!z2AD*deUm9 zC=_FXDC8MfF)S`G?*|13GzfSr(o~+Y6t4yZ1SBQ>#KXhmys>si)LK+`MV?W<&|- zlpE_B;qf8=B6i#1y^s>ES+fRfUjc>Kw?X9haKr|G91fg{BC%ZnmyN>IyTl@2JY#kB z^Wz9Q(gUJGh^jU9P!2KhQzdkDbPye8R9>8qw?|e+#@gDNwC=(E`>)=-$#z_h@6Qm` zu`epBJl}GUCO0>?)m+UYcj4;oNB%&uA(WI5FhRRrSmEU6?k*xAuuXmnyHjDkCaW&+n z=;C9kpSl~N&;3Hjf^uEBT0H08$zs_6tesyatbgIsC2cH-M!V6CEN=Tf`4ouzqN7{! z`yYQus>4-1^NZOIkcfYDiSXh`4_rq6@EC>g_Q_y1J z6J51p1v5W2b>NzdvBaZUgOd97yn}mBbMqlopN7{}96QFNoFG85LC4|bGz39aQ<{Or zuEf=l98uRP$gf}j3^x+uB9SS@T4>*Do$L|Gl*(zSVei0;k#%^;pqw{-7xH9?ov+AS zam3{UF_XN!Jb=9PK_R_F(!G}P_sx$#o~X*m)5Tp&b0u+ru`r}2T56bx#AGs=pQ{Q9-Nl|hCe*9@r*&m!#Sd6rcE~}fbUcG{r5d+nJ z9OI&(p#A3NPw}xabfnx_I`!>HLUBG(8P%SLbF8bYs~#s`3JEfly@;P7cdX9#TfTSi z-X3IlL4Zeqe_~6M#m7@B&##qnyYBYahnF+BUA~*elCgap?_BoY`qk50jcyjUwlD9V zTXpC5ZG^DOnBNRQt6Dd(q_C)Ht3mb)0+U1Fk(qbj#%LSj(=%Z8daq6+wQ9HCu$erP z`(|5xT^(W`@d5sCc=NhD3q5U4{{;qrV zBC<&6><++QtW_>;%pZUJ5yuR7luoM$@)%)o&{DW**r|3WW(h6;T=xuC?1@Z*sW)}MWHQmMD|;EC_=mG-rJc9Mq_3x*5bWK7WC z11W}^0)3*Ume!HctwU_Ba|r^70BeQjlyAj~JOmfkx5_4*A7Vy^_-Fx(!kdOjiD0w9 zT{e%_Aj*Yp@BZIyuZN%^LpxE3kqN6rajmIGY&(n#$!`9q8R6T(109d&%g5^#H5;6l zwBgHB*jjv$n5J7@E6n(Vv4T%DRf{?Es0=_Hj_AN*{KkuV!u zln**M3;;1lD$wlNv$4s#BSCRtUh5a5(clvd3rJ!im$Jv9(rZiH&!echa;vdzKDRY9 z->9JrEhM?9pwgP;xUJQpzAIaNd#;aCbYS4mK-^<}N1K5(*F;G?)OsskSs<=Y&UI*h z18iR7;K3VieHRU_!&Yazb}OrN>x&(o%fo*HGJfQIYE>}<}$9`%SdmJVSmRk|* zQONdv$sQLi|4>&`GYnpX6^?u|TuG1a-xt`rRb5SOE%qKLE1%8!6-@xXGB`LGGD@^Z z#_d-4tw3(*`ZxBo(S2cGHd-R2@apF~G^6=n6s{Jz<8D(E;m4XYXAb&(7@t%@M({2| zQHH9Vuo__LLk$XQ4Z7^Al}G4W^`}>-*8kbLCx@7QIdgci1M7w&u@nB;ouD{qu3?M< z78skEnVHo}tU-P9M4#O|L&ttNEb&?$SG_M)p^0H6UR0p`o-}Em-<&*Qr@Dw5yS?Ap zf1(w$1hu>jgC(48^|rA}@6nzsi{NxcqhCVT1i+^1(GsewaGo88D%4%Bz-MS;=FOY; zn0M{q8#P%2A9ACWiG8qn*v3PtG{^2mbaji=V@)ltrQ^aE!Op4=h-YbvKZ&Wr^RDO^}#%U0LN2R{G}J^WVao!2a1 z-hgVk&hOnG%}_L!=+&zuw%>u~|G|TUDfI{`t~jnYI>Rh+&GaD>l!*YZOf!Yen>TA{ z)F8_SQ-Q5iRcos(d;?4-Ii()5amThoP0di$-NmbS{GrcA8YV^zq{`ao|7u7c|6`d3 zyakm|^p4yjOf*-d#iXRhaB{52vKz#pkOiF63>hoYM4aq*YC!R&?brCI@>)?Hijp(1qDGXAE`uf)d zD$!w}D@sgAXt>8L7Y;7?UZV7N4`kydS97=ELqu^oed-jJBh_Qn1}jBi7an*1F)LWj z(7@aGWs}ndeP8^(KUiVHGNat$rYK2U@z&7hGl#8ebgn8{;QmA&)7mv_h!UJ&0) zyt3AbOb~+g^@!};jx0fHOS*$ix-N~oe|%JA&@e1h?oO%(f8Z^1`V+wv`9c58dj04Y z3BzND*mZ9U+gZI!b5m38_w4!QHT;^BdHEZUdwEGv>RzBer;RSt|vBXH_1>O42QyL^c_;@d$9!o9GhI&9BcV8;mVwh{m;Sb&Mv* zJ;TI}9pB=UH}#d2PGi7n*YFMBbi;$HI~rYWk>WeF_vb}V!7?W&CB5kB0g`qMoCeMAxs+C5}v5! zra!7GtE!AKx8ez*iG-C^*6+jdE^WeCj6(zZg|uc(n&wHGvKpVR2~s8TKrt+t8XJG8 z*HEaAQ|PSQLm3Iabg3R7DYB|SSOY7<@STC6!P2exVHj3mYFV@MXgua|At9B?V$)*o zaQ$Wx2@hxLq&dC0@p`t6oh99i)NQ0d#Xm;eDC|#wY7gcFnbWV3=01A(PzZ<>oQ0&- z4py>`Mq%!vS1XrZKTK&&8()OaY>ljk13GUEeDJoyF@vw1?#6upw!{zBYh97}>CsIzp-AqMp8CY!{9ndI0 zvDY0&&|$Udo3p{e7)(tNn@wbvW5_5UsZ){2zqM4Tm{u{Am%Oe@vQ=4kG+2^~gO@2s zWqL-fP{alK`pp|d{39fhI^}zOY=40Hf@xXA!&8uxgOEWw`F*~l$3!EMsVJPD-FjUamH zCyXm|YyH#n>7s3Xm$+ZfJ)n6pCa)l3&W|AVzGzy-c?Ml$pyzTAYhLrjtBl~&xdO>aUgs9`rdw|~R2oRJgU7b~My$%UwmLxPRi&4Lfz+>z~!0Y7HnlFOizDzm8RK?H` zDxFqB&#__!bS=sEo8gs59c&g|>i}pHRa)40m6NGy$Gg`=jUlpsF+6YDvZYU9s!IXy zAi3MgEuatt((ZB-$!ZMu91o zLU`>)WCWnTFWaPw&7{AAnkaBK%Xct9!yR~MTHO2-)4-g*P=Zesjl8zDHsU>%&ac6* zOY8WqL+b(Slmwa(G=lhBGjMQWGsGuD@T33@SBJ`H6;}H8q04>V)rE$geCW_)JPO2?vsp|P&E@rHp>(qGR>}KB zcdRZ~J&wi?*st9-ea2RhXn^E(#GcfxzZ@F+M@7X@YeCK3yBmM_;VFg?5yRZa?=Bk6 zog0MoNI85AM6fHAOSexgWL`RnJoS)}n&8!9!BVdq1q>`~gYhulqZvv`N-{2TJ1C+p z_iI8z0^T_H!4~)K%CPVSwYFikw*u!9%o=(#1*GDV>fNg%wv%e)-@mwojQ`(IMy48& z(G2Paj0R9TRHW(lbyKhAQfr%dlUelxApIlXULB4j-?!h~q!+cs|6WGL3gGW9Mk8{eDJDXrVpB3`_QJ=K{@ zxkS??_x%D8OGiO5Vr?jK{rH4@K0T|etbkj!wt z*-2-XjvAtL`V5!a+%aev*rJ(OeeD)iX4?mJmA?{eQosm-lnx;la&%udV}xJ_D^NEC zTi94y!Udm_oV=JMF0gUqUGr@)qC#;-tT|~H#@q)+2yw*QKA7QXzMg@$QPCZP&ZiZd zFibNM?m2JKq7Qfqd1K@Fae#xJ(;&Y(1G43ie=PpA=XcLhHTu}z0D%$tjyjEW4{T5! zK;uu2n*0}nhMB_9u@p!DgF%GPA6r945%ivy>hp<>%Hxqnv;baF6n?GD%ZSrw+||9h zIPP_mXNQ-~lk10*`3Mo#mlTxJgK(MV+;Gr-)p%_9rvw)N^W#6~$CfU8IvIcN8F=$} z7ZJR~?%e@>ZY>~~GBbzqOK`7zH$IMnol_YIQVIrsfXnnZg{GOq%+x0A;dRItz=4T~ zBqtk$bvzbmbR+3do*_pUQ#Cpi8B|9Cm(WnVveO0ciA4TAt!X{y2#F1}tPA}v=b43) z5|Y>Irb+3H4$kmV=23)O$Ec4n^~{(%o5jOZ!c;cFd51`ocQ}B|c5snXuYwe=*-%;^ zB}lFrmcurV@7O^_mbRrPC$Vbu1&%TSuz>?)|32g7r{vq*1lDo=&!>6KW)HRGJ*=)y z2cp6}#~vMB%+2lY?#}FUL+M22Y1nFjIgm&Zd4xU<^WU~@uRx|Y4p>nMK)J|0)vVl4 zU0odj53V^lv$wa$qkThm>fKdlBd=e(96Pp<2lAme+3$lmbhh|ssq+l%&H=t=2r7vhue2E$T8utZWYHX17gk64{ z>1@sA-S}vDCG0{`cf&T2Y2>e`Jg#)ZJgw!laoJ;l95Bfq8g4XIq$WChSVvh6aPw^n z%gK3LK0Q&6S3>aY#i%G5U}bQ#n;^j!qbQaV)YNDc%e73J(GKfoY{^-8!>?DLdhYHx z(~?eR`rMR6mj==%4+9AGlA4c~VUA^kc0ZbErU|hU2!%op#U?*Sv39rUp6S-|Nh472 zsJTc^VI#3y;H>LkWRN>2J7i9miOBw7-tSkg^uQ@V+l3)%7d|fX)|K?HXGQQuZ%G?d zsoSTp#X7mHDWRQT$jjRs*u0dm@IYTm-00i4{lGK9L#Rsa^q)FGq4ZgV$XTEG13}hq z>_%sIQqTzy_SxAxEatYfw%T8DYOo~&TF{%LS4>fjOc1F2g5I(VN)SM+Gt@x8r1YRA zCat~?jWDydHuX$4?i`^0^cwj$_ct^yKSC#;O&CC^0ctwlxOCT~JANq*%ks2xraKRH zy^M~Dv|jCVC;uRP*jO_pgMbdklW z()d4r@JZY6F-8KoaRU)JCbl>2l$JKQajvDATm%6CcV+O%9J zonyXBakCJQa@vJi?H`QTg29XF1D0SvSD79sKwiY?DE#>2RIhbig-?t9@`QE<0F~sF zlwo`B%$2vVe6chV|q3;Z_m5}zlnzK^)q!0`fp!z!8dHrdF^5iOAo#A7uK3V!VqU;>PC_D{~n6kj?LbYH$ZgZi+GADOeYVJOt zA7atv`S?r#vm6_0p8z|}^5=Nsu~PBQr07eaIh!_Z0!b-=CJOgG`gO1r(kI?srw!ul zg7ZC(S#j)T?VW)+a*CR&3c}F{j)qv|`ExCc2iDE`mjQ}~)yF}{qMm}SK&tBM*nx7* z^;SCMCE$CK*IoZai`8Y^Wis>P<8Qu0p9YrGokTQYa24aP(ws!%(~ixX?^XIT{YQvU zj$h|51JKy@^eI7G;diolkjJvbj4oUsseNg)c)uZf9W*!klDvKT14r>CcxnOSsAP@arw zTVKffJlonMXjAa_;;t_9dTVWKYkTBK!uT{14buYX4uc|K4=xp-5Ef+I6Awh+h_ecK>G0$;p|h^g&*PG8 z8PJBh8<;QXs%l`$A|j=zDL`;RK7bo_;GJaEr!7Fa9+!ND!`t2%)2NlSU`9V&&BkHD++`NqGl3_=&d_71 zKa&DdU&r)(3j|c?XD7>W;k5SD-P#9!Vk|YiNjn=~CCB zHfek1#+Q~|LfhCoD)9f}M@7iVGZNfaOcumYflu&sP!JgXyMO#)yQT{rB6Zz|h>7Cw zHz-|O=SHmE06+rof<%sNzEit5a2LqHyf6nsPl`OFT6C@DAG$@MZ;%Z3V^ef}Ir+{G zBD=@hI=ST;4mZ$1oHKI9iAG(LF=#<`Taqem8e-HZn;3gA;KoR8rt9B0L3*!lxrUo6 zToD$98uQFB;6I%z`K~?QdH+0)o26&5=9{g@TU{(bw1etEGT^eR(q95OYbcL({@s2BV7>9n^uVdu9%P@NyzHM&+Q-&LPa&i)6 z8L?yq4IVAF?SX0D3lQhTc!g&Ga1J$VQ!8(q;?R=;$9WH5m~MQprf<8GSf1l8dJJz7 z(k~Wpa}#nrz=Xt>1(d+OrSrW&t6~3DmSd}Am)`d1u*;XF&=cV0FJ(6xp`+X)AOMup zAkU7Bid@|xH||7??bzU9Sj1y;XSLdvvAV1_#h0VSTLOp<&o8j#uUWAnS{mx!0?>mU z&<6qV1_~erQ34V07b&*CRLC92)Z0cm5w9k!GQ>JGr`m*560(L2sULs5jq3sFGAI?q z)r*oQBI1khh6}O(qh#@rv9Uv+X`5i+t?L+o(Ze#No)d_h54LvwQ)j4F$_&n`_;vut zdxN_l)FjlvEGZ{I4ujA~!2;8TTlUSHH@NU*vDpW2V?+Lu9J2sFjGWl_(%aMHef;>f z&M13G*2C#d{a+6JWVuohzn>+YcO6WuRWe+3Bm91UgGnBHAz@X@;JWS|Qit#_+qti1 z9DnPFcAO_tu0-6^n!S#U9)JRnwzrBuo>rDF&snV=3wQ#TiN<8c2RtXo&N7q4S=Re+ z4Fp!y>MXx|`e15Sc3P4qy+WH=!--vn!F^e_;y@;U!rVq+4YSK(Rzeh)c-eE!u3ydR zr>tw{v#RGqvtj+=A`1epowBm2WSo3>doZkG)z}yYHdIxdOrPEu&-46pEyx4}uYG>C zm{R-0y22*b74vSHyq?@N-@S)K)iC81jPH*3aBBGF85-b?*A#dcoklN8oG1+J;EJIi zz(fw2)MkxXao$D5ek7b_tPY|(N#pdPfMH@rm-xrsyGUXLCG;ae3Vem`kS#;G6tiOO zpTi@I3j)v8f9V*rAS#5n3WNxsu*!i0ym0&wt9tN|!H8nT2x-x2NOsL}s_>JmYHElL zXHcp`_n7IV@QHc-mO%Bj`$V8hhF^(@AXdfUJ7JOrB@XizEh(uX{{m~oo1j3TPy#A| zpMt~V!D`r&r;1+%qE-0Ak}&bF*|}}YmV@oxKm^WHsp(V4!it2$97+M35Q;c7U)7)> zu;h<*yvH0oeUG}jg{&% zyQOz|QF?E=0RW-Db&kBw%2)%67$^u%v62h?r5lPI;P0v`)q0~#PWVV)KfjARhkMbY zH0F8he)5-dWP$*$2{d8$pJbFkG^grRL@8#kA-^~X&@DI^K9+LOeYnZ7bV*r71x@s~ zg2sB;gCqS-!+>#)d3fMOU%Y71E|=%~*cX{TNmvv16&E{_{|u8;<2TH!2HTvOQc_w< zxb93$++|F#o(F7px^Pa#Mf(x#!RmJ_!eD_Rq-3ORKHrk>jgav@9foztMrK^=Fs*Wal!N~Ir1Y;W~AfM zzto^Q?!WBczu&&I+4Rqc);OXB2pbB|CzKd@ov>$F{) z^~=@meLN%{_K2-uO)nk!VwAMX@(v>O!&}Zi|1W32*NsWovswm{8dHXvO|XQlUBd(v zkG$t(KF+iB*9yiY3A?1V>;KX=O86cUd4a4^F?o!K7T&r$N|jJ6v0g>5^aZN>%2liO zD=N~<8UDK?JDxD3_Etf<3FyliA1wGH>`!XKbkMa?qW%fSU?Wr0Skp{T7Zc!$d<~hjrRG{f(Y0Jb6f%?HK5DZ|dB`;MyMSK4^ z*g(&t@8Vj3h(*Q5mf?w^jUqivbJ@>s`HkkCUjDWuEX9v~OE4tDDRJ!z21r^(O-XEI z1iqcar?34RZ7t>zZ;*&6oE7)(5i`a5^?TCq_Uy11ZdT67$iRdBfStOeA^q}_XCOCeAi)q%d(ovpEH<5 z$v3ueZ*<^tWFMVhl%|Rf9o>vB zXh1>%$s<#@-UIg1=++|8SrGLc6CZ)db#|;}$6y$d83ch?@Rly|c;|zRt@=bxa zNw{)G7^c^HGmLX^7f5VfAtNh`L_GlgH0T*BZcE7MTEyx|Yw;=Ft1QcNePb(ib3nkB zhw6B#F)EB%?mwODzg^92P#FrP(Ou?LOvTV$y#o@d*kN{Y6-XAL}k-0cp%`6AYO9(@-HZXN> zm9mD0*>&?h035*wp{Qbd30_q^Lk3-$uDgMt7myN}81*iX@XV4ol|)h;wxJ=y6T+3( z=!GDtlalxOG=3TuZ~WKslTmX2@sK{%ORM{UBkx+FiN?wYiM1R#iSgTC<}nHu^y}$5 z^5j>H>L~E&-+AzWf)0(?mo^(>z51!&ZdA;_rM0j+c3Fdo_vk<*E}-aWA9MpCHf*O| zoavL2S5~GOr&t?!8A=*`Piov~3+fvP)kc+YV%0dj-#`8I0`b?$d?PHT5Ry74TPtxq z@7)7_kuxsK{Uuqdo#{jwe);riyh$b9-Hxt%}g zoWcXb$N~`!250zh0O8I=Jk#?}=}hxpoMD=!Yk<7_G6=7U_a>Q@t@8#`26+4Dkj2oi z;QGig%oD_(!dthV{SLo2`h(zU1LPlf0}(98jn*PUX)j`BAoYlKpMiRZ-;9Q`S(d$KA#>*xW1QRe$NZe=w?dENxCMUncA>QFqgoO zKPEu83Ya2&>>>oQ7$)sTqsUa)Zq$JB)blLpq&k^(CTv~Abq6m-1JCWcONDBG^T;b5 zayfFs?hMy?4z8n3{lGZz@a|Fh^o)&h3VQLGZrr>X%?0}W)$6>lJ|5-8c2w3CTU_?0Qivron#%)K$_W0n)U>oUJ1hZk1$*zr z9BwzC)QZgc8Mw^rJ$!3G)d0FBPOcxYFmkLn{x6ct%_Rf!_BT2#toCsJthVjECHqNj zo7YU{vM1O}uG^tmnGH+-Tedd2%XVu-E#9In(6(xm0t$lbfIZp;=ml1tZ5@}|v_#2q zyOPZ;ojuAZstiMBLaC))tIptjO8(ZIIZ-iU6@vJK^N-v;i$4X(GChQ7Cjna!vdKR2 zYlD9n!OukdrMP{wxP7vH*lH)r4a$|m+%bmyJ@Kx8Fb#M@U%NeK#iY>+jjeMfbK z+yznDen`Cp@l`U`;RB)%TO6hIpE>(aL>s=tp|O5+g)*)K-XFXm?jFW4#OanfGy}V< zv>`%%3Ce?7449(z$$(%WZTpgBRUaeADxeN*?@6#$=-@$^1N|8Bo`fx+4_OsGRjUVY zB*~eQd$ht_q(Jes7&(_pV90y(#&yuNPGFx!nSq^Ux?QdQ4oPvc&t5}Ds`4-4J;9m~ ziZ0XG^%?{zVflUN$^=5U##~*&D&FlBqk%6*(4w`t`Lr44zwDefxYr06&KSHAHSS!bJtXxcWU3 zM)v_D{G};XkO>}JBC*o+6x@A4QE>Idf)&7E455t_)P(@I!_NYeFLmz^ht`=*e4-Ji zeUO5Rb&Npw>9`dygs!gj05?oo_K$WD3Tz0kAW3)xDpBMXs=TXnqi z9Fxa*5%(S0mx(4HL;>ko9~`DFT~?H>bUy^E1BOaTNl9(;YbYjpvKk(YXGvO7$|xFw zLiV*o9W9c?VO0oitcpZoEkp7%S_$-R=qML2Sm5gBmgcxVk5t&|y*J0Nh!nXth9uiO zU~1hP!S1p$D&Mmy076k=Ija(}e3`S!i?{;&&RZ{}r={t9jQPi_)*w*g?Grx3WRfV5 z=SbSG8RHnFT^f_mB)213_wC!axI%5l;S`;?r!e)bua7QJiAEp$JD|Kyr^6O#U0mRu z$yjUZdp-AF!+3@5h$%Y-Rh4Q~n0mP#mH;$vsKrp%0tSV&CXI52HrvrQDJ;*{)7dfF zE<7uyk%uO6tOt=3xdjCPe*HoFKo%5r;liPNXY{&mQgZVtg=J-U$Sm>U= zTs?W(kY1qj7|cPk0`z>>{6(VdzMU{c2lv8F5hYG>g>a!o5)sE=I@K_cm?R3Y>FAjB z{3|huI7~5*@07$MSVy2`SZ%VO-2`dal`B_BV=VkyQD*=c5V#SIM(;}%P{t&Krx>P} zef3X-Uysm;EQ{M!BGI2H7P8O-C6=Y1e{w`k8+CYiu>Okoxw+@*IDuJeARlKr>j@5!iWIHxOeFkh%%HMg1Ptb0WI{6 zgDniy_$ON$B(WGl;4*;p1yeYLM+Za*(ZDAqYM$JhXu=wX1_U=Ka-KjTVH|U*k^ck@38Ce7caEhEA^ilpAp8!1A&5}r9BWK6`OWQ)eD#KO`O9{)rpa9$z-2)8 z0yTwea2ji8aI+L-uMXc7LVIuBn#9mL=h$vfr?)KAO3VZhQx^<`uyIp`kjOn%{#h^C%NMyL9zx zoGZkxj9}6N+JageSq4Gtg@198<+gTVNin2$;Ql6Ylc1n_PkFW|qEy3w`i;+v{rXGE z>AuPG6GA*GTffM~o*PtNUjF)a(_an0Zt84!+)n^VXfpMw9>`%S9A52@UWGn>FtEh~ zTe!bAm zHCIP3Y_>>sq~Kq?QLz84CadStumVaRWxMW8-JhSnD(6^LR6_2Anh)cqw})iOg&lSf z0VD_@wdm#3quHNY{Ja|i?0N@x(HF6npX0G#!1C~kIEw0_NqlPh$A0^5Ubp;BzE@-A(5|s4q-k4;h&9Kwjm(|?fKwo;`z=0GA*g%Sc(g1m^d;7h)uYPC#C{ggeCwcFh*+? z{7Mo8PzupTwZe`8dWG;51Acb50J@073rrL)$yLjjgDNt);d=L-&Wz|)x~4mL&TJ^! z&z@bbh~%4@O#un*j{vp-(1%(QaujPD8?zg(G*B~V6Vi-tWCL$zGZi^F8m2b_=Xnb3 z1~WF`HLPKicmkQk42pevD-iroM&bv}&C);oaI>?s9EvTLHWWEC?LDCAtX;Zv2bdTP zERgAbp44dS*?P+4ieL6Fgxg& zQTv^s#+y9Ti}lhqV+OX2#p*SMtOpLiR-w!=MeMCfHRxty8B>_geL$cDyNnk=;oZ}~ zI!DS;T$CUk6!89c$9lxAv0jURIERZX%9qt4E7E3hiLpnb2j7qbuhu9#CdqF@6HX-Y zj}gx!Fm?^)j0vn{moKFG?OfzX?OMZ*43p8Lq)pH=p@6V^i^p<**qQBFXxHnclSyD?&i(r9<-nH0g;y{#Ra!1W3f{_SKzaJHy7$Fpd z1{{Kna_FaVrSeI;z92GJsv;8wlu_v5&Dp#6Bt@F!YpSKSl#j32xlf%3O!;>DLGo_8$Z&Kk+$cUhlX=^h^A_V*4&D2G?E!)0HJ96ivZb_=O77A%` zc*f+(LGPG6$4=u6DJ8eLwgmJR2+AqQ|J1QkNi!6&0Yt>4_uFbIyaD1e>v2)vxszB} z_zsH*MTLYGE?&%!=Lz2a7&M6RAQG}-T+|YyN2!k5cJ#^@{ymX4!s)v9jK-I%se#!# zhHNV%D~5h^TuZlJ9B0cV#`%TyQEvnK60R}#D#;aU&OP0!78M#lb>YgDwiJzZccvv~ zB%<5g0>9$WGhHsvz>t#}7>^droA;!^Id0q@k{Ynv4b^xUftUkhcX{nlQ1HKdb_Efo z<3CkW)C_qe)HTk+mN-VZeKNJOZ<)XQtR?@z)cWO1uARA%{>I_Rk>_}FVmo%gLju_Y zR9K-GFDB%F_$-j7-eU(Te0T+PqELXt?d>rW4318o8k4FH0R*ITBR2TB~GiS6gGdwQ?5CY=` zdzcn}rNwI^(283{v$PK+R_pTT(Qc=%u@0Lpk-U$(f)d;VRU2A7f++>w77T0@ZaL;y zdYyV+1*Wb4%5^O`HK|eb#Y9_@{~R-u9V6-=*4NJ<=1#7G5&Lhgb(ZVpziqP2P8+BX`j8a{ zS6#+b5ojSyF=)k*#D%C7=w#1fNP=z)mkFo{mYG+6?bcU+3$mMisWtdg+^J4T1B00t zBb!W&6OOB07t^w+w3S`Tr)#kVCFd>EOrn1TQ zBf-tcB2x2dHmt|{*R*qP731OU+-JMYtvHW18K0Ci<<&U`caMf<=-=HMONgSVx%Fcr z*k-DU4sqXoBEhR;(^e=V6J{UyYIL{l?d@o(UgJ}MfICjXzYG{PL4G^+baiQWZ6ODG z{1US3&2JTD6_`)ujEs%fzz#IUm;$ci^BTIQPOtz70zo4K9Bb~JInpvRQQ?WD#Yi}U zYyvue2%Y6odojGwl9T0M-Q+|&g@?#AvdXVPG%4VuMpS#Y3lJrhb|kiikVlpbzx%n(q<;{llp3>TmlZHrE-q>)|H=!e|H$_-EWS`wu(4WiQ? zhA|FqKuNWER1YHerVu1VwW>c{2_nM6X<0$(4o2xZ1|(~}RCaqpt(Y|ri%`PTD56F< z{>6F7fXQJMZnO8{zn(p-(0=xlM~^ZpH?U5fM=k$4lV}0eBp86}Ct7^5ij*rHkpB>W z(o7@UpaKP@ZswLxCOr*<}L+?wEwN=qj})8M7{h4zcx9>p5W%`FDl z1TQAhLO1mT_CwrO!>KV;Hoi_*F3rOa~3XL zx|Ff*T(hg)mA$(8H-=6bNpG|p;2x|kLAQ+CupvUqy2L{^6)#PIOn$m9Lzp`!HBXlu zJ-AwBF3lurG&?U@A@5;<>ltXRX$MVP-v(532XI?q`YmGKJze8K`g-m>#54nD1Llck&V7%F}rklRsq+dfm)F6$}ZX zQJx*<^n7&OkXRA2YxpekTH61z_=fK|j@KWOh;bZns3MvBVH>-5el5}CL`E9u>leb= z0j0_5Uw^$;gDZ_lIza9L$`f?6!0s}#xLf8J0urCm57S+%<+VCy>AJe< zwPN9m85Jw^_UDH=-)YJ6yVwL>!p|0u<3;yG)0wx{L-~gdmhjvi_4MSKKc85~oUV-I zD?UZWiYp~0FS~w}|&Vm@w=7jSmaq$uO%3x5!J+*e5`R=f3 z_w0R%yZcWC1p$h9gLocqAo5UN%;x0WtQKW?Lif*gYsup@X?qX2wg`RlcuGcMVq!>$ z2L5xH^9SB90++^?A#f>2kA;ccw^&+5i_{8~ zR37#~H_M;$YjdqCrhaARad>X=XCgDbiz@X0Xi&6W)j9@e`aI$tEFg0vl=`(U{%!^EB?}GoMfsjX><-Dg*X-%V)|BfCK9Nmgp-qI zYxPhGSL=13rKRm0#R!DH3NJ{>q{!aD4?ix3?KdJq-_+C(%^;>Av}F;cj>k(IV1dFr zFp6kZQ7y-a!O^bajbg zzGHVhLIuDULDbS?E-v%u%`5yQer<3*DKT@*DQiXEH`zd+#xX1;MGK7^tLRfukX;!4 zZ{4yVdj2YYx?zlIriPjt7NGLNvjO!Yn{OqhfyiY-S-@sc_F@*%6N8u#Uktm^e`~O) zxLCb1A^*m={5S4AyarH&W2fnOcT9NM;<9mTjAztuM!@6_m%B@If{d-6!Z0S1*QsTr z>-+@0uS2!47J!-0IZhR=iH~CCic3mPpiTgk4_~|ut_hUdis8$I!4~E|nV{5lnrvl} zun!|!ICMo#2>)wvX7p{8Lf< zVbIw>dY4!Bcq&p-AxZwIU1pkZ``USW%lx9EmRD_d~a6 zyUDae1BG1>9@6247|ucq3wH+IdIgK+ci(-7=!%=>&G;$|E5KKc4jsAxTOB;7=yuq2 z(9E#Q>K1PeyoDQT8~$rT2ZrkiMpk%n8=OY*FOidm)sp|E@^B|LcTTF=Wkiqw=PKm;XYC%YjL0awUou{Ak}wtG#>Hs0)Ahvx_sb}mSS)Jjv!*)x;}h;n%cyI$-h6`k zaG!;^qpX5Xf#rjx7SeEMN{Z~au&jQhHTbN|Vj=V;$P@tL3Kqt!@aSP=ClEH`I4#jV zMyv%5=v9|CtYclg%Kng|4eS@Te~|Z_oSbUw2mut*i(m<?CqgK}rdYQO`yg~%rB1C#Dk(cCZir9R}L9SO!HAT=!Lr-oKpvaf=8=XI9 zTnBC^aE@vGE8C1N$20#cuWEPpN3UvrK>_fRe!${IiGV6y7Q;%Ok5`A#6_F<=ZjYR2 zREz~+W^dS-`;~_^)FPh@l0s{_70ez$C!lIg&pJp74vPPJhUt?GXZ_8%_f<&nm-S7u zq9yFD$5W92gbsyzD#Fyk^J9#xw!`O)*L(avRquX7#8mS>ok+PCf}APse$-!5$x^yf z2)V{oicz3+8F+_U;~)TfBYtx z-{U+@E$0&@#Cj#Oa#e+?J-CYSLF){^z{&g+Dyx59Y1+O@i`&!TF#I#%L%*^L7rag= z!mo|stMn+U&tTkj)peSgai}oRYj_<$4#ohljvnrmPCXO9vk5x7x`Jo3K^CGc*y%xK zJxU~abd-GDOtbHg>9z&8-*xJfx@`*>fQ`UG;Qfw}K4wh~a)3Uz^cN)))_T1yS1ayh zW|uZ|*{;&Eh)%azBT>I!yi&^NzF@t7Vorl!f666x&i(uN(QxriK0haluo;}*RU(Eu zt(-bS5K&#q z!n^0Zh$`4#zy$mXejflaLB$D+Ik=%i3ysVDN+mYN@3}!8?&zH{f=O>Vjkxf%>7}ZR1h$Bk!0IY3p$yBYZG|BD*%wj5B>hSy5W5_m}khQQ+5rs{8tDO z(UolV&R_BT>C;1#X-TC7P>T6WO<{gG+!@{7wD#+QK@PqXd2=7-9?oaq1RM%9A`2xI zXwoj^luGc&e~zG{6JYUpQLYNimnq&ivT;vB1;<)=guURBYqWvap>S&zT%`3=ifU#m zMDgtJ|L_VqyV=v; z_HtSocL_0;O}qX*mUZ#nEC>mkYt`*gtG}s5C2^>CE?chl^SWx0-jBhyP0W}6Dml4Z z?>PS4CF1Vx?LCf}AKo44(s8ykjc*(&X{+uH7*<+bkYzhJXa+SfOZzVasMfUOt2aMhen-r=Wo#<<*x~l=FV~pFe zYgZdE`Qr!Ei@v6mp0Z}8ndr@=8fLtHm2UYn*i&Lv{n}l}VHi0>3t3O@oMGE_yp0SH)uy9kXzpvA_(;zAsWY%s zk4}fj|MK_x>$Z`yKk~PKj*@+x+$Q&T#BAT`_>MoPs(YsEi&cl}-f|9kU7B7&3{Bjx zS6n|m1ErrVD$DV^v=>t2gcPiPZIxSjW!+JQ3-h1;iy8E%x~02)r&TkXU`mmL$Jp(+ zosW15HST}(hRTiVy^8aItTIc=npVip^@KDc0`Tk$Ldh9=s@SisL z|3i?~W}RSTUpp>Cp~Sz z58{`IhGY$4fWhd8tqPDA1FQ^&H*RFstal3Y%;OIuP1+1$JP88e*0_-c=;9&`WC2-* zpstoHf>=aAAfggrD=#RR9`;YbL|*mqq4pSDC%RTvv~hyU16VOVlA-|Z`% za?l){O@Pj61A-S`+H&asaqmI^*C0^s*Kgk<jHuVi=H^5p|st$i!f9P23S zkIz!s^Xi9${g3_5?LNEYfZ&GrCMPnZPp!624O!jero83u^z*-AJTx}pQEZZ~nu(rhPe-w#Xlt8WrLo{^y{g`o4_4<|qFbF$f5& literal 0 HcmV?d00001 diff --git a/docs/images/example_09_link_navigation.png b/docs/images/example_09_link_navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..260b4af8db432c0e80cd767f35b82104dd91891d GIT binary patch literal 60726 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

f>IeQUc|mYKp-V01wR9L0S8`8kB*bwIKsk8 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ℜ^{YZ7n+V(2su3F7K3j^{6Y3aP{J!hdk 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 z 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 KfFhs*0-=dO=)IQ^5fSOV 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

xcffn?g#kxcT9v|%%mXq)kD2g^0zE|i(| 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 z z5S8&DNGgh_&A?3$d#P^dmG1^XhX5=*gpg{NvO%`5-cu$e9j(x~H@fH|py6YBQhfqHdXAj8 ztkM^~gp1(JbcP1EDp*II(Ue5sPg8pS(xVr+G}G@#2y0Bg2360{)Q~@Xu;RS4-l5yn zceGWSJWmMGc4%2uZ6em*S?5cKJ$MmX+PiP_7C)e8EQTswLugC4q}iR%X<0XoINiH( z7Y3rXj42ez?&%PT1e4qWibvv_5$Cy-TPSn5a~L~1D$FraZzp_ng@;jtuhsGi ztW0jvG+=s=|@hg2-2|KXyGs|u~&+? zzXjoq3ciKU;G!fCk7s%}eN;qJoLWL+zwo&CY9V~><`R%B*RSMI8#&EPwF;jk4j6di z1vo?oU$h_`K5ZvHFXrM|5BTk#jmmII&kqaZD5BzWJw5Kq>E;L^RRV<1dce7McT6mM zYkW4VlS-3{ijQ00tO2y$i$2CyHR6SmWnKpaNyOy(*6yc?v)Y-n>WNFlC4Lsq88C7` zeEG$q!?QMn;rT!Zh;r7fO>BoLFL}&-WX;`b$wtImX0*&GUX4PdufGzaWhil+K%H5z zN8R}4u{M{+f-DE!jl_d(%Io;xHXUx0(F2uP>!32rAQn|-4e_-WoX-%Gd8sp(?Wf9o zNZ`r=_$o=!@W45gcj!j{Bs^q>BK?#D#rBD$St8C`0NcDUhmHY)tJx7=UWHB0!v)ic z4lSR?pDFfh%l2pT2C6ol2=h}6t9!Dx-7V%-knC{ijDdn42^Brin}qE~vo5j6Gz1wi2_f7@U(~=mz{PjCP|QXotQGFktvzSBk4+ zHCqax#HY*W0XzSA{y;L`zxO{rvBl>l{rdIB2e&9G@7ct{!V*BP1YdSHBqSu-ppJR5 z5?p|B8&rZ53sbZTqo5M}1F8gnfOT4|1poK`@}I`4Wv(#V2!X|RmE%6TODIYO_Tvrx zr!i}>fOjzgX=CkqD~#Xvc#3iua}a;3k~^dDG-~>W?D?i1#OVu#_Za`uW3r$l6y|~J zsj16QhZH-YSi50ubFLUc&(SM}yZimlV88H<7ZGzAN}EeR!7@Amx~c%Z|Ki}quX`Kx zLuR1-|!(3u*D`&#q#3gNOSeZ-6stJ z3*xp5;%n=j%*`1Eq@E{hj4h~L&1U_4#&JOm&a-2 z$)@0?T=ps%G*`18Kp1VJUtJW!Xl%%{M9WzWpT?`v4z{x9d;8I&$2?NP!y1*L+t})_ zkVkWnXNzqduXk9+S>gG1>VVFD2Dc{2J+3eDotj?{PrY)dT?GV?rBBftu zhW|uE6+E~@A>S3g?dYv$pdO~ezUc9I_!1mhlk@1mA@Ehfiyb0QUts*6?epf+~BK4%5y&D-ZtFC)lr(p@-0HN^tMYo>?BKx2|9A zetw_y!LQro=-UI@cmGB+@crj|)7hPK* z?FSBWeDkF;n2m?xZ)ltpxRNZA@Lr9=ex`@(Ek*4*g-SD%IYi5f;8%(2wTa~bLxA8l zLD*j8rb!(?9u5PVsP&QA9a!2IHYHHPwMaiyzJ9fic&9Fx1UWdo{2jEA5ZwFLvP&<} zR%Q0;>(4%Q`$dbk(0x9<#nUX~2K6=Bna1l-+N)=w zq@!x1!NpToWDTH*Z7;y9a)h$s8PlpTfcWry(y`&O?PtWg!<_Uw3c-=M2&Tmc0e4VD2>}|3K%uBxSS6vYi=8@yVc0zO@ zMQ??AI>*xvz~v4RH_W*;R4L#bMJD8ji)XOORE$uMUSBLCUo-oe9j09#(givt}NZeKiJ|$o@c$GY(P8jDe#A z@l zI$Z@&{-Nap_>XZed*OoZS*|>gnmPo!P2G(zya|Y`;(&VJbnw(ssQUCCjzB5RYTeWpwl2e=hSSI46<67!49Z(x{ zAZNALW;CF%bt)Fsu@+L%g)ShendTE-=?j|G|G*z_j$Swz2|FBERa~}v(%ah`J)cE0 zLvtu8tbH+ymi~&4b1nV59=)ng(o)CO%M8XFFqe69zGcz=OFx>g2{QH|<97XXjQ)y0 zMl_6+r!O@d_~!P)u0{~;t}==UVK?EQv9E9-$b&ILo-o*=;J584ienwt&{&GB&RHEvO@})nk6;RGXhQ^JL4DmX?l7K#z+i|4FY_=@t*k-B9z79zs8&2+ z0RElyo40Q5C(B>eyR;tefJUz`t0Wn0?*P_F7y30Sfq-}sw$Jd%70dJ)7=!aR9LPl> zU_uH5C`JfmiUk9XQq!?RhQk7OXs4J2&zkMgFEm3%ppbJjZcNu>?vJH7m9a3PWk#Xy z(RYu|Jwx}jT}|NjL_?a1ayk{;&~kIkepkX-QCHl0Z=sHhb-)VT?$q4{24$0}(XMVl z>pnDRm@Y^nk+!6{nA>v7M&a}d za#>D7OX@r5TM&D*u4G9)tdsx^{Ku~9@n;BP51uuEnq*Q?25fP<;V4gl%$pOz2HwD% z`Z^RNke|0eMtzAAmx<(8fYfQpu?+68-1?Q@)fEOa(S0%G#9FFIrzO~7mN)WnOw{Jh zereyv(+5>zT7?%fM=l&BjDh_z1X_Y$OhuMWYYMy1Jc8|>;@P=W%!`f{(mU6_ z8*`+pIrGe!Ry>eT&Jw;Z)DZpu@g+*W+X;a~J?1sPLT(J$9nw+99z(ULQ z;|Z?dNcgdJL@UyI;G@(*jn&UT*FUm^YDM#tPw<(t3Tubk=9v=HwtPpz7>rC)JRjky zY;`ND*4d#7My8!Euw(jsz8i!pf#Je2Z|Ct|zdlfOS;o1f6??U%S)l!k#3*195K%O- zI=*vEGw*)AT*ve?*W1}roVP8GKprl8=#Uom3fcRFjP}{bpsnW8PW08igoM}bmadvj zhM*zm(!y}vP7N3I%1m-@?Iotx+n0uoDcaoR6S5s~YKGw$Y_{YPZd@=AItuh?;cVIH z^_O80(K?v{BM||`)-nKG9RB@y6S!t*EvU?q5j*R4gGiR90E@WOQP6z1*1ofBIU?SM z!-XdtK42|cIk3Wg{uqkCWY+iMYieqoI_{bD!rJztFu+X3 z2WiIDC-d&9@cOHIcuGgV@&~J97`XcIsP0Q-h_V4uC}C<#^00<-I;-Qa1gzg3Pz94D z1nU@8GxWs@v~e=WNrQm3Y#GH!i^4p#>*&!wjj(-<;&e8O{piv*H%WVdf71l5G~F!! zfhGkW7{Sh&wDYHQRR|rGrK~^%%N`4Xt&rZ#^qE>s&AYH_vJaXN!>ghDjVN;uwuj?} zKMJlH5~@9B?ca_j`0+C_n4|VD=rq0(foqUY_0R8$*Jsb0#rR2@Uo-F^Ct4r|*UhlPC=5IQU2^k&U=VJsnk=W5(81jve}Y_a#vU=>|W-~g3kw@j#0Y}UFv18 zP8|K30?jUMhDrQfX@;CWCx_3#K8aFX^AIkGz@ZRkB*X@=*qVeD7u0bo&l!02->m{L z^&8KD>j`%Q;GrpRC!BLrmq=MiT((5d73})C7hz4D<*J6H?h?ooE4xSooP4k!N9gRe z#dIZ!g1Gm1;@;#2^C~n;kq*pqSx1X2lcH)j0xZ>xXgbvTR-b!eDV3W{cwz2XTESyP z;T5C_zZ=Jbn+hB&%-Nm4ynjL%U&(8=s)5!3x0Y$s47lGiswR$ot@TvSdeQFTVt@ds zN`H_SdyS0G!uH1m>{?EOiiruF8xNq6XI4#qUtf1pc(}$(6uVH8ljCBcN!x6MrQgM~ zH*+D?=7+TVH*7AGYsyMCs2oRyDSZd}h2UXoIz~>lt@mmV|5Pv6u2Z;s$wY?55s~;q zHx4FwXsmENO)&!lyVctm!Q^69*JGH%!q|gbe2!B-H>_&p;uW7|Wsoweb>;jp?%uyf z6V!u!xo4$yo&s+11Hvsft5_Z!zKd{+C+m>BcjCs{Ue6_NspKLssf1NAdoaG6NP(6t zX~_pYu$`P%*Zpt8rf1tmie*n{jkc3wpq&&GPdi^RYD){P>+nz0d@$^P;*V0!uqIMd z3xFfx&|^af2M6VJkRdT_{HIT!zU;1GQ~6}Y{EuMd72vh}^H{pci2G#`nyb9i7MLeN zct(X(PxKT1mwvQT0XUWT;raS;{qbgI@r#cbh3$;W-Q9sq8%-=>|dIg0hAk7_*CWE0VMi>)qjR+hC?6xrv5t#Q~_- zvx3{I2`b1C|N5R1?&6WnFTtyA1D_Pe1Ga{{ZZD_#i&3Ov;D#EiKFu<#iEXZifS57D zJMTArvpHY@cxvtSAQ9|pf*!Z6t0mE}ZdwU{gEit%tAYp=U3v1U8#IAI2M6Q6(^qK^ z@)P5Hz>~A3hX^8?380J2G-Y59bDDA^1m&~M>y!LQ`P2chubNTNE=Ti4r5?-|3b44S zgc1$~2)qr-Pr`;wm*loq9Jzxa)_RtM+Xw{E1bAiVihDp+Nr35qT`BTl0_4g}pkMji z7K!`)zm`r^=Y!TWX8 z$re)1HA!Q|{)rix)S{gejw|a8Fdw$;)0Sd+Z&Y!J+Wt!hgM)+4c_M7*X~PiBQY zLC%CKWdLk1!eO9n$=~7Py~Um8Jas&oK(yr>+WiFDEi^?}dY1paY6jQa z4FxZc3;Ds^9|qN@VKBUe!^b2nK_MJ}D4hCUe&|ueEt^xrX^0m)mBtsD{iK|s&CndIzN#BPATJCOSWP`5RY9ydSx{U3 z7$zdUh#GsyuYrdsE-D;JO*^ryiWcbYh!`J+_RY8{7>=xbhZQo%-+U1zYfg2bZ~UP- z(XB=s9P?3-*z#E9;1M%)@AS<0a;fVuOg%@*HLO92yOW?+R;fNLW>#p!6l4`#FJM@& z@a((>5pM9e?A@w%Q7k|4`ijbIfIA)R)q64lTMz=@JUqp1lVwG7?Jfg6#7B9vebIRlJ~1R_Kd7g?U`ccY7&@=AoJ8*O)tkN13#$J9Sy_IIen(+ud%dSaU@&2 z{xE-+%_Sa6Ug>dcJ7UCSI5|BITW#HG#TzrQ^SFvZUO*6|t!nu(+go2cz{flW9e2&) zQ?SA{9bz9N?_*D|7P$d}7xmG9o zA;+NEmVR5K0^ui5f(qlMsU`6)vnV=gUqS^ z22Fz;oCtg$auZ)%z->R_kntk!ZusAnpCaxqDP}aN5w5xSs>i*?xWhUF!tMy(_eSe5pNuFkEM;axpKGh#nu43z# z{!&K0D2nANj@FSivA3^B!E`amd1$&wO=IZLEA%{{e0yDAKWy*lYy=OfPV9g%>4ATK z&^?zyxvqN00F!wCZU@^k)G-lD^eXv5_oEO(=f4k=;{y}FKo6F=H7mC5ced|ymN*n4 z$2@++Ag5{1tT>}ot}VF_%;>fvcBD|Ko9*TnOzp0;%>!l8P;t;K(U|0uqp}?0XNC2N zh7=f;jB zkk?9Ry|BfHW7ozz-rlQUle~2S0r(aOlX|syx zV%O<^=_kEf1ctA0zwO7bLsCt0Vcc2ixX;r|Wx-nH1#MvEYilu|KzmPsNEHla#bY#&+J!ubDsrBYE|wX*p4>lhVKtQZe&tx|49 zNfu|2xsq;=)-HE4INWhnn!XkzMe!2u`&Do}SHk793Yu39VFPrA6~#@+X&#gnV`UXQ z8`4*g^M{ecVb|?Dm|wewM)48z^Ns%`h11*m*rO}!9-KO2Khk+L3;5BXsqM*f8q(Hh zDLrH`{cMdy2weO3KBjr!wPJn_GJN>m9Mr63H!K;8b|X~rK)_45Fw*JNu=GR9Ux;C^ zzTdUX`+~aU9HV~_!=is7hUIz#b*u>*3G4Mm0Y6B<1Akj7r)dce=!`-(?fj6*dEj`I z&vW{u+qDds07BvH#H-%}HeDG6)%M3hwJTv@#eLBG7<-(PCD1j7+ZFy+0Z1n91!76A z+dYkOe&HojToZMWKFf3?fO{;ocM?iqZ{&(4Rl85Nov(tf2bP=|$@|$);P=no>>j8M zIJBW=;#GW+O|tdr$qWj>l*a!YDL0DAr!FEg#R1-V3*|OK4GkBM)b;s+kS!Q}F zljajxznr>`P%2SvTmBffNG!HV&m_O9pawAbkMWa^aFr!c_h3uo;l(&LDnv}e$EOPR zeU-3#8TtqlP1D^IS@=H&8np~Wtp*~MQ6o&jqLUEeZGNMT6Pe!RCo>^GVFoKSCu|k- z_=`NKEn&WT|Dba%bu^rgnG>n7#z=r%Uv6aIv5tcCHabe3iI|Ua`rZo!ecOhVgYL!u@I%wcF5m*#;fXCyoaq!jYbsWZuuO~chH;!VY zXpd$!qYSgc6BP&64#3*6jwMQvUI~7_y5;x>Z%?Q)o9q2bkk`tC`k!AJJk) z{We<6v=72+^Ni*m{)##PFw|w6*BJ#H!@e;=Ip#1FvhGiSnaQa!2}VwWX`W@%n;;rL zPMB^?;41G#l?r|WC-y8DNV?&Dhy55h*b>0cpJ4mpDGXnNPHgAE$s~%~2c7Jq|40YZ za206Kh>eRIYFB_aDJnku$=4yx$>msc%N(7_Ks0I(S{WMmqHls3oJtg z?V5Jq9-gI`0(1IM^a)PB-Q35hd1yQq%|wUdG8X3}W^J&L4JAwTf5;_U_t2%-S?hGcC=vBv|&+6CHWZ zp^&|x`Z2jX#k4FR_?L09*N`?8=Rr&oPh3Sbk{D)H5 z3~50M^ZQFFOrg-6qt-1@}QYqpOPFf`5tYB?x5gp-f%J zx7mG%Wi$mOiqj8R#;%=>F#xV=qb6e+Eda|He1yCw66zOPz%o{b3-P|USFh&JjG=D` zIJS>$Yog^^7kB1aPKI*yS3)G{!R{?yK>n}-i7zt zBz?Uw+r=79=7N32z+TSGpz{2(x!UyyC2RmRa>5rTx776~Fn(K)B%yA3Uj|xc!R#N> zX@2AeFsX$Gux$$sVE-lC51Q?M_kZ=j%b+cAfaZVv%2amZDC;>^FS~^MTQG zSM(+*K1PL}p`z#0r`}&#Qwb`~W{K7ByC%l_RO@~{GU1gs(k^p_ZH^Eaxo?qX>T#;w zFc`9fK5^*rES&ZV@Pxl&$Ad9flBV>9hj(jB1zZ=y7v-%tm2%N9*D@+biCclG#K;uq zSf>L{5Sv<~1jPsov{EWb1o7YLS2ETdhztsVC6ov1nEcIkJ1gO^>(o7uEZe!fpeVan zG%dJ(RKXuEN)!TFKq|uxH1#pS1ct$F3SSNNvKFWb6V!#SN$q9gRqe2$szV^MMwB`` z)3DY6L?O8c*21{p;)xruihjK7q?tP|k zo}GED*P?8iAh{jqv;AUUk>$UvT}|AE2C1Nf z^ycDpUt)K}`SyMALidBM_~8|Os*2@$D{)Z#RV0DT;8}U?m((uK_6Pa9^yzq9u<;Ap zWqiWQ4DF-DP!f-Nry^0)X6_v_SY7|a!`-{3|N)s3Z<4pN)?oB zL(7~ou?&H**lb}uGc@+4O2%>Yz|(N*o_w4!L^0k}hMVMEoJx>3H=F|MB~dqx9ihPp z`#PSyhaFcX1kSA9JS;vQ&IKa5nd!&E@tIGyc2-ndnJQ<&NVQtKo&VmkE8D8_xh6h> zo9zK#PS{OH70?(fJ~gtkt=pCMWgw$O(BR_8H9ZP>S=L^wBgLew+Vp!Bc@gk9d7j`l z<(}>Ynh^Yy+q6Hton2j}=nf8V8TZW}jN96MTVLXb1V36DVSWrrm6A>9=cH z>FIQgbv_51RJ8F$-l1o5FaY((R5iga}w zujFsjO@a>UR7hWDaZQ-hEqcrC4hhSTk0n-YLnJ|>^J();Ol`66-GBi05s0dX8n{nA zcarUJn~bQ+Y~1ImxaGJ}(xN7XRps=#u&~adh)Ri8l_UDID?DVTgDPn=RjNYd>_m(X z@N}A(mO37(JN6nVt?ZBQvIP2T{Qd{g>@a=J|K-|9Q2bIeC!^Z);Gk@-Q_j}SxKuzr#Dt-a z4a^X*Yz%?9yQSqo#H|F~_sIS7=GZ_&VtAajHJ&y5{IEXcJ$iNo>%0`>rqSwKDod0b z#8109#Hl-f%BmL6;`3@JQI2F>t3QD(HzMBC*T9F+Qg|*c4ChW2JIOALW4CIihk=%DOE9dp?o;B17v@X0m@3$+F zPQ3@}msyIqoOsD6e&I{$7RhKIfdN_o1CO>3O!l?3RxnQGY0e5f1FpV zq+J*A$^Xo<^1sUAO~ep=ZZeH{|}yC>*_I4!!IH##ikjk-`Ue*uC($<;Thf?uRpQ-x^kN z`JCVfAOdY=j^BDvEm`eVyj<{``=aOfylDdU-!e8fwi32Q+y9^T-aMS@{Ez=W5=tqh zjS{IWsgyNjSGFu!i!4KyV@U~>U9=|(3CX@^KL}ZBDy8iE5h`Ur*^iyy>wPTMw9L$R z=KH(v>%PDL%yrGwWzIRy=ks~JU(e_B@f6x5YlRCB$Zm$S(vF(vvej1ZSrndUFcEG> zfNE}sZMg}YHrqW`i|#{^&C*fSN?DVvE4;(llLu~Ie0#KU``~NM3rApHSljT!qn#VJ zt)F&EC+yNMczhgZcC@zx9^u?@jqZc1aaGAkIn-)HWS8F0f(+wEOT`QdR2EM=+;{}6 z3&vyOk4C<*HrWl^1}uaDOtj=_3wX($=WW58&zlitn84j3QZ@?vgHrW}$10QWfMoNW za<2`nm#V=}GigV|K2lJuydn@vu0wKO2MeThRfK!wIE|;rgY7w_>jCs!Cl+!&g~#)c zp7SRPU7BsXYhJ z!cW?{S3NA9%3RAKS(A0daRiJxbK}jm%y7@-E4WfD%D+L9X_DACgL3+~Q_c98lbr9y zVZ^i4IjimBIwBOF^)Bj-T#laMlrCF*N^teel4Vr%lDZf^P^L`r$1ftxo&AQQ9xm7M z8(sA&p&^Pg=U)f$B_e;*_*k2_BJu{8hQZL*4uSMEp8b083m1S#O$HMR*tbX{R}{+9 zflg`3ZSux!bi%A6#y#p3Ye9LfINjA0pdOgKe|o7M21I-QitKXKs4jTsgXcKZc`3qx zqzG3LA>lblP`z$)j+Jv8oZ&~^R`SGPu)~$D+ok?;29&S|Nx#F6Cle`_Witn3m-vgM z*++-Ag*^NBHL8dzHZ)cQfhKuJn@$2W(2c@pA2x2J@D5U}+npNFhbW7X9ctQXN3+SU zx<8~AnXNvY(C3jCm-HJ9^An3^0{io9JME4CzEA}QsFwTYgjto3v!w#94UqAo*STK4ro;WMM?5b))WfA z+@_91=oE`-m$ODewP`(kjS#kK$D||hY%9Ta&sPRgk583LgPg7cgejxdUvL|jG`!lA zWZ&KIyRa$Q6NP*8#7swSk5sCjDYGIKv%JE2*vSzt#{+n^Hl`a9*B&rA4`3ydgOex@ zv`EpN0<5CtdlFXh<%SUXwhPuItYXyBIjka#Kr?_MtjkqcdG2#rOUP`WEYmj`!_Wx5y4s$OJljz%-UyMRj%c-ojacN9n&Txn6|oJi%>e3R1(yjvlaB zWcIbfl%qDqZ8LN+1@7*Flr#+YAWRbk*G(YQWZVN|a18#2I!q~)%CbB)_BI`5R$#AT z=#F@7d$Bp7LSS2!Cokq`(SC>GNG@b6&hL}ytjpM;Xnz0Q(^76A=`w8gX|~YEXr2!aXr%jxby!D3S>%?!+@N z()7HDJ5E@bXeK{t<>P@!Eoj^6bG*jpY z^|UtNL*DGR_jvlVx{L)|kVDL7XV@THi40@{?LkMtx;fVvnpLf?uj4SRE*Q>$Z|uh^ zYw9Q^OoV&pOgvNFyL$Zu9QQi7k?EWz_8u2L8uuqbt?wN@KS*nViIs0%sbS&3i_QEL z3YGZ*CJcV|(e(WYS^A_6*1l5OdP`AtOR!M3U5}>l?@@u+6iB<>gU8H0gidVV8o;Sl zMj80CoL1xaIjwbXy8jb}D%|BG*s@K~-U#s%r*AB2A1eobV$@C&KapvspGp`TC0f}k ztqjI?E9xYoc>)S*HRT5@HTj6iVLb+#Qa;l`Ofn;}8S+}PX5js4TS#gJg}Tl(P65az z!`)qvvoFEZcaTlp-u3?7#;$oCPvH`xbluJ~Iqo#N`aG5>;i1iUT2ta_=v|M(nF))b z=Nt7K=(nMiR;onwDKkj-X9w4jRysb?0*5GeJG>Df?8smRyBQGei~=>gzAe?9fA&GXtxuB4knAPPN+E2r}q}lX-mX&iM<Z!rkT=tl8{J?4p`CF7tnka(TaCDy`<{3R2MZG%o1ZAX3J69 zJ|^V;9Zi(Bk5}W^Lh1`kGrZIV$K!}{R6SgpPt0yzy6jxHS}m7_3PI#I)mdDi_Gsgm zh!$v{^7&QfH$T(uid1z8X)(S3g`6lf_F>*Gv>0FVpSGJ&Ik;P)Mb^8ut#F%KtY|+Yvx~=iT_ijFhvMX^!HV%!tn>-kqd@>Ob{BL zz(WuS`;aS9b7^^`RjnCU{gGHjT>b4v$_wO`=oF=Zy&-}yNZn7VPtvKEK6EG$Qev@V zrhdDR-*KT8KTr8nBeZ}O{v=Z{p#LQ-%GoUdGcS$Uu$UJbhJCq+PuOG7PUM<9bwC_-~r3>uj3 zj-f6vXp%n(Kh6k>aDD^N#fFYtQux3b{?>?20XSXmA(RprR~j1&!BxJ)fq(*O=(-1@ zVTDWby8~ckM0O6BluPfbdkJv5Lck-->56VoQBB5yOxTPq?fD|zbh;$0B!?+gOh47Y zy$kki_xt1?y86S0BN$%FGh#Wl!Bmpl$(KG)l=v4NN?Az=6s{hp@}#8)h9C=6kD3y) zM~B~$Fge;6i?<+qx3hU+tSUR=e$~t;%s`s)u23ur%#)E*-b3f|+()t>9HEev6iJ6q zYV$D|bxOg+{lN)MM7`q39ZGx=slHbv8~K6m>XZ`}2|u=NHEn<`NcDIH+GkU3I=s(f zmvTTk^magsWKieDLZaPvtL|B-%($jEd9H9WWPG?L~anSvbW^YL z!P?$D<|rEe6wDGms&|E^Ge=udHdNHP*Qmhb$%2pPzOl16zs4%qE|@>QINz@^8KDwSLB-p-!im@Fk+V4@3Zo$}x6?seU`H5CE zU3P*flCXR%>F zkD!k<-1&ey>M=8j6$<-a0n?G0&uKkN(fXMHR?S=Zm*g)ewKzINS|nZq(&CqqCQD7# z!QbYgU6p^|U@E`6dCZ~BO6ch>tRQ_0MZ`r8%15dS`43*zdU))&TzdjTe-5>LCQb}? zIeBDY>GV3IS@h3zu#unXU|aqp9qi#c&JdUP(YF+14cSXS%udeEh&eLU>CX%XsH~@7 zWhNeX9P;e>DPSc^jSV-;(IY=cgXhE8rgKBPAVc12*^Dzne2O_mq-DNWouaaUO!Hfu zs~Ox!P-H{SCB^n_hK-NEQW&o2S#naVjDWVk4C-hV_ULRDqn(9R=ES795TnfW=9pUnZAv-tw59+3@1=l9lOhg*l@p7C-*-Y4g(=Le#e-If!(GCF zLEw}n2xq_PITHBb<d9~OoH5($5crzL7fBeg`0-p z2dg`lLYkZ*U4r4M6pSZF;MD&HG}aGJ`KOw5vkDJ@CMQir9`D6JJeU;>P!-SIP1xK$ z6~Dj!Ukr>LF|cW9|09~mL{{U+>!NR5 zH5l3tn3FJ%*uOa8{(OD`^#0rn8@Tz$h`TR|fmtj$fX*FDIRXAzlV?}g@b_2rdW!;Q z&GCL8tY=8@9NnGZy1hYj;v>WlV@Qr+1{KKj4hmpV1~-l89E=_dhGBky{iX9X!-w_G z%0UiQlR<@WgOd77ureXqVw+R>k6bx`)EPSiK`us}a{!4xMZU?ElR0QhWz@9*O=Fnb zH|r>~&3j`e5pO;Stdw{#P24Tjn}qnC+{E+}e_gS&-8J#cvpAyA|7vWDS4ls7q7qg~I;eyUx>o{Wa!pc*=;$0kJFkiQ1>{eFjB_K%?3UBI zutj2A38Gc;Dp}l}MDGx4b)q;yzPz?1$|VIpr(pQT6pON@@%w$l#A}^tUhp6XYw2Bh zPDyehTUjW*?n}{QtJy$8@+jOa?AbA5cNPyJHfFHygm!obj+dXd?ZU=ZxR-$??J-hDbzb1_Z@mrZwSux0Vn|zo&LBs1lhGwwIE8iu! zh}*t!54AOFzmdg~x(#gpToyYEI&j{wIIT(K@l_)O^*d`mwqJ5Z#|GFB-|YxC1GDDtW5G z1Jn8UDf_M09_f?zFvHvHu*+#tqF+o;853$O1kDP@FS-Ek<@ zBX_X|ZL$Jt>kiOt_j?f@oGt(` zZw?W8@A2f^FDsuvM;%JYN;=+Ag-sJ)QXa1-S+8&l7?T`(c;3bMc)eTqz7V!I?GQpp z6Bq&qPFcHR_t+7AZTXsqF(wDr6ySQBl5_-uhbB5Jw}ZuyKG?DQ<8T<7eEhlgv|7;X zUr6NCe7FzBP{rX3sBUFEeTEqP?i07`j8jv}vrxt))#&R8YDN#Kk>(T0s2|>cf+J|E zVgYohou($5HSgXL(b82c^=*L%^WW>xR@P4|tj;5`p&`Y*Jx0#_r2(RV+D8$#g0{`%+bz><}Jz2#<> ze+~E29y`jnv}g!2uXz%5NOLB$4?^l62ivQLAgdj~-?_Tb{O@UF@1jYSV?t;<0|>*G z-66}O)~#3aMao#*n*S+dD3Sk9m9a5k#~32BEfbe|oDbvGg7X6`CqEtf@SQxycl7Ad zc9@7bT$bltcvAmUdi0@P+BkL(wkUdYVHlM7!S_RBcT`X4$!CR5gpguzV}JJ}Yq23y zA7EiPG#}nW=9|Wxv$0^8^bZ6X`(KT5Au9Hu1KIftOGx%(`g>>sTeKhV9!Gs2fGxzD z3h<@YnqMop+#elw=4+;5tCar#Q_5Ku6HI0by_S9OEF@4TX1g8-QrP{D_!(pbOz!*UVTd;2}IH?2{awguy(W#}|8Jy<95nOw%gtyXtufNN40WSyW3TZ$~$rIVm`3^bgkIy!xDv}M>0{QyV3cREJAzpEwTKUB{A2GJ4*(bpT? zIslH8K1vshFn4!U^gkfK(Hy)lF5GOxB#F#(?E2^Y8K_tX=;?IYJ_SRIhVNzE*Cqng!D;MtmNKVbC* zZ-{8{$#b=103o>52k9rGx7duC%gngx5b77Fq;yDnoVXSO=PHfUXS7XX5eyRK#Xajq z9f@xae-1Jp_{>yMk7F1t6YgtRPh0x9(@}J0tW9MjJ(-!{*n)>SWX+)6`==CVM?dR2 zN-2bj<~QR+5y!`4M=r-MgG!?Utk$bfd5-}ay&(@rn9LFI$J~|^z|!3Ji-~ozFWq8Z z`$nHpr6OYVtxh@bqrs|olAJ~byot-A_~dh{JRg`44<$0sRHeGiPPPVni%z_B@m{qq zkQaLgL7m$E2I&x-NfGO5_w?+FKoNRFftT2Q12A(nDGe6R^b}g|i`c2+DL}|pNw{ya zkn_T*DKuz*sJoMP?zoP3JbAs5Uc!nQ_N$0e)!T7xPwaLpH5cLn?x){Tqa3cTlRe=9 ztb!j_%lj!46g-wN-@#TSt&104*VC#kLxJeSYqf9Difw-a};FVtJDtggoJe1U&>^A-*CkuBm6eU~Gk zZATCOWzzHtYHlZveU;4mY`ZxaHOWn^H_IxK0FVBBX|Nh|Wbr5Uk;Rrz3RC2kABff< z^^plF9=#HIel$F0c+|V~A1iACtKutvD6Dxm*KOSwA{uSWu9QU1LWN*LS0AbM4O3Z@ zNwfY3#_m5^RsF;xj_^rryLOcT05R;uJ|Q6?_IlI|M#}(sc8S6mfp&xW4HdVv{IjK_ z7ql|Y1Al=0=|*Qhx0-G()fJq~Xuyi z@Xh%6uXhJ9#VcgWX3f{O51i1K&bh9tFDEf7tXC95h+~p1KA$dD4dL{K2i0V~b$O+IdZh(yCrzN}BF)Nbo`-FJumXxfgd5|&?8D-T+L-fl zqw}DlI36`-76h0ke>4VAsd zt`;S`CKy$gG4QfiT5vM<{{VS-?_1hrhvy$@pMEG+I-`beZjEkMo!H)>*l8)pf!0{fYZ5mcc4kU|D9Nk zeXRz=sDA^XJ*4gpyTcjg$hve^xA~;+u8K@phZ72RmR4{aQYmD5>kmaBdEHURYf%Y^ z#&slsgEN}yp^hAAQ1PHaReXE}An8*0015J;nrlLc?%kn%q?mq<7%YlfvLZji#`-=7 zoX0qzvMe5)-nvQ9c?*6g1R$6yqHh-i}C#^Md0fM+Y zm&iuJY0V%PDRKK;okj+ZTAY_Ik-kRU?zLS0kyF!TEbdj*{#kMAoFk@kUF7*zFSA`W z@ftScS=De`0*ZKRIg^;n{ay?5Wn$a($xzUJ_KFExArxRYq*xH!kmi}V6@e8Q4a{E9 z-8Tj0+0vW7hu7o*#D`z-wfoG@mrhh+W#=ByC z!If<vWGkA;y+KF8&c^&U9;48T_l_{bU}`Td)COtdbBt z4Nj{@nyY~Q!No7p6WXSTbs)imnEr0ytk8>DF7(AJzcQUx~e0C2({d`ajxd)g-Yo&GiZ+1<2CZ=k0uK@@yeea zE;xl&OqU}*Incc2Gpol`yeeq`+|L`hU@RFc;;|(ZWh6i>?vz8WV0U}Wv=lfN_&^m_ zvdOXAg8Q|^2FJn7*tWRVmjPU%rE^7R%nsIf)VMBK52P=vDNLZ@P}FwhsTJc~&zFXA znX__l)@Y3v*^bfEBN>0(4O2}l{AC#CNkJEX@0zY*O0Llh9~&=`-TVs*;_6Q8qE4f^ zjx+8{sN-z^1i9ZIJI?+mvXP>7e@$d7Qdr}5;?e0Rj|WRQ5oP~O!OXiS=ENxW#e|mYzq7aS~9Yi zfG1Lf+oJ2x$ZFBCA{591+tCXYJVv=~gnrw`v3>36&qM3ldO}eNfA0CgV3v7cPJSx! zX9e^Q0wTd1g{(Isy9`yzFf~HL^&$8z8j)nQjgxb#n$#|4e#{i&ye~)HjZA{-qKPe1bPYMRl+ao=D_bJMbKe5ENN?GRE5)Z1JH7k9S;ts-umGISigO= z;dt*UlncnEd&m(@Z`Nn9uK+}chB6}ff9wrnc-^36J9xno{Yz3}6YgO29JS}FK=;mQ zTd~?g1QyDTs#T@<10}J^R;hMp<=Nr$XjwMhQyuGl9D*t7uR#RangKy|9aj`ory@tF zW%9MrI{@=Tl3}XOW^%{3Cc4hf*sB(E{z7bP^0S%){`^vEV|N_`yTit6Bp>N)4L#Q< z4nv$_VNBq9!U~D8SN|x6wgmgD7@9Ir)=4om#nvORK|v5M-Au5)`!nuiJaD=Qm|K8l z?mSc5G&Wgz$8eq@M4W3HBPvDV{t!a@_zy#9{i;;VsC2CqRE+7pisqN4)}1yxu3GbC zoN~u9F1QSDgb3F4-nghx%4)HpfmAE@8otDYJGqoMbFpJ(=EeGi`Cc*hPs&|XfTk`K z{P+P3Q}#s>^pbsr*)0yQML#FG=KeMrgX}+y<*+flk2w*_DQolVN-<4-UKM3C*8nhk zN9zf5oK3}FTU1NA>@X3H)O+|Q>^HP*9)@eO7b7lME%dw{?fksEj6TV+OmPUD12E4m z)kx}%do9trW_0>|&+jnixSk~yiiNl=y*d|!r_7tv*|KjvPmzL*(=dAy{sNVXJXtMd z&mKGb1*kGTrel4wA+GmYCH0Z1Rt55P1m%IRaEZN9ErK6wo`d(bC@K1=`V%g`V%lMLMc1$LCLpV*SeBW_!DBgd2%5(Le zNodO}ZGQ}4)|U@X#Jp8wi>1;?(Q7^xQ$3iknd6t8Fq-=XoOpEv9L`(;Ah|UK{ys+T zi|Q!B(hv;RhQ`uQiwmFu)EqNhHrFXeZRqDsP=;SDVMwD~LrZNaDq9>FkS<(FTb2JA zJMpyCG39NX*=k*_ALG!;dMGk}5&@xY*M2In(1ePe3xEos&p^?8 z-^(~pQ4(qA`vNhQa8w|c6tQbsjjanx>G*BTx5t^#!je3=b1{1cxRk7B6h31+9BY?! zJmbZKwNb4K#Z~(Kx4t*2hi5k!T!SSWA*Jqq5ZN~E_8bHbsHtAFL`oUmz;KgasLvGs`4T)QC*Xi zK!d(8NiWP&OjSSDc7ogs1FGABZF7CE|B{Udb|h{;`BDEbGaUV9{RPOwqB7sfDyh4Y zIRcExRfxOuA(@8R#`6%y1{Tqz4ptbJUyM5d`R)jSt*6mdcKM;x=@>|peOnnexfS{- z4m{`DDZn*`|0yV17OX+W{hqv1R%|e3w1Wf`zPChyt-*P@GaZW}^d|uTfEOO`Go|(D zK?Tb$=PkFx|Fmb~(#seLwV$-EO6l@4_r`w{Gn3NYg5e5;H8yxhc|n}v0Xc1z-BH@5 zA`Zj*ZfxYra*y1-2*LSvINsycFD8?T()p$bQp-ncfpckPqzoZbqr(x?{GT6e&_VaD8)s&AFE~l zUj5%|W#%8M979#|MipxndqwFAt$x9V9@*($a=)gB?EUWf>BnFIrk@qY`)Q@+J&qQG zP^vTpsk4l~mnq9T_*MH?I;VN}|D8;kz&2FOf);|UE|W=u&{Y5&hRf3p*v*5*T|?8E z`ps;(B?+Y33`pm{9Y8W7u>(Q@F_5z4KXihu9Q^6VeQZjvLG{@gzDSTWUiNAA>}&lQ zLs*=cn(n=Azfkm5b_aX;)#$Xy{D6}09<3bO(#~W-LOBynB7&Re49Q!8?mcZyq}=)Z z(V4a*;gXknp_`<;G58P|;0Q)=NXQUdy~sC2W+i^)c_v>{8=x#Y{tP(%NCjKRskz%; zKag@QKxKI$s?P@xaelDY7LL;GW1?+1t69Jaw1b%_Dv8QFro{OD+2jBTD9xb*+#U7|l`T@}J z8U4NPv(~%FSfEnsQ*V(R)}h3-{!O@M>6cb7a;nx^;iQ}6RS#Kw>exTYduUTIMQG5v zVviMqkW(tdDCuLjO#>!OQ`-_lGd#75JYjUN_O7*BknU zd~N#R&}=;@Gs!uuj7`K>3m_=-#oj2IXdDfzH0PYOWl)ats@B*43xG^GA!U#E@o^N{zd7EPWq z+%wce8T+gLE=7vUzXUBLJiGRFe^;wxD!-bAzO0kbwjNlkX7bEHYA|tHFEjgN7K!IK zy4>nn9Vr{u2Kz*4+kT z#}Mlaae4HWV8zll9fCrMRrjRokDB{wYVpcV03{>#(bV#honR9~&puM0>TL>(!^)d$ zP+f%09?!eyGPW&-!HDd$2uoD30rT`MD#du}fP@}qQR73Mwh1J1v`eCQCJQw)x99LC zJ(x(M%t2!jd7=NmzAhw?z55EB^sBzE)MetRv>8Xte!?aCYbh|Qh4JUsP$sM*1JTXP1i~aFC=pz) zs5YVEZ3qh2j6Sglj!}IN;pO#G^({5fz$PC+u^*T)){ymCLzjH8jJm zjozI=LwK80oplAgu=ucN*x7DEXfQ9ig$%qZSXgb zp4kofz-*dvV!1r3i?OsmHYYRj^Y&%71mqzegtTON%=S3Z z$vj9tR!Ko@(qy#BjO*4aDwSKgzP{(~=zutLyY5Y>SZgx%i^5{cZt~h$?9e!fqE@pH znGZWAO~s$cNp~*5C*U{7){=2$r-md-;C0vNRaar0-ywf>l zSebJvHM1SV^}9WMr|ZLX$&18o%}eGrre4dpV9T`Hdm5tShNh8r)zzg(1q2AIRkihD z$KpG+)e8z4+nFs`mE&Em>FN}EpTh0(?xdwi>cq~9OG0-TJ6i29n!;)a)%5UNpGY)G z(0A|5ms86BnE(7Wi^^;Qf0Pu1?Msgd)947+4MpHJ1Ol^pWVdjp`O0sjTv3ZV7P>7c z%w+*#t~TpIe^k)^ZJNuIK)sB4k#aawxqGp1%V+EFE0#t8<D(--KQ+E6XyME%eQ2s^xBk@MyoPaQ6hEM$noix_eSB@VVN_R^c+(OV zW(+iHg+X^F(Yhe{hrq+-a`d+!LHsgk!yfH>fE5jbeMk`GIfCJK6$qP|!K!T<{#^Pk zyTKn#9D;!)5M<%GcJ>KSGz5U|xw0e^gAw1fbYZ=c!O1vK`aqV9qfCiw0o^hXsI!``!5G7Ai?Bdhd#^o+EJg!FkN)BS zv2{cP_O+=a=U_&#|7RUsYFX?5LIc;-d)M*T`bVJHyw@^E%={6e?q@6`nHhGIJynL+ zi3-r)6f?P6cGss^TT_CjA`t2{crE1G0E3vzV`R{uegu(pBC8FQKb(b1dmgBN@ZE28 zIB%n@2S3dYJDvgOWXd3MCxAtB!{y2gT4Yv+dx3Kaw9UMMUH_ZxRu_w^SY>O(&(hk1 zMi;6hcnkYEFLSYmbz7f|!gUZ>10^p}H@c!gx!ohwP?3g8KSMi!0LfODJbd=C??H!T z%2TK_V)!gw?$)ZTsNvt`k}Qu zTz5}9t`MWwjagW*SQkahi9lpNwCPqXFxc&1?RRmYcq+9J=Zbizn(-=#f;gzFr&VDy zNwms~N+ydu#ZZueCk-<_7KfM1URB-dHyAtY0kz7lTWrpN8Clq%a%K2v>z6E-b2UON z-!cUXh7u;ZeEkl=^>MxKcz^uOzP?YqZpxhwvQ77izrTu~dl$N#n;GKRaD}hs#(h;& zLi_}e5uUZ$!Pi`nz~vSogsHT=beF+dcts!01hk@^DTujmL71(0Qv`tqDa=|jzhGg5 zB8*J@QAn2Y1GAZAs7YE4_mb&S8o5M+0^5l-%=6<&uQ{(1yZQ7kWagB5~#& zjN;g~98J}=YYV+qyFFATRi#jy73aPIS1uYAoY?al{GL&ZFLolShkaS))@x%DvhrZ( z){&KKQs{JebsLhE(Z-Z-*QKv{3pSt4Y?4|Dw`@}L1?)>Pj~CKi`atmNv($^eRWChO znQjfl2FtP@a2_Her{(8(-)fHpt&~$*SvaQ@W2a9^$;Z;3+7j@5l^vhTLt81+zJ$80 zpE2E2|Eh2F)Lt#rEg?@>r+CV&trzAM0_7Qs{r|0&uG3NtO4~TC-lJZK%?YGlh!WDK zBw^)KWz_2Mip{~>R_K~dD<3BuT%hH=_GTkIDthIlv?ZCiPhNF?aEkp}<(krx?D`== ziWS#NrFX?u&>ROs&uxxx#U{It=L|jWf(+0UCN-kNHi9jejRRI=(w3B8Nljf_gYPe5 z8*RFUZHCBM6Zo2)qFjhEH+tbDW$g}!UzJos)roU)iPFu=bn zjqZRx@V!<0X&-frstfI0ul^B@MoOwq5P z{{Rctf^<}J^y8Lr)Vco0x^hy57BoZ%+3$l0(U(yK>MOK+JD|Rt4{qsRiiLsmc9&OR zEACn}ymb4Ku*>R>dwg~5$+V{({NiuGGEM53zz4Y0g-Ljvm{4@DSuW0- zS|>L0JHr$!>Y5`l!~yrk2ahQ}KJ%JXCSgbv&0*>$JvGHg-p*&+g$v4`SvP&bJiE-@eIn^95Rtmrb)yxBMw}-&ImL~?AW%Lu5w!3v4l+w zk~S>T3l-~)-c2~H5!HW3TZ;aVwWU`Vpw{8_6a9q_^P4JD

NVBYf5_9gj;E5e<@9 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�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

Zv*y`!J7bqwt6ekt-W9it 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 literal 0 HcmV?d00001 diff --git a/docs/images/example_14_interactive_table.png b/docs/images/example_14_interactive_table.png new file mode 100644 index 0000000000000000000000000000000000000000..573582d18a0af8a924fab544cb907d2cbda85aaf GIT binary patch literal 24437 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 literal 0 HcmV?d00001 diff --git a/docs/images/font_family_switching.png b/docs/images/font_family_switching.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd23e4b8bedc849d13a26a6e07942ce8704290c GIT binary patch literal 63976 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 literal 0 HcmV?d00001 diff --git a/docs/images/font_family_switching_vertical.png b/docs/images/font_family_switching_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..6b271b421ca0d07f0d2d15b5a9fd30c9bf53dcde GIT binary patch literal 62818 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 CjhKM| literal 0 HcmV?d00001 diff --git a/examples/01_simple_page_rendering.py b/examples/01_simple_page_rendering.py new file mode 100644 index 0000000..9b9110e --- /dev/null +++ b/examples/01_simple_page_rendering.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +""" +Simple Page Rendering Example + +This example demonstrates: +- Creating pages with different styles +- Setting borders, padding, and background colors +- Understanding the page layout system +- Rendering pages to images + +This is a foundational example showing the basic Page API. +""" + +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.concrete.page import Page +import sys +from pathlib import Path +from PIL import Image, ImageDraw, ImageFont + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +def draw_placeholder_content(page: Page): + """Draw some placeholder content directly on the page to visualize the layout.""" + if page.draw is None: + # Trigger canvas creation + page.render() + + draw = page.draw + + # Draw content area boundary (for visualization) + content_x = page.border_size + page.style.padding_left + content_y = page.border_size + page.style.padding_top + content_w = page.content_size[0] + content_h = page.content_size[1] + + # Draw a light blue rectangle showing the content area + draw.rectangle( + [content_x, content_y, content_x + content_w, content_y + content_h], + outline=(100, 150, 255), + width=1 + ) + + # Add some text labels + try: + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12) + except BaseException: + font = ImageFont.load_default() + + # Label the areas + draw.text( + (content_x + 10, + content_y + 10), + "Content Area", + fill=( + 100, + 100, + 100), + font=font) + draw.text( + (10, 10), f"Border: {page.border_size}px", fill=( + 150, 150, 150), font=font) + draw.text( + (content_x + 10, + content_y + 30), + f"Size: {content_w}x{content_h}", + fill=( + 100, + 100, + 100), + font=font) + + +def create_example_1(): + """Example 1: Default page style.""" + print("\n Creating Example 1: Default style...") + + page = Page(size=(400, 300)) + draw_placeholder_content(page) + + return page + + +def create_example_2(): + """Example 2: Page with visible borders.""" + print(" Creating Example 2: With borders...") + + page_style = PageStyle( + border_width=3, + border_color=(255, 100, 100), + padding=(20, 20, 20, 20), + background_color=(255, 250, 250) + ) + + page = Page(size=(400, 300), style=page_style) + draw_placeholder_content(page) + + return page + + +def create_example_3(): + """Example 3: Page with generous padding.""" + print(" Creating Example 3: With padding...") + + page_style = PageStyle( + border_width=2, + border_color=(100, 100, 255), + padding=(40, 40, 40, 40), + background_color=(250, 250, 255) + ) + + page = Page(size=(400, 300), style=page_style) + draw_placeholder_content(page) + + return page + + +def create_example_4(): + """Example 4: Clean, borderless design.""" + print(" Creating Example 4: Borderless...") + + page_style = PageStyle( + border_width=0, + padding=(30, 30, 30, 30), + background_color=(245, 245, 245) + ) + + page = Page(size=(400, 300), style=page_style) + draw_placeholder_content(page) + + return page + + +def combine_into_grid(pages, title): + """Combine multiple pages into a 2x2 grid with title.""" + print("\n Combining pages into grid...") + + # Render all pages + images = [page.render() for page in pages] + + # Grid layout + padding = 20 + title_height = 40 + cols = 2 + rows = 2 + + # Calculate dimensions + img_width = images[0].size[0] + img_height = images[0].size[1] + + total_width = cols * img_width + (cols + 1) * padding + total_height = rows * img_height + (rows + 1) * padding + title_height + + # Create combined image + combined = Image.new('RGB', (total_width, total_height), (250, 250, 250)) + draw = ImageDraw.Draw(combined) + + # Draw title + try: + title_font = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20) + except BaseException: + title_font = ImageFont.load_default() + + # Center the title + bbox = draw.textbbox((0, 0), title, font=title_font) + text_width = bbox[2] - bbox[0] + title_x = (total_width - text_width) // 2 + draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font) + + # Place pages in grid + y_offset = title_height + padding + for row in range(rows): + x_offset = padding + for col in range(cols): + idx = row * cols + col + if idx < len(images): + combined.paste(images[idx], (x_offset, y_offset)) + x_offset += img_width + padding + y_offset += img_height + padding + + return combined + + +def main(): + """Demonstrate basic page rendering.""" + print("Simple Page Rendering Example") + print("=" * 50) + + # Create different page examples + pages = [ + create_example_1(), + create_example_2(), + create_example_3(), + create_example_4() + ] + + # Combine into a single demonstration image + combined_image = combine_into_grid(pages, "Page Styles: Border & Padding Examples") + + # Save output + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / "example_01_page_rendering.png" + combined_image.save(output_path) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels") + print(f" Created {len(pages)} page examples") + + return combined_image + + +if __name__ == "__main__": + main() diff --git a/examples/02_text_and_layout.py b/examples/02_text_and_layout.py new file mode 100644 index 0000000..8699175 --- /dev/null +++ b/examples/02_text_and_layout.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +""" +Text and Layout Example + +This example demonstrates text rendering using the pyWebLayout system: +- Different text alignments +- Font sizes and styles +- Multi-line paragraphs +- Document layout and pagination + +This example uses the HTML parsing system to create rich text layouts. +""" + +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.concrete.page import Page +from pyWebLayout.style import Font +from pyWebLayout.io.readers.html_extraction import parse_html_string +import sys +from pathlib import Path +from PIL import Image, ImageDraw, ImageFont + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +def create_sample_document(): + """Create different HTML samples demonstrating various features.""" + samples = [] + + # Sample 1: Text alignment examples + samples.append(( + "Text Alignment", + """ + +

Left Aligned

+

This is left-aligned text. It is the default alignment for most text.

+ +

Justified Text

+

This paragraph is justified. The text stretches to fill + the entire width of the line, creating clean edges on both sides.

+ +

Centered

+

This text is centered.

+ + """ + )) + + # Sample 2: Font sizes + samples.append(( + "Font Sizes", + """ + +

Heading 1

+

Heading 2

+

Heading 3

+

Normal paragraph text at the default size.

+

Small text for fine print.

+ + """ + )) + + # Sample 3: Text styles + samples.append(( + "Text Styles", + """ + +

Normal text with bold words and italic text.

+

Completely bold paragraph.

+

Completely italic paragraph.

+

Text with underlined words for emphasis.

+ + """ + )) + + # Sample 4: Mixed content + samples.append(( + "Mixed Content", + """ + +

Document Title

+

A paragraph with bold, italic, and normal text all mixed together.

+

Subsection

+

Another paragraph demonstrating the layout system.

+ + """ + )) + + return samples + + +def render_html_to_image(html_content, page_size=(500, 400)): + """Render HTML content to an image using the pyWebLayout system.""" + # Create a page + page_style = PageStyle( + border_width=2, + border_color=(200, 200, 200), + padding=(30, 30, 30, 30), + background_color=(255, 255, 255) + ) + + page = Page(size=page_size, style=page_style) + + # Parse HTML + base_font = Font(font_size=14) + blocks = parse_html_string(html_content, base_font=base_font) + + # For now, just render the page structure + # (The full layout engine would place the blocks, but we'll show the page) + image = page.render() + draw = ImageDraw.Draw(image) + + # Add a note that this is HTML-parsed content + try: + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11) + except BaseException: + font = ImageFont.load_default() + + # Draw info about what was parsed + content_x = page.border_size + page.style.padding_left + 10 + content_y = page.border_size + page.style.padding_top + 10 + + draw.text((content_x, content_y), + f"Parsed {len(blocks)} block(s) from HTML", + fill=(100, 100, 100), font=font) + + # List the block types + y_offset = content_y + 25 + for i, block in enumerate(blocks[:10]): # Show first 10 + block_type = type(block).__name__ + draw.text((content_x, y_offset), + f" {i + 1}. {block_type}", + fill=(60, 60, 60), font=font) + y_offset += 18 + + if y_offset > page.size[1] - 60: # Don't overflow + break + + return image + + +def combine_samples(samples): + """Combine multiple sample renders into a grid.""" + print("\n Rendering samples...") + + images = [] + for title, html in samples: + print(f" - {title}") + img = render_html_to_image(html) + + # Add title to image + draw = ImageDraw.Draw(img) + try: + font = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14) + except BaseException: + font = ImageFont.load_default() + + draw.text((10, 10), title, fill=(50, 50, 150), font=font) + images.append(img) + + # Create grid (2x2) + padding = 20 + cols = 2 + rows = 2 + + img_width = images[0].size[0] + img_height = images[0].size[1] + + total_width = cols * img_width + (cols + 1) * padding + total_height = rows * img_height + (rows + 1) * padding + + combined = Image.new('RGB', (total_width, total_height), (240, 240, 240)) + + # Place images + y_offset = padding + for row in range(rows): + x_offset = padding + for col in range(cols): + idx = row * cols + col + if idx < len(images): + combined.paste(images[idx], (x_offset, y_offset)) + x_offset += img_width + padding + y_offset += img_height + padding + + return combined + + +def main(): + """Demonstrate text and layout features.""" + print("Text and Layout Example") + print("=" * 50) + + # Create sample documents + samples = create_sample_document() + + # Render and combine + combined_image = combine_samples(samples) + + # Save output + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / "example_02_text_and_layout.png" + combined_image.save(output_path) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels") + print(" Note: This example demonstrates HTML parsing") + print(" Full layout rendering requires the typesetting engine") + + return combined_image + + +if __name__ == "__main__": + main() diff --git a/examples/03_page_layouts.py b/examples/03_page_layouts.py new file mode 100644 index 0000000..49a5af3 --- /dev/null +++ b/examples/03_page_layouts.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +""" +Page Layouts Example + +This example demonstrates different page layout configurations: +- Various page sizes (small, medium, large) +- Different aspect ratios (portrait, landscape, square) +- Border and padding variations +- Color schemes + +Shows how the pyWebLayout system handles different page dimensions. +""" + +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.concrete.page import Page +import sys +from pathlib import Path +from PIL import Image, ImageDraw, ImageFont + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +def add_page_info(page: Page, title: str): + """Add informational text to a page showing its properties.""" + if page.draw is None: + page.render() + + draw = page.draw + + try: + font_large = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14) + font_small = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11) + except BaseException: + font_large = ImageFont.load_default() + font_small = ImageFont.load_default() + + # Title + content_x = page.border_size + page.style.padding_left + 5 + content_y = page.border_size + page.style.padding_top + 5 + + draw.text((content_x, content_y), title, fill=(40, 40, 40), font=font_large) + + # Page info + y = content_y + 25 + info = [ + f"Page: {page.size[0]}×{page.size[1]}px", + f"Content: {page.content_size[0]}×{page.content_size[1]}px", + f"Border: {page.border_size}px", + f"Padding: {page.style.padding}", + ] + + for line in info: + draw.text((content_x, y), line, fill=(80, 80, 80), font=font_small) + y += 16 + + # Draw content area boundary + cx = page.border_size + page.style.padding_left + cy = page.border_size + page.style.padding_top + cw = page.content_size[0] + ch = page.content_size[1] + + draw.rectangle( + [cx, cy, cx + cw, cy + ch], + outline=(150, 150, 255), + width=1 + ) + + +def create_layouts(): + """Create various page layout examples.""" + layouts = [] + + # 1. Small portrait page + print("\n Creating layout examples...") + print(" - Small portrait") + style1 = PageStyle( + border_width=2, + border_color=(100, 100, 100), + padding=(15, 15, 15, 15), + background_color=(255, 255, 255) + ) + page1 = Page(size=(300, 400), style=style1) + add_page_info(page1, "Small Portrait") + layouts.append(("small_portrait", page1)) + + # 2. Large portrait page + print(" - Large portrait") + style2 = PageStyle( + border_width=3, + border_color=(150, 100, 100), + padding=(30, 30, 30, 30), + background_color=(255, 250, 250) + ) + page2 = Page(size=(400, 600), style=style2) + add_page_info(page2, "Large Portrait") + layouts.append(("large_portrait", page2)) + + # 3. Landscape page + print(" - Landscape") + style3 = PageStyle( + border_width=2, + border_color=(100, 150, 100), + padding=(20, 40, 20, 40), + background_color=(250, 255, 250) + ) + page3 = Page(size=(600, 350), style=style3) + add_page_info(page3, "Landscape") + layouts.append(("landscape", page3)) + + # 4. Square page + print(" - Square") + style4 = PageStyle( + border_width=3, + border_color=(100, 100, 150), + padding=(25, 25, 25, 25), + background_color=(250, 250, 255) + ) + page4 = Page(size=(400, 400), style=style4) + add_page_info(page4, "Square") + layouts.append(("square", page4)) + + # 5. Minimal padding + print(" - Minimal padding") + style5 = PageStyle( + border_width=1, + border_color=(180, 180, 180), + padding=(5, 5, 5, 5), + background_color=(245, 245, 245) + ) + page5 = Page(size=(350, 300), style=style5) + add_page_info(page5, "Minimal Padding") + layouts.append(("minimal", page5)) + + # 6. Generous padding + print(" - Generous padding") + style6 = PageStyle( + border_width=2, + border_color=(150, 120, 100), + padding=(50, 50, 50, 50), + background_color=(255, 250, 245) + ) + page6 = Page(size=(400, 400), style=style6) + add_page_info(page6, "Generous Padding") + layouts.append(("generous", page6)) + + return layouts + + +def create_layout_showcase(layouts): + """Create a showcase image displaying all layouts.""" + print("\n Creating layout showcase...") + + # Render all pages + images = [(name, page.render()) for name, page in layouts] + + # Calculate grid layout (3×2) + padding = 15 + title_height = 50 + cols = 3 + rows = 2 + + # Find max dimensions for each row/column + max_widths = [] + for col in range(cols): + col_images = [images[row * cols + col][1] + for row in range(rows) if row * cols + col < len(images)] + if col_images: + max_widths.append(max(img.size[0] for img in col_images)) + + max_heights = [] + for row in range(rows): + row_images = [images[row * cols + col][1] + for col in range(cols) if row * cols + col < len(images)] + if row_images: + max_heights.append(max(img.size[1] for img in row_images)) + + # Calculate total size + total_width = sum(max_widths) + padding * (cols + 1) + total_height = sum(max_heights) + padding * (rows + 1) + title_height + + # Create combined image + combined = Image.new('RGB', (total_width, total_height), (235, 235, 235)) + draw = ImageDraw.Draw(combined) + + # Add title + try: + title_font = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 24) + except BaseException: + title_font = ImageFont.load_default() + + title_text = "Page Layout Examples" + bbox = draw.textbbox((0, 0), title_text, font=title_font) + text_width = bbox[2] - bbox[0] + title_x = (total_width - text_width) // 2 + draw.text((title_x, 15), title_text, fill=(50, 50, 50), font=title_font) + + # Place images in grid + y_offset = title_height + padding + for row in range(rows): + x_offset = padding + for col in range(cols): + idx = row * cols + col + if idx < len(images): + name, img = images[idx] + # Center image in its cell + cell_width = max_widths[col] + cell_height = max_heights[row] + img_x = x_offset + (cell_width - img.size[0]) // 2 + img_y = y_offset + (cell_height - img.size[1]) // 2 + combined.paste(img, (img_x, img_y)) + x_offset += max_widths[col] + padding if col < len(max_widths) else 0 + y_offset += max_heights[row] + padding if row < len(max_heights) else 0 + + return combined + + +def main(): + """Demonstrate page layout variations.""" + print("Page Layouts Example") + print("=" * 50) + + # Create different layouts + layouts = create_layouts() + + # Create showcase + combined_image = create_layout_showcase(layouts) + + # Save output + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / "example_03_page_layouts.png" + combined_image.save(output_path) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels") + print(f" Created {len(layouts)} layout examples") + + return combined_image + + +if __name__ == "__main__": + main() diff --git a/examples/04_table_rendering.py b/examples/04_table_rendering.py new file mode 100644 index 0000000..6fa7b21 --- /dev/null +++ b/examples/04_table_rendering.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python3 +""" +Table Rendering Example + +This example demonstrates rendering HTML tables: +- Simple tables with headers +- Tables with multiple rows and columns +- Tables with colspan and borders +- Tables with formatted content +- Tables parsed from HTML using DocumentLayouter + +Shows the HTML-first rendering pipeline. +""" + +from pyWebLayout.abstract.block import Table +from pyWebLayout.style import Font +from pyWebLayout.io.readers.html_extraction import parse_html_string +from pyWebLayout.layout.document_layouter import DocumentLayouter +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.concrete.table import TableStyle +from pyWebLayout.concrete.page import Page +import sys +from pathlib import Path +from PIL import Image, ImageDraw + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +def create_simple_table_example(): + """Create a simple table from HTML.""" + print(" - Simple data table") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameAgeCity
Alice28Paris
Bob34London
Charlie25Tokyo
+ """ + + return html, "Simple Table" + + +def create_styled_table_example(): + """Create a table with custom styling.""" + print(" - Styled table") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Monthly Sales Report
MonthRevenueExpensesProfit
January$50,000$30,000$20,000
February$55,000$32,000$23,000
March$60,000$35,000$25,000
+ """ + + return html, "Styled Table" + + +def create_complex_table_example(): + """Create a table with colspan.""" + print(" - Complex table with colspan") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Product Specifications
ProductFeaturesPrice
Laptop16GB RAM, 512GB SSD$1,299
Monitor27 inch, 4K Resolution$599
KeyboardMechanical, RGB$129
+ """ + + return html, "Complex Table" + + +def create_data_table_example(): + """Create a table with numerical data.""" + print(" - Data table") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Test Results
TestScoreStatus
Unit Tests98%Pass
Integration95%Pass
Performance87%Pass
+ """ + + return html, "Data Table" + + +def render_table_example( + html: str, + title: str, + style_variant: int = 0, + page_size=( + 500, + 400)): + """Render a table from HTML to an image using DocumentLayouter.""" + # Create page with varying backgrounds + bg_colors = [ + (255, 255, 255), # White + (250, 255, 250), # Light green tint + (255, 250, 245), # Light orange tint + (245, 250, 255), # Light blue tint + ] + + page_style = PageStyle( + border_width=2, + border_color=(200, 200, 200), + padding=(20, 20, 20, 20), + background_color=bg_colors[style_variant % len(bg_colors)] + ) + + page = Page(size=page_size, style=page_style) + + # Parse HTML to get table + base_font = Font(font_size=12) + blocks = parse_html_string(html, base_font=base_font) + + # Find the table block + table = None + for block in blocks: + if isinstance(block, Table): + table = block + break + + # Table styles with different themes + table_styles = [ + # Style 0: Classic blue header + TableStyle( + border_width=1, + border_color=(80, 80, 80), + cell_padding=(8, 10, 8, 10), + header_bg_color=(70, 130, 180), # Steel blue + cell_bg_color=(255, 255, 255), + alternate_row_color=(240, 248, 255) # Alice blue + ), + # Style 1: Green theme + TableStyle( + border_width=2, + border_color=(34, 139, 34), # Forest green + cell_padding=(10, 12, 10, 12), + header_bg_color=(144, 238, 144), # Light green + cell_bg_color=(255, 255, 255), + alternate_row_color=(240, 255, 240) # Honeydew + ), + # Style 2: Minimal style + TableStyle( + border_width=0, + border_color=(200, 200, 200), + cell_padding=(6, 8, 6, 8), + header_bg_color=(245, 245, 245), + cell_bg_color=(255, 255, 255), + alternate_row_color=None # No alternating + ), + # Style 3: Bold borders + TableStyle( + border_width=3, + border_color=(0, 0, 0), + cell_padding=(10, 10, 10, 10), + header_bg_color=(255, 215, 0), # Gold + cell_bg_color=(255, 255, 255), + alternate_row_color=(255, 250, 205) # Lemon chiffon + ), + ] + + table_style = table_styles[style_variant % len(table_styles)] + + if table: + # Create DocumentLayouter + layouter = DocumentLayouter(page) + + # Use DocumentLayouter to layout the table + layouter.layout_table(table, style=table_style) + + # Get the rendered canvas + _ = page.draw # Ensure canvas exists + image = page._canvas + else: + # No table found - create empty page + _ = page.draw # Ensure canvas exists + draw = ImageDraw.Draw(page._canvas) + draw.text((page.border_size + 10, page.border_size + 50), + "No table found in HTML", + fill=(200, 0, 0)) + image = page._canvas + + return image + + +def combine_examples(examples): + """Combine multiple table examples into a grid.""" + print("\n Rendering table examples...") + + images = [] + for i, (html, title) in enumerate(examples): + img = render_table_example(html, title, style_variant=i) + images.append(img) + + # Create grid (2x2) + padding = 15 + cols = 2 + rows = 2 + + img_width = images[0].size[0] + img_height = images[0].size[1] + + total_width = cols * img_width + (cols + 1) * padding + total_height = rows * img_height + (rows + 1) * padding + 50 # Extra for main title + + combined = Image.new('RGB', (total_width, total_height), (240, 240, 240)) + draw = ImageDraw.Draw(combined) + + # Add main title + from PIL import ImageFont + try: + main_font = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20) + except BaseException: + main_font = ImageFont.load_default() + + title_text = "Table Rendering Examples" + bbox = draw.textbbox((0, 0), title_text, font=main_font) + text_width = bbox[2] - bbox[0] + title_x = (total_width - text_width) // 2 + draw.text((title_x, 15), title_text, fill=(50, 50, 50), font=main_font) + + # Place images + y_offset = 50 + padding + for row in range(rows): + x_offset = padding + for col in range(cols): + idx = row * cols + col + if idx < len(images): + combined.paste(images[idx], (x_offset, y_offset)) + x_offset += img_width + padding + y_offset += img_height + padding + + return combined + + +def main(): + """Demonstrate table rendering.""" + print("Table Rendering Example") + print("=" * 50) + + # Create table examples + print("\n Creating table examples...") + examples = [ + create_simple_table_example(), + create_styled_table_example(), + create_complex_table_example(), + create_data_table_example() + ] + + # Render and combine + combined_image = combine_examples(examples) + + # Save output + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / "example_04_table_rendering.png" + combined_image.save(output_path) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels") + print(f" Created {len(examples)} table examples") + + return combined_image + + +if __name__ == "__main__": + main() diff --git a/examples/05_html_table_with_images.py b/examples/05_html_table_with_images.py new file mode 100644 index 0000000..39231a1 --- /dev/null +++ b/examples/05_html_table_with_images.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +""" +HTML Table with Images Example - End-to-End Rendering + +This example demonstrates the complete pipeline: +1. HTML table source with tags in cells +2. parse_html_string() converts HTML → Abstract document structure +3. DocumentLayouter handles all layout and rendering + +No custom rendering code needed - DocumentLayouter handles everything! +""" + +from pyWebLayout.style import Font +from pyWebLayout.concrete.table import TableStyle +from pyWebLayout.layout.document_layouter import DocumentLayouter +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.concrete.page import Page +from pyWebLayout.io.readers.html_extraction import parse_html_string +import sys +from pathlib import Path +from PIL import Image + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +def create_book_catalog_html(): + """Create HTML for a book catalog table with actual tags.""" + # Get base path for images - use absolute paths for the img src + data_path = Path(__file__).parent.parent / "tests" / "data" + + html = f""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CoverTitleAuthorPrice
The Great AdventureThe Great AdventureJohn Smith$19.99
Mystery of the AgesMystery of the AgesJane Doe$24.99
Science TodayScience TodayDr. Brown$29.99
Art & DesignArt & DesignM. Artist$34.99
+ + + """ + return html + + +def create_product_showcase_html(): + """Create HTML for a product showcase table with images.""" + data_path = Path(__file__).parent.parent / "tests" / "data" + + html = f""" + + + + + + + + + + + + + + + + + + + +
ProductDescription
Premium EditionPremium Edition - Hardcover with gold embossing
Collector's ItemCollector's Item - Limited print run
+ + + """ + return html + + +def render_html_with_layouter(html_string: str, title: str, + table_style: TableStyle, + page_size=(600, 500)): + """ + Render HTML using DocumentLayouter - the proper way! + + This function demonstrates the correct usage: + 1. Parse HTML → Abstract blocks + 2. Create Page + 3. Create DocumentLayouter + 4. Layout all blocks using layouter + + Args: + html_string: HTML source containing table with tags + title: Title for the output (for logging) + table_style: Table styling configuration + page_size: Page dimensions + + Returns: + PIL Image with rendered content + """ + print(f"\n Processing '{title}'...") + + # Step 1: Parse HTML to abstract blocks + print(" 1. Parsing HTML → Abstract blocks...") + base_font = Font(font_size=11) + blocks = parse_html_string(html_string, base_font=base_font) + print(f" → Parsed {len(blocks)} blocks") + + # Step 2: Create page + print(" 2. Creating page...") + page_style = PageStyle( + border_width=2, + border_color=(180, 180, 180), + padding=(20, 20, 20, 20), + background_color=(255, 255, 255) + ) + page = Page(size=page_size, style=page_style) + + # Step 3: Create DocumentLayouter + print(" 3. Creating DocumentLayouter...") + layouter = DocumentLayouter(page) + + # Step 4: Layout all blocks using the layouter + print(" 4. Laying out all blocks...") + for block in blocks: + # For tables, we can pass a custom style + from pyWebLayout.abstract.block import Table + if isinstance(block, Table): + success = layouter.layout_table(block, style=table_style) + else: + # For other blocks (paragraphs, headings, images), use layout_document + success = layouter.layout_document([block]) + + if not success: + print(f" ⚠ Warning: Block {type(block).__name__} didn't fit on page") + + print(" ✓ Layout complete!") + + # Step 5: Get the rendered canvas + # Note: Tables render directly onto page._canvas + # We access page.draw to ensure canvas is initialized + print(" 5. Getting rendered canvas...") + _ = page.draw # Ensure canvas exists + return page._canvas + + +def main(): + """Demonstrate end-to-end HTML table with images rendering using DocumentLayouter.""" + print("HTML Table with Images Example - DocumentLayouter") + print("=" * 60) + print("\nThis example demonstrates:") + print(" 1. HTML with tags inside cells") + print(" 2. parse_html_string() automatically handles images") + print(" 3. DocumentLayouter handles all layout and rendering") + print(" 4. NO manual TableRenderer or custom rendering code!") + + # Verify images exist + print("\n Checking for cover images...") + data_path = Path(__file__).parent.parent / "tests" / "data" + cover_count = 0 + for i in range(1, 5): + cover_path = data_path / f"cover {i}.png" + if cover_path.exists(): + cover_count += 1 + print(f" ✓ Found cover {i}.png") + + if cover_count == 0: + print(" ✗ No cover images found! This example requires cover images.") + return + + # Create HTML sources with tags + print("\n Creating HTML sources with tags...") + print(" - Book catalog HTML") + book_html = create_book_catalog_html() + + print(" - Product showcase HTML") + product_html = create_product_showcase_html() + + # Define table styles + book_style = TableStyle( + border_width=1, + border_color=(100, 100, 100), + cell_padding=(8, 10, 8, 10), + header_bg_color=(70, 130, 180), + cell_bg_color=(255, 255, 255), + alternate_row_color=(245, 248, 250) + ) + + product_style = TableStyle( + border_width=2, + border_color=(60, 120, 60), + cell_padding=(10, 12, 10, 12), + header_bg_color=(144, 238, 144), + cell_bg_color=(255, 255, 255), + alternate_row_color=(240, 255, 240) + ) + + # Render using DocumentLayouter - the proper way! + print("\n Rendering with DocumentLayouter (HTML → Abstract → Layout → PNG)...") + + book_image = render_html_with_layouter( + book_html, + "Book Catalog", + book_style, + page_size=(700, 600) + ) + + product_image = render_html_with_layouter( + product_html, + "Product Showcase", + product_style, + page_size=(600, 350) + ) + + # Combine images side by side + print("\n Combining output images...") + padding = 20 + total_width = book_image.size[0] + product_image.size[0] + padding * 3 + total_height = max(book_image.size[1], product_image.size[1]) + padding * 2 + + combined = Image.new('RGB', (total_width, total_height), (240, 240, 240)) + combined.paste(book_image, (padding, padding)) + combined.paste(product_image, (book_image.size[0] + padding * 2, padding)) + + # Save output + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / "example_05_html_table_with_images.png" + combined.save(output_path) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined.size[0]}x{combined.size[1]} pixels") + print("\nThe complete pipeline:") + print(" 1. HTML with tags → parse_html_string() → Abstract blocks") + print(" 2. Abstract blocks → DocumentLayouter → Concrete objects") + print(" 3. Page.render() → PNG output") + print("\n ✓ Using DocumentLayouter - NO custom rendering code!") + + return combined + + +if __name__ == "__main__": + main() diff --git a/examples/06_functional_elements_demo.py b/examples/06_functional_elements_demo.py new file mode 100644 index 0000000..7ae5722 --- /dev/null +++ b/examples/06_functional_elements_demo.py @@ -0,0 +1,292 @@ +""" +Demonstration of functional elements (buttons, forms, links) with callback binding. + +This example shows how to: +1. Create functional elements programmatically +2. Layout them on a page +3. Bind callbacks after layout using the CallbackRegistry +4. Simulate user interactions + +This pattern is useful for: +- Manual GUI construction +- Applications where callbacks need access to runtime state +- Interactive document interfaces +""" + +from pyWebLayout.concrete import Page +from pyWebLayout.abstract.functional import Button, Form, FormField, FormFieldType +from pyWebLayout.abstract import Paragraph, Word +from pyWebLayout.style import Font +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.layout.document_layouter import DocumentLayouter +import numpy as np + + +class SimpleApp: + """ + A simple application that demonstrates functional element usage. + + This app has: + - A settings form + - Save and Cancel buttons + - Application state that callbacks can access + """ + + def __init__(self): + self.settings = { + "username": "", + "theme": "light", + "notifications": True + } + self.saved = False + + def on_save_click(self, point, **kwargs): + """Callback for save button""" + print(f"Save button clicked at {point}") + self.saved = True + print("Settings saved!") + return "saved" + + def on_cancel_click(self, point, **kwargs): + """Callback for cancel button""" + print(f"Cancel button clicked at {point}") + print("Changes cancelled!") + return "cancelled" + + def on_reset_click(self, point, **kwargs): + """Callback for reset button""" + print(f"Reset button clicked at {point}") + self.settings = { + "username": "", + "theme": "light", + "notifications": True + } + print("Settings reset to defaults!") + return "reset" + + +def create_settings_page(): + """ + Create a settings page with functional elements. + + Returns: + Tuple of (page, app, element_ids) where element_ids maps + semantic names to registered callback ids + """ + # Create the application instance + app = SimpleApp() + + # Create page + page = Page(size=(600, 800), style=PageStyle(border_width=10)) + layouter = DocumentLayouter(page) + + # Create content + font = Font(font_size=16, colour=(0, 0, 0)) + + # Title paragraph + title_font = Font(font_size=24, colour=(0, 0, 100)) + title = Paragraph(title_font) + title.add_word(Word("Settings", title_font)) + + # Description paragraph + desc = Paragraph(font) + desc.add_word(Word("Configure", font)) + desc.add_word(Word("your", font)) + desc.add_word(Word("application", font)) + desc.add_word(Word("preferences", font)) + desc.add_word(Word("below.", font)) + + # Layout title and description + layouter.layout_paragraph(title) + page._current_y_offset += 10 # Add some spacing + layouter.layout_paragraph(desc) + page._current_y_offset += 20 # Add more spacing before form + + # Create form + settings_form = Form( + form_id="settings-form", + action="/save-settings", + html_id="settings-form" + ) + + # Add form fields + username_field = FormField( + name="username", + field_type=FormFieldType.TEXT, + label="Username", + value="john_doe" + ) + + theme_field = FormField( + name="theme", + field_type=FormFieldType.SELECT, + label="Theme", + value="light", + options=[("light", "Light"), ("dark", "Dark")] + ) + + notifications_field = FormField( + name="notifications", + field_type=FormFieldType.CHECKBOX, + label="Enable Notifications", + value=True + ) + + settings_form.add_field(username_field) + settings_form.add_field(theme_field) + settings_form.add_field(notifications_field) + + # Layout the form + success, field_ids = layouter.layout_form(settings_form) + + if not success: + print("Warning: Form didn't fit on page!") + + page._current_y_offset += 20 # Spacing before buttons + + # Create buttons (NO callbacks yet - will be bound later) + save_button = Button( + label="Save Settings", + callback=None, # No callback yet! + html_id="save-btn" + ) + + cancel_button = Button( + label="Cancel", + callback=None, # No callback yet! + html_id="cancel-btn" + ) + + reset_button = Button( + label="Reset to Defaults", + callback=None, # No callback yet! + html_id="reset-btn" + ) + + # Layout buttons + button_font = Font(font_size=14, colour=(255, 255, 255)) + success1, save_id = layouter.layout_button(save_button, font=button_font) + page._current_y_offset += 10 # Spacing between buttons + success2, cancel_id = layouter.layout_button(cancel_button, font=button_font) + page._current_y_offset += 10 + success3, reset_id = layouter.layout_button(reset_button, font=button_font) + + # ============================================================== + # IMPORTANT: Callbacks are bound AFTER layout is complete + # This allows callbacks to access the application instance + # ============================================================== + + # Bind callbacks using the page's callback registry + page.callbacks.set_callback("save-btn", app.on_save_click) + page.callbacks.set_callback("cancel-btn", app.on_cancel_click) + page.callbacks.set_callback("reset-btn", app.on_reset_click) + + # Track element ids for later reference + element_ids = { + "save_button": save_id, + "cancel_button": cancel_id, + "reset_button": reset_id, + "form_fields": field_ids + } + + return page, app, element_ids + + +def demonstrate_callback_binding(): + """Demonstrate various callback binding patterns""" + + print("=" * 60) + print("Functional Elements Demo: Manual GUI Construction") + print("=" * 60) + print() + + # Create the page + page, app, element_ids = create_settings_page() + + print(f"Page created with {page.callbacks.count()} interactable elements") + print() + + # Show what's registered + print("Registered interactables:") + for id_name in page.callbacks.get_all_ids(): + print(f" - {id_name}") + print() + + # Show breakdown by type + print("Breakdown by type:") + for type_name in page.callbacks.get_all_types(): + count = page.callbacks.count_by_type(type_name) + print(f" - {type_name}: {count}") + print() + + # Simulate user clicking the save button + print("Simulating user interaction:") + print("-" * 60) + print() + + # Get the save button + save_button = page.callbacks.get_by_id("save-btn") + print(f"Retrieved save button: {save_button}") + + # Simulate a click at position (50, 200) + click_point = np.array([50, 200]) + print(f"Simulating click at {click_point}...") + result = save_button.interact(click_point) + print(f"Button interaction returned: {result}") + print(f"App.saved state: {app.saved}") + print() + + # Simulate clicking cancel + cancel_button = page.callbacks.get_by_id("cancel-btn") + print("Simulating cancel button click...") + result = cancel_button.interact(click_point) + print(f"Button interaction returned: {result}") + print() + + # Simulate clicking reset + reset_button = page.callbacks.get_by_id("reset-btn") + print("Simulating reset button click...") + result = reset_button.interact(click_point) + print(f"Button interaction returned: {result}") + print() + + # Demonstrate batch callback modification + print("-" * 60) + print("Demonstrating batch callback modification:") + print() + + def log_all_clicks(point, **kwargs): + """Generic click logger""" + print(f" [LOG] Button clicked at {point}") + return "logged" + + # Set this callback for all buttons + count = page.callbacks.set_callbacks_by_type("button", log_all_clicks) + print(f"Set logging callback for {count} buttons") + print() + + # Now clicking any button will just log + print("Clicking save button again (now with logging callback):") + result = save_button.interact(click_point) + print(f"Returned: {result}") + print() + + # Render the page + print("-" * 60) + print("Rendering page...") + image = page.render() + print(f"Page rendered: {image.size}") + + # Save to file + output_path = "functional_elements_demo.png" + image.save(output_path) + print(f"Saved to: {output_path}") + print() + + print("=" * 60) + print("Demo complete!") + print("=" * 60) + + +if __name__ == "__main__": + demonstrate_callback_binding() diff --git a/examples/07_pressed_state_demo.py b/examples/07_pressed_state_demo.py new file mode 100644 index 0000000..c7c1fb9 --- /dev/null +++ b/examples/07_pressed_state_demo.py @@ -0,0 +1,452 @@ +""" +Demonstration of pressed/depressed states for buttons and links with visual feedback. + +This example shows: +1. How to use the InteractionHandler for automatic press/release cycles +2. How to manually manage states for custom event loops +3. How the dirty flag system tracks when re-rendering is needed +4. Visual differences between normal, hovered, and pressed states + +The demo creates a page with buttons and links, then simulates clicking them +with proper visual feedback timing. +""" + +from pyWebLayout.concrete import Page +from pyWebLayout.concrete.interaction_handler import InteractionHandler, InteractionStateManager +from pyWebLayout.abstract.functional import Button, Link, LinkType +from pyWebLayout.abstract import Paragraph, Word +from pyWebLayout.abstract.inline import LinkedWord +from pyWebLayout.style import Font +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.layout.document_layouter import DocumentLayouter +import numpy as np +import time + + +def create_interactive_demo_page(): + """ + Create a page with various interactive elements demonstrating state changes. + """ + # Create page + page = Page(size=(600, 500), style=PageStyle(border_width=10)) + layouter = DocumentLayouter(page) + + # Create fonts + title_font = Font(font_size=24, colour=(0, 0, 100)) + body_font = Font(font_size=16, colour=(0, 0, 0)) + button_font = Font(font_size=14, colour=(255, 255, 255)) + + # Title + title = Paragraph(title_font) + title.add_word(Word("Interactive", title_font)) + title.add_word(Word("Elements", title_font)) + title.add_word(Word("Demo", title_font)) + layouter.layout_paragraph(title) + page._current_y_offset += 15 + + # Description + desc = Paragraph(body_font) + desc.add_word(Word("Click", body_font)) + desc.add_word(Word("the", body_font)) + desc.add_word(Word("buttons", body_font)) + desc.add_word(Word("and", body_font)) + desc.add_word(Word("links", body_font)) + desc.add_word(Word("below", body_font)) + desc.add_word(Word("to", body_font)) + desc.add_word(Word("see", body_font)) + desc.add_word(Word("pressed", body_font)) + desc.add_word(Word("state", body_font)) + desc.add_word(Word("feedback!", body_font)) + layouter.layout_paragraph(desc) + page._current_y_offset += 20 + + # Callback functions + def on_save(): + print("💾 Save button clicked!") + return "saved" + + def on_cancel(): + print("❌ Cancel button clicked!") + return "cancelled" + + def on_link_click(location, point): + print(f"🔗 Link clicked: {location} at {point}") + return location + + # Create buttons + save_button = Button( + label="Save Document", + callback=lambda point, **kwargs: on_save(), + html_id="save-btn" + ) + + cancel_button = Button( + label="Cancel", + callback=lambda point, **kwargs: on_cancel(), + html_id="cancel-btn" + ) + + # Layout buttons + success1, save_id = layouter.layout_button(save_button, font=button_font) + page._current_y_offset += 12 + success2, cancel_id = layouter.layout_button(cancel_button, font=button_font) + page._current_y_offset += 25 + + # Create paragraph with links + link_para = Paragraph(body_font) + link_para.add_word(Word("Visit", body_font)) + link_para.add_word(Word("our", body_font)) + + # Add a link + internal_link = Link( + location="https://example.com", + link_type=LinkType.EXTERNAL, + callback=on_link_click, + title="Example website" + ) + link_para.add_word(LinkedWord( + "website", + body_font, + location="https://example.com", + link_type=LinkType.EXTERNAL, + callback=on_link_click, + title="Example website" + )) + link_para.add_word(Word("or", body_font)) + + # Add another link + docs_link = Link( + location="/docs", + link_type=LinkType.INTERNAL, + callback=on_link_click, + title="Documentation" + ) + link_para.add_word(LinkedWord( + "documentation", + body_font, + location="/docs", + link_type=LinkType.INTERNAL, + callback=on_link_click, + title="Documentation" + )) + link_para.add_word(Word("page.", body_font)) + + layouter.layout_paragraph(link_para) + + return page, save_id, cancel_id + + +def demo_automatic_interaction(): + """ + Demonstrate automatic interaction handling with InteractionHandler. + + This shows the simplest usage pattern where InteractionHandler manages + the complete press/release cycle automatically. + """ + print("=" * 70) + print("Demo 1: Automatic Interaction with Visual Feedback") + print("=" * 70) + print() + + # Create the page + page, save_id, cancel_id = create_interactive_demo_page() + + # Create interaction handler + handler = InteractionHandler(page, press_duration_ms=150) + + print("Initial render:") + initial_render = page.render() + initial_render.save("demo_07_initial.png") + print(f" ✓ Saved: demo_07_initial.png") + print(f" ✓ Page dirty flag: {page.is_dirty}") + print() + + # Get the save button + save_button = page.callbacks.get_by_id("save-btn") + click_point = np.array([50, 150]) + + print("Simulating button click with automatic feedback...") + print(f" → Setting pressed state at t=0ms") + + # Execute with automatic feedback + pressed_frame, released_frame, result = handler.execute_with_feedback( + save_button, + click_point + ) + + print(f" → Showing pressed state for 150ms") + print(f" → Executing callback") + print(f" → Result: {result}") + print(f" → Setting released state") + + # Save the frames + pressed_frame.save("demo_07_pressed.png") + print(f" ✓ Saved: demo_07_pressed.png") + + released_frame.save("demo_07_released.png") + print(f" ✓ Saved: demo_07_released.png") + print() + + +def demo_manual_state_management(): + """ + Demonstrate manual state management for custom event loops. + + This shows how an application with its own event loop can manage + states and check the dirty flag before re-rendering. + """ + print("=" * 70) + print("Demo 2: Manual State Management with Dirty Flag Checking") + print("=" * 70) + print() + + # Create the page + page, save_id, cancel_id = create_interactive_demo_page() + + # Initial render + print("Initial render:") + current_frame = page.render() + print(f" ✓ Page dirty: {page.is_dirty} (cleaned after render)") + print() + + # Get the cancel button + cancel_button = page.callbacks.get_by_id("cancel-btn") + + # Simulate mouse down + print("Mouse down event:") + # Set page reference if not already set + if not hasattr(cancel_button, '_page') or cancel_button._page is None: + cancel_button.set_page(page) + cancel_button.set_pressed(True) + print(f" ✓ Set pressed state") + print(f" ✓ Page dirty: {page.is_dirty} (needs re-render)") + + # Check if we need to re-render + if page.is_dirty: + print(" → Re-rendering (dirty flag is set)") + current_frame = page.render() + current_frame.save("demo_07_manual_pressed.png") + print(f" ✓ Saved: demo_07_manual_pressed.png") + print(f" ✓ Page dirty: {page.is_dirty} (cleaned after render)") + print() + + # Wait a bit + print("Waiting 150ms for visual feedback...") + time.sleep(0.15) + print() + + # Execute callback + print("Executing callback:") + result = cancel_button.interact(np.array([50, 200])) + print(f" ✓ Result: {result}") + print() + + # Simulate mouse up + print("Mouse up event:") + cancel_button.set_pressed(False) + print(f" ✓ Set released state") + print(f" ✓ Page dirty: {page.is_dirty} (needs re-render)") + + # Check if we need to re-render + if page.is_dirty: + print(" → Re-rendering (dirty flag is set)") + current_frame = page.render() + current_frame.save("demo_07_manual_released.png") + print(f" ✓ Saved: demo_07_manual_released.png") + print(f" ✓ Page dirty: {page.is_dirty} (cleaned after render)") + print() + + +def demo_state_manager(): + """ + Demonstrate the InteractionStateManager for hover/press tracking. + + This shows how to use the high-level state manager that automatically + handles hover and press states based on cursor position. + """ + print("=" * 70) + print("Demo 3: InteractionStateManager for Hover and Press Tracking") + print("=" * 70) + print() + + # Create the page + page, save_id, cancel_id = create_interactive_demo_page() + + # Create state manager + state_mgr = InteractionStateManager(page) + + # Initial render + print("Initial render:") + current_frame = page.render() + print(f" ✓ Rendered initial state") + print() + + # Simulate cursor moving over a button + button_center = (150, 150) + print(f"Cursor moves to button position {button_center}:") + hover_frame = state_mgr.update_hover(button_center) + if hover_frame: + print(f" ✓ Hover state changed, page re-rendered") + hover_frame.save("demo_07_hover.png") + print(f" ✓ Saved: demo_07_hover.png") + print() + + # Simulate mouse down + print(f"Mouse down at {button_center}:") + pressed_frame = state_mgr.handle_mouse_down(button_center) + if pressed_frame: + print(f" ✓ Pressed state set, page re-rendered") + pressed_frame.save("demo_07_state_mgr_pressed.png") + print(f" ✓ Saved: demo_07_state_mgr_pressed.png") + print() + + # Wait for visual feedback + time.sleep(0.15) + + # Simulate mouse up + print(f"Mouse up at {button_center}:") + released_frame, result = state_mgr.handle_mouse_up(button_center) + if released_frame: + print(f" ✓ Released state set, page re-rendered") + print(f" ✓ Callback result: {result}") + released_frame.save("demo_07_state_mgr_released.png") + print(f" ✓ Saved: demo_07_state_mgr_released.png") + print() + + # Simulate cursor moving away + away_point = (50, 50) + print(f"Cursor moves away to {away_point}:") + away_frame = state_mgr.update_hover(away_point) + if away_frame: + print(f" ✓ Hover state cleared, page re-rendered") + away_frame.save("demo_07_no_hover.png") + print(f" ✓ Saved: demo_07_no_hover.png") + print() + + +def demo_performance_optimization(): + """ + Demonstrate how the dirty flag prevents unnecessary re-renders. + """ + print("=" * 70) + print("Demo 4: Performance Optimization with Dirty Flag") + print("=" * 70) + print() + + # Create the page + page, save_id, cancel_id = create_interactive_demo_page() + + print("Scenario: Multiple state queries without changes") + print() + + # Initial render + page.render() + print(f"1. After initial render - dirty: {page.is_dirty}") + + # Check if dirty before rendering again + print(f"2. Check dirty flag: {page.is_dirty}") + if not page.is_dirty: + print(" → Skipping render (no changes)") + print() + + # Now make a change + button = page.callbacks.get_by_id("save-btn") + print("3. Setting button to pressed state") + # Ensure page reference is set + if not hasattr(button, '_page') or button._page is None: + button.set_page(page) + button.set_pressed(True) + print(f" → dirty: {page.is_dirty}") + print() + + # This time we need to render + print(f"4. Check dirty flag: {page.is_dirty}") + if page.is_dirty: + print(" → Re-rendering (state changed)") + page.render() + print(f" → dirty after render: {page.is_dirty}") + print() + + print("Benefit: Only render when actual changes occur!") + print() + + +def create_animated_gif(): + """ + Create an animated GIF showing the button press sequence. + """ + from PIL import Image + import os + + print("=" * 70) + print("Creating Animated GIF") + print("=" * 70) + print() + + # Check if the PNG files exist + png_files = [ + "demo_07_initial.png", + "demo_07_pressed.png", + "demo_07_released.png" + ] + + if not all(os.path.exists(f) for f in png_files): + print(" ⚠ PNG files not found, skipping GIF creation") + return + + # Load the images + initial = Image.open('demo_07_initial.png') + pressed = Image.open('demo_07_pressed.png') + released = Image.open('demo_07_released.png') + + # Create animated GIF showing the button interaction sequence + # Sequence: initial (1000ms) -> pressed (200ms) -> released (500ms) -> loop + frames = [initial, pressed, released] + durations = [1000, 200, 500] # milliseconds per frame + + output_path = "docs/images/example_07_button_animation.gif" + + # Create docs/images directory if it doesn't exist + os.makedirs("docs/images", exist_ok=True) + + # Save as animated GIF + initial.save( + output_path, + save_all=True, + append_images=[pressed, released], + duration=durations, + loop=0 # 0 means loop forever + ) + + print(f" ✓ Created: {output_path}") + print(f" ✓ Frames: {len(frames)}") + print(f" ✓ Sequence: initial (1000ms) → pressed (200ms) → released (500ms)") + print() + + +if __name__ == "__main__": + print("\n") + print("╔" + "═" * 68 + "╗") + print("║" + " " * 15 + "PRESSED STATE DEMONSTRATION" + " " * 26 + "║") + print("╚" + "═" * 68 + "╝") + print() + + # Run all demos + demo_automatic_interaction() + print("\n") + + demo_manual_state_management() + print("\n") + + demo_state_manager() + print("\n") + + demo_performance_optimization() + print("\n") + + # Create animated GIF + create_animated_gif() + + print("=" * 70) + print("All demos complete! Check the generated PNG files and animated GIF.") + print("=" * 70) diff --git a/examples/08_bundled_fonts_demo.py b/examples/08_bundled_fonts_demo.py new file mode 100644 index 0000000..f74e076 --- /dev/null +++ b/examples/08_bundled_fonts_demo.py @@ -0,0 +1,227 @@ +""" +Demonstration of bundled fonts in pyWebLayout. + +This example shows: +1. How to use the bundled DejaVu font families +2. Different font variants (regular, bold, italic, bold-italic) +3. The three font families (Sans, Serif, Monospace) +4. Convenient Font.from_family() method for easy font selection + +The demo creates a page showcasing all bundled fonts with different styles. +""" + +from pyWebLayout.concrete import Page +from pyWebLayout.abstract import Paragraph, Word +from pyWebLayout.style import Font, FontWeight, FontStyle, BundledFont +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.layout.document_layouter import DocumentLayouter + + +def create_font_showcase_page(): + """ + Create a page demonstrating all bundled fonts and variants. + """ + # Create page with some padding + page = Page(size=(800, 1000), style=PageStyle(border_width=20)) + layouter = DocumentLayouter(page) + + # Title + title_font = Font.from_family( + BundledFont.SANS, + font_size=32, + colour=(0, 0, 100), + weight=FontWeight.BOLD + ) + title = Paragraph(title_font) + title.add_word(Word("Bundled", title_font)) + title.add_word(Word("Fonts", title_font)) + title.add_word(Word("Showcase", title_font)) + layouter.layout_paragraph(title) + page._current_y_offset += 20 + + # Introduction + intro_font = Font.from_family(BundledFont.SANS, font_size=14, colour=(50, 50, 50)) + intro = Paragraph(intro_font) + intro_text = "pyWebLayout bundles the DejaVu font family with three font types and four variants each." + for word in intro_text.split(): + intro.add_word(Word(word, intro_font)) + layouter.layout_paragraph(intro) + page._current_y_offset += 25 + + # --- Sans Serif Section --- + section_font = Font.from_family( + BundledFont.SANS, + font_size=20, + colour=(0, 100, 0), + weight=FontWeight.BOLD + ) + sans_section = Paragraph(section_font) + sans_section.add_word(Word("Sans-Serif", section_font)) + sans_section.add_word(Word("(DejaVu", section_font)) + sans_section.add_word(Word("Sans)", section_font)) + layouter.layout_paragraph(sans_section) + page._current_y_offset += 10 + + # Sans Regular + sans_regular = Font.from_family(BundledFont.SANS, font_size=16) + demo_text_paragraph(layouter, page, sans_regular, "Regular:") + + # Sans Bold + sans_bold = Font.from_family(BundledFont.SANS, font_size=16, weight=FontWeight.BOLD) + demo_text_paragraph(layouter, page, sans_bold, "Bold:") + + # Sans Italic + sans_italic = Font.from_family(BundledFont.SANS, font_size=16, style=FontStyle.ITALIC) + demo_text_paragraph(layouter, page, sans_italic, "Italic:") + + # Sans Bold Italic + sans_bold_italic = Font.from_family( + BundledFont.SANS, + font_size=16, + weight=FontWeight.BOLD, + style=FontStyle.ITALIC + ) + demo_text_paragraph(layouter, page, sans_bold_italic, "Bold Italic:") + page._current_y_offset += 20 + + # --- Serif Section --- + serif_section = Paragraph(section_font) + serif_section.add_word(Word("Serif", section_font)) + serif_section.add_word(Word("(DejaVu", section_font)) + serif_section.add_word(Word("Serif)", section_font)) + layouter.layout_paragraph(serif_section) + page._current_y_offset += 10 + + # Serif Regular + serif_regular = Font.from_family(BundledFont.SERIF, font_size=16) + demo_text_paragraph(layouter, page, serif_regular, "Regular:") + + # Serif Bold + serif_bold = Font.from_family(BundledFont.SERIF, font_size=16, weight=FontWeight.BOLD) + demo_text_paragraph(layouter, page, serif_bold, "Bold:") + + # Serif Italic + serif_italic = Font.from_family(BundledFont.SERIF, font_size=16, style=FontStyle.ITALIC) + demo_text_paragraph(layouter, page, serif_italic, "Italic:") + + # Serif Bold Italic + serif_bold_italic = Font.from_family( + BundledFont.SERIF, + font_size=16, + weight=FontWeight.BOLD, + style=FontStyle.ITALIC + ) + demo_text_paragraph(layouter, page, serif_bold_italic, "Bold Italic:") + page._current_y_offset += 20 + + # --- Monospace Section --- + mono_section = Paragraph(section_font) + mono_section.add_word(Word("Monospace", section_font)) + mono_section.add_word(Word("(DejaVu", section_font)) + mono_section.add_word(Word("Sans", section_font)) + mono_section.add_word(Word("Mono)", section_font)) + layouter.layout_paragraph(mono_section) + page._current_y_offset += 10 + + # Mono Regular + mono_regular = Font.from_family(BundledFont.MONOSPACE, font_size=14) + demo_code_paragraph(layouter, page, mono_regular, "Regular:") + + # Mono Bold + mono_bold = Font.from_family(BundledFont.MONOSPACE, font_size=14, weight=FontWeight.BOLD) + demo_code_paragraph(layouter, page, mono_bold, "Bold:") + + # Mono Italic + mono_italic = Font.from_family(BundledFont.MONOSPACE, font_size=14, style=FontStyle.ITALIC) + demo_code_paragraph(layouter, page, mono_italic, "Italic:") + + # Mono Bold Italic + mono_bold_italic = Font.from_family( + BundledFont.MONOSPACE, + font_size=14, + weight=FontWeight.BOLD, + style=FontStyle.ITALIC + ) + demo_code_paragraph(layouter, page, mono_bold_italic, "Bold Italic:") + page._current_y_offset += 20 + + # Footer + footer_font = Font.from_family(BundledFont.SANS, font_size=12, colour=(100, 100, 100)) + footer = Paragraph(footer_font) + footer_text = "All fonts are free and open source under the Bitstream Vera License." + for word in footer_text.split(): + footer.add_word(Word(word, footer_font)) + layouter.layout_paragraph(footer) + + return page + + +def demo_text_paragraph(layouter, page, font, label): + """Create a paragraph showing sample text with the given font.""" + # Label in smaller font + label_font = Font.from_family(BundledFont.SANS, font_size=12, colour=(100, 100, 100)) + label_para = Paragraph(label_font) + label_para.add_word(Word(label, label_font)) + layouter.layout_paragraph(label_para) + page._current_y_offset += 5 + + # Sample text + para = Paragraph(font) + sample = "The quick brown fox jumps over the lazy dog. 0123456789" + for word in sample.split(): + para.add_word(Word(word, font)) + layouter.layout_paragraph(para) + page._current_y_offset += 8 + + +def demo_code_paragraph(layouter, page, font, label): + """Create a paragraph showing sample code with the given font.""" + # Label in smaller font + label_font = Font.from_family(BundledFont.SANS, font_size=12, colour=(100, 100, 100)) + label_para = Paragraph(label_font) + label_para.add_word(Word(label, label_font)) + layouter.layout_paragraph(label_para) + page._current_y_offset += 5 + + # Sample code + para = Paragraph(font) + code = "def hello(): print('Hello, World!') # 0123456789" + for word in code.split(): + para.add_word(Word(word, font)) + layouter.layout_paragraph(para) + page._current_y_offset += 8 + + +if __name__ == "__main__": + print("\n") + print("=" * 70) + print("Bundled Fonts Demonstration") + print("=" * 70) + print() + + print("Creating font showcase page...") + page = create_font_showcase_page() + + print("Rendering page...") + image = page.render() + + output_file = "demo_08_bundled_fonts.png" + image.save(output_file) + print(f"Saved: {output_file}") + + print() + print("=" * 70) + print("Demo complete!") + print() + print("The page showcases all bundled fonts:") + print(" - DejaVu Sans (Sans-serif)") + print(" - DejaVu Serif (Serif)") + print(" - DejaVu Sans Mono (Monospace)") + print() + print("Each family has 4 variants:") + print(" - Regular") + print(" - Bold") + print(" - Italic") + print(" - Bold Italic") + print("=" * 70) + print() diff --git a/examples/08_pagination_demo.py b/examples/08_pagination_demo.py new file mode 100644 index 0000000..dab48a8 --- /dev/null +++ b/examples/08_pagination_demo.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python3 +""" +Pagination Example with PageBreak + +This example demonstrates: +- Using PageBreak to force content onto new pages +- Multi-page document layout with automatic page creation +- Different content types across multiple pages +- Page numbering and document flow +- Combining text, images, and tables across pages + +This shows how to create multi-page documents with explicit page breaks. +""" + +import sys +from pathlib import Path +from PIL import Image, ImageDraw + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from pyWebLayout.concrete.page import Page +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.style.fonts import Font +from pyWebLayout.abstract.inline import Word +from pyWebLayout.abstract.block import Paragraph, PageBreak, Image as AbstractImage +from pyWebLayout.layout.document_layouter import DocumentLayouter + + +def create_sample_paragraph(text: str, font_size: int = 14) -> Paragraph: + """Create a paragraph from plain text.""" + font = Font(font_size=font_size, colour=(50, 50, 50)) + paragraph = Paragraph(style=font) + for word in text.split(): + paragraph.add_word(Word(word, font)) + return paragraph + + +def create_title_paragraph(text: str) -> Paragraph: + """Create a title paragraph with larger font.""" + font = Font(font_size=24, colour=(0, 0, 100), weight='bold') + paragraph = Paragraph(style=font) + for word in text.split(): + paragraph.add_word(Word(word, font)) + return paragraph + + +def create_heading_paragraph(text: str) -> Paragraph: + """Create a heading paragraph.""" + font = Font(font_size=18, colour=(50, 50, 100), weight='bold') + paragraph = Paragraph(style=font) + for word in text.split(): + paragraph.add_word(Word(word, font)) + return paragraph + + +def create_placeholder_image(width: int, height: int, label: str) -> AbstractImage: + """Create a placeholder image for demonstration.""" + img = Image.new('RGB', (width, height), (200, 220, 240)) + draw = ImageDraw.Draw(img) + + # Draw border + draw.rectangle([0, 0, width-1, height-1], outline=(100, 120, 140), width=2) + + # Add label + text_bbox = draw.textbbox((0, 0), label) + text_width = text_bbox[2] - text_bbox[0] + text_height = text_bbox[3] - text_bbox[1] + text_x = (width - text_width) // 2 + text_y = (height - text_height) // 2 + draw.text((text_x, text_y), label, fill=(80, 80, 120)) + + return AbstractImage(source=img) + + +def create_example_document_with_pagebreaks(): + """ + Example: Multi-page document with explicit page breaks. + + This demonstrates how PageBreak forces content onto new pages. + """ + print("\n Creating multi-page document with PageBreaks...") + + # Define common page style + page_style = PageStyle( + border_width=2, + border_color=(100, 100, 150), + padding=(30, 40, 30, 40), + background_color=(255, 255, 255), + line_spacing=6 + ) + + # Create document content with page breaks + content = [ + # Page 1: Title and Introduction + create_title_paragraph("Multi-Page Document Example"), + create_sample_paragraph( + "This document demonstrates how to use PageBreak elements to control " + "document pagination. Each PageBreak forces subsequent content to start " + "on a new page, allowing you to structure multi-page documents precisely." + ), + create_sample_paragraph( + "Page breaks are particularly useful for creating chapters, sections, or " + "ensuring that important content starts at the top of a fresh page rather " + "than being split across page boundaries." + ), + + # Force page break - next content will be on page 2 + PageBreak(), + + # Page 2: First Section + create_heading_paragraph("Section 1: Text Content"), + create_sample_paragraph( + "This is the second page of our document. It starts with a clean break " + "from the previous page, ensuring the section heading appears at the top." + ), + create_sample_paragraph( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod " + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat." + ), + create_sample_paragraph( + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum " + "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " + "proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ), + + # Another page break + PageBreak(), + + # Page 3: Images + create_heading_paragraph("Section 2: Visual Content"), + create_sample_paragraph( + "This page contains image content, demonstrating that page breaks work " + "correctly with different content types." + ), + create_placeholder_image(300, 200, "Figure 1: Sample Image"), + create_sample_paragraph("The image above is placed on this dedicated page."), + + # Final page break + PageBreak(), + + # Page 4: Conclusion + create_heading_paragraph("Conclusion"), + create_sample_paragraph( + "This final page demonstrates that you can create complex multi-page " + "documents by strategically placing PageBreak elements in your content." + ), + create_sample_paragraph( + "Key benefits of using PageBreak: 1) Control where pages start, " + "2) Prevent awkward content splits, 3) Create professional-looking " + "documents with proper sectioning, 4) Ensure important content gets " + "visual prominence at page tops." + ), + create_sample_paragraph( + "Thank you for reviewing this pagination example. Try experimenting " + "with PageBreak placement to create your own multi-page documents!" + ), + ] + + # Layout the document across multiple pages + pages = [] + current_page = Page(size=(600, 800), style=page_style) + layouter = DocumentLayouter(current_page) + + for element in content: + if isinstance(element, PageBreak): + # Save current page and create a new one + pages.append(current_page) + current_page = Page(size=(600, 800), style=page_style) + layouter = DocumentLayouter(current_page) + elif isinstance(element, Paragraph): + success, _, _ = layouter.layout_paragraph(element) + if not success: + # Page is full, create new page and retry + pages.append(current_page) + current_page = Page(size=(600, 800), style=page_style) + layouter = DocumentLayouter(current_page) + success, _, _ = layouter.layout_paragraph(element) + if not success: + print(" WARNING: Content too large for page") + elif isinstance(element, AbstractImage): + success = layouter.layout_image(element) + if not success: + # Image doesn't fit, try on new page + pages.append(current_page) + current_page = Page(size=(600, 800), style=page_style) + layouter = DocumentLayouter(current_page) + success = layouter.layout_image(element) + if not success: + print(" WARNING: Image too large for page") + + # Add the final page + pages.append(current_page) + + print(f" Created {len(pages)} pages") + return pages + + +def create_auto_pagination_example(): + """ + Example: Document that automatically flows to multiple pages. + + This shows the difference between automatic pagination (when content + doesn't fit) vs explicit PageBreak usage. + """ + print("\n Creating auto-paginated document (no explicit breaks)...") + + page_style = PageStyle( + border_width=1, + border_color=(150, 150, 150), + padding=(20, 30, 20, 30), + background_color=(250, 250, 250), + line_spacing=5 + ) + + # Create lots of content that will naturally overflow + content = [ + create_heading_paragraph("Auto-Pagination Example"), + create_sample_paragraph( + "This document does NOT use PageBreak. Instead, it demonstrates how " + "content automatically flows to new pages when the current page is full." + ), + ] + + # Add many paragraphs to force automatic page breaks + for i in range(1, 11): + content.append( + create_sample_paragraph( + f"Paragraph {i}: This is automatically laid out content. " + f"When this paragraph doesn't fit on the current page, the layouter " + f"will create a new page automatically. This is different from using " + f"PageBreak which forces a new page regardless of available space. " + f"Auto-pagination is useful for flowing content naturally." + ) + ) + + # Layout across pages + pages = [] + current_page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(current_page) + + for element in content: + if isinstance(element, Paragraph): + success, _, _ = layouter.layout_paragraph(element) + if not success: + # Auto page break - content didn't fit + pages.append(current_page) + current_page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(current_page) + layouter.layout_paragraph(element) + + pages.append(current_page) + + print(f" Auto-created {len(pages)} pages") + return pages + + +def add_page_numbers(pages, start_number: int = 1): + """Add page numbers to rendered pages.""" + numbered_pages = [] + font = Font(font_size=10, colour=(100, 100, 100)) + + for i, page in enumerate(pages, start=start_number): + # Render the page + img = page.render() + draw = ImageDraw.Draw(img) + + # Add page number at bottom center + page_text = f"Page {i}" + bbox = draw.textbbox((0, 0), page_text) + text_width = bbox[2] - bbox[0] + x = (img.size[0] - text_width) // 2 + y = img.size[1] - 20 + + draw.text((x, y), page_text, fill=(100, 100, 100)) + numbered_pages.append(img) + + return numbered_pages + + +def combine_pages_vertically(pages, title: str = ""): + """Combine multiple pages into a vertical strip.""" + if not pages: + return None + + padding = 20 + title_height = 40 if title else 0 + + # Calculate dimensions + page_width = pages[0].size[0] + page_height = pages[0].size[1] + + total_width = page_width + 2 * padding + total_height = len(pages) * (page_height + padding) + padding + title_height + + # Create combined image + combined = Image.new('RGB', (total_width, total_height), (240, 240, 240)) + draw = ImageDraw.Draw(combined) + + # Draw title if provided + if title: + from PIL import ImageFont + try: + title_font = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16 + ) + except: + title_font = ImageFont.load_default() + + bbox = draw.textbbox((0, 0), title, font=title_font) + text_width = bbox[2] - bbox[0] + title_x = (total_width - text_width) // 2 + draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font) + + # Place pages vertically + y_offset = title_height + padding + for page_img in pages: + combined.paste(page_img, (padding, y_offset)) + y_offset += page_height + padding + + return combined + + +def main(): + """Demonstrate pagination with PageBreak.""" + print("Pagination Example with PageBreak") + print("=" * 50) + + # Example 1: Explicit page breaks + pages1 = create_example_document_with_pagebreaks() + rendered_pages1 = add_page_numbers(pages1) + combined1 = combine_pages_vertically( + rendered_pages1, + "Example 1: Explicit PageBreak Usage" + ) + + # Example 2: Auto pagination + pages2 = create_auto_pagination_example() + rendered_pages2 = add_page_numbers(pages2) + combined2 = combine_pages_vertically( + rendered_pages2, + "Example 2: Automatic Pagination" + ) + + # Save outputs + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + + output_path1 = output_dir / "example_08_pagination_explicit.png" + output_path2 = output_dir / "example_08_pagination_auto.png" + + combined1.save(output_path1) + combined2.save(output_path2) + + print("\n✓ Example completed!") + print(f" Output 1 saved to: {output_path1}") + print(f" - {len(pages1)} pages with explicit PageBreaks") + print(f" Output 2 saved to: {output_path2}") + print(f" - {len(pages2)} pages with auto-pagination") + + return combined1, combined2 + + +if __name__ == "__main__": + main() diff --git a/examples/09_link_navigation_demo.py b/examples/09_link_navigation_demo.py new file mode 100644 index 0000000..96fbb03 --- /dev/null +++ b/examples/09_link_navigation_demo.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python3 +""" +Link Navigation Example + +This example demonstrates: +- Creating clickable links with LinkedWord +- Different link types (INTERNAL, EXTERNAL, API, FUNCTION) +- Link styling with underlines and colors +- Link callbacks and event handling +- Interactive link states (hover, pressed) +- Organizing linked content in paragraphs + +This shows how to create interactive documents with hyperlinks. +""" + +import sys +from pathlib import Path +from typing import List + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from pyWebLayout.concrete.page import Page +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.style.fonts import Font +from pyWebLayout.abstract.inline import Word, LinkedWord +from pyWebLayout.abstract.functional import LinkType +from pyWebLayout.abstract.block import Paragraph +from pyWebLayout.layout.document_layouter import DocumentLayouter + + +# Track link clicks for demonstration +link_clicks = [] + + +def link_callback(link_id: str): + """Callback for link clicks""" + def callback(): + link_clicks.append(link_id) + print(f" Link clicked: {link_id}") + return callback + + +def create_paragraph_with_links( + text_parts: List[tuple], + font_size: int = 14) -> Paragraph: + """ + Create a paragraph with mixed text and links. + + Args: + text_parts: List of tuples where each is either: + ('text', "word1 word2") for normal text + ('link', "word", location, link_type, callback_id) + font_size: Base font size + + Returns: + Paragraph with words and links + """ + font = Font(font_size=font_size, colour=(50, 50, 50)) + paragraph = Paragraph(style=font) + + for part in text_parts: + if part[0] == 'text': + # Add normal words + for word_text in part[1].split(): + paragraph.add_word(Word(word_text, font)) + elif part[0] == 'link': + # Add linked word + word_text, location, link_type, callback_id = part[1:] + callback = link_callback(callback_id) + linked_word = LinkedWord( + text=word_text, + style=font, + location=location, + link_type=link_type, + callback=callback, + title=f"Click to: {location}" + ) + paragraph.add_word(linked_word) + + return paragraph + + +def create_example_1_internal_links(): + """Example 1: Internal navigation links within a document.""" + print("\n Creating Example 1: Internal links...") + + page_style = PageStyle( + border_width=2, + border_color=(150, 150, 200), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255), + line_spacing=6 + ) + + page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(page) + + # Title + title_font = Font(font_size=20, colour=(0, 0, 100), weight='bold') + title = Paragraph(style=title_font) + for word in "Internal Navigation Links".split(): + title.add_word(Word(word, title_font)) + + # Content with internal links + intro = create_paragraph_with_links([ + ('text', "This document demonstrates"), + ('link', "internal", "#section1", LinkType.INTERNAL, "goto_section1"), + ('text', "navigation links that jump to different parts of the document."), + ]) + + section1 = create_paragraph_with_links([ + ('text', "Jump to"), + ('link', "Section 2", "#section2", LinkType.INTERNAL, "goto_section2"), + ('text', "or"), + ('link', "Section 3", "#section3", LinkType.INTERNAL, "goto_section3"), + ('text', "within this document."), + ]) + + section2 = create_paragraph_with_links([ + ('text', "You are in Section 2. Return to"), + ('link', "top", "#top", LinkType.INTERNAL, "goto_top"), + ('text', "or go to"), + ('link', "Section 3", "#section3", LinkType.INTERNAL, "goto_section3_from2"), + ]) + + section3 = create_paragraph_with_links([ + ('text', "This is Section 3. Go back to"), + ('link', "Section 1", "#section1", LinkType.INTERNAL, "goto_section1_from3"), + ('text', "or"), + ('link', "top", "#top", LinkType.INTERNAL, "goto_top_from3"), + ]) + + # Layout content + layouter.layout_paragraph(title) + layouter.layout_paragraph(intro) + layouter.layout_paragraph(section1) + layouter.layout_paragraph(section2) + layouter.layout_paragraph(section3) + + return page + + +def create_example_2_external_links(): + """Example 2: External links to websites.""" + print(" Creating Example 2: External links...") + + page_style = PageStyle( + border_width=2, + border_color=(150, 200, 150), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255), + line_spacing=6 + ) + + page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(page) + + # Title + title_font = Font(font_size=20, colour=(0, 100, 0), weight='bold') + title = Paragraph(style=title_font) + for word in "External Web Links".split(): + title.add_word(Word(word, title_font)) + + # Content with external links + intro = create_paragraph_with_links([ + ('text', "Click"), + ('link', "here", "https://example.com", LinkType.EXTERNAL, "visit_example"), + ('text', "to visit an external website."), + ]) + + resources = create_paragraph_with_links([ + ('text', "Useful resources:"), + ('link', "Documentation", "https://docs.example.com", LinkType.EXTERNAL, "visit_docs"), + ('text', "and"), + ('link', "GitHub", "https://github.com/example", LinkType.EXTERNAL, "visit_github"), + ]) + + more_links = create_paragraph_with_links([ + ('text', "Learn more at"), + ('link', "Wikipedia", "https://wikipedia.org", LinkType.EXTERNAL, "visit_wiki"), + ('text', "or check out"), + ('link', "Python.org", "https://python.org", LinkType.EXTERNAL, "visit_python"), + ]) + + # Layout content + layouter.layout_paragraph(title) + layouter.layout_paragraph(intro) + layouter.layout_paragraph(resources) + layouter.layout_paragraph(more_links) + + return page + + +def create_example_3_api_links(): + """Example 3: API links that trigger actions.""" + print(" Creating Example 3: API links...") + + page_style = PageStyle( + border_width=2, + border_color=(200, 150, 150), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255), + line_spacing=6 + ) + + page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(page) + + # Title + title_font = Font(font_size=20, colour=(150, 0, 0), weight='bold') + title = Paragraph(style=title_font) + for word in "API Action Links".split(): + title.add_word(Word(word, title_font)) + + # Content with API links + settings = create_paragraph_with_links([ + ('text', "Click"), + ('link', "Settings", "/api/settings", LinkType.API, "open_settings"), + ('text', "to configure the application."), + ]) + + actions = create_paragraph_with_links([ + ('text', "Actions:"), + ('link', "Save", "/api/save", LinkType.API, "save_action"), + ('text', "or"), + ('link', "Export", "/api/export", LinkType.API, "export_action"), + ('text', "your data."), + ]) + + management = create_paragraph_with_links([ + ('text', "Manage:"), + ('link', "Users", "/api/users", LinkType.API, "manage_users"), + ('text', "or"), + ('link', "Permissions", "/api/permissions", LinkType.API, "manage_perms"), + ]) + + # Layout content + layouter.layout_paragraph(title) + layouter.layout_paragraph(settings) + layouter.layout_paragraph(actions) + layouter.layout_paragraph(management) + + return page + + +def create_example_4_function_links(): + """Example 4: Function links that execute code.""" + print(" Creating Example 4: Function links...") + + page_style = PageStyle( + border_width=2, + border_color=(150, 200, 200), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255), + line_spacing=6 + ) + + page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(page) + + # Title + title_font = Font(font_size=20, colour=(0, 120, 120), weight='bold') + title = Paragraph(style=title_font) + for word in "Function Execution Links".split(): + title.add_word(Word(word, title_font)) + + # Content with function links + intro = create_paragraph_with_links([ + ('text', "These links execute"), + ('link', "functions", "calculate()", LinkType.FUNCTION, "exec_calculate"), + ('text', "directly in the application."), + ]) + + calculations = create_paragraph_with_links([ + ('text', "Run:"), + ('link', "analyze()", "analyze()", LinkType.FUNCTION, "exec_analyze"), + ('text', "or"), + ('link', "process()", "process()", LinkType.FUNCTION, "exec_process"), + ]) + + utilities = create_paragraph_with_links([ + ('text', "Utilities:"), + ('link', "validate()", "validate()", LinkType.FUNCTION, "exec_validate"), + ('text', "and"), + ('link', "cleanup()", "cleanup()", LinkType.FUNCTION, "exec_cleanup"), + ]) + + # Layout content + layouter.layout_paragraph(title) + layouter.layout_paragraph(intro) + layouter.layout_paragraph(calculations) + layouter.layout_paragraph(utilities) + + return page + + +def combine_pages_into_grid(pages, title): + """Combine multiple pages into a 2x2 grid.""" + from PIL import Image, ImageDraw, ImageFont + + print("\n Combining pages into grid...") + + # Render all pages + images = [page.render() for page in pages] + + # Grid layout + padding = 20 + title_height = 40 + cols = 2 + rows = 2 + + # Calculate dimensions + img_width = images[0].size[0] + img_height = images[0].size[1] + + total_width = cols * img_width + (cols + 1) * padding + total_height = rows * img_height + (rows + 1) * padding + title_height + + # Create combined image + combined = Image.new('RGB', (total_width, total_height), (240, 240, 240)) + draw = ImageDraw.Draw(combined) + + # Draw title + try: + title_font = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18 + ) + except: + title_font = ImageFont.load_default() + + # Center the title + bbox = draw.textbbox((0, 0), title, font=title_font) + text_width = bbox[2] - bbox[0] + title_x = (total_width - text_width) // 2 + draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font) + + # Place pages in grid + y_offset = title_height + padding + for row in range(rows): + x_offset = padding + for col in range(cols): + idx = row * cols + col + if idx < len(images): + combined.paste(images[idx], (x_offset, y_offset)) + x_offset += img_width + padding + y_offset += img_height + padding + + return combined + + +def main(): + """Demonstrate link navigation across different link types.""" + global link_clicks + link_clicks = [] + + print("Link Navigation Example") + print("=" * 50) + + # Create examples for each link type + pages = [ + create_example_1_internal_links(), + create_example_2_external_links(), + create_example_3_api_links(), + create_example_4_function_links() + ] + + # Combine into demonstration image + combined_image = combine_pages_into_grid( + pages, + "Link Types: Internal | External | API | Function" + ) + + # Save output + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / "example_09_link_navigation.png" + combined_image.save(output_path) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels") + print(f" Created {len(pages)} link type examples") + print(f" Total links created: {len(link_clicks)} callbacks registered") + + return combined_image, link_clicks + + +if __name__ == "__main__": + main() diff --git a/examples/10_forms_demo.py b/examples/10_forms_demo.py new file mode 100644 index 0000000..abcf036 --- /dev/null +++ b/examples/10_forms_demo.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +""" +Comprehensive Forms Example + +This example demonstrates: +- All FormFieldType variations (TEXT, PASSWORD, EMAIL, etc.) +- Form layout with multiple fields +- Field labels and validation +- Form submission callbacks +- Organizing forms on pages + +This shows how to create interactive forms with all available field types. +""" + +import sys +from pathlib import Path + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from pyWebLayout.concrete.page import Page +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.style.fonts import Font +from pyWebLayout.abstract.functional import Form, FormField, FormFieldType +from pyWebLayout.layout.document_layouter import DocumentLayouter +from PIL import Image, ImageDraw + + +# Track form submissions +form_submissions = [] + + +def form_submit_callback(form_id: str): + """Callback for form submissions""" + def callback(data): + form_submissions.append((form_id, data)) + print(f" Form submitted: {form_id} with data: {data}") + return callback + + +def create_example_1_text_fields(): + """Example 1: Text input fields""" + print("\n Creating Example 1: Text input fields...") + + page_style = PageStyle( + border_width=2, + border_color=(150, 150, 200), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255) + ) + + page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(page) + + # Create form with text fields + form = Form(form_id="text_form", html_id="text_form", callback=form_submit_callback("text_form")) + + # Add various text-based fields + form.add_field(FormField( + name="username", + label="Username", + field_type=FormFieldType.TEXT, + required=True + )) + + form.add_field(FormField( + name="email", + label="Email Address", + field_type=FormFieldType.EMAIL, + required=True + )) + + form.add_field(FormField( + name="password", + label="Password", + field_type=FormFieldType.PASSWORD, + required=True + )) + + form.add_field(FormField( + name="website", + label="Website URL", + field_type=FormFieldType.URL, + required=False + )) + + form.add_field(FormField( + name="bio", + label="Biography", + field_type=FormFieldType.TEXTAREA, + required=False + )) + + # Layout the form + font = Font(font_size=12, colour=(50, 50, 50)) + success, field_ids = layouter.layout_form(form, font=font) + + print(f" Laid out {len(field_ids)} text fields") + return page + + +def create_example_2_number_fields(): + """Example 2: Number and date/time fields""" + print(" Creating Example 2: Number and date/time fields...") + + page_style = PageStyle( + border_width=2, + border_color=(150, 200, 150), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255) + ) + + page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(page) + + # Create form with number/date fields + form = Form(form_id="number_form", html_id="number_form", callback=form_submit_callback("number_form")) + + form.add_field(FormField( + name="age", + label="Age", + field_type=FormFieldType.NUMBER, + required=True + )) + + form.add_field(FormField( + name="birth_date", + label="Birth Date", + field_type=FormFieldType.DATE, + required=True + )) + + form.add_field(FormField( + name="appointment", + label="Appointment Time", + field_type=FormFieldType.TIME, + required=False + )) + + form.add_field(FormField( + name="rating", + label="Rating (1-10)", + field_type=FormFieldType.RANGE, + required=False + )) + + form.add_field(FormField( + name="color", + label="Favorite Color", + field_type=FormFieldType.COLOR, + required=False + )) + + # Layout the form + font = Font(font_size=12, colour=(50, 50, 50)) + success, field_ids = layouter.layout_form(form, font=font) + + print(f" Laid out {len(field_ids)} number/date fields") + return page + + +def create_example_3_selection_fields(): + """Example 3: Checkbox, radio, and select fields""" + print(" Creating Example 3: Selection fields...") + + page_style = PageStyle( + border_width=2, + border_color=(200, 150, 150), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255) + ) + + page = Page(size=(500, 600), style=page_style) + layouter = DocumentLayouter(page) + + # Create form with selection fields + form = Form(form_id="selection_form", html_id="selection_form", callback=form_submit_callback("selection_form")) + + form.add_field(FormField( + name="newsletter", + label="Subscribe to Newsletter", + field_type=FormFieldType.CHECKBOX, + required=False + )) + + form.add_field(FormField( + name="terms", + label="Accept Terms and Conditions", + field_type=FormFieldType.CHECKBOX, + required=True + )) + + form.add_field(FormField( + name="gender", + label="Gender", + field_type=FormFieldType.RADIO, + required=False + )) + + form.add_field(FormField( + name="country", + label="Country", + field_type=FormFieldType.SELECT, + required=True + )) + + form.add_field(FormField( + name="hidden_token", + label="", # Hidden fields don't display labels + field_type=FormFieldType.HIDDEN, + required=False + )) + + # Layout the form + font = Font(font_size=12, colour=(50, 50, 50)) + success, field_ids = layouter.layout_form(form, font=font) + + print(f" Laid out {len(field_ids)} selection fields") + return page + + +def create_example_4_complete_form(): + """Example 4: Complete registration form with mixed field types""" + print(" Creating Example 4: Complete registration form...") + + page_style = PageStyle( + border_width=2, + border_color=(150, 200, 200), + padding=(20, 30, 20, 30), + background_color=(255, 255, 255) + ) + + page = Page(size=(500, 700), style=page_style) + layouter = DocumentLayouter(page) + + # Create comprehensive registration form + form = Form(form_id="registration_form", html_id="registration_form", callback=form_submit_callback("registration")) + + # Personal information + form.add_field(FormField( + name="full_name", + label="Full Name", + field_type=FormFieldType.TEXT, + required=True + )) + + form.add_field(FormField( + name="email", + label="Email", + field_type=FormFieldType.EMAIL, + required=True + )) + + form.add_field(FormField( + name="password", + label="Password", + field_type=FormFieldType.PASSWORD, + required=True + )) + + form.add_field(FormField( + name="age", + label="Age", + field_type=FormFieldType.NUMBER, + required=True + )) + + # Preferences + form.add_field(FormField( + name="notifications", + label="Enable Notifications", + field_type=FormFieldType.CHECKBOX, + required=False + )) + + # Layout the form + font = Font(font_size=12, colour=(50, 50, 50)) + success, field_ids = layouter.layout_form(form, font=font, field_spacing=15) + + print(f" Laid out complete form with {len(field_ids)} fields") + return page + + +def combine_pages_into_grid(pages, title): + """Combine multiple pages into a 2x2 grid.""" + print("\n Combining pages into grid...") + + # Render all pages + images = [page.render() for page in pages] + + # Grid layout + padding = 20 + title_height = 40 + cols = 2 + rows = 2 + + # Calculate dimensions + img_width = images[0].size[0] + img_height = images[0].size[1] + + total_width = cols * img_width + (cols + 1) * padding + total_height = rows * img_height + (rows + 1) * padding + title_height + + # Create combined image + combined = Image.new('RGB', (total_width, total_height), (240, 240, 240)) + draw = ImageDraw.Draw(combined) + + # Draw title + from PIL import ImageFont + try: + title_font = ImageFont.truetype( + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18 + ) + except: + title_font = ImageFont.load_default() + + bbox = draw.textbbox((0, 0), title, font=title_font) + text_width = bbox[2] - bbox[0] + title_x = (total_width - text_width) // 2 + draw.text((title_x, 10), title, fill=(50, 50, 50), font=title_font) + + # Place pages in grid + y_offset = title_height + padding + for row in range(rows): + x_offset = padding + for col in range(cols): + idx = row * cols + col + if idx < len(images): + combined.paste(images[idx], (x_offset, y_offset)) + x_offset += img_width + padding + y_offset += img_height + padding + + return combined + + +def main(): + """Demonstrate comprehensive form field types.""" + global form_submissions + form_submissions = [] + + print("Comprehensive Forms Example") + print("=" * 50) + + # Create examples for different form types + pages = [ + create_example_1_text_fields(), + create_example_2_number_fields(), + create_example_3_selection_fields(), + create_example_4_complete_form() + ] + + # Combine into demonstration image + combined_image = combine_pages_into_grid( + pages, + "Form Field Types: Text | Numbers | Selection | Complete" + ) + + # Save output + output_dir = Path("docs/images") + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / "example_10_forms.png" + combined_image.save(output_path) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined_image.size[0]}x{combined_image.size[1]} pixels") + print(f" Created {len(pages)} form examples") + print(f" Total form callbacks registered: {len(form_submissions)}") + + return combined_image, form_submissions + + +if __name__ == "__main__": + main() diff --git a/examples/11_font_family_switching_demo.py b/examples/11_font_family_switching_demo.py new file mode 100644 index 0000000..75e1e1e --- /dev/null +++ b/examples/11_font_family_switching_demo.py @@ -0,0 +1,240 @@ +""" +Demonstration of dynamic font family switching in the ereader. + +This example shows how to: +1. Initialize an ereader with content +2. Dynamically switch between different font families (Sans, Serif, Monospace) +3. Maintain reading position across font changes +4. Use the font family API + +The ereader manager provides a high-level API for changing fonts on-the-fly +without losing your place in the document. +""" + +from pyWebLayout.abstract import Paragraph, Heading, Word +from pyWebLayout.abstract.block import HeadingLevel +from pyWebLayout.style import Font +from pyWebLayout.style.fonts import BundledFont, FontWeight +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.layout.ereader_manager import create_ereader_manager +from PIL import Image + + +def create_sample_content(): + """Create sample document content with various text styles""" + blocks = [] + + # Create a default font for the content + default_font = Font.from_family(BundledFont.SANS, font_size=16) + heading_font = Font.from_family(BundledFont.SANS, font_size=24, weight=FontWeight.BOLD) + + # Title + title = Heading(level=HeadingLevel.H1, style=heading_font) + for word in "Font Family Switching Demo".split(): + title.add_word(Word(word, heading_font)) + blocks.append(title) + + # Introduction paragraph + intro_font = Font.from_family(BundledFont.SANS, font_size=16) + intro = Paragraph(intro_font) + intro_text = ( + "This demonstration shows how the ereader can dynamically switch between " + "different font families while maintaining your reading position. " + "The three bundled font families (Sans, Serif, and Monospace) can be " + "changed on-the-fly without recreating the document." + ) + for word in intro_text.split(): + intro.add_word(Word(word, intro_font)) + blocks.append(intro) + + # Section 1 + section1_heading = Heading(level=HeadingLevel.H2, style=heading_font) + for word in "Sans-Serif Font".split(): + section1_heading.add_word(Word(word, heading_font)) + blocks.append(section1_heading) + + para1 = Paragraph(default_font) + text1 = ( + "Sans-serif fonts like DejaVu Sans are clean and modern, making them " + "ideal for screen reading. They lack the decorative strokes (serifs) " + "found in traditional typefaces, which can improve legibility on digital displays. " + "Many ereader applications default to sans-serif fonts for this reason." + ) + for word in text1.split(): + para1.add_word(Word(word, default_font)) + blocks.append(para1) + + # Section 2 + section2_heading = Heading(level=HeadingLevel.H2, style=heading_font) + for word in "Serif Font".split(): + section2_heading.add_word(Word(word, heading_font)) + blocks.append(section2_heading) + + para2 = Paragraph(default_font) + text2 = ( + "Serif fonts like DejaVu Serif have small decorative strokes at the ends " + "of letter strokes. These fonts are traditionally used in print media and " + "can give a more formal, classic appearance. Many readers prefer serif fonts " + "for long-form reading as they find them easier on the eyes." + ) + for word in text2.split(): + para2.add_word(Word(word, default_font)) + blocks.append(para2) + + # Section 3 + section3_heading = Heading(level=HeadingLevel.H2, style=heading_font) + for word in "Monospace Font".split(): + section3_heading.add_word(Word(word, heading_font)) + blocks.append(section3_heading) + + para3 = Paragraph(default_font) + text3 = ( + "Monospace fonts like DejaVu Sans Mono have equal spacing between all characters. " + "They are commonly used for displaying code, technical documentation, and typewriter-style " + "text. While less common for general reading, some users prefer the uniform character " + "spacing for certain types of content." + ) + for word in text3.split(): + para3.add_word(Word(word, default_font)) + blocks.append(para3) + + # Final paragraph + conclusion = Paragraph(default_font) + conclusion_text = ( + "The ability to switch fonts dynamically is a key feature of modern ereaders. " + "It allows readers to customize their reading experience based on personal preference, " + "lighting conditions, and content type. Try switching between the three font families " + "to see which one you prefer for different types of reading." + ) + for word in conclusion_text.split(): + conclusion.add_word(Word(word, default_font)) + blocks.append(conclusion) + + return blocks + + +def render_pages_with_different_fonts(manager, output_prefix="demo_11"): + """Render the same page with different font families""" + + print("\nRendering pages with different font families...") + print("=" * 70) + + font_families = [ + (None, "Original (Sans)"), + (BundledFont.SERIF, "Serif"), + (BundledFont.MONOSPACE, "Monospace"), + (BundledFont.SANS, "Sans (explicit)") + ] + + images = [] + + for font_family, name in font_families: + print(f"\nRendering with {name} font...") + + # Switch font family + manager.set_font_family(font_family) + + # Get current page + page = manager.get_current_page() + + # Render to image + image = page.render() + filename = f"{output_prefix}_{name.lower().replace(' ', '_').replace('(', '').replace(')', '')}.png" + image.save(filename) + print(f" Saved: {filename}") + + images.append((name, image)) + + return images + + +def demonstrate_font_switching(): + """Main demonstration function""" + print("\n") + print("=" * 70) + print("Font Family Switching Demonstration") + print("=" * 70) + print() + + # Create sample content + print("Creating sample document...") + blocks = create_sample_content() + print(f" Created {len(blocks)} blocks") + + # Initialize ereader manager + print("\nInitializing ereader manager...") + page_size = (600, 800) + manager = create_ereader_manager( + blocks, + page_size, + document_id="font_switching_demo" + ) + print(f" Page size: {page_size[0]}x{page_size[1]}") + print(f" Initial font family: {manager.get_font_family()}") + + # Render pages with different fonts + images = render_pages_with_different_fonts(manager) + + # Show position info + print("\nPosition information after font switches:") + print(" " + "-" * 66) + pos_info = manager.get_position_info() + print(f" Current position: Block {pos_info['position']['block_index']}, " + f"Word {pos_info['position']['word_index']}") + print(f" Font family: {pos_info['font_family'] or 'Original'}") + print(f" Font scale: {pos_info['font_scale']}") + print(f" Reading progress: {pos_info['progress']:.1%}") + + # Test navigation with font switching + print("\nTesting navigation with font switching...") + print(" " + "-" * 66) + + # Reset to beginning + manager.jump_to_position(manager.current_position.__class__()) + + # Advance a few pages with serif font + manager.set_font_family(BundledFont.SERIF) + print(f" Switched to SERIF font") + + for i in range(3): + next_page = manager.next_page() + if next_page: + print(f" Page {i+2}: Advanced successfully") + + # Switch to monospace + manager.set_font_family(BundledFont.MONOSPACE) + print(f" Switched to MONOSPACE font") + current_page = manager.get_current_page() + print(f" Re-rendered current page with new font") + + # Go back a page + prev_page = manager.previous_page() + if prev_page: + print(f" Navigated back successfully") + + # Cache statistics + print("\nCache statistics:") + print(" " + "-" * 66) + stats = manager.get_cache_stats() + for key, value in stats.items(): + print(f" {key}: {value}") + + print() + print("=" * 70) + print("Demo complete!") + print() + print("Key features demonstrated:") + print(" ✓ Dynamic font family switching (Sans, Serif, Monospace)") + print(" ✓ Position preservation across font changes") + print(" ✓ Automatic cache invalidation on font change") + print(" ✓ Navigation with different fonts") + print(" ✓ Font family info in position tracking") + print() + print("The rendered pages show the same content in different font families.") + print("Notice how the layout adapts while maintaining readability.") + print("=" * 70) + print() + + +if __name__ == "__main__": + demonstrate_font_switching() diff --git a/examples/11_table_text_wrapping_demo.py b/examples/11_table_text_wrapping_demo.py new file mode 100644 index 0000000..0998f20 --- /dev/null +++ b/examples/11_table_text_wrapping_demo.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +""" +Table Text Wrapping Example + +This example demonstrates the line wrapping functionality in table cells: +- Tables with long text that wraps across multiple lines +- Automatic word wrapping within cell boundaries +- Hyphenation support for long words +- Multiple paragraphs per cell +- Comparison of narrow vs. wide columns + +Shows how the Line-based text layout system handles text overflow in tables. +""" + +from pyWebLayout.io.readers.html_extraction import parse_html_string +from pyWebLayout.layout.document_layouter import DocumentLayouter +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.concrete.table import TableStyle +from pyWebLayout.concrete.page import Page +import sys +from pathlib import Path +from PIL import Image + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +def create_narrow_columns_example(): + """Create a table with narrow columns to show aggressive wrapping.""" + print(" - Narrow columns with text wrapping") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureDescriptionBenefits
Automatic Line WrappingText automatically wraps to fit within the available cell width, creating multiple lines as needed.Improves readability and prevents horizontal overflow in tables.
Hyphenation SupportLong words are intelligently hyphenated using pyphen library or brute-force splitting when necessary.Handles extraordinarily long words that wouldn't fit on a single line.
Multi-paragraph CellsEach cell can contain multiple paragraphs or headings, all properly wrapped.Allows rich content within table cells.
+ """ + + return html, "Text Wrapping in Narrow Columns" + + +def create_mixed_content_example(): + """Create a table with both short and long content.""" + print(" - Mixed content lengths") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Product Comparison
ProductShort DescriptionDetailed Features
Widget ProPremiumAdvanced functionality with enterprise-grade reliability, comprehensive warranty coverage, and dedicated customer support available around the clock.
Widget LiteBasicEssential features for everyday use with straightforward operation and minimal learning curve.
Widget MaxUltimateEverything from Widget Pro plus additional customization options, API integration capabilities, and advanced analytics dashboard.
+ """ + + return html, "Mixed Short and Long Content" + + +def create_technical_documentation_example(): + """Create a table like technical documentation.""" + print(" - Technical documentation style") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
API MethodParametersDescriptionReturn Value
render_table()table, origin, width, draw, styleRenders a table with automatic text wrapping in cells. Uses the Line class for intelligent word placement and hyphenation.Rendered table with calculated height and width properties.
add_word()word, pretextAttempts to add a word to the current line. If it doesn't fit, tries hyphenation strategies including pyphen and brute-force splitting.Tuple of (success, overflow_text) indicating whether word was added and any remaining text.
calculate_spacing()text_objects, width, min_spacing, max_spacingDetermines optimal spacing between words to achieve proper justification within the specified constraints.Calculated spacing value and position offset for alignment.
+ """ + + return html, "Technical Documentation Table" + + +def create_news_article_example(): + """Create a table with article-style content.""" + print(" - News article layout") + + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + +
DateHeadlineSummary
2024-01-15New Text Wrapping FeaturePyWebLayout now supports automatic line wrapping in table cells, bringing sophisticated text layout capabilities to table rendering. The implementation leverages the existing Line class infrastructure.
2024-01-10Hyphenation ImprovementsEnhanced hyphenation algorithms now include both dictionary-based pyphen hyphenation and intelligent brute-force splitting for edge cases.
2024-01-05Performance OptimizationTable rendering performance improved through better caching and reduced font object creation overhead.
+ """ + + return html, "News Article Layout" + + +def render_table_example(html, title, style_variant=0): + """Render a single table example.""" + from pyWebLayout.style import Font + from pyWebLayout.abstract.block import Table + + # Parse HTML + base_font = Font(font_size=12) + blocks = parse_html_string(html, base_font=base_font) + + # Find the table block + table = None + for block in blocks: + if isinstance(block, Table): + table = block + break + + if not table: + print(f" Warning: No table found in {title}") + return None + + # Create page style + page_style = PageStyle( + padding=(20, 20, 20, 20), + background_color=(255, 255, 255) + ) + + # Create page + page_size = (900, 600) + page = Page(size=page_size, style=page_style) + + # Create table style variants + table_styles = [ + # Default style + TableStyle( + border_width=1, + border_color=(0, 0, 0), + cell_padding=(8, 8, 8, 8), + header_bg_color=(240, 240, 240), + cell_bg_color=(255, 255, 255) + ), + # Blue header style + TableStyle( + border_width=2, + border_color=(70, 130, 180), + cell_padding=(10, 10, 10, 10), + header_bg_color=(176, 196, 222), + cell_bg_color=(245, 250, 255) + ), + # Minimal style + TableStyle( + border_width=1, + border_color=(200, 200, 200), + cell_padding=(6, 6, 6, 6), + header_bg_color=(250, 250, 250), + cell_bg_color=(255, 255, 255) + ), + ] + + table_style = table_styles[style_variant % len(table_styles)] + + # Create layouter and render table + layouter = DocumentLayouter(page) + layouter.layout_table(table, style=table_style) + + # Get the rendered canvas + _ = page.draw # Ensure canvas exists + img = page._canvas + + return img + + +def combine_examples(examples): + """Combine multiple example images into one.""" + images = [] + titles = [] + + for html, title in examples: + img = render_table_example(html, title) + if img: + images.append(img) + titles.append(title) + + if not images: + return None + + # Calculate combined image size + max_width = max(img.width for img in images) + total_height = sum(img.height for img in images) + 40 * len(images) # Extra space between images + + # Create combined image + combined = Image.new('RGB', (max_width, total_height), color=(255, 255, 255)) + + # Paste images + y_offset = 20 + for img in images: + combined.paste(img, (0, y_offset)) + y_offset += img.height + 40 + + return combined + + +def main(): + """Run the table text wrapping example.""" + print("\nTable Text Wrapping Example") + print("=" * 50) + + # Create examples + print("\n Creating table examples...") + examples = [ + create_narrow_columns_example(), + create_mixed_content_example(), + create_technical_documentation_example(), + create_news_article_example(), + ] + + print("\n Rendering table examples...") + combined_image = combine_examples(examples) + + if combined_image: + # Save the output + output_dir = Path(__file__).parent.parent / 'docs' / 'images' + output_dir.mkdir(parents=True, exist_ok=True) + output_path = output_dir / 'example_11_table_text_wrapping.png' + + combined_image.save(str(output_path)) + + print("\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {combined_image.width}x{combined_image.height} pixels") + print(f" Created {len(examples)} table examples with text wrapping") + else: + print("\n✗ Failed to generate examples") + + +if __name__ == '__main__': + main() diff --git a/examples/11b_simple_table_wrapping.py b/examples/11b_simple_table_wrapping.py new file mode 100644 index 0000000..a3af467 --- /dev/null +++ b/examples/11b_simple_table_wrapping.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +""" +Simple Table Text Wrapping Example + +A minimal example showing text wrapping in table cells. +Perfect for quick testing and demonstration. +""" + +import sys +from pathlib import Path + +# Add pyWebLayout to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from pyWebLayout.io.readers.html_extraction import parse_html_string +from pyWebLayout.layout.document_layouter import DocumentLayouter +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.style import Font +from pyWebLayout.concrete.table import TableStyle +from pyWebLayout.concrete.page import Page +from pyWebLayout.abstract.block import Table + + +def main(): + """Create a simple table with text wrapping.""" + print("\nSimple Table Text Wrapping Example") + print("=" * 50) + + # HTML with a table containing long text + html = """ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Text Wrapping Demonstration
Column 1Column 2Column 3
This is a cell with quite a lot of text that will need to wrap across multiple lines.Short textAnother cell with enough content to demonstrate the automatic line wrapping functionality.
Cell AThis middle cell contains a paragraph with several words that should wrap nicely within the available space.Cell C
Words like supercalifragilisticexpialidocious might need hyphenation.Normal textThe wrapping algorithm handles both regular word wrapping and hyphenation seamlessly.
+ """ + + print("\n Parsing HTML and creating table...") + + # Parse HTML + base_font = Font(font_size=12) + blocks = parse_html_string(html, base_font=base_font) + + # Find table + table = None + for block in blocks: + if isinstance(block, Table): + table = block + break + + if not table: + print(" ✗ No table found!") + return + + print(" ✓ Table parsed successfully") + + # Create page + page_style = PageStyle( + padding=(30, 30, 30, 30), + background_color=(255, 255, 255) + ) + page = Page(size=(800, 600), style=page_style) + + # Create table style + table_style = TableStyle( + border_width=2, + border_color=(70, 130, 180), + cell_padding=(10, 10, 10, 10), + header_bg_color=(176, 196, 222), + cell_bg_color=(245, 250, 255) + ) + + print(" Rendering table with text wrapping...") + + # Layout and render + layouter = DocumentLayouter(page) + layouter.layout_table(table, style=table_style) + + # Get rendered image + _ = page.draw + img = page._canvas + + # Save output + output_path = Path(__file__).parent.parent / 'docs' / 'images' / 'example_11b_simple_wrapping.png' + output_path.parent.mkdir(parents=True, exist_ok=True) + img.save(str(output_path)) + + print(f"\n✓ Example completed!") + print(f" Output saved to: {output_path}") + print(f" Image size: {img.width}x{img.height} pixels") + print(f"\n The table demonstrates:") + print(f" • Automatic line wrapping in cells") + print(f" • Proper word spacing and alignment") + print(f" • Hyphenation for very long words") + print(f" • Multi-line text within cell boundaries") + + +if __name__ == '__main__': + main() diff --git a/examples/12_optimized_table_layout_demo.py b/examples/12_optimized_table_layout_demo.py new file mode 100644 index 0000000..8aa19a9 --- /dev/null +++ b/examples/12_optimized_table_layout_demo.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +""" +Demo: Optimized Table Column Width Layout + +This example demonstrates the intelligent table column width optimization: +- Automatic width distribution based on content +- HTML width overrides (fixed column widths) +- Sampling for performance (large tables) +- Comparison: before (equal distribution) vs after (optimized) + +The optimizer: +1. Samples first ~5 rows from each section +2. Measures minimum and preferred widths for each column +3. Distributes available space proportionally +4. Respects HTML width attributes +""" + +from pyWebLayout.concrete.page import Page +from pyWebLayout.concrete.table import TableRenderer, TableStyle +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.style import Font +from pyWebLayout.abstract.block import Table, TableRow, TableCell, Paragraph +from pyWebLayout.abstract.inline import Word +from PIL import ImageDraw + + +def create_demo_table_1(): + """Create a table with varying content lengths (shows optimization).""" + table = Table() + table.caption = "Example 1: Optimized Width Distribution" + + font = Font(font_size=11) + + # Header + header_row = TableRow() + for text in ["ID", "Name", "Description"]: + cell = TableCell(is_header=True) + para = Paragraph(font) + para.add_word(Word(text, font)) + cell.add_block(para) + header_row.add_cell(cell) + table.add_row(header_row, section="header") + + # Body rows with varying content lengths + data = [ + ("1", "Alice", "Short description"), + ("2", "Bob", "This is a much longer description that demonstrates how the optimizer allocates more space to columns with longer content"), + ("3", "Charlie", "Medium length description here"), + ("4", "Diana", "Another longer description that shows the column width optimization working effectively for content-heavy cells"), + ] + + for row_data in data: + row = TableRow() + for text in row_data: + cell = TableCell() + para = Paragraph(font) + for word in text.split(): + para.add_word(Word(word, font)) + cell.add_block(para) + row.add_cell(cell) + table.add_row(row, section="body") + + return table + + +def create_demo_table_2(): + """Create a table with HTML width overrides.""" + table = Table() + table.caption = "Example 2: Fixed Column Widths (HTML override)" + + font = Font(font_size=11) + + # Header with width attributes + header_row = TableRow() + + # Fixed width column + cell1 = TableCell(is_header=True) + cell1.width = "80px" # HTML width override! + para1 = Paragraph(font) + para1.add_word(Word("ID", font)) + para1.add_word(Word("(Fixed", font)) + para1.add_word(Word("80px)", font)) + cell1.add_block(para1) + header_row.add_cell(cell1) + + # Auto-width columns + for text in ["Name (Auto)", "Description (Auto)"]: + cell = TableCell(is_header=True) + para = Paragraph(font) + for word in text.split(): + para.add_word(Word(word, font)) + cell.add_block(para) + header_row.add_cell(cell) + + table.add_row(header_row, section="header") + + # Body rows + data = [ + ("1", "Alice", "The first two columns adapt to remaining space"), + ("2", "Bob", "ID column stays fixed at 80px width"), + ("3", "Charlie", "Name and Description share the remaining width proportionally"), + ] + + for row_data in data: + row = TableRow() + # First cell also has fixed width + cell = TableCell() + cell.width = "80px" + para = Paragraph(font) + para.add_word(Word(row_data[0], font)) + cell.add_block(para) + row.add_cell(cell) + + # Other cells auto-width + for text in row_data[1:]: + cell = TableCell() + para = Paragraph(font) + for word in text.split(): + para.add_word(Word(word, font)) + cell.add_block(para) + row.add_cell(cell) + table.add_row(row, section="body") + + return table + + +def create_demo_table_3(): + """Create a large table (demonstrates sampling).""" + table = Table() + table.caption = "Example 3: Large Table (uses sampling for performance)" + + font = Font(font_size=10) + + # Header + header_row = TableRow() + for text in ["Index", "Data A", "Data B", "Data C"]: + cell = TableCell(is_header=True) + para = Paragraph(font) + para.add_word(Word(text, font)) + cell.add_block(para) + header_row.add_cell(cell) + table.add_row(header_row, section="header") + + # Many body rows (only first ~5 will be sampled for measurement) + for i in range(50): + row = TableRow() + + # Index + cell = TableCell() + para = Paragraph(font) + para.add_word(Word(str(i + 1), font)) + cell.add_block(para) + row.add_cell(cell) + + # Data columns with varying content + if i % 3 == 0: + data = ["Short", "Medium length", "Longer content here"] + elif i % 3 == 1: + data = ["Medium", "Short", "Also longer content"] + else: + data = ["Longer text", "Short", "Medium"] + + for text in data: + cell = TableCell() + para = Paragraph(font) + for word in text.split(): + para.add_word(Word(word, font)) + cell.add_block(para) + row.add_cell(cell) + + table.add_row(row, section="body") + + return table + + +def main(): + # Create page + page_style = PageStyle( + border_width=1, + padding=(20, 20, 20, 20), + background_color=(255, 255, 255) + ) + page = Page(size=(800, 2200), style=page_style) + + # Get canvas and draw + canvas = page._create_canvas() + page._canvas = canvas + page._draw = ImageDraw.Draw(canvas) + + current_y = 30 + + # Table style + table_style = TableStyle( + border_width=1, + border_color=(100, 100, 100), + cell_padding=(8, 8, 8, 8), + header_bg_color=(220, 230, 240), + cell_bg_color=(255, 255, 255), + alternate_row_color=(248, 248, 248) + ) + + # Render Example 1: Optimized distribution + table1 = create_demo_table_1() + renderer1 = TableRenderer( + table1, + origin=(20, current_y), + available_width=760, + draw=page._draw, + style=table_style, + canvas=canvas + ) + renderer1.render() + current_y += renderer1.height + 40 + + # Render Example 2: Fixed widths + table2 = create_demo_table_2() + renderer2 = TableRenderer( + table2, + origin=(20, current_y), + available_width=760, + draw=page._draw, + style=table_style, + canvas=canvas + ) + renderer2.render() + current_y += renderer2.height + 40 + + # Render Example 3: Large table with sampling + table3 = create_demo_table_3() + renderer3 = TableRenderer( + table3, + origin=(20, current_y), + available_width=760, + draw=page._draw, + style=table_style, + canvas=canvas + ) + renderer3.render() + + # Save + output_path = "docs/images/example_12_optimized_table_layout.png" + canvas.save(output_path) + + print(f"✓ Optimized table layout demo created!") + print(f" Output: {output_path}") + print(f" Image size: {canvas.size}") + print(f"\nExamples demonstrated:") + print(f" 1. Content-aware width distribution") + print(f" 2. HTML width overrides (80px fixed column)") + print(f" 3. Large table with sampling (50 rows, only ~5 measured)") + + +if __name__ == "__main__": + main() diff --git a/examples/13_table_pagination_demo.py b/examples/13_table_pagination_demo.py new file mode 100644 index 0000000..249bf88 --- /dev/null +++ b/examples/13_table_pagination_demo.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +""" +Demo: Table Pagination + +This example demonstrates table pagination when content exceeds page height: +- Large table that spans multiple pages +- Automatic row-level pagination (entire rows move to next page) +- Continuation markers ("continued on next page", "continued from previous page") +- Headers repeated on each page + +The pagination system: +1. Renders rows sequentially until page height limit reached +2. Moves entire row to next page if it doesn't fit +3. Repeats header row on continuation pages +4. Adds visual markers to show table continues +""" + +from pyWebLayout.concrete.page import Page +from pyWebLayout.concrete.table import TableRenderer, TableStyle +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.style import Font +from pyWebLayout.abstract.block import Table, TableRow, TableCell, Paragraph +from pyWebLayout.abstract.inline import Word +from PIL import Image, ImageDraw + + +def create_large_table(): + """Create a table with many rows that will require pagination.""" + table = Table() + table.caption = "Employee Directory (Paginated)" + + font = Font(font_size=11) + + # Header + header_row = TableRow() + for text in ["ID", "Name", "Department", "Email", "Phone"]: + cell = TableCell(is_header=True) + para = Paragraph(font) + para.add_word(Word(text, font)) + cell.add_block(para) + header_row.add_cell(cell) + table.add_row(header_row, section="header") + + # Many body rows (will span multiple pages) + departments = ["Engineering", "Sales", "Marketing", "HR", "Finance", "Operations", "Support"] + + for i in range(60): + row = TableRow() + + # ID + cell = TableCell() + para = Paragraph(font) + para.add_word(Word(f"EMP{i+1001}", font)) + cell.add_block(para) + row.add_cell(cell) + + # Name + cell = TableCell() + para = Paragraph(font) + names = ["Alice Johnson", "Bob Smith", "Charlie Brown", "Diana Lee", + "Eve Wilson", "Frank Miller", "Grace Davis", "Henry Taylor"] + para.add_word(Word(names[i % len(names)], font)) + cell.add_block(para) + row.add_cell(cell) + + # Department + cell = TableCell() + para = Paragraph(font) + para.add_word(Word(departments[i % len(departments)], font)) + cell.add_block(para) + row.add_cell(cell) + + # Email + cell = TableCell() + para = Paragraph(font) + email = f"{names[i % len(names)].lower().replace(' ', '.')}@company.com" + para.add_word(Word(email, font)) + cell.add_block(para) + row.add_cell(cell) + + # Phone + cell = TableCell() + para = Paragraph(font) + para.add_word(Word(f"+1-555-{(i*17)%1000:04d}", font)) + cell.add_block(para) + row.add_cell(cell) + + table.add_row(row, section="body") + + return table + + +def render_table_with_pagination(table, page_size, max_pages=3): + """ + Render a table across multiple pages. + + Args: + table: The table to render + page_size: Tuple of (width, height) for each page + max_pages: Maximum number of pages to render + + Returns: + List of PIL Images (one per page) + """ + pages = [] + + page_style = PageStyle( + border_width=1, + padding=(20, 20, 20, 20), + background_color=(255, 255, 255) + ) + + table_style = TableStyle( + border_width=1, + border_color=(100, 100, 100), + cell_padding=(6, 8, 6, 8), + header_bg_color=(220, 230, 240), + cell_bg_color=(255, 255, 255), + alternate_row_color=(248, 248, 248) + ) + + # Get all rows + all_rows = list(table.all_rows()) + header_rows = [row for section, row in all_rows if section == "header"] + body_rows = [row for section, row in all_rows if section == "body"] + + # Calculate header height once + temp_page = Page(size=page_size, style=page_style) + temp_canvas = temp_page._create_canvas() + temp_draw = ImageDraw.Draw(temp_canvas) + + # Create temporary table with just header to measure + header_table = Table() + header_table.caption = table.caption + for header_row in header_rows: + header_table.add_row(header_row, section="header") + + header_renderer = TableRenderer( + header_table, + origin=(20, 20), + available_width=page_size[0] - 40, + draw=temp_draw, + style=table_style, + canvas=temp_canvas + ) + header_height = header_renderer.height + + # Available height for body rows + available_body_height = page_size[1] - 60 - header_height # margins + header + + # Paginate body rows + current_page_rows = [] + current_height = 0 + page_num = 0 + + for i, body_row in enumerate(body_rows): + if page_num >= max_pages: + break + + # Estimate row height (simplified - actual would measure each row) + # For this demo, assume ~30px per row + row_height = 35 + + if current_height + row_height > available_body_height and current_page_rows: + # Render current page + page_canvas = render_page( + table, + header_rows, + current_page_rows, + page_size, + page_style, + table_style, + page_num, + is_last=False + ) + pages.append(page_canvas) + + # Start new page + page_num += 1 + current_page_rows = [] + current_height = 0 + + current_page_rows.append(body_row) + current_height += row_height + + # Render final page + if current_page_rows and page_num < max_pages: + page_canvas = render_page( + table, + header_rows, + current_page_rows, + page_size, + page_style, + table_style, + page_num, + is_last=(i == len(body_rows) - 1) + ) + pages.append(page_canvas) + + return pages + + +def render_page(table, header_rows, body_rows, page_size, page_style, table_style, page_num, is_last): + """Render a single page with header and body rows.""" + page = Page(size=page_size, style=page_style) + canvas = page._create_canvas() + page._canvas = canvas + page._draw = ImageDraw.Draw(canvas) + + # Create table for this page + page_table = Table() + if page_num == 0: + page_table.caption = table.caption + else: + page_table.caption = f"{table.caption} (continued)" + + # Add header rows + for header_row in header_rows: + page_table.add_row(header_row, section="header") + + # Add body rows for this page + for body_row in body_rows: + page_table.add_row(body_row, section="body") + + # Render table + renderer = TableRenderer( + page_table, + origin=(20, 20), + available_width=page_size[0] - 40, + draw=page._draw, + style=table_style, + canvas=canvas + ) + renderer.render() + + # Add continuation marker at bottom + if not is_last: + font = Font(font_size=10) + y_pos = page_size[1] - 30 + page._draw.text( + (page_size[0] // 2 - 100, y_pos), + "(continued on next page)", + fill=(100, 100, 100), + font=font.font + ) + + # Add page number + page._draw.text( + (page_size[0] // 2 - 20, page_size[1] - 15), + f"Page {page_num + 1}", + fill=(150, 150, 150), + font=Font(font_size=9).font + ) + + return canvas + + +def main(): + # Create large table + table = create_large_table() + + # Render with pagination (3 pages max for demo) + page_size = (900, 700) + pages = render_table_with_pagination(table, page_size, max_pages=3) + + # Combine pages side-by-side for visualization + total_width = page_size[0] * len(pages) + (len(pages) - 1) * 20 # 20px spacing + combined = Image.new('RGB', (total_width, page_size[1]), (240, 240, 240)) + + x_offset = 0 + for i, page_canvas in enumerate(pages): + combined.paste(page_canvas, (x_offset, 0)) + x_offset += page_size[0] + 20 + + # Save + output_path = "docs/images/example_13_table_pagination.png" + combined.save(output_path) + + print(f"✓ Table pagination demo created!") + print(f" Output: {output_path}") + print(f" Pages rendered: {len(pages)}") + print(f" Image size: {combined.size}") + print(f"\nDemonstrates:") + print(f" - Large table (60 rows) paginated across {len(pages)} pages") + print(f" - Header repeated on each page") + print(f" - Continuation markers") + print(f" - Page numbers") + + +if __name__ == "__main__": + main() diff --git a/examples/14_interactive_table.py b/examples/14_interactive_table.py new file mode 100644 index 0000000..8832eaa --- /dev/null +++ b/examples/14_interactive_table.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +""" +Demo: Working Interactive Table with Buttons + +This example shows a fully working interactive table where buttons are +actually rendered inside table cells and can handle click events. + +This uses a hybrid approach: +1. Tables are rendered normally for structure +2. Buttons are rendered on top at calculated positions +3. Click detection maps coordinates to button callbacks +""" + +from pyWebLayout.concrete.page import Page +from pyWebLayout.concrete.table import TableRenderer, TableStyle +from pyWebLayout.concrete.functional import ButtonText +from pyWebLayout.abstract.functional import Button +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.style import Font +from pyWebLayout.abstract.block import Table, TableRow, TableCell, Paragraph +from pyWebLayout.abstract.inline import Word +from PIL import Image, ImageDraw +import numpy as np + + +def create_interactive_table(): + """Create a table structure (buttons will be overlaid).""" + table = Table() + table.caption = "User Management with Interactive Buttons" + + font = Font(font_size=11) + + # Header + header_row = TableRow() + for i, text in enumerate(["ID", "Name", "Email", "Actions"]): + cell = TableCell(is_header=True) + # Set width for Actions column + if text == "Actions": + cell.width = "220px" # Enough for 3 buttons + para = Paragraph(font) + para.add_word(Word(text, font)) + cell.add_block(para) + header_row.add_cell(cell) + table.add_row(header_row, section="header") + + # Body rows + users = [ + ("U001", "Alice Johnson", "alice@example.com"), + ("U002", "Bob Smith", "bob@example.com"), + ("U003", "Charlie Brown", "charlie@example.com"), + ] + + for user_id, name, email in users: + row = TableRow() + + # ID + cell = TableCell() + para = Paragraph(font) + para.add_word(Word(user_id, font)) + cell.add_block(para) + row.add_cell(cell) + + # Name + cell = TableCell() + para = Paragraph(font) + para.add_word(Word(name, font)) + cell.add_block(para) + row.add_cell(cell) + + # Email + cell = TableCell() + para = Paragraph(font) + para.add_word(Word(email, font)) + cell.add_block(para) + row.add_cell(cell) + + # Actions - leave empty for buttons to be overlaid + # Set width hint to ensure space for 3 buttons + cell = TableCell() + cell.width = "220px" # Enough for 3 buttons (3 × 65px + padding) + para = Paragraph(font) + para.add_word(Word("", font)) # Empty placeholder + cell.add_block(para) + row.add_cell(cell) + + table.add_row(row, section="body") + + return table + + +def render_buttons_in_table(canvas, draw, table_origin, column_widths, row_heights, users): + """ + Render interactive buttons inside the table cells. + + This calculates the exact position of each button based on the table + layout and renders ButtonText objects at those positions. + + Args: + canvas: PIL Image canvas + draw: PIL ImageDraw object + table_origin: (x, y) position of table top-left + column_widths: List of column widths + row_heights: Dict with 'header', 'body', 'footer' keys + users: User data for button labels + + Returns: + List of (button, bounds) for click detection + """ + button_font = Font(font_size=10) + buttons_with_bounds = [] + + # Calculate Actions column position (column 3, index 3) + actions_col_x = table_origin[0] + sum(column_widths[:3]) + 3 * 2 # +borders + actions_col_width = column_widths[3] + + # Start after caption and header row + # Caption takes 20px + 10px spacing = 30px + caption_height = 30 + header_height = row_heights.get("header", 30) + current_y = table_origin[1] + caption_height + header_height + 2 # +caption +header +border + + for i, (user_id, name, email) in enumerate(users): + row_height = row_heights.get("body", 30) # All body rows have same height + + # Position buttons horizontally in the Actions cell + button_x = actions_col_x + 10 # Padding from cell edge + button_y = current_y + (row_height - 30) // 2 # Center vertically + + # Create buttons for this row + # Note: Button callbacks receive click point as first argument + buttons = [ + ("View", lambda point, uid=user_id: print(f"View {uid}")), + ("Edit", lambda point, uid=user_id: print(f"Edit {uid}")), + ("Delete", lambda point, uid=user_id: print(f"Delete {uid}")) + ] + + for label, callback in buttons: + # Create button + abstract_button = Button(label=label, callback=callback) + button_text = ButtonText( + button=abstract_button, + font=button_font, + draw=draw, + padding=(6, 12, 6, 12) + ) + + # Set position + button_text._origin = np.array([button_x, button_y]) + + # Render button + button_text.render() + + # Store bounds for click detection + button_width = 60 # Approximate + button_height = 25 + bounds = (button_x, button_y, button_x + button_width, button_y + button_height) + buttons_with_bounds.append((abstract_button, bounds)) + + # Move to next button position + button_x += 65 + + # Move to next row + current_y += row_height + 1 # +border + + return buttons_with_bounds + + +def handle_click(click_pos, buttons_with_bounds): + """ + Handle a click event by checking if it's inside any button bounds. + + Args: + click_pos: (x, y) tuple of click position + buttons_with_bounds: List of (button, bounds) tuples + + Returns: + True if a button was clicked, False otherwise + """ + click_x, click_y = click_pos + + for button, (x1, y1, x2, y2) in buttons_with_bounds: + if x1 <= click_x <= x2 and y1 <= click_y <= y2: + # Click is inside this button! + button.execute(click_pos) + return True + + return False + + +def main(): + # Create page + page_size = (900, 500) # Increased height to fit instructions + page_style = PageStyle( + border_width=1, + padding=(20, 20, 20, 20), + background_color=(255, 255, 255) + ) + page = Page(size=page_size, style=page_style) + canvas = page._create_canvas() + page._canvas = canvas + page._draw = ImageDraw.Draw(canvas) + + # Table style + table_style = TableStyle( + border_width=1, + border_color=(100, 100, 100), + cell_padding=(8, 10, 8, 10), + header_bg_color=(220, 230, 240), + cell_bg_color=(255, 255, 255), + alternate_row_color=(248, 248, 248) + ) + + # User data + users = [ + ("U001", "Alice Johnson", "alice@example.com"), + ("U002", "Bob Smith", "bob@example.com"), + ("U003", "Charlie Brown", "charlie@example.com"), + ] + + # Create and render table + table = create_interactive_table() + table_origin = (20, 30) + renderer = TableRenderer( + table, + origin=table_origin, + available_width=860, + draw=page._draw, + style=table_style, + canvas=canvas + ) + renderer.render() + + # Get table dimensions for button positioning + column_widths = renderer._column_widths + row_heights = renderer._row_heights + + # Render interactive buttons on top of table + buttons_with_bounds = render_buttons_in_table( + canvas, page._draw, table_origin, + column_widths, row_heights, users + ) + + # Add instructions (position below the table) + # Calculate actual table height based on rows + header_height = row_heights.get("header", 30) + body_height = row_heights.get("body", 30) * len(users) + actual_table_height = header_height + body_height + (len(users) + 2) * 2 # +borders + + inst_font = Font(font_size=12) + y_offset = table_origin[1] + actual_table_height + 50 + page._draw.text( + (20, y_offset), + "Interactive Table Demo:", + fill=(50, 50, 50), + font=inst_font.font + ) + + note_font = Font(font_size=10) + page._draw.text( + (20, y_offset + 25), + "• Buttons are rendered at calculated positions within table cells", + fill=(80, 80, 80), + font=note_font.font + ) + page._draw.text( + (20, y_offset + 45), + "• Click detection maps coordinates to button callbacks", + fill=(80, 80, 80), + font=note_font.font + ) + page._draw.text( + (20, y_offset + 65), + "• Try simulated clicks below:", + fill=(80, 80, 80), + font=note_font.font + ) + + # Save + output_path = "docs/images/example_14_interactive_table.png" + canvas.save(output_path) + + print(f"✓ Working interactive table demo created!") + print(f" Output: {output_path}") + print(f" Image size: {canvas.size}") + print(f"\nDemonstrating button click detection:") + + # Simulate some clicks to demonstrate functionality + actions_col_x = table_origin[0] + sum(column_widths[:3]) + 6 + caption_height = 30 + header_height = row_heights.get("header", 30) + body_row_height = row_heights.get("body", 30) + + # Calculate first body row position (after caption + header + border) + first_row_y = table_origin[1] + caption_height + header_height + 2 + + test_clicks = [ + (100, 100, "Click outside table"), + (actions_col_x + 10, first_row_y + 15, "View button - Alice"), + (actions_col_x + 75, first_row_y + 15, "Edit button - Alice"), + (actions_col_x + 140, first_row_y + 15, "Delete button - Alice"), + (actions_col_x + 10, first_row_y + body_row_height + 17, "View button - Bob"), + ] + + for x, y, desc in test_clicks: + print(f"\n Click at ({x}, {y}) - {desc}:") + clicked = handle_click((x, y), buttons_with_bounds) + if not clicked: + print(f" No button at this position") + + +if __name__ == "__main__": + main() diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..3927c91 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,310 @@ +# PyWebLayout Examples + +This directory contains example scripts demonstrating the pyWebLayout library. + +## Getting Started Examples + +These examples demonstrate the core rendering capabilities of pyWebLayout: + +### 01. Simple Page Rendering +**`01_simple_page_rendering.py`** - Introduction to the Page system + +```bash +python 01_simple_page_rendering.py +``` + +Demonstrates: +- Creating pages with different styles +- Setting borders, padding, and backgrounds +- Understanding page layout structure +- Basic rendering to images + +![Page Rendering Example](../docs/images/example_01_page_rendering.png) + +### 02. Text and Layout +**`02_text_and_layout.py`** - HTML parsing and text rendering + +```bash +python 02_text_and_layout.py +``` + +Demonstrates: +- Parsing HTML content +- Text alignment options +- Font sizes and styles +- Document structure + +![Text and Layout Example](../docs/images/example_02_text_and_layout.png) + +### 03. Page Layouts +**`03_page_layouts.py`** - Different page configurations + +```bash +python 03_page_layouts.py +``` + +Demonstrates: +- Various page sizes (portrait, landscape, square) +- Different aspect ratios +- Border and padding variations +- Color schemes + +![Page Layouts Example](../docs/images/example_03_page_layouts.png) + +### 04. Table Rendering +**`04_table_rendering.py`** - HTML table rendering with styling + +```bash +python 04_table_rendering.py +``` + +Demonstrates: +- Rendering HTML tables +- Table headers and body rows +- Cell borders and padding +- Caption support +- Custom table styling + +![Table Rendering Example](../docs/images/example_04_table_rendering.png) + +### 05. Tables with Images +**`05_html_table_with_images.py`** - Tables containing images and mixed content + +```bash +python 05_html_table_with_images.py +``` + +Demonstrates: +- Creating tables programmatically +- Adding images to table cells +- Book catalog and product showcase tables +- Mixed content (images and text) in cells +- Using cover images from test data +- HTML table parsing with `` tags + +![Table with Images Example](../docs/images/example_05_html_table_with_images.png) + +### 06. Functional Elements (Interactive) +**`06_functional_elements_demo.py`** - Interactive buttons and forms with callbacks + +```bash +python 06_functional_elements_demo.py +``` + +Demonstrates: +- Creating interactive buttons +- Building forms with multiple field types +- Post-layout callback binding +- CallbackRegistry system for managing interactables +- Accessing application state from callbacks +- Batch callback operations +- Simulating user interactions + +![Functional Elements Example](../docs/images/example_06_functional_elements.png) + +### 07. Button Pressed States (Interactive) +**`07_pressed_state_demo.py`** - Visual feedback for button interactions + +```bash +python 07_pressed_state_demo.py +``` + +Demonstrates: +- Button pressed/released state management +- Visual feedback timing (150ms press duration) +- Automatic interaction handling with `InteractionHandler` +- Manual state management for custom event loops +- Dirty flag system for optimized re-rendering +- State tracking with `InteractionStateManager` + +![Button Pressed State Animation](../docs/images/example_07_button_animation.gif) + +*Animated GIF showing button press sequence: initial → pressed → released* + +--- + +## 🆕 New Examples (2024-11) + +These examples address critical coverage gaps and demonstrate advanced features: + +### 08. Bundled Fonts Showcase +**`08_bundled_fonts_demo.py`** - Demonstration of all bundled fonts + +```bash +python 08_bundled_fonts_demo.py +``` + +Demonstrates: +- DejaVu Sans (Sans-serif) +- DejaVu Serif (Serif) +- DejaVu Sans Mono (Monospace) +- All font variants: Regular, Bold, Italic, Bold Italic + +![Bundled Fonts Example](../docs/images/demo_08_bundled_fonts.png) + +### 08. Pagination with PageBreak ✅ +**`08_pagination_demo.py`** - Multi-page documents with explicit and automatic pagination + +```bash +python 08_pagination_demo.py +``` + +**Test Coverage:** [tests/examples/test_08_pagination_demo.py](../tests/examples/test_08_pagination_demo.py) - 11 tests + +Demonstrates: +- Using `PageBreak` to force content onto new pages +- Multi-page document layout with explicit breaks +- Automatic pagination when content overflows +- Page numbering functionality +- Document flow control +- Combining pages into vertical strips + +**Coverage Impact:** Fills critical gap - PageBreak layouter had NO examples before this! + +![Pagination Example](../docs/images/example_08_pagination_explicit.png) + +### 09. Link Navigation (NEW) ✅ +**`09_link_navigation_demo.py`** - All link types and interactive navigation + +```bash +python 09_link_navigation_demo.py +``` + +**Test Coverage:** [tests/examples/test_09_link_navigation_demo.py](../tests/examples/test_09_link_navigation_demo.py) - 10 tests + +Demonstrates: +- **Internal links** - Document navigation (`#section1`, `#section2`) +- **External links** - Web URLs (`https://example.com`) +- **API links** - API endpoints (`/api/settings`, `/api/save`) +- **Function links** - Direct function calls (`calculate()`, `process()`) +- Link styling (underlined, color-coded by type) +- Link callbacks and interactivity +- Mixed text and link paragraphs + +**Coverage Impact:** Comprehensive - All 4 LinkType variations demonstrated! + +![Link Navigation Example](../docs/images/example_09_link_navigation.png) + +### 10. Comprehensive Forms (NEW) ✅ +**`10_forms_demo.py`** - All 14 form field types with validation + +```bash +python 10_forms_demo.py +``` + +**Test Coverage:** [tests/examples/test_10_forms_demo.py](../tests/examples/test_10_forms_demo.py) - 9 tests + +Demonstrates all 14 FormFieldType variations: + +**Text-Based Fields:** +- TEXT, EMAIL, PASSWORD, URL, TEXTAREA + +**Number/Date/Time Fields:** +- NUMBER, DATE, TIME, RANGE, COLOR + +**Selection Fields:** +- CHECKBOX, RADIO, SELECT, HIDDEN + +**Coverage Impact:** Complete - All 14 field types across 4 practical form examples! + +![Comprehensive Forms Example](../docs/images/example_10_forms.png) + +### 11. Table Text Wrapping (NEW) ✅ +**`11_table_text_wrapping_demo.py`** - Automatic line wrapping in table cells + +```bash +python 11_table_text_wrapping_demo.py +``` + +**Simple Version:** `11b_simple_table_wrapping.py` - Quick demonstration + +Demonstrates: +- **Automatic line wrapping** - Text wraps across multiple lines within cells +- **Word hyphenation** - Long words are intelligently hyphenated +- **Narrow columns** - Aggressive wrapping for tight spaces +- **Mixed content** - Both short and long text in the same table +- **Technical documentation** - API reference style tables +- **News layouts** - Article-style table content + +**Implementation:** Uses the Line class from `pyWebLayout.concrete.text` with: +- Word-by-word fitting with intelligent spacing +- Pyphen-based dictionary hyphenation +- Brute-force splitting for edge cases +- Proper baseline alignment and metrics + +![Table Text Wrapping Example](../docs/images/example_11_table_text_wrapping.png) + +--- + +## Running the Examples + +All examples can be run directly from the examples directory: + +```bash +cd examples + +# Getting Started (01-07) +python 01_simple_page_rendering.py # Page layouts +python 02_text_and_layout.py # Text alignment with justified text +python 03_page_layouts.py # Various page sizes +python 04_table_rendering.py # Table styles +python 05_html_table_with_images.py # HTML tables with images +python 06_functional_elements_demo.py # Interactive buttons and forms +python 07_pressed_state_demo.py # Button pressed states (generates GIF) + +# Advanced Features (08-11) +python 08_bundled_fonts_demo.py # Bundled font showcase +python 08_pagination_demo.py # Multi-page documents +python 09_link_navigation_demo.py # All link types +python 10_forms_demo.py # All form field types +python 11_table_text_wrapping_demo.py # Table text wrapping +python 11b_simple_table_wrapping.py # Simple wrapping demo +``` + +Output images are saved to the `docs/images/` directory. + +## Recent Improvements + +### ✅ Justified Text Fix (2024-11-10) +Lines using justified alignment now properly fill the entire width by: +- Calculating base spacing and remainder pixels +- Distributing remainder across word gaps to eliminate short lines +- Removing max_spacing constraint for true justification + +**Affected examples:** 02, 11, 11b - All text now perfectly justified! + +### ✅ Animated Button States (2024-11-10) +Example 07 now automatically generates an animated GIF showing button interactions: +- Initial state (1000ms) +- Pressed state (200ms) +- Released state (500ms) +- Loops continuously + +**Output:** `docs/images/example_07_button_animation.gif` + +### Running Tests + +All new examples (08, 09, 10) include comprehensive test coverage: + +```bash +# Run all example tests +python -m pytest tests/examples/ -v + +# Run specific test file +python -m pytest tests/examples/test_08_pagination_demo.py -v +python -m pytest tests/examples/test_09_link_navigation_demo.py -v +python -m pytest tests/examples/test_10_forms_demo.py -v +``` + +**Total Test Coverage:** 30 tests (11 + 10 + 9), all passing ✅ + +## Additional Documentation + +- `README_HTML_MULTIPAGE.md` - HTML multi-page rendering guide +- `../ARCHITECTURE.md` - Detailed explanation of the Abstract/Concrete architecture +- `../docs/images/README.md` - Visual documentation index with all examples +- `../pyWebLayout/layout/README_EREADER_API.md` - EbookReader API reference + +## Debug/Development Scripts + +Low-level debug and rendering scripts have been moved to the `scripts/` directory. diff --git a/examples/generate_readme_font_demo.py b/examples/generate_readme_font_demo.py new file mode 100644 index 0000000..d0aa9ba --- /dev/null +++ b/examples/generate_readme_font_demo.py @@ -0,0 +1,252 @@ +""" +Generate a demo image for README.md showing font family switching feature. + +Creates a side-by-side comparison of the same content rendered in +Sans, Serif, and Monospace fonts. +""" + +from pyWebLayout.abstract import Paragraph, Heading, Word +from pyWebLayout.abstract.block import HeadingLevel +from pyWebLayout.style import Font +from pyWebLayout.style.fonts import BundledFont, FontWeight +from pyWebLayout.style.page_style import PageStyle +from pyWebLayout.layout.ereader_manager import create_ereader_manager +from PIL import Image, ImageDraw, ImageFont + + +def create_demo_content(): + """Create concise demo content that fits nicely on a small page""" + blocks = [] + + # Title + title_font = Font.from_family(BundledFont.SANS, font_size=28, weight=FontWeight.BOLD) + title = Heading(level=HeadingLevel.H1, style=title_font) + for word in "The Adventure Begins".split(): + title.add_word(Word(word, title_font)) + blocks.append(title) + + # Paragraph + body_font = Font.from_family(BundledFont.SANS, font_size=14) + para = Paragraph(body_font) + text = ( + "In the quiet village of Millbrook, young Emma discovered an ancient map " + "hidden in her grandmother's attic. The parchment revealed a mysterious " + "forest path marked with symbols she had never seen before. With courage " + "in her heart and the map in her pocket, she set out at dawn to uncover " + "the secrets that lay beyond the old oak trees." + ) + for word in text.split(): + para.add_word(Word(word, body_font)) + blocks.append(para) + + return blocks + + +def render_with_font_family(blocks, page_size, font_family, family_name): + """Render a page with a specific font family""" + manager = create_ereader_manager( + blocks, + page_size, + document_id=f"demo_{family_name.lower()}" + ) + + # Set font family (None means original/default) + manager.set_font_family(font_family) + + # Get the first page + page = manager.get_current_page() + return page.render() + + +def create_comparison_image(): + """Create a side-by-side comparison of all three font families""" + + # Page size for each panel + page_width = 400 + page_height = 300 + + # Create demo content + print("Creating demo content...") + blocks = create_demo_content() + + # Render with each font family + print("Rendering with Sans font...") + sans_image = render_with_font_family( + blocks, (page_width, page_height), BundledFont.SANS, "Sans" + ) + + print("Rendering with Serif font...") + serif_image = render_with_font_family( + blocks, (page_width, page_height), BundledFont.SERIF, "Serif" + ) + + print("Rendering with Monospace font...") + mono_image = render_with_font_family( + blocks, (page_width, page_height), BundledFont.MONOSPACE, "Monospace" + ) + + # Create a composite image with all three side by side + spacing = 20 + label_height = 30 + total_width = page_width * 3 + spacing * 4 + total_height = page_height + label_height + spacing * 2 + + composite = Image.new('RGB', (total_width, total_height), color='#f5f5f5') + + # Paste the three images + x_positions = [ + spacing, + spacing * 2 + page_width, + spacing * 3 + page_width * 2 + ] + + for img, x_pos in zip([sans_image, serif_image, mono_image], x_positions): + composite.paste(img, (x_pos, label_height + spacing)) + + # Add labels + draw = ImageDraw.Draw(composite) + + # Try to use a nice font, fallback to default if not available + try: + label_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20) + except: + label_font = ImageFont.load_default() + + labels = ["Sans-Serif", "Serif", "Monospace"] + for label, x_pos in zip(labels, x_positions): + # Calculate text position to center it + bbox = draw.textbbox((0, 0), label, font=label_font) + text_width = bbox[2] - bbox[0] + text_x = x_pos + (page_width - text_width) // 2 + + draw.text((text_x, 5), label, fill='#333333', font=label_font) + + # Save the image + output_path = "docs/images/font_family_switching.png" + composite.save(output_path, quality=95) + print(f"\n✓ Saved demo image to: {output_path}") + print(f" Image size: {total_width}x{total_height}") + + return output_path + + +def create_single_vertical_comparison(): + """Create a vertical comparison that's better for README""" + + # Page size for each panel + page_width = 700 + page_height = 280 + + # Create demo content + print("\nCreating vertical comparison for README...") + blocks = create_demo_content() + + # Render with each font family + print(" Rendering Sans...") + sans_image = render_with_font_family( + blocks, (page_width, page_height), BundledFont.SANS, "Sans" + ) + + print(" Rendering Serif...") + serif_image = render_with_font_family( + blocks, (page_width, page_height), BundledFont.SERIF, "Serif" + ) + + print(" Rendering Monospace...") + mono_image = render_with_font_family( + blocks, (page_width, page_height), BundledFont.MONOSPACE, "Monospace" + ) + + # Create a composite image stacked vertically + spacing = 15 + label_width = 120 + total_width = page_width + label_width + spacing * 2 + total_height = page_height * 3 + spacing * 4 + + composite = Image.new('RGB', (total_width, total_height), color='#ffffff') + + # Add a subtle border + draw = ImageDraw.Draw(composite) + draw.rectangle([(0, 0), (total_width-1, total_height-1)], outline='#e0e0e0', width=1) + + # Paste the three images vertically + y_positions = [ + spacing, + spacing * 2 + page_height, + spacing * 3 + page_height * 2 + ] + + images_data = [ + (sans_image, "Sans-Serif", "#4A90E2"), + (serif_image, "Serif", "#E94B3C"), + (mono_image, "Monospace", "#50C878") + ] + + # Try to use a nice font + try: + label_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16) + small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11) + except: + label_font = ImageFont.load_default() + small_font = ImageFont.load_default() + + for (img, label, color), y_pos in zip(images_data, y_positions): + # Paste the page image + composite.paste(img, (label_width + spacing, y_pos)) + + # Draw label background + draw.rectangle( + [(spacing, y_pos + 10), (label_width, y_pos + 40)], + fill=color + ) + + # Draw label text + draw.text( + (spacing + 10, y_pos + 17), + label, + fill='#ffffff', + font=label_font + ) + + # Draw font description + descriptions = { + "Sans-Serif": "Clean & Modern", + "Serif": "Classic & Formal", + "Monospace": "Code & Technical" + } + draw.text( + (spacing + 5, y_pos + 50), + descriptions[label], + fill='#666666', + font=small_font + ) + + # Save the image + output_path = "docs/images/font_family_switching_vertical.png" + composite.save(output_path, quality=95) + print(f" ✓ Saved: {output_path}") + print(f" Size: {total_width}x{total_height}") + + return output_path + + +if __name__ == "__main__": + print("=" * 70) + print("Generating README Demo Images") + print("=" * 70) + + # Create both versions + horizontal_path = create_comparison_image() + vertical_path = create_single_vertical_comparison() + + print("\n" + "=" * 70) + print("Demo images generated successfully!") + print("=" * 70) + print(f"\nHorizontal comparison: {horizontal_path}") + print(f"Vertical comparison: {vertical_path}") + print("\nRecommended for README: vertical version") + print("\nMarkdown snippet:") + print("```markdown") + print("![Font Family Switching](docs/images/font_family_switching_vertical.png)") + print("```") + print() diff --git a/pyWebLayout/__init__.py b/pyWebLayout/__init__.py new file mode 100644 index 0000000..345308a --- /dev/null +++ b/pyWebLayout/__init__.py @@ -0,0 +1,22 @@ +""" +PyWebLayout - A Python library for HTML-like layout and rendering. + +This library provides classes for rendering HTML-like content to images +using a box-based layout system. It includes support for text, tables, +and containers, as well as parsers for HTML and EPUB content. It also +supports pagination for ebook-like content with the ability to pause, +save state, and resume rendering. +""" + +__version__ = '0.1.1' + +# Core abstractions + +# Style components + + +# Abstract document model + +# Concrete implementations + +# Abstract components diff --git a/pyWebLayout/abstract/__init__.py b/pyWebLayout/abstract/__init__.py new file mode 100644 index 0000000..adc4694 --- /dev/null +++ b/pyWebLayout/abstract/__init__.py @@ -0,0 +1,22 @@ +""" +Abstract layer for the pyWebLayout library. + +This package contains abstract representations of document elements that are +independent of rendering specifics. +""" + +from .inline import Word, FormattedSpan +from .block import Paragraph, Heading, Image, HeadingLevel +from .document import Document +from .functional import LinkType + +__all__ = [ + 'Word', + 'FormattedSpan', + 'Paragraph', + 'Heading', + 'Image', + 'HeadingLevel', + 'Document', + 'LinkType', +] diff --git a/pyWebLayout/abstract/block.py b/pyWebLayout/abstract/block.py new file mode 100644 index 0000000..51f9684 --- /dev/null +++ b/pyWebLayout/abstract/block.py @@ -0,0 +1,1415 @@ +from typing import List, Iterator, Tuple, Dict, Optional, Any +from enum import Enum +import os +import tempfile +import urllib.request +import urllib.parse +from PIL import Image as PILImage +from .inline import Word, FormattedSpan +from ..core import Hierarchical, Styleable, FontRegistry, ContainerAware, BlockContainer + + +class BlockType(Enum): + """Enumeration of different block types for classification purposes""" + PARAGRAPH = 1 + HEADING = 2 + QUOTE = 3 + CODE_BLOCK = 4 + LIST = 5 + LIST_ITEM = 6 + TABLE = 7 + TABLE_ROW = 8 + TABLE_CELL = 9 + HORIZONTAL_RULE = 10 + LINE_BREAK = 11 + IMAGE = 12 + PAGE_BREAK = 13 + + +class Block(Hierarchical): + """ + Base class for all block-level elements. + Block elements typically represent visual blocks of content that stack vertically. + + Uses Hierarchical mixin for parent-child relationship management. + """ + + def __init__(self, block_type: BlockType): + """ + Initialize a block element. + + Args: + block_type: The type of block this element represents + """ + super().__init__() + self._block_type = block_type + + @property + def block_type(self) -> BlockType: + """Get the type of this block element""" + return self._block_type + + +class Paragraph(Styleable, FontRegistry, ContainerAware, Block): + """ + A paragraph is a block-level element that contains a sequence of words. + + Uses Styleable mixin for style property management. + Uses FontRegistry mixin for font caching with parent delegation. + """ + + def __init__(self, style=None): + """ + Initialize an empty paragraph + + Args: + style: Optional default style for words in this paragraph + """ + super().__init__(style=style, block_type=BlockType.PARAGRAPH) + self._words: List[Word] = [] + self._spans: List[FormattedSpan] = [] + + @classmethod + def create_and_add_to(cls, container, style=None) -> 'Paragraph': + """ + Create a new Paragraph and add it to a container, inheriting style from + the container if not explicitly provided. + + Args: + container: The container to add the paragraph to (must have add_block method and style property) + style: Optional style override. If None, inherits from container + + Returns: + The newly created Paragraph object + + Raises: + AttributeError: If the container doesn't have the required add_block method + """ + # Validate container and inherit style using ContainerAware utilities + cls._validate_container(container) + style = cls._inherit_style(container, style) + + # Create the new paragraph + paragraph = cls(style) + + # Add the paragraph to the container + container.add_block(paragraph) + + return paragraph + + def add_word(self, word: Word): + """ + Add a word to this paragraph. + + Args: + word: The Word object to add + """ + self._words.append(word) + + def create_word(self, text: str, style=None, background=None) -> Word: + """ + Create a new word and add it to this paragraph, inheriting paragraph's style if not specified. + + This is a convenience method that uses Word.create_and_add_to() to create words + that automatically inherit styling from this paragraph. + + Args: + text: The text content of the word + style: Optional Font style override. If None, attempts to inherit from paragraph + background: Optional background color override + + Returns: + The newly created Word object + """ + return Word.create_and_add_to(text, self, style, background) + + def add_span(self, span: FormattedSpan): + """ + Add a formatted span to this paragraph. + + Args: + span: The FormattedSpan object to add + """ + self._spans.append(span) + + def create_span(self, style=None, background=None) -> FormattedSpan: + """ + Create a new formatted span with inherited style. + + Args: + style: Optional Font style override. If None, inherits from paragraph + background: Optional background color override + + Returns: + The newly created FormattedSpan object + """ + return FormattedSpan.create_and_add_to(self, style, background) + + @property + def words(self) -> List[Word]: + """Get the list of words in this paragraph""" + return self._words + + def words_iter(self) -> Iterator[Tuple[int, Word]]: + """ + Iterate over the words in this paragraph. + + Yields: + Tuples of (index, word) for each word in the paragraph + """ + for i, word in enumerate(self._words): + yield i, word + + def spans(self) -> Iterator[FormattedSpan]: + """ + Iterate over the formatted spans in this paragraph. + + Yields: + Each FormattedSpan in the paragraph + """ + for span in self._spans: + yield span + + @property + def word_count(self) -> int: + """Get the number of words in this paragraph""" + return len(self._words) + + def __len__(self): + return self.word_count + + # get_or_create_font() is provided by FontRegistry mixin + + +class HeadingLevel(Enum): + """Enumeration representing HTML heading levels (h1-h6)""" + H1 = 1 + H2 = 2 + H3 = 3 + H4 = 4 + H5 = 5 + H6 = 6 + + +class Heading(Paragraph): + """ + A heading element (h1, h2, h3, etc.) that contains text with a specific heading level. + Headings inherit from Paragraph as they contain words but have additional properties. + """ + + def __init__(self, level: HeadingLevel = HeadingLevel.H1, style=None): + """ + Initialize a heading element. + + Args: + level: The heading level (h1-h6) + style: Optional default style for words in this heading + """ + super().__init__(style) + self._block_type = BlockType.HEADING + self._level = level + + @classmethod + def create_and_add_to( + cls, + container, + level: HeadingLevel = HeadingLevel.H1, + style=None) -> 'Heading': + """ + Create a new Heading and add it to a container, inheriting style from + the container if not explicitly provided. + + Args: + container: The container to add the heading to (must have add_block method and style property) + level: The heading level (h1-h6) + style: Optional style override. If None, inherits from container + + Returns: + The newly created Heading object + + Raises: + AttributeError: If the container doesn't have the required add_block method + """ + # Validate container and inherit style using ContainerAware utilities + cls._validate_container(container) + style = cls._inherit_style(container, style) + + # Create the new heading + heading = cls(level, style) + + # Add the heading to the container + container.add_block(heading) + + return heading + + @property + def level(self) -> HeadingLevel: + """Get the heading level""" + return self._level + + @level.setter + def level(self, level: HeadingLevel): + """Set the heading level""" + self._level = level + + +class Quote(BlockContainer, ContainerAware, Block): + """ + A blockquote element that can contain other block elements. + """ + + def __init__(self, style=None): + """ + Initialize an empty blockquote + + Args: + style: Optional default style for child blocks + """ + super().__init__(BlockType.QUOTE) + self._style = style + + @classmethod + def create_and_add_to(cls, container, style=None) -> 'Quote': + """ + Create a new Quote and add it to a container, inheriting style from + the container if not explicitly provided. + + Args: + container: The container to add the quote to (must have add_block method and style property) + style: Optional style override. If None, inherits from container + + Returns: + The newly created Quote object + + Raises: + AttributeError: If the container doesn't have the required add_block method + """ + # Validate container and inherit style using ContainerAware utilities + cls._validate_container(container) + style = cls._inherit_style(container, style) + + # Create the new quote + quote = cls(style) + + # Add the quote to the container + container.add_block(quote) + + return quote + + @property + def style(self): + """Get the default style for this quote""" + return self._style + + @style.setter + def style(self, style): + """Set the default style for this quote""" + self._style = style + + +class CodeBlock(Block): + """ + A code block element containing pre-formatted text with syntax highlighting. + """ + + def __init__(self, language: str = ""): + """ + Initialize a code block. + + Args: + language: The programming language for syntax highlighting + """ + super().__init__(BlockType.CODE_BLOCK) + self._language = language + self._lines: List[str] = [] + + @classmethod + def create_and_add_to(cls, container, language: str = "") -> 'CodeBlock': + """ + Create a new CodeBlock and add it to a container. + + Args: + container: The container to add the code block to (must have add_block method) + language: The programming language for syntax highlighting + + Returns: + The newly created CodeBlock object + + Raises: + AttributeError: If the container doesn't have the required add_block method + """ + # Create the new code block + code_block = cls(language) + + # Add the code block to the container + if hasattr(container, 'add_block'): + container.add_block(code_block) + else: + raise AttributeError( + f"Container {type(container).__name__} must have an 'add_block' method" + ) + + return code_block + + @property + def language(self) -> str: + """Get the programming language""" + return self._language + + @language.setter + def language(self, language: str): + """Set the programming language""" + self._language = language + + def add_line(self, line: str): + """ + Add a line of code to this code block. + + Args: + line: The line of code to add + """ + self._lines.append(line) + + def lines(self) -> Iterator[Tuple[int, str]]: + """ + Iterate over the lines in this code block. + + Yields: + Tuples of (line_number, line_text) for each line + """ + for i, line in enumerate(self._lines): + yield i, line + + @property + def line_count(self) -> int: + """Get the number of lines in this code block""" + return len(self._lines) + + +class ListStyle(Enum): + """Enumeration of list styles""" + UNORDERED = 1 #