removed unused funcs
Some checks failed
Python CI / test (push) Failing after 47s

This commit is contained in:
Duncan Tourolle 2025-06-07 18:30:19 +02:00
parent 4029fdff96
commit da02d48555
20 changed files with 392 additions and 1625 deletions

View File

@ -1,5 +1,5 @@
<svg width="140" height="20" viewBox="0 0 140 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> <svg width="140" height="20" viewBox="0 0 140 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<title>interrogate: 90.0%</title> <title>interrogate: 90.1%</title>
<g transform="matrix(1,0,0,1,22,0)"> <g transform="matrix(1,0,0,1,22,0)">
<g id="backgrounds" transform="matrix(1.32789,0,0,1,-22.3892,0)"> <g id="backgrounds" transform="matrix(1.32789,0,0,1,-22.3892,0)">
<rect x="0" y="0" width="71" height="20" style="fill:rgb(85,85,85);"/> <rect x="0" y="0" width="71" height="20" style="fill:rgb(85,85,85);"/>
@ -12,8 +12,8 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="590" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="610">interrogate</text> <text x="590" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="610">interrogate</text>
<text x="590" y="140" transform="scale(.1)" textLength="610">interrogate</text> <text x="590" y="140" transform="scale(.1)" textLength="610">interrogate</text>
<text x="1160" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370" data-interrogate="result">90.0%</text> <text x="1160" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370" data-interrogate="result">90.1%</text>
<text x="1160" y="140" transform="scale(.1)" textLength="370" data-interrogate="result">90.0%</text> <text x="1160" y="140" transform="scale(.1)" textLength="370" data-interrogate="result">90.1%</text>
</g> </g>
<g id="logo-shadow" serif:id="logo shadow" transform="matrix(0.854876,0,0,0.854876,-6.73514,1.732)"> <g id="logo-shadow" serif:id="logo shadow" transform="matrix(0.854876,0,0,0.854876,-6.73514,1.732)">
<g transform="matrix(0.299012,0,0,0.299012,9.70229,-6.68582)"> <g transform="matrix(0.299012,0,0,0.299012,9.70229,-6.68582)">

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1 +1 @@
33.9% 35.0%

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> <text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="31.5" y="14">coverage</text> <text x="31.5" y="14">coverage</text>
<text x="80" y="15" fill="#010101" fill-opacity=".3">34%</text> <text x="80" y="15" fill="#010101" fill-opacity=".3">35%</text>
<text x="80" y="14">34%</text> <text x="80" y="14">35%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 904 B

After

Width:  |  Height:  |  Size: 904 B

View File

