mirror of
https://github.com/1j01/textual-paint.git
synced 2024-09-11 15:56:03 +03:00
WIP: open SVG files saved with the app
Progress! print
This commit is contained in:
parent
55419d671e
commit
3892dc503e
@ -57,6 +57,7 @@
|
||||
"pypixelart",
|
||||
"pyright",
|
||||
"pyxdg",
|
||||
"rects",
|
||||
"renderable",
|
||||
"rmam",
|
||||
"setterm",
|
||||
@ -69,6 +70,8 @@
|
||||
"textconv",
|
||||
"tlaplus",
|
||||
"truecolor",
|
||||
"tspan",
|
||||
"tspans",
|
||||
"ufeff",
|
||||
"undoable",
|
||||
"undos",
|
||||
|
524
samples/combative_character_grid.svg
Normal file
524
samples/combative_character_grid.svg
Normal file
@ -0,0 +1,524 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09, custom)"
|
||||
sodipodi:docname="combative_character_grid.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.9647944"
|
||||
inkscape:cx="200.18252"
|
||||
inkscape:cy="285.17998"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#666666;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect1071"
|
||||
width="59.459286"
|
||||
height="54.615498"
|
||||
x="23.159849"
|
||||
y="44.881004" />
|
||||
<rect
|
||||
style="fill:#00ff00;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect234"
|
||||
width="7.1056752"
|
||||
height="7.3895073"
|
||||
x="29.769499"
|
||||
y="51.718258" />
|
||||
<rect
|
||||
style="fill:#00ff00;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect236"
|
||||
width="7.187295"
|
||||
height="7.2463527"
|
||||
x="36.875175"
|
||||
y="59.107765" />
|
||||
<rect
|
||||
style="fill:#00ff00;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect238"
|
||||
width="7.2217975"
|
||||
height="7.1936398"
|
||||
x="44.716133"
|
||||
y="51.737007" />
|
||||
<rect
|
||||
style="fill:#00ff00;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect240"
|
||||
width="7.6014237"
|
||||
height="7.6129446"
|
||||
x="51.937931"
|
||||
y="58.930649" />
|
||||
<rect
|
||||
style="fill:#00ff00;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect242"
|
||||
width="7.1834297"
|
||||
height="7.024179"
|
||||
x="60.015602"
|
||||
y="51.594803" />
|
||||
<rect
|
||||
style="fill:#00ff00;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect244"
|
||||
width="8.8927784"
|
||||
height="8.2646675"
|
||||
x="67.199036"
|
||||
y="58.61898" />
|
||||
<rect
|
||||
style="fill:#00ffff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect300"
|
||||
width="7.187294"
|
||||
height="7.3895073"
|
||||
x="36.875175"
|
||||
y="51.718258" />
|
||||
<rect
|
||||
style="fill:#00ffff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect302"
|
||||
width="7.6014252"
|
||||
height="7.1936412"
|
||||
x="51.937931"
|
||||
y="51.737007" />
|
||||
<rect
|
||||
style="fill:#00ffff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect306"
|
||||
width="8.8927803"
|
||||
height="5.4191537"
|
||||
x="67.199036"
|
||||
y="53.199825" />
|
||||
<rect
|
||||
style="fill:#00ffff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect308"
|
||||
width="7.6099586"
|
||||
height="6.5727797"
|
||||
x="59.956795"
|
||||
y="59.928776" />
|
||||
<rect
|
||||
style="fill:#00ffff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect310"
|
||||
width="5.4237928"
|
||||
height="5.424016"
|
||||
x="45.091705"
|
||||
y="59.720421" />
|
||||
<rect
|
||||
style="fill:#00ffff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect312"
|
||||
width="6.9445157"
|
||||
height="6.687995"
|
||||
x="28.791224"
|
||||
y="60.221149" />
|
||||
<rect
|
||||
style="fill:#008080;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect366"
|
||||
width="6.9887633"
|
||||
height="7.5608659"
|
||||
x="37.215893"
|
||||
y="67.071541" />
|
||||
<rect
|
||||
style="fill:#008080;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect368"
|
||||
width="6.2547202"
|
||||
height="6.9023681"
|
||||
x="52.295708"
|
||||
y="67.335579" />
|
||||
<rect
|
||||
style="fill:#008080;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect370"
|
||||
width="8.3901863"
|
||||
height="7.4900045"
|
||||
x="67.705612"
|
||||
y="67.190552" />
|
||||
<rect
|
||||
style="fill:#008080;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect374"
|
||||
width="8.0209265"
|
||||
height="8.4574337"
|
||||
x="29.288734"
|
||||
y="75.805641" />
|
||||
<rect
|
||||
style="fill:#008080;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect376"
|
||||
width="6.3965511"
|
||||
height="8.722991"
|
||||
x="44.968143"
|
||||
y="74.749695" />
|
||||
<rect
|
||||
style="fill:#008080;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect378"
|
||||
width="8.4150238"
|
||||
height="7.7497745"
|
||||
x="58.147022"
|
||||
y="74.808571" />
|
||||
<rect
|
||||
style="fill:#ff00ff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect432"
|
||||
width="6.865623"
|
||||
height="7.784771"
|
||||
x="29.335079"
|
||||
y="67.875359" />
|
||||
<rect
|
||||
style="fill:#ff00ff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect434"
|
||||
width="5.8369441"
|
||||
height="6.5972581"
|
||||
x="44.66724"
|
||||
y="67.808578" />
|
||||
<rect
|
||||
style="fill:#ff00ff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect436"
|
||||
width="7.024261"
|
||||
height="6.9038763"
|
||||
x="59.493877"
|
||||
y="67.325905" />
|
||||
<rect
|
||||
style="fill:#ff00ff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect438"
|
||||
width="7.8678956"
|
||||
height="7.5887809"
|
||||
x="67.85334"
|
||||
y="75.658279" />
|
||||
<rect
|
||||
style="fill:#ff00ff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect440"
|
||||
width="5.583735"
|
||||
height="7.7397962"
|
||||
x="52.176022"
|
||||
y="75.317574" />
|
||||
<rect
|
||||
style="fill:#ff00ff;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect442"
|
||||
width="7.6048656"
|
||||
height="8.1720171"
|
||||
x="37.447536"
|
||||
y="75.883972" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="31.517002"
|
||||
y="57.453945"
|
||||
id="text498"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan496"
|
||||
style="stroke-width:0.264583"
|
||||
x="31.517002"
|
||||
y="57.453945">a</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="46.675823"
|
||||
y="56.624329"
|
||||
id="text502"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan500"
|
||||
style="stroke-width:0.264583"
|
||||
x="46.675823"
|
||||
y="56.624329">c</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="62.156319"
|
||||
y="57.102715"
|
||||
id="text506"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan504"
|
||||
style="stroke-width:0.264583"
|
||||
x="62.156319"
|
||||
y="57.102715">e</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="38.029388"
|
||||
y="58.176018"
|
||||
id="text510"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan508"
|
||||
style="stroke-width:0.264583"
|
||||
x="38.029388"
|
||||
y="58.176018">b</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="53.024918"
|
||||
y="57.744404"
|
||||
id="text514"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan512"
|
||||
style="stroke-width:0.264583"
|
||||
x="53.024918"
|
||||
y="57.744404">d</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="68.862602"
|
||||
y="57.419685"
|
||||
id="text568"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan566"
|
||||
style="stroke-width:0.264583"
|
||||
x="68.862602"
|
||||
y="57.419685">f</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="31.138842"
|
||||
y="65.673546"
|
||||
id="text572"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan570"
|
||||
style="stroke-width:0.264583"
|
||||
x="31.138842"
|
||||
y="65.673546">1</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="38.994606"
|
||||
y="64.295349"
|
||||
id="text576"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan574"
|
||||
style="stroke-width:0.264583"
|
||||
x="38.994606"
|
||||
y="64.295349">2</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="46.905895"
|
||||
y="64.697449"
|
||||
id="text580"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan578"
|
||||
style="stroke-width:0.264583"
|
||||
x="46.905895"
|
||||
y="64.697449">3</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="54.023323"
|
||||
y="65.124672"
|
||||
id="text584"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan582"
|
||||
style="stroke-width:0.264583"
|
||||
x="54.023323"
|
||||
y="65.124672">4</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="62.525013"
|
||||
y="65.455658"
|
||||
id="text588"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan586"
|
||||
style="stroke-width:0.264583"
|
||||
x="62.525013"
|
||||
y="65.455658">5</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="69.084427"
|
||||
y="64.884781"
|
||||
id="text592"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan590"
|
||||
style="stroke-width:0.264583"
|
||||
x="69.084427"
|
||||
y="64.884781">6</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="30.446806"
|
||||
y="73.221672"
|
||||
id="text596"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan594"
|
||||
style="stroke-width:0.264583"
|
||||
x="30.446806"
|
||||
y="73.221672">!</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="38.135998"
|
||||
y="73.597939"
|
||||
id="text600"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan598"
|
||||
style="stroke-width:0.264583"
|
||||
x="38.135998"
|
||||
y="73.597939">@</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="52.994148"
|
||||
y="72.917671"
|
||||
id="text604"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan602"
|
||||
style="stroke-width:0.264583"
|
||||
x="52.994148"
|
||||
y="72.917671">%</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="45.951057"
|
||||
y="74.073425"
|
||||
id="text608"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan606"
|
||||
style="stroke-width:0.264583"
|
||||
x="45.951057"
|
||||
y="74.073425">#</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="60.751328"
|
||||
y="73.908005"
|
||||
id="text612"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan610"
|
||||
style="stroke-width:0.264583"
|
||||
x="60.751328"
|
||||
y="73.908005">^</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="69.583992"
|
||||
y="73.499489"
|
||||
id="text616"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan614"
|
||||
style="stroke-width:0.264583"
|
||||
x="69.583992"
|
||||
y="73.499489">&</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="29.849113"
|
||||
y="83.530884"
|
||||
id="text620"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan618"
|
||||
style="stroke-width:0.264583"
|
||||
x="29.849113"
|
||||
y="83.530884">*</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="38.157631"
|
||||
y="82.527267"
|
||||
id="text624"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan622"
|
||||
style="stroke-width:0.264583"
|
||||
x="38.157631"
|
||||
y="82.527267">(</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="45.558525"
|
||||
y="82.517113"
|
||||
id="text628"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan626"
|
||||
style="stroke-width:0.264583"
|
||||
x="45.558525"
|
||||
y="82.517113">)</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="53.204704"
|
||||
y="82.395538"
|
||||
id="text632"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan630"
|
||||
style="stroke-width:0.264583"
|
||||
x="53.204704"
|
||||
y="82.395538">-</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="59.230049"
|
||||
y="81.894844"
|
||||
id="text636"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan634"
|
||||
style="stroke-width:0.264583"
|
||||
x="59.230049"
|
||||
y="81.894844">=</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583;fill:#d40000"
|
||||
x="68.591667"
|
||||
y="82.460602"
|
||||
id="text640"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan638"
|
||||
style="stroke-width:0.264583;fill:#d40000"
|
||||
x="68.591667"
|
||||
y="82.460602">+</tspan></text>
|
||||
<rect
|
||||
style="fill:#d40000;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect746"
|
||||
width="6.7182059"
|
||||
height="6.9547753"
|
||||
x="29.621984"
|
||||
y="85.841278" />
|
||||
<rect
|
||||
style="fill:#ffff00;stroke:#000000;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
|
||||
id="rect748"
|
||||
width="6.0744557"
|
||||
height="7.4931664"
|
||||
x="45.746864"
|
||||
y="85.3237" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="60.07822"
|
||||
y="91.148125"
|
||||
id="text752"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan750"
|
||||
style="stroke-width:0.264583"
|
||||
x="60.07822"
|
||||
y="91.148125">text</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
|
||||
x="46.475227"
|
||||
y="91.889862"
|
||||
id="text1699"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1697"
|
||||
style="stroke-width:0.264583;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
|
||||
x="46.475227"
|
||||
y="91.889862" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:10.5833px;line-height:1.25;font-family:'Cloister Black';-inkscape-font-specification:'Cloister Black, ';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
|
||||
x="45.396358"
|
||||
y="93.368546"
|
||||
id="text1703"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1701"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:sans-serif;stroke-width:0.264583"
|
||||
x="45.396358"
|
||||
y="93.368546">✅</tspan></text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 20 KiB |
@ -1166,6 +1166,121 @@ class AnsiArtDocument:
|
||||
document.bg[y][x] = "#" + hex(r)[2:].zfill(2) + hex(g)[2:].zfill(2) + hex(b)[2:].zfill(2)
|
||||
return document
|
||||
|
||||
@staticmethod
|
||||
def from_svg(svg: str, default_bg: str = "#ffffff", default_fg: str = "#000000") -> 'AnsiArtDocument':
|
||||
"""Creates a document from an SVG containing a character grid with rects for cell backgrounds.
|
||||
|
||||
- rect elements can be in any order.
|
||||
- rect elements can even be in different groups, however transforms are not considered.
|
||||
- rect elements can vary in size slightly.
|
||||
- rect elements can be missing, in which case the default background is used.
|
||||
- rects that are outlying in size are ignored.
|
||||
- rects that are outlying in position, however, increase the document size.
|
||||
- fill or style attributes are used to determine the background/foreground colors.
|
||||
- text elements are assumed to be belonging to the cell their x/y is within,
|
||||
without regard to their size/alignment.
|
||||
- parsing stylesheets is not supported.
|
||||
- not all CSS/SVG color formats are supported.
|
||||
|
||||
To test the flexibility of this loader, I created combative_character_grid.svg
|
||||
It contains out-of-order unevenly sized rects, missing rects, a background rect, and an emoji.
|
||||
It doesn't currently contain varying color formats, font sizes, alignments, or transforms,
|
||||
and it only uses style rather than fill, and text with tspan rather than text without.
|
||||
"""
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(svg)
|
||||
# Search for rect elements to define the background, and the cell locations.
|
||||
rects = root.findall(".//{http://www.w3.org/2000/svg}rect")
|
||||
if len(rects) == 0:
|
||||
raise ValueError("No rect elements found in SVG.")
|
||||
# Find the cell size, removing outliers until all cells are within
|
||||
# a certain relative difference from the average size.
|
||||
max_relative_difference = 0.1
|
||||
for attribute in ["width", "height"]:
|
||||
settled = False
|
||||
while not settled:
|
||||
settled = True
|
||||
avg = sum(float(rect.attrib[attribute]) for rect in rects) / len(rects)
|
||||
for rect in rects:
|
||||
if abs(float(rect.attrib[attribute]) - avg) / avg > max_relative_difference:
|
||||
rects.remove(rect)
|
||||
settled = False
|
||||
print("Ignoring outlier rect: " + ET.tostring(rect, encoding="unicode"))
|
||||
break
|
||||
else:
|
||||
break
|
||||
# Find the cell size.
|
||||
# TODO: use spacing, not average size.
|
||||
cell_width = sum(float(rect.attrib["width"]) for rect in rects) / len(rects)
|
||||
cell_height = sum(float(rect.attrib["height"]) for rect in rects) / len(rects)
|
||||
# Find the document bounds.
|
||||
min_x = min(float(rect.attrib["x"]) for rect in rects)
|
||||
min_y = min(float(rect.attrib["y"]) for rect in rects)
|
||||
max_x = max(float(rect.attrib["x"]) + float(rect.attrib["width"]) for rect in rects)
|
||||
max_y = max(float(rect.attrib["y"]) + float(rect.attrib["height"]) for rect in rects)
|
||||
width = int((max_x - min_x) / cell_width)
|
||||
height = int((max_y - min_y) / cell_height)
|
||||
# Create the document.
|
||||
document = AnsiArtDocument(width, height, default_bg, default_fg)
|
||||
# Fill the document with the background colors.
|
||||
def get_fill(el: ET.Element) -> Optional[str]:
|
||||
fill = None
|
||||
try:
|
||||
fill = el.attrib["fill"]
|
||||
except KeyError:
|
||||
try:
|
||||
style = el.attrib["style"]
|
||||
except KeyError:
|
||||
print("Warning: element has no fill or style attribute: " + ET.tostring(el, encoding="unicode"))
|
||||
else:
|
||||
for style_part in style.split(";"):
|
||||
if style_part.startswith("fill:"):
|
||||
fill = style_part[len("fill:"):]
|
||||
break
|
||||
if fill is None:
|
||||
print("Warning: element has no fill defined: " + ET.tostring(el, encoding="unicode"))
|
||||
return None
|
||||
r, g, b = Color.parse(fill).rgb
|
||||
return "#" + hex(r)[2:].zfill(2) + hex(g)[2:].zfill(2) + hex(b)[2:].zfill(2)
|
||||
|
||||
for rect in rects:
|
||||
x = int((float(rect.attrib["x"]) - min_x) / cell_width)
|
||||
y = int((float(rect.attrib["y"]) - min_y) / cell_height)
|
||||
|
||||
fill = get_fill(rect)
|
||||
if fill is not None:
|
||||
document.bg[y][x] = fill
|
||||
|
||||
# Find text elements to define the foreground.
|
||||
texts = root.findall(".//{http://www.w3.org/2000/svg}text")
|
||||
if len(texts) == 0:
|
||||
raise ValueError("No text elements found in SVG.")
|
||||
for text in texts:
|
||||
x = int((float(text.attrib["x"]) - min_x) / cell_width)
|
||||
y = int((float(text.attrib["y"]) - min_y) / cell_height)
|
||||
ch = text.text
|
||||
if ch is None:
|
||||
# look for tspan element(s)
|
||||
tspans = text.findall(".//{http://www.w3.org/2000/svg}tspan")
|
||||
if len(tspans) == 0:
|
||||
print("Warning: text element has no text or tspan: " + ET.tostring(text, encoding="unicode"))
|
||||
continue
|
||||
ch = ""
|
||||
for tspan in tspans:
|
||||
if tspan.text is not None:
|
||||
ch += tspan.text
|
||||
else:
|
||||
print("Warning: tspan element has no text: " + ET.tostring(tspan, encoding="unicode"))
|
||||
fill = get_fill(text)
|
||||
try:
|
||||
document.ch[y][x] = ch
|
||||
if fill is not None:
|
||||
document.fg[y][x] = fill
|
||||
except IndexError:
|
||||
print("Warning: text element is out of bounds: " + ET.tostring(text, encoding="unicode"))
|
||||
continue
|
||||
return document
|
||||
|
||||
@staticmethod
|
||||
def decode_based_on_file_extension(content: bytes, file_path: str, default_bg: str = "#ffffff", default_fg: str = "#000000") -> 'AnsiArtDocument':
|
||||
"""Creates a document from the given bytes, detecting the file format.
|
||||
@ -1184,7 +1299,9 @@ class AnsiArtDocument:
|
||||
return AnsiArtDocument.from_ansi(content.decode('utf-8'), default_bg, default_fg)
|
||||
elif format_id == "PLAINTEXT":
|
||||
return AnsiArtDocument.from_plain(content.decode('utf-8'), default_bg, default_fg)
|
||||
elif format_id in Image.SAVE or format_id in ["HTML", "SVG", "RICH_CONSOLE_MARKUP"]:
|
||||
elif format_id == "SVG":
|
||||
return AnsiArtDocument.from_svg(content.decode('utf-8'), default_bg, default_fg)
|
||||
elif format_id in Image.SAVE or format_id in ["HTML", "RICH_CONSOLE_MARKUP"]:
|
||||
# This is a write-only format.
|
||||
raise FormatReadNotSupported(localized_message=_("Cannot read files saved as %1 format.", format_id))
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user