@ -1,5 +1,5 @@
<?xml version="1.0" ?> <?xml version="1.0" ?>
<coverage version="7.8.2" timestamp="1749312034554" lines-valid="3794" lines-covered="1519" line-rate="0.4004" branches-valid="1364" branches-covered="227" branch-rate="0.1664" complexity="0"> <coverage version="7.8.2" timestamp="1749313463565" lines-valid="3863" lines-covered="1591" line-rate="0.4119" branches-valid="1384" branches-covered="247" branch-rate="0.1785" complexity="0">
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.8.2 --> <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.8.2 -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd --> <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources> <sources>
@ -652,7 +652,7 @@
</class> </class>
</classes> </classes>
</package> </package>
<package name="abstract" line-rate="0.8326" branch-rate="0.5756" complexity="0"> <package name="abstract" line-rate="0.8352" branch-rate="0.6042" complexity="0">
<classes> <classes>
<class name="__init__.py" filename="abstract/__init__.py" complexity="0" line-rate="1" branch-rate="1"> <class name="__init__.py" filename="abstract/__init__.py" complexity="0" line-rate="1" branch-rate="1">
<methods/> <methods/>
@ -665,18 +665,18 @@
<line number="6" hits="1"/> <line number="6" hits="1"/>
</lines> </lines>
</class> </class>
<class name="block.py" filename="abstract/block.py" complexity="0" line-rate="0.7248" branch-rate="0.3333"> <class name="block.py" filename="abstract/block.py" complexity="0" line-rate="0.7442" branch-rate="0.4273">
<methods/> <methods/>
<lines> <lines>
<line number="1" hits="1"/> <line number="1" hits="1"/>
<line number="2" hits="1"/> <line number="2" hits="1"/>
<line number="3" hits="1"/> <line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="5" hits="1"/>
<line number="6" hits="1"/> <line number="6" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/> <line number="8" hits="1"/>
<line number="9" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/> <line number="11" hits="1"/>
<line number="12" hits="1"/>
<line number="13" hits="1"/> <line number="13" hits="1"/>
<line number="14" hits="1"/> <line number="14" hits="1"/>
<line number="15" hits="1"/> <line number="15" hits="1"/>
@ -684,12 +684,14 @@
<line number="17" hits="1"/> <line number="17" hits="1"/>
<line number="18" hits="1"/> <line number="18" hits="1"/>
<line number="19" hits="1"/> <line number="19" hits="1"/>
<line number="20" hits="1"/>
<line number="21" hits="1"/>
<line number="22" hits="1"/> <line number="22" hits="1"/>
<line number="28" hits="1"/> <line number="23" hits="1"/>
<line number="35" hits="1"/> <line number="24" hits="1"/>
<line number="36" hits="1"/> <line number="27" hits="1"/>
<line number="38" hits="1"/> <line number="33" hits="1"/>
<line number="39" hits="1"/> <line number="40" hits="1"/>
<line number="41" hits="1"/> <line number="41" hits="1"/>
<line number="43" hits="1"/> <line number="43" hits="1"/>
<line number="44" hits="1"/> <line number="44" hits="1"/>
@ -697,253 +699,253 @@
<line number="48" hits="1"/> <line number="48" hits="1"/>
<line number="49" hits="1"/> <line number="49" hits="1"/>
<line number="51" hits="1"/> <line number="51" hits="1"/>
<line number="53" hits="1"/>
<line number="54" hits="1"/> <line number="54" hits="1"/>
<line number="56" hits="1"/>
<line number="59" hits="1"/> <line number="59" hits="1"/>
<line number="66" hits="1"/> <line number="64" hits="1"/>
<line number="67" hits="1"/>
<line number="68" hits="1"/>
<line number="69" hits="1"/>
<line number="71" hits="1"/> <line number="71" hits="1"/>
<line number="72" hits="1"/> <line number="72" hits="1"/>
<line number="88" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="89,90"/> <line number="73" hits="1"/>
<line number="89" hits="0"/> <line number="74" hits="1"/>
<line number="90" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="91,94"/> <line number="76" hits="1"/>
<line number="91" hits="0"/> <line number="77" hits="1"/>
<line number="93" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="94,95"/>
<line number="94" hits="0"/> <line number="94" hits="0"/>
<line number="97" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="98,100"/> <line number="95" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="96,99"/>
<line number="98" hits="0"/> <line number="96" hits="0"/>
<line number="100" hits="0"/> <line number="99" hits="0"/>
<line number="102" hits="0"/> <line number="102" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="103,105"/>
<line number="104" hits="1"/> <line number="103" hits="0"/>
<line number="105" hits="1"/> <line number="105" hits="0"/>
<line number="107" hits="0"/> <line number="107" hits="0"/>
<line number="109" hits="1"/> <line number="109" hits="1"/>
<line number="110" hits="1"/> <line number="110" hits="1"/>
<line number="112" hits="0"/> <line number="112" hits="0"/>
<line number="114" hits="1"/> <line number="114" hits="1"/>
<line number="121" hits="1"/> <line number="115" hits="1"/>
<line number="123" hits="1"/> <line number="117" hits="0"/>
<line number="138" hits="0"/> <line number="119" hits="1"/>
<line number="140" hits="1"/> <line number="126" hits="1"/>
<line number="147" hits="0"/> <line number="128" hits="1"/>
<line number="149" hits="1"/> <line number="143" hits="0"/>
<line number="160" hits="0"/> <line number="145" hits="1"/>
<line number="162" hits="1"/> <line number="152" hits="0"/>
<line number="169" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="154" hits="1"/>
<line number="170" hits="1"/> <line number="165" hits="0"/>
<line number="172" hits="1"/> <line number="167" hits="1"/>
<line number="179" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,180"/> <line number="174" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="180" hits="0"/> <line number="175" hits="1"/>
<line number="182" hits="1"/> <line number="177" hits="1"/>
<line number="183" hits="1"/> <line number="184" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,185"/>
<line number="185" hits="1"/> <line number="185" hits="0"/>
<line number="187" hits="1"/> <line number="187" hits="1"/>
<line number="189" hits="1"/> <line number="188" hits="1"/>
<line number="190" hits="1"/>
<line number="192" hits="1"/> <line number="192" hits="1"/>
<line number="194" hits="1"/> <line number="194" hits="1"/>
<line number="195" hits="1"/>
<line number="196" hits="1"/>
<line number="197" hits="1"/> <line number="197" hits="1"/>
<line number="198" hits="1"/>
<line number="199" hits="1"/> <line number="199" hits="1"/>
<line number="200" hits="1"/>
<line number="201" hits="1"/>
<line number="202" hits="1"/> <line number="202" hits="1"/>
<line number="208" hits="1"/> <line number="203" hits="1"/>
<line number="216" hits="1"/> <line number="204" hits="1"/>
<line number="217" hits="1"/> <line number="207" hits="1"/>
<line number="218" hits="1"/> <line number="213" hits="1"/>
<line number="220" hits="1"/>
<line number="221" hits="1"/> <line number="221" hits="1"/>
<line number="238" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="239,240"/> <line number="222" hits="1"/>
<line number="239" hits="0"/> <line number="223" hits="1"/>
<line number="240" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="241,244"/> <line number="225" hits="1"/>
<line number="241" hits="0"/> <line number="226" hits="1"/>
<line number="243" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="244,245"/>
<line number="244" hits="0"/> <line number="244" hits="0"/>
<line number="247" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="248,250"/> <line number="245" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="246,249"/>
<line number="248" hits="0"/> <line number="246" hits="0"/>
<line number="250" hits="0"/> <line number="249" hits="0"/>
<line number="252" hits="0"/> <line number="252" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="253,255"/>
<line number="254" hits="1"/> <line number="253" hits="0"/>
<line number="255" hits="1"/> <line number="255" hits="0"/>
<line number="257" hits="1"/> <line number="257" hits="0"/>
<line number="259" hits="1"/> <line number="259" hits="1"/>
<line number="260" hits="1"/> <line number="260" hits="1"/>
<line number="262" hits="1"/> <line number="262" hits="1"/>
<line number="264" hits="1"/>
<line number="265" hits="1"/> <line number="265" hits="1"/>
<line number="267" hits="1"/>
<line number="270" hits="1"/> <line number="270" hits="1"/>
<line number="277" hits="1"/> <line number="275" hits="1"/>
<line number="278" hits="1"/>
<line number="279" hits="1"/>
<line number="281" hits="1"/>
<line number="282" hits="1"/> <line number="282" hits="1"/>
<line number="298" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="299,300"/> <line number="283" hits="1"/>
<line number="299" hits="0"/> <line number="284" hits="1"/>
<line number="300" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="301,304"/> <line number="286" hits="1"/>
<line number="301" hits="0"/> <line number="287" hits="1"/>
<line number="303" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="304,305"/>
<line number="304" hits="0"/> <line number="304" hits="0"/>
<line number="307" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="308,310"/> <line number="305" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="306,309"/>
<line number="308" hits="0"/> <line number="306" hits="0"/>
<line number="310" hits="0"/> <line number="309" hits="0"/>
<line number="312" hits="0"/> <line number="312" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="313,315"/>
<line number="314" hits="1"/> <line number="313" hits="0"/>
<line number="315" hits="1"/> <line number="315" hits="0"/>
<line number="317" hits="0"/> <line number="317" hits="0"/>
<line number="319" hits="1"/> <line number="319" hits="1"/>
<line number="320" hits="1"/> <line number="320" hits="1"/>
<line number="322" hits="0"/> <line number="322" hits="0"/>
<line number="324" hits="1"/> <line number="324" hits="1"/>
<line number="331" hits="1"/> <line number="325" hits="1"/>
<line number="332" hits="1"/> <line number="327" hits="0"/>
<line number="334" hits="1"/> <line number="329" hits="1"/>
<line number="344" hits="0"/> <line number="336" hits="1"/>
<line number="346" hits="1"/> <line number="337" hits="1"/>
<line number="357" hits="0"/> <line number="339" hits="1"/>
<line number="359" hits="1"/> <line number="349" hits="0"/>
<line number="366" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="351" hits="1"/>
<line number="367" hits="1"/> <line number="362" hits="0"/>
<line number="370" hits="1"/> <line number="364" hits="1"/>
<line number="371" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="372" hits="1"/>
<line number="375" hits="1"/> <line number="375" hits="1"/>
<line number="382" hits="1"/> <line number="380" hits="1"/>
<line number="383" hits="1"/>
<line number="384" hits="1"/>
<line number="386" hits="1"/>
<line number="387" hits="1"/> <line number="387" hits="1"/>
<line number="402" hits="0"/> <line number="388" hits="1"/>
<line number="405" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="406,408"/> <line number="389" hits="1"/>
<line number="406" hits="0"/> <line number="391" hits="1"/>
<line number="408" hits="0"/> <line number="392" hits="1"/>
<line number="410" hits="0"/> <line number="407" hits="0"/>
<line number="412" hits="1"/> <line number="410" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="411,413"/>
<line number="413" hits="1"/> <line number="411" hits="0"/>
<line number="415" hits="1"/> <line number="413" hits="0"/>
<line number="415" hits="0"/>
<line number="417" hits="1"/> <line number="417" hits="1"/>
<line number="418" hits="1"/> <line number="418" hits="1"/>
<line number="420" hits="1"/> <line number="420" hits="1"/>
<line number="422" hits="1"/> <line number="422" hits="1"/>
<line number="429" hits="1"/> <line number="423" hits="1"/>
<line number="431" hits="1"/> <line number="425" hits="1"/>
<line number="438" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="427" hits="1"/>
<line number="439" hits="1"/> <line number="434" hits="1"/>
<line number="441" hits="1"/> <line number="436" hits="1"/>
<line number="442" hits="1"/> <line number="443" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="444" hits="1"/> <line number="444" hits="1"/>
<line number="446" hits="1"/>
<line number="447" hits="1"/> <line number="447" hits="1"/>
<line number="449" hits="1"/> <line number="449" hits="1"/>
<line number="450" hits="1"/> <line number="452" hits="1"/>
<line number="451" hits="1"/>
<line number="454" hits="1"/> <line number="454" hits="1"/>
<line number="455" hits="1"/>
<line number="456" hits="1"/>
<line number="459" hits="1"/> <line number="459" hits="1"/>
<line number="467" hits="1"/> <line number="464" hits="1"/>
<line number="468" hits="1"/>
<line number="469" hits="1"/>
<line number="470" hits="1"/>
<line number="472" hits="1"/> <line number="472" hits="1"/>
<line number="473" hits="1"/> <line number="473" hits="1"/>
<line number="490" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="491,492"/> <line number="474" hits="1"/>
<line number="491" hits="0"/> <line number="475" hits="1"/>
<line number="492" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="493,496"/> <line number="477" hits="1"/>
<line number="493" hits="0"/> <line number="478" hits="1"/>
<line number="495" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="496,497"/>
<line number="496" hits="0"/> <line number="496" hits="0"/>
<line number="499" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="500,502"/> <line number="497" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="498,501"/>
<line number="500" hits="0"/> <line number="498" hits="0"/>
<line number="502" hits="0"/> <line number="501" hits="0"/>
<line number="504" hits="0"/> <line number="504" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="505,507"/>
<line number="506" hits="1"/> <line number="505" hits="0"/>
<line number="507" hits="1"/> <line number="507" hits="0"/>
<line number="509" hits="1"/> <line number="509" hits="0"/>
<line number="511" hits="1"/> <line number="511" hits="1"/>
<line number="512" hits="1"/> <line number="512" hits="1"/>
<line number="514" hits="1"/> <line number="514" hits="1"/>
<line number="516" hits="1"/> <line number="516" hits="1"/>
<line number="517" hits="1"/> <line number="517" hits="1"/>
<line number="519" hits="0"/> <line number="519" hits="1"/>
<line number="521" hits="1"/> <line number="521" hits="1"/>
<line number="522" hits="1"/> <line number="522" hits="1"/>
<line number="524" hits="0"/> <line number="524" hits="0"/>
<line number="526" hits="1"/> <line number="526" hits="1"/>
<line number="533" hits="1"/> <line number="527" hits="1"/>
<line number="534" hits="1"/> <line number="529" hits="0"/>
<line number="536" hits="1"/> <line number="531" hits="1"/>
<line number="547" hits="0"/> <line number="538" hits="1"/>
<line number="549" hits="1"/> <line number="539" hits="1"/>
<line number="556" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="541" hits="1"/>
<line number="557" hits="1"/> <line number="552" hits="0"/>
<line number="559" hits="1"/> <line number="554" hits="1"/>
<line number="560" hits="1"/> <line number="561" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="562" hits="1"/> <line number="562" hits="1"/>
<line number="564" hits="1"/>
<line number="565" hits="1"/> <line number="565" hits="1"/>
<line number="567" hits="1"/>
<line number="570" hits="1"/> <line number="570" hits="1"/>
<line number="578" hits="1"/> <line number="575" hits="1"/>
<line number="579" hits="1"/>
<line number="580" hits="1"/>
<line number="581" hits="1"/>
<line number="583" hits="1"/> <line number="583" hits="1"/>
<line number="584" hits="1"/> <line number="584" hits="1"/>
<line number="601" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="602,603"/> <line number="585" hits="1"/>
<line number="602" hits="0"/> <line number="586" hits="1"/>
<line number="603" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="604,607"/> <line number="588" hits="1"/>
<line number="604" hits="0"/> <line number="589" hits="1"/>
<line number="606" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="607,608"/>
<line number="607" hits="0"/> <line number="607" hits="0"/>
<line number="610" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="611,613"/> <line number="608" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="609,612"/>
<line number="611" hits="0"/> <line number="609" hits="0"/>
<line number="613" hits="0"/> <line number="612" hits="0"/>
<line number="615" hits="0"/> <line number="615" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="616,618"/>
<line number="617" hits="1"/> <line number="616" hits="0"/>
<line number="618" hits="1"/> <line number="618" hits="0"/>
<line number="620" hits="1"/> <line number="620" hits="0"/>
<line number="622" hits="1"/> <line number="622" hits="1"/>
<line number="623" hits="1"/> <line number="623" hits="1"/>
<line number="625" hits="1"/> <line number="625" hits="1"/>
<line number="627" hits="1"/> <line number="627" hits="1"/>
<line number="628" hits="1"/> <line number="628" hits="1"/>
<line number="630" hits="0"/> <line number="630" hits="1"/>
<line number="632" hits="1"/> <line number="632" hits="1"/>
<line number="633" hits="1"/> <line number="633" hits="1"/>
<line number="635" hits="0"/> <line number="635" hits="0"/>
<line number="637" hits="1"/> <line number="637" hits="1"/>
<line number="644" hits="1"/> <line number="638" hits="1"/>
<line number="645" hits="1"/> <line number="640" hits="0"/>
<line number="647" hits="1"/> <line number="642" hits="1"/>
<line number="657" hits="0"/> <line number="649" hits="1"/>
<line number="659" hits="1"/> <line number="650" hits="1"/>
<line number="670" hits="0"/> <line number="652" hits="1"/>
<line number="672" hits="1"/> <line number="662" hits="0"/>
<line number="679" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="680"/> <line number="664" hits="1"/>
<line number="680" hits="0"/> <line number="675" hits="0"/>
<line number="683" hits="1"/> <line number="677" hits="1"/>
<line number="684" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="685" hits="1"/>
<line number="688" hits="1"/> <line number="688" hits="1"/>
<line number="698" hits="1"/> <line number="693" hits="1"/>
<line number="699" hits="1"/>
<line number="700" hits="1"/>
<line number="701" hits="1"/>
<line number="702" hits="1"/>
<line number="703" hits="1"/> <line number="703" hits="1"/>
<line number="704" hits="1"/>
<line number="705" hits="1"/> <line number="705" hits="1"/>
<line number="706" hits="1"/> <line number="706" hits="1"/>
<line number="726" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="727,730"/> <line number="707" hits="1"/>
<line number="727" hits="0"/> <line number="708" hits="1"/>
<line number="730" hits="0"/> <line number="710" hits="1"/>
<line number="733" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="734,736"/> <line number="711" hits="1"/>
<line number="734" hits="0"/> <line number="731" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="732,735"/>
<line number="736" hits="0"/> <line number="732" hits="0"/>
<line number="738" hits="0"/> <line number="735" hits="0"/>
<line number="740" hits="1"/> <line number="738" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="739,741"/>
<line number="741" hits="1"/> <line number="739" hits="0"/>
<line number="743" hits="1"/> <line number="741" hits="0"/>
<line number="743" hits="0"/>
<line number="745" hits="1"/> <line number="745" hits="1"/>
<line number="746" hits="1"/> <line number="746" hits="1"/>
<line number="748" hits="0"/> <line number="748" hits="1"/>
<line number="750" hits="1"/> <line number="750" hits="1"/>
<line number="751" hits="1"/> <line number="751" hits="1"/>
<line number="753" hits="1"/> <line number="753" hits="0"/>
<line number="755" hits="1"/> <line number="755" hits="1"/>
<line number="756" hits="1"/> <line number="756" hits="1"/>
<line number="758" hits="0"/> <line number="758" hits="1"/>
<line number="760" hits="1"/> <line number="760" hits="1"/>
<line number="761" hits="1"/> <line number="761" hits="1"/>
<line number="763" hits="1"/> <line number="763" hits="0"/>
<line number="765" hits="1"/> <line number="765" hits="1"/>
<line number="766" hits="1"/> <line number="766" hits="1"/>
<line number="768" hits="0"/> <line number="768" hits="1"/>
<line number="770" hits="1"/> <line number="770" hits="1"/>
<line number="771" hits="1"/> <line number="771" hits="1"/>
<line number="773" hits="0"/> <line number="773" hits="0"/>
@ -951,71 +953,71 @@
<line number="776" hits="1"/> <line number="776" hits="1"/>
<line number="778" hits="0"/> <line number="778" hits="0"/>
<line number="780" hits="1"/> <line number="780" hits="1"/>
<line number="787" hits="1"/> <line number="781" hits="1"/>
<line number="788" hits="1"/> <line number="783" hits="0"/>
<line number="790" hits="1"/> <line number="785" hits="1"/>
<line number="800" hits="0"/> <line number="792" hits="1"/>
<line number="802" hits="1"/> <line number="793" hits="1"/>
<line number="813" hits="0"/> <line number="795" hits="1"/>
<line number="815" hits="1"/> <line number="805" hits="0"/>
<line number="822" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,823"/> <line number="807" hits="1"/>
<line number="823" hits="0"/> <line number="818" hits="0"/>
<line number="826" hits="1"/> <line number="820" hits="1"/>
<line number="827" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,828"/>
<line number="828" hits="0"/>
<line number="831" hits="1"/> <line number="831" hits="1"/>
<line number="838" hits="1"/> <line number="836" hits="1"/>
<line number="839" hits="1"/>
<line number="840" hits="1"/>
<line number="842" hits="1"/>
<line number="843" hits="1"/> <line number="843" hits="1"/>
<line number="860" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="861,864"/> <line number="844" hits="1"/>
<line number="861" hits="0"/> <line number="845" hits="1"/>
<line number="864" hits="0"/> <line number="847" hits="1"/>
<line number="867" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="868,870"/> <line number="848" hits="1"/>
<line number="868" hits="0"/> <line number="865" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="866,869"/>
<line number="870" hits="0"/> <line number="866" hits="0"/>
<line number="872" hits="0"/> <line number="869" hits="0"/>
<line number="874" hits="1"/> <line number="872" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="873,875"/>
<line number="875" hits="1"/> <line number="873" hits="0"/>
<line number="875" hits="0"/>
<line number="877" hits="0"/> <line number="877" hits="0"/>
<line number="879" hits="1"/> <line number="879" hits="1"/>
<line number="880" hits="1"/> <line number="880" hits="1"/>
<line number="882" hits="0"/> <line number="882" hits="0"/>
<line number="884" hits="1"/> <line number="884" hits="1"/>
<line number="891" hits="1"/> <line number="885" hits="1"/>
<line number="892" hits="1"/> <line number="887" hits="0"/>
<line number="894" hits="1"/> <line number="889" hits="1"/>
<line number="907" hits="0"/> <line number="896" hits="1"/>
<line number="909" hits="1"/> <line number="897" hits="1"/>
<line number="916" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,917"/> <line number="899" hits="1"/>
<line number="917" hits="0"/> <line number="912" hits="0"/>
<line number="919" hits="1"/> <line number="914" hits="1"/>
<line number="920" hits="1"/> <line number="921" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,922"/>
<line number="922" hits="1"/> <line number="922" hits="0"/>
<line number="924" hits="1"/>
<line number="925" hits="1"/> <line number="925" hits="1"/>
<line number="927" hits="1"/>
<line number="930" hits="1"/> <line number="930" hits="1"/>
<line number="938" hits="1"/> <line number="935" hits="1"/>
<line number="939" hits="1"/>
<line number="940" hits="1"/>
<line number="941" hits="1"/>
<line number="942" hits="1"/>
<line number="943" hits="1"/> <line number="943" hits="1"/>
<line number="944" hits="1"/>
<line number="945" hits="1"/> <line number="945" hits="1"/>
<line number="946" hits="1"/> <line number="946" hits="1"/>
<line number="963" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="964,965"/> <line number="947" hits="1"/>
<line number="964" hits="0"/> <line number="948" hits="1"/>
<line number="965" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="966,969"/> <line number="950" hits="1"/>
<line number="966" hits="0"/> <line number="951" hits="1"/>
<line number="968" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="969,970"/>
<line number="969" hits="0"/> <line number="969" hits="0"/>
<line number="972" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="973,975"/> <line number="970" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="971,974"/>
<line number="973" hits="0"/> <line number="971" hits="0"/>
<line number="975" hits="0"/> <line number="974" hits="0"/>
<line number="977" hits="0"/> <line number="977" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="978,980"/>
<line number="979" hits="1"/> <line number="978" hits="0"/>
<line number="980" hits="1"/> <line number="980" hits="0"/>
<line number="982" hits="1"/> <line number="982" hits="0"/>
<line number="984" hits="1"/> <line number="984" hits="1"/>
<line number="985" hits="1"/> <line number="985" hits="1"/>
<line number="987" hits="0"/> <line number="987" hits="1"/>
<line number="989" hits="1"/> <line number="989" hits="1"/>
<line number="990" hits="1"/> <line number="990" hits="1"/>
<line number="992" hits="0"/> <line number="992" hits="0"/>
@ -1023,50 +1025,50 @@
<line number="995" hits="1"/> <line number="995" hits="1"/>
<line number="997" hits="0"/> <line number="997" hits="0"/>
<line number="999" hits="1"/> <line number="999" hits="1"/>
<line number="1007" hits="1"/> <line number="1000" hits="1"/>
<line number="1009" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1002" hits="0"/>
<line number="1010" hits="1"/> <line number="1004" hits="1"/>
<line number="1011" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1012" hits="1"/> <line number="1012" hits="1"/>
<line number="1014" hits="1"/> <line number="1014" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1016" hits="1"/> <line number="1015" hits="1"/>
<line number="1027" hits="0"/> <line number="1016" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1029" hits="1"/> <line number="1017" hits="1"/>
<line number="1036" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1019" hits="1"/>
<line number="1037" hits="1"/> <line number="1021" hits="1"/>
<line number="1039" hits="1"/> <line number="1032" hits="0"/>
<line number="1046" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1034" hits="1"/>
<line number="1047" hits="1"/> <line number="1041" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1049" hits="1"/> <line number="1042" hits="1"/>
<line number="1056" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1044" hits="1"/>
<line number="1057" hits="1"/> <line number="1051" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1059" hits="1"/> <line number="1052" hits="1"/>
<line number="1066" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1054" hits="1"/>
<line number="1067" hits="1"/> <line number="1061" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1068" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1062" hits="1"/>
<line number="1069" hits="1"/> <line number="1064" hits="1"/>
<line number="1070" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1071" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1071" hits="1"/> <line number="1072" hits="1"/>
<line number="1073" hits="1"/> <line number="1073" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1074" hits="1"/> <line number="1074" hits="1"/>
<line number="1075" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1076" hits="1"/> <line number="1076" hits="1"/>
<line number="1084" hits="1"/> <line number="1078" hits="1"/>
<line number="1079" hits="1"/>
<line number="1081" hits="1"/>
<line number="1089" hits="1"/> <line number="1089" hits="1"/>
<line number="1099" hits="1"/> <line number="1094" hits="1"/>
<line number="1100" hits="1"/> <line number="1104" hits="1"/>
<line number="1101" hits="1"/>
<line number="1102" hits="1"/>
<line number="1103" hits="1"/>
<line number="1105" hits="1"/> <line number="1105" hits="1"/>
<line number="1106" hits="1"/> <line number="1106" hits="1"/>
<line number="1125" hits="0"/> <line number="1107" hits="1"/>
<line number="1128" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1129,1131"/> <line number="1108" hits="1"/>
<line number="1129" hits="0"/> <line number="1110" hits="1"/>
<line number="1131" hits="0"/> <line number="1111" hits="1"/>
<line number="1133" hits="0"/> <line number="1130" hits="0"/>
<line number="1135" hits="1"/> <line number="1133" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1134,1136"/>
<line number="1136" hits="1"/> <line number="1134" hits="0"/>
<line number="1138" hits="1"/> <line number="1136" hits="0"/>
<line number="1138" hits="0"/>
<line number="1140" hits="1"/> <line number="1140" hits="1"/>
<line number="1141" hits="1"/> <line number="1141" hits="1"/>
<line number="1143" hits="1"/> <line number="1143" hits="1"/>
@ -1089,32 +1091,99 @@
<line number="1171" hits="1"/> <line number="1171" hits="1"/>
<line number="1173" hits="1"/> <line number="1173" hits="1"/>
<line number="1175" hits="1"/> <line number="1175" hits="1"/>
<line number="1182" hits="1"/> <line number="1176" hits="1"/>
<line number="1184" hits="1"/> <line number="1178" hits="1"/>
<line number="1191" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="1180" hits="1"/>
<line number="1192" hits="1"/> <line number="1187" hits="1"/>
<line number="1193" hits="1"/> <line number="1189" hits="1"/>
<line number="1195" hits="1"/> <line number="1196" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1207" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1208"/> <line number="1197" hits="1"/>
<line number="1208" hits="0"/> <line number="1198" hits="1"/>
<line number="1210" hits="1"/> <line number="1200" hits="1"/>
<line number="1213" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1217"/> <line number="1212" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1213"/>
<line number="1214" hits="1"/> <line number="1213" hits="0"/>
<line number="1215" hits="1"/> <line number="1215" hits="1"/>
<line number="1217" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1218"/> <line number="1218" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1222"/>
<line number="1218" hits="0"/> <line number="1219" hits="1"/>
<line number="1219" hits="0"/> <line number="1220" hits="1"/>
<line number="1221" hits="1"/> <line number="1222" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1223"/>
<line number="1224" hits="1"/> <line number="1223" hits="0"/>
<line number="1229" hits="1"/> <line number="1224" hits="0"/>
<line number="1231" hits="1"/> <line number="1226" hits="1"/>
<line number="1233" hits="1"/> <line number="1228" hits="1"/>
<line number="1234" hits="1"/> <line number="1238" hits="1"/>
<line number="1248" hits="0"/> <line number="1239" hits="1"/>
<line number="1251" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1252,1254"/> <line number="1241" hits="1"/>
<line number="1252" hits="0"/> <line number="1255" hits="1"/>
<line number="1254" hits="0"/> <line number="1257" hits="1"/>
<line number="1256" hits="0"/> <line number="1259" hits="1"/>
<line number="1261" hits="1"/>
<line number="1262" hits="1"/>
<line number="1264" hits="1"/>
<line number="1265" hits="1"/>
<line number="1267" hits="1"/>
<line number="1268" hits="1"/>
<line number="1269" hits="0"/>
<line number="1270" hits="0"/>
<line number="1271" hits="1"/>
<line number="1272" hits="1"/>
<line number="1273" hits="0"/>
<line number="1274" hits="0"/>
<line number="1275" hits="1"/>
<line number="1277" hits="1"/>
<line number="1288" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1289" hits="1"/>
<line number="1291" hits="1"/>
<line number="1292" hits="1"/>
<line number="1294" hits="1"/>
<line number="1295" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1297" hits="1"/>
<line number="1298" hits="1"/>
<line number="1301" hits="1"/>
<line number="1304" hits="1"/>
<line number="1306" hits="1"/>
<line number="1309" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1310" hits="1"/>
<line number="1313" hits="1"/>
<line number="1315" hits="1"/>
<line number="1317" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1318"/>
<line number="1318" hits="0"/>
<line number="1319" hits="0"/>
<line number="1320" hits="0"/>
<line number="1321" hits="0"/>
<line number="1322" hits="1"/>
<line number="1324" hits="1"/>
<line number="1332" hits="1"/>
<line number="1334" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1335" hits="1"/>
<line number="1338" hits="1"/>
<line number="1339" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1361"/>
<line number="1341" hits="1"/>
<line number="1352" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1357"/>
<line number="1353" hits="1"/>
<line number="1354" hits="1"/>
<line number="1357" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1358" hits="1"/>
<line number="1359" hits="1"/>
<line number="1361" hits="1"/>
<line number="1370" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1374"/>
<line number="1371" hits="1"/>
<line number="1374" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="1375" hits="1"/>
<line number="1376" hits="1"/>
<line number="1377" hits="0"/>
<line number="1378" hits="0"/>
<line number="1380" hits="1"/>
<line number="1383" hits="1"/>
<line number="1388" hits="1"/>
<line number="1390" hits="1"/>
<line number="1392" hits="1"/>
<line number="1393" hits="1"/>
<line number="1407" hits="0"/>
<line number="1410" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1411,1413"/>
<line number="1411" hits="0"/>
<line number="1413" hits="0"/>
<line number="1415" hits="0"/>
</lines> </lines>
</class> </class>
<class name="document.py" filename="abstract/document.py" complexity="0" line-rate="0.8324" branch-rate="0.5938"> <class name="document.py" filename="abstract/document.py" complexity="0" line-rate="0.8324" branch-rate="0.5938">
@ -2356,7 +2425,7 @@
</class> </class>
</classes> </classes>
</package> </package>
<package name="io.readers" line-rate="0.3445" branch-rate="0.1902" complexity="0"> <package name="io.readers" line-rate="0.3542" branch-rate="0.1948" complexity="0">
<classes> <classes>
<class name="__init__.py" filename="io/readers/__init__.py" complexity="0" line-rate="1" branch-rate="1"> <class name="__init__.py" filename="io/readers/__init__.py" complexity="0" line-rate="1" branch-rate="1">
<methods/> <methods/>
@ -2872,7 +2941,7 @@
<line number="186" hits="0"/> <line number="186" hits="0"/>
</lines> </lines>
</class> </class>
<class name="html_extraction.py" filename="io/readers/html_extraction.py" complexity="0" line-rate="0.796" branch-rate="0.6019"> <class name="html_extraction.py" filename="io/readers/html_extraction.py" complexity="0" line-rate="0.83" branch-rate="0.6165">
<methods/> <methods/>
<lines> <lines>
<line number="9" hits="1"/> <line number="9" hits="1"/>
@ -2907,9 +2976,9 @@
<line number="104" hits="1"/> <line number="104" hits="1"/>
<line number="107" hits="1"/> <line number="107" hits="1"/>
<line number="110" hits="1"/> <line number="110" hits="1"/>
<line number="111" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="112"/> <line number="111" hits="1" branch="true" condition-coverage="100% (2/2)"/>
<line number="112" hits="0"/> <line number="112" hits="1"/>
<line number="117" hits="0"/> <line number="117" hits="1"/>
<line number="118" hits="1"/> <line number="118" hits="1"/>
<line number="121" hits="1"/> <line number="121" hits="1"/>
<line number="122" hits="1" branch="true" condition-coverage="100% (2/2)"/> <line number="122" hits="1" branch="true" condition-coverage="100% (2/2)"/>
@ -3194,21 +3263,21 @@
<line number="619" hits="1"/> <line number="619" hits="1"/>
<line number="621" hits="1"/> <line number="621" hits="1"/>
<line number="624" hits="1"/> <line number="624" hits="1"/>
<line number="626" hits="0"/> <line number="626" hits="1"/>
<line number="629" hits="1"/> <line number="629" hits="1"/>
<line number="632" hits="0"/> <line number="632" hits="0"/>
<line number="635" hits="1"/> <line number="635" hits="1"/>
<line number="637" hits="0"/> <line number="637" hits="1"/>
<line number="638" hits="0"/> <line number="638" hits="1"/>
<line number="641" hits="0"/> <line number="641" hits="1"/>
<line number="642" hits="0"/> <line number="642" hits="1"/>
<line number="643" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="644,645"/> <line number="643" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="645"/>
<line number="644" hits="0"/> <line number="644" hits="1"/>
<line number="645" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="646,650"/> <line number="645" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="650"/>
<line number="646" hits="0"/> <line number="646" hits="1"/>
<line number="647" hits="0"/> <line number="647" hits="0"/>
<line number="648" hits="0"/> <line number="648" hits="0"/>
<line number="650" hits="0"/> <line number="650" hits="1"/>
<line number="653" hits="1"/> <line number="653" hits="1"/>
<line number="655" hits="1"/> <line number="655" hits="1"/>
<line number="658" hits="1"/> <line number="658" hits="1"/>

View File

@ -34,8 +34,6 @@ from pyWebLayout.concrete.page import Container, Page
# Abstract components # Abstract components
from pyWebLayout.abstract.inline import Word from pyWebLayout.abstract.inline import Word
# Layout components
from pyWebLayout.table import Table, TableCell
# IO functionality (reading and writing) # IO functionality (reading and writing)
from pyWebLayout.io import ( from pyWebLayout.io import (

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from enum import Enum from enum import Enum
from typing import Callable, Dict, Any, Optional, Union, List, Tuple from typing import Callable, Dict, Any, Optional, Union, List, Tuple
from pyWebLayout.base import Interactable from pyWebLayout.core.base import Interactable
class LinkType(Enum): class LinkType(Enum):

View File

@ -1,5 +1,5 @@
from __future__ import annotations from __future__ import annotations
from pyWebLayout.base import Queriable from pyWebLayout.core.base import Queriable
from pyWebLayout.style import Font from pyWebLayout.style import Font
from typing import Tuple, Union, List, Optional, Dict from typing import Tuple, Union, List, Optional, Dict
import pyphen import pyphen

View File

@ -1,68 +0,0 @@
from abc import ABC
import numpy as np
from pyWebLayout.style import Alignment
class Renderable(ABC):
"""
Abstract base class for any object that can be rendered to an image.
All renderable objects must implement the render method.
"""
def render(self):
"""
Render the object to an image.
Returns:
PIL.Image: The rendered image
"""
pass
class Interactable(ABC):
"""
Abstract base class for any object that can be interacted with.
Interactable objects must have a callback that is executed when interacted with.
"""
def __init__(self, callback=None):
"""
Initialize an interactable object.
Args:
callback: The function to call when this object is interacted with
"""
self._callback = callback
def interact(self, point: np.generic):
"""
Handle interaction at the given point.
Args:
point: The coordinates of the interaction
Returns:
The result of calling the callback function with the point
"""
if self._callback is None:
return None
return self._callback(point)
class Layoutable(ABC):
"""
Abstract base class for any object that can be laid out.
Layoutable objects must implement the layout method which arranges their contents.
"""
def layout(self):
"""
Layout the object's contents.
This method should be called before rendering to properly arrange the object's contents.
"""
pass
class Queriable(ABC):
def in_object(self, point:np.generic):
"""
check if a point is in the object
"""
pass

View File

@ -1,8 +1,8 @@
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from pyWebLayout.base import Renderable, Queriable from pyWebLayout.core.base import Renderable, Queriable
from pyWebLayout.layout import Alignment from pyWebLayout.style.layout import Alignment
class Box(Renderable, Queriable): class Box(Renderable, Queriable):

View File

@ -3,7 +3,7 @@ from typing import Optional, Dict, Any, Tuple, List, Union
import numpy as np import numpy as np
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from pyWebLayout.base import Renderable, Queriable from pyWebLayout.core.base import Renderable, Queriable
from pyWebLayout.abstract.functional import Link, Button, Form, FormField, LinkType, FormFieldType from pyWebLayout.abstract.functional import Link, Button, Form, FormField, LinkType, FormFieldType
from pyWebLayout.style import Font, TextDecoration from pyWebLayout.style import Font, TextDecoration
from .box import Box from .box import Box

View File

@ -3,10 +3,10 @@ from typing import Optional, Tuple, Union, Dict, Any
import numpy as np import numpy as np
from PIL import Image as PILImage, ImageDraw, ImageFont from PIL import Image as PILImage, ImageDraw, ImageFont
from pyWebLayout.base import Renderable, Queriable from pyWebLayout.core.base import Renderable, Queriable
from pyWebLayout.abstract.block import Image as AbstractImage from pyWebLayout.abstract.block import Image as AbstractImage
from .box import Box from .box import Box
from pyWebLayout.layout import Alignment from pyWebLayout.style.layout import Alignment
class RenderableImage(Box, Queriable): class RenderableImage(Box, Queriable):

View File

@ -2,9 +2,9 @@ from typing import List, Tuple, Optional, Dict, Any
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from pyWebLayout.base import Renderable, Layoutable from pyWebLayout.core.base import Renderable, Layoutable
from .box import Box from .box import Box
from pyWebLayout.layout import Alignment from pyWebLayout.style.layout import Alignment
class Container(Box, Layoutable): class Container(Box, Layoutable):

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pyWebLayout.base import Renderable, Queriable from pyWebLayout.core.base import Renderable, Queriable
from .box import Box from .box import Box
from pyWebLayout.layout import Alignment from pyWebLayout.style.layout import Alignment
from pyWebLayout.style import Font, FontStyle, FontWeight, TextDecoration from pyWebLayout.style import Font, FontStyle, FontWeight, TextDecoration
from pyWebLayout.abstract.inline import Word from pyWebLayout.abstract.inline import Word
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont

View File

@ -1,919 +0,0 @@
import re
from html.parser import HTMLParser as BaseHTMLParser
from typing import Dict, List, Optional, Tuple, Union, Any, Set, Callable
import urllib.parse
from PIL import Image
from .style import Font, FontStyle, FontWeight, TextDecoration
from .abstract.document import Document, MetadataType, Book, Chapter
from .abstract.block import (
Block, BlockType, Paragraph, Heading, HeadingLevel, Quote, CodeBlock,
HList, ListStyle, ListItem, Table, TableRow, TableCell, HorizontalRule
)
from .abstract.inline import Word, FormattedSpan, LineBreak
from .abstract.functional import Link, LinkType, Button, Form, FormField, FormFieldType
from .concrete.page import Page
from pyWebLayout.layout import Alignment
class HTMLParser(BaseHTMLParser):
"""
HTML parser that builds an abstract document representation from HTML content.
This parser converts HTML to abstract document classes without any rendering specifics.
"""
def __init__(self, base_url: Optional[str] = None):
"""
Initialize the HTML parser.
Args:
base_url: Base URL for resolving relative links
"""
super().__init__()
# Document structure
self.document = Document()
# State variables
self._current_block = None
self._block_stack: List[Block] = []
# Text handling
self._current_paragraph = None
self._current_span = None
self._text_buffer = ""
# Style state
self._style_stack: List[Dict[str, Any]] = []
self._current_style = {
'font_size': 12,
'font_weight': FontWeight.NORMAL,
'font_style': FontStyle.NORMAL,
'decoration': TextDecoration.NONE,
'color': (0, 0, 0),
'background': None,
'language': 'en_US'
}
# Tag state
self._list_stack: List[HList] = []
self._table_stack: List[Table] = []
self._current_table_row = None
# Link handling
self._base_url = base_url
self._in_link = False
self._current_link = None
# Special state flags
self._in_head = False
self._in_title = False
self._in_script = False
self._in_style = False
self._script_buffer = ""
self._style_buffer = ""
self._title_buffer = ""
def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]):
"""
Handle the start of an HTML tag.
Args:
tag: The tag name
attrs: List of attribute tuples (name, value)
"""
tag = tag.lower()
attrs_dict = dict(attrs)
# Special handling for elements where we collect content
if self._in_script and tag != 'script':
return
if self._in_style and tag != 'style':
return
# Parse style attribute if present
style = {}
if 'style' in attrs_dict:
style = self._parse_style(attrs_dict['style'])
# Apply tag-specific styling based on the tag
tag_style = self._get_tag_style(tag)
for key, value in tag_style.items():
if key not in style:
style[key] = value
# Push the current style and apply the new style
self._push_style(style)
# Handle specific tags
if tag == 'html':
# Set document language if specified
if 'lang' in attrs_dict:
self.document.set_metadata(MetadataType.LANGUAGE, attrs_dict['lang'])
elif tag == 'head':
self._in_head = True
elif tag == 'title' and self._in_head:
self._in_title = True
self._title_buffer = ""
elif tag == 'meta' and self._in_head:
self._handle_meta_tag(attrs_dict)
elif tag == 'link' and self._in_head:
self._handle_link_tag(attrs_dict)
elif tag == 'script':
self._in_script = True
self._script_buffer = ""
elif tag == 'style':
self._in_style = True
self._style_buffer = ""
elif tag == 'body':
# Body attributes can contain style information
pass
elif tag == 'p':
self._flush_text() # Flush any pending text
self._current_paragraph = Paragraph()
# Add the paragraph to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(self._current_paragraph)
else:
self.document.add_block(self._current_paragraph)
# Push to block stack
self._block_stack.append(self._current_paragraph)
self._current_block = self._current_paragraph
elif tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'):
self._flush_text() # Flush any pending text
# Determine heading level
level_map = {
'h1': HeadingLevel.H1,
'h2': HeadingLevel.H2,
'h3': HeadingLevel.H3,
'h4': HeadingLevel.H4,
'h5': HeadingLevel.H5,
'h6': HeadingLevel.H6
}
heading = Heading(level=level_map[tag])
# Add the heading to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(heading)
else:
self.document.add_block(heading)
# Push to block stack
self._block_stack.append(heading)
self._current_block = heading
self._current_paragraph = heading # Heading inherits from Paragraph
elif tag == 'div':
self._flush_text() # Flush any pending text
# For divs, we create a new paragraph as a container
div_para = Paragraph()
# Add the div to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(div_para)
else:
self.document.add_block(div_para)
# Push to block stack
self._block_stack.append(div_para)
self._current_block = div_para
self._current_paragraph = div_para
elif tag == 'blockquote':
self._flush_text() # Flush any pending text
quote = Quote()
# Add the quote to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(quote)
else:
self.document.add_block(quote)
# Push to block stack
self._block_stack.append(quote)
self._current_block = quote
elif tag == 'pre':
self._flush_text() # Flush any pending text
# Pre can optionally contain a code block
# We'll create a paragraph for now, and if we find a code tag inside,
# we'll replace it with a code block
pre_para = Paragraph()
# Add the pre to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(pre_para)
else:
self.document.add_block(pre_para)
# Push to block stack
self._block_stack.append(pre_para)
self._current_block = pre_para
self._current_paragraph = pre_para
elif tag == 'code':
# If we're inside a pre, replace the paragraph with a code block
if self._block_stack and isinstance(self._block_stack[-1], Paragraph):
pre_para = self._block_stack.pop()
# Get the language from class if specified (e.g., class="language-python")
language = ""
if 'class' in attrs_dict:
class_attr = attrs_dict['class']
if class_attr.startswith('language-'):
language = class_attr[9:]
code_block = CodeBlock(language=language)
# Replace the paragraph with the code block
if pre_para.parent:
parent = pre_para.parent
if hasattr(parent, '_blocks'):
# Find the paragraph in the parent's blocks and replace it
for i, block in enumerate(parent._blocks):
if block == pre_para:
parent._blocks[i] = code_block
break
# Push the code block to the stack
self._block_stack.append(code_block)
self._current_block = code_block
self._current_paragraph = None
else:
# If not in a pre, just create a formatted span for code
self._current_span = None # Force creation of a new span with code style
elif tag in ('ul', 'ol', 'dl'):
self._flush_text() # Flush any pending text
# Determine list style
style_map = {
'ul': ListStyle.UNORDERED,
'ol': ListStyle.ORDERED,
'dl': ListStyle.DEFINITION
}
list_block = HList(style=style_map[tag])
# Add the list to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(list_block)
else:
self.document.add_block(list_block)
# Push to block stack and list stack
self._block_stack.append(list_block)
self._list_stack.append(list_block)
self._current_block = list_block
self._current_paragraph = None
elif tag == 'li' and self._list_stack:
self._flush_text() # Flush any pending text
list_item = ListItem()
# Add to the current list
current_list = self._list_stack[-1]
current_list.add_item(list_item)
# Push to block stack
self._block_stack.append(list_item)
self._current_block = list_item
self._current_paragraph = None
elif tag == 'dt' and self._list_stack and self._list_stack[-1].style == ListStyle.DEFINITION:
self._flush_text() # Flush any pending text
# For definition term, we create a list item with a term
list_item = ListItem(term="") # Will be filled by content
# Add to the current list
current_list = self._list_stack[-1]
current_list.add_item(list_item)
# Push to block stack
self._block_stack.append(list_item)
self._current_block = list_item
# Create a paragraph for the term content
term_para = Paragraph()
list_item.add_block(term_para)
self._current_paragraph = term_para
elif tag == 'dd' and self._list_stack and self._list_stack[-1].style == ListStyle.DEFINITION:
self._flush_text() # Flush any pending text
# Find the last dt item
current_list = self._list_stack[-1]
if current_list._items:
list_item = current_list._items[-1]
# Create a paragraph for the description content
desc_para = Paragraph()
list_item.add_block(desc_para)
# Update current state
self._current_paragraph = desc_para
else:
# If no dt found, create a new list item
list_item = ListItem()
current_list.add_item(list_item)
# Push to block stack
self._block_stack.append(list_item)
self._current_block = list_item
# Create a paragraph for the description content
desc_para = Paragraph()
list_item.add_block(desc_para)
self._current_paragraph = desc_para
elif tag == 'table':
self._flush_text() # Flush any pending text
# Create a new table
caption = None
if 'summary' in attrs_dict:
caption = attrs_dict['summary']
table = Table(caption=caption)
# Add the table to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(table)
else:
self.document.add_block(table)
# Push to block stack and table stack
self._block_stack.append(table)
self._table_stack.append(table)
self._current_block = table
self._current_paragraph = None
elif tag in ('thead', 'tbody', 'tfoot') and self._table_stack:
# Just track the current section - no need to create new objects
self._current_table_section = tag
elif tag == 'tr' and self._table_stack:
self._flush_text() # Flush any pending text
# Create a new row
row = TableRow()
# Add to the current table
current_table = self._table_stack[-1]
# Determine the section based on context
section = "body"
if hasattr(self, '_current_table_section'):
if self._current_table_section == 'thead':
section = "header"
elif self._current_table_section == 'tfoot':
section = "footer"
current_table.add_row(row, section=section)
# Update state
self._current_table_row = row
self._current_paragraph = None
elif tag in ('td', 'th') and self._current_table_row:
self._flush_text() # Flush any pending text
# Parse attributes
colspan = 1
rowspan = 1
if 'colspan' in attrs_dict:
try:
colspan = int(attrs_dict['colspan'])
except (ValueError, TypeError):
pass
if 'rowspan' in attrs_dict:
try:
rowspan = int(attrs_dict['rowspan'])
except (ValueError, TypeError):
pass
# Create a new cell
is_header = (tag == 'th')
cell = TableCell(is_header=is_header, colspan=colspan, rowspan=rowspan)
# Add to the current row
self._current_table_row.add_cell(cell)
# Push to block stack
self._block_stack.append(cell)
self._current_block = cell
# Create a paragraph for the cell content
cell_para = Paragraph()
cell.add_block(cell_para)
self._current_paragraph = cell_para
elif tag == 'a':
self._flush_text() # Flush any pending text
# Parse attributes
href = attrs_dict.get('href', '')
title = attrs_dict.get('title', '')
# Determine link type
link_type = LinkType.INTERNAL
if href.startswith('http://') or href.startswith('https://'):
link_type = LinkType.EXTERNAL
elif href.startswith('javascript:'):
link_type = LinkType.FUNCTION
elif href.startswith('api:'):
link_type = LinkType.API
href = href[4:] # Remove api: prefix
# If we have a base URL and the href is relative, resolve it
if self._base_url and not href.startswith(('http://', 'https://', 'javascript:', 'api:', '#')):
href = urllib.parse.urljoin(self._base_url, href)
# Create a Link object
self._current_link = Link(
location=href,
link_type=link_type,
title=title if title else None
)
# Set the flag to indicate we're inside a link
self._in_link = True
# Force creation of a new span with link style
self._current_span = None
elif tag == 'img':
# Handle image
src = attrs_dict.get('src', '')
alt = attrs_dict.get('alt', '')
# Parse width and height if provided
width = None
height = None
if 'width' in attrs_dict:
try:
width = int(attrs_dict['width'])
except (ValueError, TypeError):
pass
if 'height' in attrs_dict:
try:
height = int(attrs_dict['height'])
except (ValueError, TypeError):
pass
# If we have a base URL and the src is relative, resolve it
if self._base_url and not src.startswith(('http://', 'https://')):
src = urllib.parse.urljoin(self._base_url, src)
# Create an Image block
from .abstract.block import Image
image = Image(source=src, alt_text=alt, width=width, height=height)
# Add the image to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(image)
else:
self.document.add_block(image)
# Also add as a resource for backwards compatibility
resource_name = f"img_{len(self.document._resources) + 1}"
self.document.add_resource(resource_name, {
'type': 'image',
'src': src,
'alt': alt,
'width': width,
'height': height,
'image_object': image
})
elif tag == 'br':
# Add a line break
if self._current_paragraph:
line_break = LineBreak()
if hasattr(self._current_paragraph, 'add_block'):
self._current_paragraph.add_block(line_break)
# Flush any text before the break
self._flush_text()
elif tag == 'hr':
self._flush_text() # Flush any pending text
# Create a horizontal rule
hr = HorizontalRule()
# Add to the current block or document
if self._current_block and hasattr(self._current_block, 'add_block'):
self._current_block.add_block(hr)
else:
self.document.add_block(hr)
elif tag in ('b', 'strong'):
# Bold text
self._current_style['font_weight'] = FontWeight.BOLD
self._current_span = None # Force creation of a new span
elif tag in ('i', 'em'):
# Italic text
self._current_style['font_style'] = FontStyle.ITALIC
self._current_span = None # Force creation of a new span
elif tag == 'u':
# Underlined text
self._current_style['decoration'] = TextDecoration.UNDERLINE
self._current_span = None # Force creation of a new span
elif tag == 'span':
# Span can have style attributes
self._current_span = None # Force creation of a new span
elif tag == 'form':
self._flush_text() # Flush any pending text
# Parse attributes
form_id = attrs_dict.get('id', f"form_{len(self.document._resources) + 1}")
action = attrs_dict.get('action', '')
# Create a Form object
form = Form(form_id=form_id, action=action)
# Add as a resource
self.document.add_resource(form_id, form)
# TODO: Create a proper Form block class and add it to the document
elif tag == 'input':
# Parse attributes
input_type = attrs_dict.get('type', 'text')
input_name = attrs_dict.get('name', '')
input_value = attrs_dict.get('value', '')
input_required = 'required' in attrs_dict
# Map HTML input types to FormFieldType
type_map = {
'text': FormFieldType.TEXT,
'password': FormFieldType.PASSWORD,
'checkbox': FormFieldType.CHECKBOX,
'radio': FormFieldType.RADIO,
'number': FormFieldType.NUMBER,
'date': FormFieldType.DATE,
'time': FormFieldType.TIME,
'email': FormFieldType.EMAIL,
'url': FormFieldType.URL,
'color': FormFieldType.COLOR,
'range': FormFieldType.RANGE,
'hidden': FormFieldType.HIDDEN
}
field_type = type_map.get(input_type, FormFieldType.TEXT)
# Create a FormField object
field = FormField(
name=input_name,
field_type=field_type,
label=attrs_dict.get('placeholder', input_name),
value=input_value,
required=input_required
)
# TODO: Add the field to a form if inside a form
elif tag == 'textarea':
# Similar to input but with multiline content
# We'll handle the content in handle_data
pass
elif tag == 'select':
# Similar to input but with options
# We'll handle the options in handle_data
pass
elif tag == 'button':
# Parse attributes
button_type = attrs_dict.get('type', 'button')
button_name = attrs_dict.get('name', '')
# TODO: Create a Button object and add it to the document
def handle_endtag(self, tag: str):
"""
Handle the end of an HTML tag.
Args:
tag: The tag name
"""
tag = tag.lower()
# Special handling for elements where we collect content
if tag == 'script' and self._in_script:
self._in_script = False
self.document.add_script(self._script_buffer)
self._script_buffer = ""
self._pop_style()
return
if tag == 'style' and self._in_style:
self._in_style = False
# Parse the style and add to document
stylesheet = self._parse_css(self._style_buffer)
if stylesheet:
self.document.add_stylesheet(stylesheet)
self._style_buffer = ""
self._pop_style()
return
if tag == 'title' and self._in_title:
self._in_title = False
self.document.set_title(self._title_buffer.strip())
self._title_buffer = ""
self._pop_style()
return
if self._in_script and tag != 'script':
return
if self._in_style and tag != 'style':
return
# Flush any accumulated text
self._flush_text()
# Handle specific end tags
if tag == 'head':
self._in_head = False
elif tag == 'body':
pass # Nothing special to do
elif tag in ('p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'):
# Pop from block stack
if self._block_stack:
self._block_stack.pop()
# Update current block
if self._block_stack:
self._current_block = self._block_stack[-1]
else:
self._current_block = None
# Reset current paragraph
self._current_paragraph = None
self._current_span = None
elif tag == 'code':
# If we're inside a code block, no need to do anything special
pass
elif tag in ('ul', 'ol', 'dl'):
# Pop from block stack and list stack
if self._block_stack:
self._block_stack.pop()
if self._list_stack:
self._list_stack.pop()
# Update current block
if self._block_stack:
self._current_block = self._block_stack[-1]
else:
self._current_block = None
# Reset current paragraph
self._current_paragraph = None
self._current_span = None
elif tag in ('li', 'dt', 'dd'):
# Pop from block stack
if self._block_stack:
self._block_stack.pop()
# Update current block
if self._block_stack:
self._current_block = self._block_stack[-1]
else:
self._current_block = None
# Reset current paragraph
self._current_paragraph = None
self._current_span = None
elif tag == 'table':
# Pop from block stack and table stack
if self._block_stack:
self._block_stack.pop()
if self._table_stack:
self._table_stack.pop()
# Update current block
if self._block_stack:
self._current_block = self._block_stack[-1]
else:
self._current_block = None
# Reset current paragraph and table state
self._current_paragraph = None
self._current_span = None
self._current_table_row = None
if hasattr(self, '_current_table_section'):
delattr(self, '_current_table_section')
elif tag in ('thead', 'tbody', 'tfoot'):
# Clear current section
if hasattr(self, '_current_table_section'):
delattr(self, '_current_table_section')
elif tag == 'tr':
# Reset current row
self._current_table_row = None
elif tag in ('td', 'th'):
# Pop from block stack
if self._block_stack:
self._block_stack.pop()
# Update current block
if self._block_stack:
self._current_block = self._block_stack[-1]
else:
self._current_block = None
# Reset current paragraph
self._current_paragraph = None
self._current_span = None
elif tag == 'a':
# End of link
self._in_link = False
self._current_link = None
elif tag in ('b', 'strong', 'i', 'em', 'u', 'span'):
# End of styled text
self._current_span = None
# Pop style regardless of tag
self._pop_style()
def handle_data(self, data: str):
"""
Handle text data.
Args:
data: The text data
"""
if self._in_script:
self._script_buffer += data
return
if self._in_style:
self._style_buffer += data
return
if self._in_title:
self._title_buffer += data
return
# Add to text buffer
self._text_buffer += data
def handle_entityref(self, name: str):
"""
Handle an HTML entity reference.
Args:
name: The entity name
"""
# Map common entity references to characters
entities = {
'lt': '<',
'gt': '>',
'amp': '&',
'quot': '"',
'apos': "'",
'nbsp': ' ',
'copy': '©',
'reg': '®',
'trade': '',
}
if name in entities:
char = entities[name]
else:
try:
import html.entities
char = chr(html.entities.name2codepoint[name])
except (KeyError, ImportError):
char = f'&{name};'
# Handle based on context
if self._in_script:
self._script_buffer += char
elif self._in_style:
self._style_buffer += char
elif self._in_title:
self._title_buffer += char
else:
self._text_buffer += char
def handle_charref(self, name: str):
"""
Handle a character reference.
Args:
name: The character reference (decimal or hex)
"""
# Convert character reference to character
if name.startswith('x'):
# Hexadecimal reference
char = chr(int(name[1:], 16))
else:
# Decimal reference
char = chr(int(name))
# Handle based on context
if self._in_script:
self._script_buffer += char
elif self._in_style:
self._style_buffer += char
elif self._in_title:
self._title_buffer += char
else:
self._text_buffer += char
def _push_style(self, style: Dict[str, Any]):
"""
Push a new style onto the style stack.
Args:
style: The style to push
"""
# Save the current style
self._style_stack.append(self._current_style.copy())
# Apply the new style
for key, value in style.items():
self._current_style[key] = value
def _pop_style(self):
"""Pop a style from the style stack."""
if self._style_stack:
self._current_style = self._style_stack.pop()
def _get_tag_style(self, tag: str) -> Dict[str, Any]:
"""
Get the default style for a tag.
Args:
tag: The tag name
Returns:
A dictionary of style properties
"""
# Default styles for common tags
tag_styles = {
'h1': {'font_size': 24, 'font_weight': FontWeight.BOLD},
'h2': {'font_size': 20, 'font_weight': FontWeight.BOLD},
'h3': {'font_size': 18, 'font_weight': FontWeight.BOLD},
'h4': {'font_size': 16, 'font_weight': FontWeight.BOLD},
'h5': {'font_size': 14, 'font_weight': FontWeight.BOLD},
'h6': {'font_size': 12, 'font_weight': FontWeight.BOLD},
'b': {'font_weight': FontWeight.BOLD},
'strong': {'font_weight': FontWeight.BOLD},
'i': {'font_style': FontStyle.ITALIC},
'em': {'font_style': FontStyle.ITALIC},
'u': {'decoration': TextDecoration.UNDERLINE},
'a': {'decoration': TextDecoration.UNDERLINE, 'color': (0, 0, 255)},
'code': {'font_family': 'monospace', 'background': (240, 240, 240, 255)},
'pre': {'font_family': 'monospace'},
}
return tag_styles.get(tag, {})
def _create_font(self) -> Font:
"""
Create a Font object from the current style.
Returns:
Font: A font object with the current style settings
"""

View File

@ -1 +0,0 @@
## list langauges

View File

@ -1,176 +0,0 @@
# this should contain classes for how different object can be rendered, e.g. bold, italic, regular
from PIL import ImageFont
from enum import Enum
from typing import Tuple, Union, Optional
class FontWeight(Enum):
NORMAL = "normal"
BOLD = "bold"
class FontStyle(Enum):
NORMAL = "normal"
ITALIC = "italic"
class TextDecoration(Enum):
NONE = "none"
UNDERLINE = "underline"
STRIKETHROUGH = "strikethrough"
class Font:
"""
Font class to manage text rendering properties including font face, size, color, and styling.
This class is used by the text renderer to determine how to render text.
"""
def __init__(self,
font_path: Optional[str] = None,
font_size: int = 12,
colour: Tuple[int, int, int] = (0, 0, 0),
weight: FontWeight = FontWeight.NORMAL,
style: FontStyle = FontStyle.NORMAL,
decoration: TextDecoration = TextDecoration.NONE,
background: Optional[Tuple[int, int, int, int]] = None,
language = "en_EN"):
"""
Initialize a Font object with the specified properties.
Args:
font_path: Path to the font file (.ttf, .otf). If None, uses default font.
font_size: Size of the font in points.
colour: RGB color tuple for the text.
weight: Font weight (normal or bold).
style: Font style (normal or italic).
decoration: Text decoration (none, underline, or strikethrough).
background: RGBA background color for the text. If None, transparent background.
"""
self._font_path = font_path
self._font_size = font_size
self._colour = colour
self._weight = weight
self._style = style
self._decoration = decoration
self._background = background if background else (255, 255, 255, 0)
self.language = language
# Load the font file or use default
self._load_font()
def _load_font(self):
"""Load the font using PIL's ImageFont"""
try:
if self._font_path:
self._font = ImageFont.truetype(
self._font_path,
self._font_size
)
else:
# Use default font
self._font = ImageFont.load_default()
if self._font_size != 12: # Default size might not be 12
self._font = ImageFont.truetype(self._font.path, self._font_size)
except Exception as e:
print(f"Error loading font: {e}")
self._font = ImageFont.load_default()
@property
def font(self):
"""Get the PIL ImageFont object"""
return self._font
@property
def font_size(self):
"""Get the font size"""
return self._font_size
@property
def colour(self):
"""Get the text color"""
return self._colour
@property
def color(self):
"""Alias for colour (American spelling)"""
return self._colour
@property
def background(self):
"""Get the background color"""
return self._background
@property
def weight(self):
"""Get the font weight"""
return self._weight
@property
def style(self):
"""Get the font style"""
return self._style
@property
def decoration(self):
"""Get the text decoration"""
return self._decoration
def with_size(self, size: int):
"""Create a new Font object with modified size"""
return Font(
self._font_path,
size,
self._colour,
self._weight,
self._style,
self._decoration,
self._background
)
def with_colour(self, colour: Tuple[int, int, int]):
"""Create a new Font object with modified colour"""
return Font(
self._font_path,
self._font_size,
colour,
self._weight,
self._style,
self._decoration,
self._background
)
def with_weight(self, weight: FontWeight):
"""Create a new Font object with modified weight"""
return Font(
self._font_path,
self._font_size,
self._colour,
weight,
self._style,
self._decoration,
self._background
)
def with_style(self, style: FontStyle):
"""Create a new Font object with modified style"""
return Font(
self._font_path,
self._font_size,
self._colour,
self._weight,
style,
self._decoration,
self._background
)
def with_decoration(self, decoration: TextDecoration):
"""Create a new Font object with modified decoration"""
return Font(
self._font_path,
self._font_size,
self._colour,
self._weight,
self._style,
decoration,
self._background
)

View File

@ -1,137 +0,0 @@
from pyWebLayout.base import Renderable
from .concrete.box import Box
from pyWebLayout.layout import Alignment
import numpy as np
from PIL import Image, ImageDraw
from typing import List, Tuple, Optional
class TableCell(Box):
def __init__(self, origin, size, content: Optional[Renderable] = None,
callback=None, sheet=None, mode=None,
halign=Alignment.CENTER, valign=Alignment.CENTER,
padding: Tuple[int, int, int, int] = (5, 5, 5, 5)):
"""
Initialize a table cell.
Args:
origin: Top-left corner coordinates
size: Width and height of the cell
content: Optional renderable content to place in the cell
callback: Optional callback function
sheet: Optional image sheet
mode: Optional image mode
halign: Horizontal alignment
valign: Vertical alignment
padding: Padding as (top, right, bottom, left)
"""
super().__init__(origin, size, callback, sheet, mode, halign, valign)
self._content = content
self._padding = padding # (top, right, bottom, left)
def set_content(self, content: Renderable):
"""Set the content of this cell"""
self._content = content
def render(self) -> Image:
"""Render the cell with its content and border"""
# Create the base canvas
canvas = super().render()
draw = ImageDraw.Draw(canvas)
# Draw border (optional - can be customized)
draw.rectangle([(0, 0), tuple(self._size - np.array([1, 1]))],
outline=(0, 0, 0), width=1)
return canvas
class Table(Box):
def __init__(self, rows: int, columns: int, origin, size,
cell_padding: Tuple[int, int, int, int] = (5, 5, 5, 5),
callback=None, sheet=None, mode=None,
halign=Alignment.CENTER, valign=Alignment.CENTER):
"""
Initialize a table with specified number of rows and columns.
Args:
rows: Number of rows in the table
columns: Number of columns in the table
origin: Top-left corner coordinates
size: Width and height of the table
cell_padding: Padding for each cell as (top, right, bottom, left)
callback: Optional callback function
sheet: Optional image sheet
mode: Optional image mode
halign: Horizontal alignment
valign: Vertical alignment
"""
super().__init__(origin, size, callback, sheet, mode, halign, valign)
self._rows = rows
self._columns = columns
self._cell_padding = cell_padding
# Calculate cell dimensions
cell_width = size[0] // columns
cell_height = size[1] // rows
# Create a 2D array of cells
self._cells: List[List[TableCell]] = []
for row in range(rows):
cell_row = []
for col in range(columns):
# Calculate cell position
cell_origin = np.array([col * cell_width, row * cell_height])
cell_size = np.array([cell_width, cell_height])
# Create the cell
cell = TableCell(
origin=cell_origin,
size=cell_size,
sheet=sheet,
mode=mode,
halign=halign,
valign=valign,
padding=cell_padding
)
cell_row.append(cell)
self._cells.append(cell_row)
def add_to_cell(self, x: int, y: int, content: Renderable):
"""
Add content to a specific cell in the table.
Args:
x: Column index (0-based)
y: Row index (0-based)
content: Renderable content to add to the cell
"""
if 0 <= y < self._rows and 0 <= x < self._columns:
self._cells[y][x].set_content(content)
else:
raise IndexError(f"Cell indices ({x}, {y}) out of range. Table is {self._columns}x{self._rows}")
def render(self) -> Image:
"""Render the complete table with all cells"""
# Create base canvas
canvas = super().render()
# Render each cell and paste it onto the canvas
for row in range(self._rows):
for col in range(self._columns):
cell = self._cells[row][col]
cell_img = cell.render()
# Get the position for this cell
cell_pos = (col * (self._size[0] // self._columns),
row * (self._size[1] // self._rows))
# Paste the cell onto the canvas
canvas.paste(cell_img, cell_pos, cell_img)
return canvas

View File

@ -17,6 +17,7 @@ dependencies = [
"numpy", "numpy",
"pyphen", "pyphen",
"beautifulsoup4", "beautifulsoup4",
"flask"
] ]
[tool.coverage.run] [tool.coverage.run]