├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── asciidoctor-example ├── asciidoctor-html.sh ├── asciidoctor-pdf.sh ├── example.adoc ├── example.html ├── example.pdf ├── wavedrom_test0.svg ├── wavedrom_test1.svg ├── wavedrom_test10.svg ├── wavedrom_test11.svg ├── wavedrom_test12.svg ├── wavedrom_test1n.svg ├── wavedrom_test2.svg ├── wavedrom_test3.svg ├── wavedrom_test4.svg ├── wavedrom_test5.svg ├── wavedrom_test6.svg ├── wavedrom_test7.svg ├── wavedrom_test8.svg └── wavedrom_test9.svg ├── css └── default.css ├── doc ├── demo1.svg ├── demo2.svg └── demo3.svg ├── setup.cfg ├── setup.py ├── test ├── brick_regressions.py ├── conftest.py ├── diff.py ├── files │ ├── assign_74ls688.json │ ├── assign_binary2gray.json │ ├── assign_gray2binary.json │ ├── assign_iec60617.json │ ├── assign_xor.json │ ├── bitfield_0.json │ ├── issue_10.json │ ├── issue_11.json │ ├── issue_13.json │ ├── issue_14.json │ ├── issue_16.json │ ├── issue_37.json │ ├── issue_7.json │ ├── signal_0.json │ ├── subcycle_0.json │ ├── subcycle_1.json │ ├── tutorial_0.json │ ├── tutorial_0n.json │ ├── tutorial_1.json │ ├── tutorial_10.json │ ├── tutorial_11.json │ ├── tutorial_12.json │ ├── tutorial_1n.json │ ├── tutorial_2.json │ ├── tutorial_3.json │ ├── tutorial_4.json │ ├── tutorial_5.json │ ├── tutorial_6.json │ ├── tutorial_7.json │ ├── tutorial_8.json │ └── tutorial_9.json ├── single_test.py ├── test_brick_regression.py └── test_render.py ├── tox.ini └── wavedrom ├── __init__.py ├── assign.py ├── attrdict.py ├── base.py ├── bitfield.py ├── css.py ├── tspan.py ├── waveform.py └── waveskin.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | wavedrom/version.py 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | test/*.svg 105 | test/*.png 106 | test/*.pdf 107 | 108 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.7" 4 | - "3.8" 5 | - "3.9" 6 | - "3.10" 7 | 8 | before_install: 9 | - sudo apt-get install -y npm 10 | 11 | install: 12 | - pip install tox-travis 13 | 14 | script: 15 | - tox 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-2019 Aliaksei Chapyzhenka, BreizhGeek, Kazuki Yamamoto, 4 | MutantPlatypus, Stefan Wallentowitz 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WaveDromPy 2 | 3 | This is a python module and command line fully compatible with [WaveDrom](https://wavedrom.com/), which is originally implemented in JavaScript. It is useful if you want to generate wavedrom diagrams from a python environment or simply don't want to install the _Node.js_ environment just to use WaveDrom as simple command line. 4 | 5 | WaveDromPy is for example used in [sphinxcontrib-wavedrom](https://pypi.org/project/sphinxcontrib-wavedrom/) to render wavedrom for Sphinx documentation. While the original project renders the diagrams in JavaScript in the browser, WaveDromPy renders them to SVG files. 6 | 7 | This tool is a direct translation of original Javascript file _WaveDrom.js_ to Python. No extra feature added. We seek to have it fully compatible. 8 | 9 | The tool _WaveDromPy_ directly converts _WaveDrom_ compatible JSON files into SVG format. 10 | 11 | [![Build Status](https://travis-ci.org/wallento/wavedrompy.svg?branch=master)](https://travis-ci.org/wallento/wavedrompy) 12 | [![PyPI version](https://badge.fury.io/py/wavedrom.svg)](https://badge.fury.io/py/wavedrom) 13 | 14 | ## Installation 15 | 16 | It is most easy to just install wavedrom via pip/pypi: 17 | 18 | pip install wavedrom 19 | 20 | Alternatively you can install the latest version from this repository: 21 | 22 | pip install git+https://github.com/wallento/wavedrompy 23 | 24 | or from your local copy: 25 | 26 | pip install . 27 | 28 | ## Usage 29 | 30 | You can either use the tool from Python: 31 | 32 | import wavedrom 33 | svg = wavedrom.render(""" 34 | { "signal": [ 35 | { "name": "CK", "wave": "P.......", "period": 2 }, 36 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 }, 37 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 }, 38 | { "name": "DQS", "wave": "z.......0.1010z." }, 39 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" } 40 | ]}""") 41 | svg.saveas("demo1.svg") 42 | 43 | This will render a waveform as: 44 | 45 | ![Example 1](https://raw.githubusercontent.com/wallento/wavedrompy/2e8568d50561f534133d036fee3bd35756f416d9/doc/demo1.svg?sanitize=true "Example 1") 46 | 47 | You can find more examples [in the WaveDrom tutorial](https://wavedrom.com/tutorial.html). 48 | 49 | A second feature is that WaveDrom can render logic circuit diagrams: 50 | 51 | import wavedrom 52 | svg = wavedrom.render(""" 53 | { "assign":[ 54 | ["out", 55 | ["|", 56 | ["&", ["~", "a"], "b"], 57 | ["&", ["~", "b"], "a"] 58 | ] 59 | ] 60 | ]}""") 61 | svg.saveas("demo2.svg") 62 | 63 | This will render a as: 64 | 65 | ![Example 2](https://raw.githubusercontent.com/wallento/wavedrompy/2e8568d50561f534133d036fee3bd35756f416d9/doc/demo2.svg?sanitize=true "Example 2") 66 | 67 | You can find more examples [in the WaveDrom tutorial2](https://wavedrom.com/tutorial2.html). 68 | 69 | Finally, wavedrom can draw registers as bitfields: 70 | 71 | import wavedrom 72 | svg = wavedrom.render(""" 73 | {"reg": [ 74 | { "name": "IPO", "bits": 8, "attr": "RO" }, 75 | { "bits": 7 }, 76 | { "name": "BRK", "bits": 5, "attr": "RW", "type": 4 }, 77 | { "name": "CPK", "bits": 1 }, 78 | { "name": "Clear", "bits": 3 }, 79 | { "bits": 8 } 80 | ] 81 | ]}""") 82 | svg.saveas("demo3.svg") 83 | 84 | 85 | This will render as: 86 | 87 | ![Example 3](https://raw.githubusercontent.com/wallento/wavedrompy/2e8568d50561f534133d036fee3bd35756f416d9/doc/demo3.svg?sanitize=true "Example 3") 88 | 89 | This mode is documented as part of the [bit-field](https://www.npmjs.com/package/bit-field) JavaScript package. 90 | 91 | Alternatively, WaveDromPy can be called from the command line: 92 | 93 | wavedrompy --input input.json --svg output.svg 94 | 95 | ## Important notice 96 | 97 | The command line uses Python's JSON interpreter that is more restrictive (coherent with the JSOC spec), while the JavaScript json is more relaxed: 98 | 99 | * All strings have to be written between quotes (""), 100 | * Extra comma (,) not supported at end of lists or dictionaries 101 | 102 | ## AsciiDoctor example 103 | 104 | An _AsciiDoctor_ example is provided to directly generate timing diagrams from _AsciiDoctor_ formatted documents. 105 | 106 | -------------------------------------------------------------------------------- /asciidoctor-example/asciidoctor-html.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export PATH=$PATH:$(pwd)/.. 3 | asciidoctor -r asciidoctor-diagram example.adoc 4 | -------------------------------------------------------------------------------- /asciidoctor-example/asciidoctor-pdf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export PATH=$PATH:$(pwd)/.. 3 | asciidoctor-pdf -r asciidoctor-diagram example.adoc 4 | -------------------------------------------------------------------------------- /asciidoctor-example/example.adoc: -------------------------------------------------------------------------------- 1 | = AsciiDoctor demo for HDL designers = 2 | v1.0 3 | :toc: left 4 | :icons: font 5 | :numbered: 6 | :source-autofit: 7 | :doctype: book 8 | :description: An AsciiDoctor demo with wavedrom python command line 9 | :source-highlighter: pygments 10 | 11 | == WaveDrom diagrams examples == 12 | 13 | === Test #0 === 14 | [wavedrom,wavedrom_test0,svg] 15 | ---- 16 | { "signal": [{ "name": "Alfa", "wave": "01.zx=ud.23.45" }] } 17 | ---- 18 | 19 | === Test #1 (default) === 20 | [wavedrom,wavedrom_test1,svg] 21 | ---- 22 | { "signal": [ 23 | { "name": "pclk", "wave": "p......." }, 24 | { "name": "Pclk", "wave": "P......." }, 25 | { "name": "nclk", "wave": "n......." }, 26 | { "name": "Nclk", "wave": "N......." }, 27 | {}, 28 | { "name": "clk0", "wave": "phnlPHNL" }, 29 | { "name": "clk1", "wave": "xhlhLHl." }, 30 | { "name": "clk2", "wave": "hpHplnLn" }, 31 | { "name": "clk3", "wave": "nhNhplPl" }, 32 | { "name": "clk4", "wave": "xlh.L.Hx" } 33 | ]} 34 | ---- 35 | 36 | === Test #1 (narrow) === 37 | [wavedrom,wavedrom_test1n,svg] 38 | ---- 39 | { "signal": [ 40 | { "name": "pclk", "wave": "p......." }, 41 | { "name": "Pclk", "wave": "P......." }, 42 | { "name": "nclk", "wave": "n......." }, 43 | { "name": "Nclk", "wave": "N......." }, 44 | {}, 45 | { "name": "clk0", "wave": "phnlPHNL" }, 46 | { "name": "clk1", "wave": "xhlhLHl." }, 47 | { "name": "clk2", "wave": "hpHplnLn" }, 48 | { "name": "clk3", "wave": "nhNhplPl" }, 49 | { "name": "clk4", "wave": "xlh.L.Hx" } 50 | ], 51 | "config": { "skin": "narrow" } 52 | } 53 | ---- 54 | 55 | === Test #2 === 56 | [wavedrom,wavedrom_test2,svg] 57 | ---- 58 | { "signal": [ 59 | { "name": "clk", "wave": "P......" }, 60 | { "name": "bus", "wave": "x.==.=x", "data": ["head", "body", "tail", "data"] }, 61 | { "name": "wire", "wave": "0.1..0." } 62 | ]} 63 | ---- 64 | 65 | === Test #3 === 66 | [wavedrom,wavedrom_test3,svg] 67 | ---- 68 | { "signal": [ 69 | { "name": "clk", "wave": "p.....|..." }, 70 | { "name": "data", "wave": "x.345x|=.x", "data": ["head", "body", "tail", "data"] }, 71 | { "name": "Request", "wave": "0.1..0|1.0" }, 72 | {}, 73 | { "name": "Acknowledge", "wave": "1.....|01." } 74 | ]} 75 | ---- 76 | 77 | === Test #4 === 78 | [wavedrom,wavedrom_test4,svg] 79 | ---- 80 | { "signal": [ 81 | { "name": "clk", "wave": "p..Pp..P"}, 82 | ["Master", 83 | ["ctrl", 84 | {"name": "write", "wave": "01.0...."}, 85 | {"name": "read", "wave": "0...1..0"} 86 | ], 87 | { "name": "addr", "wave": "x3.x4..x", "data": "A1 A2"}, 88 | { "name": "wdata", "wave": "x3.x....", "data": "D1" } 89 | ], 90 | {}, 91 | ["Slave", 92 | ["ctrl", 93 | {"name": "ack", "wave": "x01x0.1x"} 94 | ], 95 | { "name": "rdata", "wave": "x.....4x", "data": "Q2"} 96 | ] 97 | ]} 98 | ---- 99 | 100 | === Test #5 === 101 | [wavedrom,wavedrom_test5,svg] 102 | ---- 103 | { "signal": [ 104 | { "name": "CK", "wave": "P.......", "period": 2 }, 105 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 }, 106 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 }, 107 | { "name": "DQS", "wave": "z.......0.1010z." }, 108 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" } 109 | ]} 110 | ---- 111 | 112 | === Test #6 === 113 | [wavedrom,wavedrom_test6,svg] 114 | ---- 115 | { "signal": [ 116 | { "name": "clk", "wave": "p...." }, 117 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] }, 118 | { "name": "Request", "wave": "01..0" } 119 | ], 120 | "config": { "hscale": 1 } 121 | } 122 | ---- 123 | 124 | === Test #7 === 125 | [wavedrom,wavedrom_test7,svg] 126 | ---- 127 | { "signal" : [ 128 | { "name": "clk", "wave": "p...." }, 129 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] }, 130 | { "name": "Request", "wave": "01..0" } 131 | ], 132 | "config" : { "hscale" : 2 } 133 | } 134 | ---- 135 | 136 | === Test #8 === 137 | [wavedrom,wavedrom_test8,svg] 138 | ---- 139 | { "signal" : [ 140 | { "name": "clk", "wave": "p...." }, 141 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] }, 142 | { "name": "Request", "wave": "01..0" } 143 | ], 144 | "config" : { "hscale" : 3 } 145 | } 146 | ---- 147 | 148 | === Test #9 === 149 | [wavedrom,wavedrom_test9,svg] 150 | ---- 151 | {"signal": [ 152 | {"name":"clk", "wave": "p...." }, 153 | {"name":"data", "wave": "x345x", "data": "a b c" }, 154 | {"name":"Request", "wave": "01..0" } 155 | ], 156 | "head":{ 157 | "text":"WaveDrom example", 158 | "tick":0 159 | }, 160 | "foot":{ 161 | "text":"Figure 100", 162 | "tock":9 163 | } 164 | } 165 | ---- 166 | 167 | === Test #10 === 168 | [wavedrom,wavedrom_test10,svg] 169 | ---- 170 | {"signal": [ 171 | {"name":"clk", "wave": "p.....PPPPp...." }, 172 | {"name":"dat", "wave": "x....2345x.....", "data": "a b c d" }, 173 | {"name":"req", "wave": "0....1...0....." } 174 | ], 175 | "head": {"text": 176 | ["tspan", 177 | ["tspan", {"class":"error h1"}, "error "], 178 | ["tspan", {"class":"warning h2"}, "warning "], 179 | ["tspan", {"class":"info h3"}, "info "], 180 | ["tspan", {"class":"success h4"}, "success "], 181 | ["tspan", {"class":"muted h5"}, "muted "], 182 | ["tspan", {"class":"h6"}, "h6 "], 183 | "default ", 184 | ["tspan", {"fill":"pink", "font-weight":"bold", "font-style":"italic"}, "pink-bold-italic"] 185 | ] 186 | }, 187 | "foot": {"text": 188 | ["tspan", "E=mc", 189 | ["tspan", {"dy":"-5"}, "2"], 190 | ["tspan", {"dy": "5"}, ". "], 191 | ["tspan", {"font-size":"25"}, "B "], 192 | ["tspan", {"text-decoration":"overline"},"over "], 193 | ["tspan", {"text-decoration":"underline"},"under "], 194 | ["tspan", {"baseline-shift":"sub"}, "sub "], 195 | ["tspan", {"baseline-shift":"super"}, "super "] 196 | ],"tock":-5 197 | } 198 | } 199 | ---- 200 | 201 | === Test #11 === 202 | [wavedrom,wavedrom_test11,svg] 203 | ---- 204 | { "signal": [ 205 | { "name": "A", "wave": "01........0....", "node": ".a........j" }, 206 | { "name": "B", "wave": "0.1.......0.1..", "node": "..b.......i" }, 207 | { "name": "C", "wave": "0..1....0...1..", "node": "...c....h.." }, 208 | { "name": "D", "wave": "0...1..0.....1.", "node": "....d..g..." }, 209 | { "name": "E", "wave": "0....10.......1", "node": ".....ef...." } 210 | ], 211 | "edge": [ 212 | "a~b t1", "c-~a t2", "c-~>d time 3", "d~-e", 213 | "e~>f", "f->g", "g-~>h", "h~>i some text", "h~->j" 214 | ] 215 | } 216 | ---- 217 | 218 | === Test #12 === 219 | [wavedrom,wavedrom_test12,svg] 220 | ---- 221 | { "signal": [ 222 | { "name": "A", "wave": "01..0..", "node": ".a..e.." }, 223 | { "name": "B", "wave": "0.1..0.", "node": "..b..d.", "phase":0.5 }, 224 | { "name": "C", "wave": "0..1..0", "node": "...c..f" }, 225 | { "node": "...g..h" } 226 | ], 227 | "edge": [ 228 | "b-|a t1", "a-|c t2", "b-|-c t3", "c-|->e t4", "e-|>f more text", 229 | "e|->d t6", "c-g", "f-h", "g<->h 3 ms" 230 | ] 231 | } 232 | ---- 233 | 234 | == Port description example == 235 | 236 | [width="100%",cols="<2,^1,^7",options="header"] 237 | |================================= 238 | |Signal name |Type | Description 239 | |clk | in <| Clk input 240 | |reset | in <| Reset 241 | |address | in <| Address bus 242 | |read | in <| Read signal 243 | |readdata | out <| Read data bus 244 | |readvalid | out <| Read valid signal 245 | |write | in <| Write signal 246 | |writedata | in <| Write data bus 247 | |================================= 248 | 249 | == Register description example == 250 | 251 | [width="100%",cols="^2,^1,^2,^1,^4",options="header"] 252 | |========================================================= 253 | |Address |Bits | Field Name |Access |Description 254 | .4+|0x00000000 255 | |31:24 | NU |RO <| Not used. 256 | |23:16 | VMAJ |RO <| Version major. 257 | |15:8 | VMIN |RO <| Version minor. 258 | |7:0 | VPATCH |RO <| Version patch. 259 | .7+|0x00000004 260 | |31:16 | STATUS | RO <| Status bits. 261 | |15 | PLL_LOCKED | RO <| PLL locked. 262 | |14 | DDR_INIT_DONE | RO <| DDR Init_done. 263 | |13:12 | NU | RO <| Not used. 264 | |11:8 | GROUP_0_INTR | R/W <| Group #0 interrupt requests. 265 | |7:4 | GROUP_1_INTR | R/W <| Group #1 interrupt requests. 266 | |3:0 | GROUP_2_INTR | R/W <| Group #2 interrupt requests. 267 | |========================================================= 268 | 269 | == VHDL syntax coloring example == 270 | [source,vhdl] 271 | ---- 272 | proc_column_counter : process ( reset, clk ) 273 | begin 274 | if reset = '1' then 275 | col <= 0; 276 | elsif rising_edge( clk ) then 277 | if enable then 278 | if sink_endofpacket = '1' then 279 | col <= 0; 280 | elsif col = g_width - g_data_size / c_pixel_size then 281 | col <= 0; 282 | else 283 | col <= col + g_data_size / c_pixel_size; 284 | end if; 285 | end if; 286 | end if; 287 | end process proc_column_counter; 288 | ---- -------------------------------------------------------------------------------- /asciidoctor-example/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallento/wavedrompy/82a0e75cf201ed4e9f9c2ec3e56e54b6ec93c11a/asciidoctor-example/example.pdf -------------------------------------------------------------------------------- /asciidoctor-example/wavedrom_test0.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | Alfa 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | -------------------------------------------------------------------------------- /asciidoctor-example/wavedrom_test6.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | clk 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | data 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | head 692 | 693 | 694 | 695 | body 696 | 697 | 698 | 699 | tail 700 | 701 | 702 | 703 | 704 | 705 | 706 | Request 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | -------------------------------------------------------------------------------- /asciidoctor-example/wavedrom_test9.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | WaveDrom example 656 | 657 | Figure 100 658 | 659 | 0 660 | 661 | 1 662 | 663 | 2 664 | 665 | 3 666 | 667 | 4 668 | 669 | 5 670 | 671 | 9 672 | 673 | 10 674 | 675 | 11 676 | 677 | 12 678 | 679 | 13 680 | 681 | 682 | 683 | 684 | clk 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | data 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | a 718 | 719 | 720 | 721 | b 722 | 723 | 724 | 725 | c 726 | 727 | 728 | 729 | 730 | 731 | 732 | Request 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | -------------------------------------------------------------------------------- /css/default.css: -------------------------------------------------------------------------------- 1 | text{font-size:11pt;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;fill-opacity:1;font-family:Helvetica}.muted{fill:#aaa}.warning{fill:#f6b900}.error{fill:#f60000}.info{fill:#0041c4}.success{fill:#00ab00}.h1{font-size:33pt;font-weight:bold}.h2{font-size:27pt;font-weight:bold}.h3{font-size:20pt;font-weight:bold}.h4{font-size:14pt;font-weight:bold}.h5{font-size:11pt;font-weight:bold}.h6{font-size:8pt;font-weight:bold}.s1{fill:none;stroke:#000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none}.s2{fill:none;stroke:#000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none}.s3{color:#000;fill:none;stroke:#000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 3;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s4{color:#000;fill:none;stroke:#000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible}.s5{fill:#fff;stroke:none}.s6{color:#000;fill:#ffffb4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s7{color:#000;fill:#ffe0b9;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s8{color:#000;fill:#b9e0ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s9{fill:#000;fill-opacity:1;stroke:none}.s10{color:#000;fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s11{fill:#0041c4;fill-opacity:1;stroke:none}.s12{fill:none;stroke:#0041c4;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none} 2 | -------------------------------------------------------------------------------- /doc/demo2.svg: -------------------------------------------------------------------------------- 1 | 2 | outout|&~aabb&~bbaa -------------------------------------------------------------------------------- /doc/demo3.svg: -------------------------------------------------------------------------------- 1 | 2 | 0781415IPOBRKRORW16192021232431BRKCPKClearRW -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | 2 | [bdist_wheel] 3 | # This flag says to generate wheels that support both Python 2 and Python 4 | # 3. If your code will not run unchanged on both Python 2 and 3, you will 5 | # need to generate separate wheels for each Python version that you 6 | # support. 7 | universal=1 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | See: 3 | https://packaging.python.org/en/latest/distributing.html 4 | https://github.com/pypa/sampleproject 5 | """ 6 | 7 | # Always prefer setuptools over distutils 8 | import sys 9 | 10 | from setuptools import setup, find_packages 11 | # To use a consistent encoding 12 | from codecs import open 13 | from os import path 14 | 15 | here = path.abspath(path.dirname(__file__)) 16 | 17 | # Get the long description from the README file 18 | with open(path.join(here, "README.md"), encoding="utf-8") as f: 19 | long_description = f.read() 20 | 21 | # Arguments marked as "Required" below must be included for upload to PyPI. 22 | # Fields marked as "Optional" may be commented out. 23 | 24 | requires = ["svgwrite", 25 | "six", # 2 and 3 compatibility 26 | "pyyaml" 27 | ] 28 | setup( 29 | # This is the name of your project. The first time you publish this 30 | # package, this name will be registered for you. It will determine how 31 | # users can install this project, e.g.: 32 | # 33 | # $ pip install sampleproject 34 | # 35 | # And where it will live on PyPI: https://pypi.org/project/sampleproject/ 36 | # 37 | # There are some restrictions on what makes a valid project name 38 | # specification here: 39 | # https://packaging.python.org/specifications/core-metadata/#name 40 | name="wavedrom", # Required 41 | 42 | # Versions should comply with PEP 440: 43 | # https://www.python.org/dev/peps/pep-0440/ 44 | # 45 | # For a discussion on single-sourcing the version across setup.py and the 46 | # project code, see 47 | # https://packaging.python.org/en/latest/single_source_version.html 48 | use_scm_version={ 49 | "relative_to": __file__, 50 | "write_to": "wavedrom/version.py", 51 | }, 52 | 53 | # This is a one-line description or tagline of what your project does. This 54 | # corresponds to the "Summary" metadata field: 55 | # https://packaging.python.org/specifications/core-metadata/#summary 56 | description="WaveDrom compatible python command line", # Required 57 | 58 | # This is an optional longer description of your project that represents 59 | # the body of text which users will see when they visit PyPI. 60 | # 61 | # Often, this is the same as your README, so you can just read it in from 62 | # that file directly (as we have already done above) 63 | # 64 | # This field corresponds to the "Description" metadata field: 65 | # https://packaging.python.org/specifications/core-metadata/#description-optional 66 | long_description=long_description, # Optional 67 | long_description_content_type='text/markdown', 68 | 69 | # This should be a valid link to your project"s main homepage. 70 | # 71 | # This field corresponds to the "Home-Page" metadata field: 72 | # https://packaging.python.org/specifications/core-metadata/#home-page-optional 73 | url="https://github.com/wallento/wavedrompy", # Optional 74 | 75 | # This should be your name or the name of the organization which owns the 76 | # project. 77 | author="Aliaksei Chapyzhenka, BreizhGeek, Kazuki Yamamoto, Stefan Wallentowitz", # Optional 78 | 79 | # This should be a valid email address corresponding to the author listed 80 | # above. 81 | author_email="k4zuki@github.com, stefan@wallentowitz.de", # Optional 82 | 83 | # Classifiers help users find your project by categorizing it. 84 | # 85 | # For a list of valid classifiers, see 86 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers 87 | classifiers=[ # Optional 88 | # How mature is this project? Common values are 89 | # 3 - Alpha 90 | # 4 - Beta 91 | # 5 - Production/Stable 92 | "Development Status :: 4 - Beta", 93 | 94 | # Indicate who your project is intended for 95 | "Intended Audience :: Developers", 96 | "Topic :: Software Development :: Build Tools", 97 | 98 | # Pick your license as you wish 99 | "License :: OSI Approved :: MIT License", 100 | 101 | # Specify the Python versions you support here. In particular, ensure 102 | # that you indicate whether you support Python 2, Python 3 or both. 103 | "Programming Language :: Python :: 3" 104 | "Programming Language :: Python :: 3.7", 105 | "Programming Language :: Python :: 3.8", 106 | "Programming Language :: Python :: 3.9", 107 | "Programming Language :: Python :: 3.10" 108 | ], 109 | 110 | # This field adds keywords for your project which will appear on the 111 | # project page. What does your project relate to? 112 | # 113 | # Note that this is a string of words separated by whitespace, not a list. 114 | keywords="wavedrom svg", # Optional 115 | 116 | # You can just specify package directories manually here if your project is 117 | # simple. Or you can use find_packages(). 118 | # 119 | # Alternatively, if you just want to distribute a single Python file, use 120 | # the `py_modules` argument instead as follows, which will expect a file 121 | # called `my_module.py` to exist: 122 | # 123 | # py_modules=["my_module"], 124 | # 125 | packages=find_packages(exclude=["contrib", "docs", "tests"]), # Required 126 | 127 | # This field lists other packages that your project depends on to run. 128 | # Any package you put here will be installed by pip when your project is 129 | # installed, so they must be valid existing projects. 130 | # 131 | # For an analysis of "install_requires" vs pip"s requirements files see: 132 | # https://packaging.python.org/en/latest/requirements.html 133 | install_requires=requires, # Optional 134 | 135 | # The minimum required Python version for installation 136 | python_requires='>=3.7', 137 | 138 | # List additional groups of dependencies here (e.g. development 139 | # dependencies). Users will be able to install these using the "extras" 140 | # syntax, for example: 141 | # 142 | # $ pip install sampleproject[dev] 143 | # 144 | # Similar to `install_requires` above, these must be valid existing 145 | # projects. 146 | extras_require={ # Optional 147 | # "dev": ["check-manifest"], 148 | "test": [ 149 | "xmldiff", 150 | #Per release notes, Python 2 support dropped at version 2.0.0 151 | "cairosvg<2" if sys.version_info.major < 3 else "cairosvg", 152 | "pillow" 153 | ], 154 | }, 155 | 156 | setup_requires=[ 157 | 'setuptools_scm', 158 | ], 159 | 160 | # If there are data files included in your packages that need to be 161 | # installed, specify them here. 162 | # 163 | # If using Python 2.6 or earlier, then these have to be included in 164 | # MANIFEST.in as well. 165 | package_data={ # Optional 166 | # "sample": ["package_data.dat"], 167 | }, 168 | 169 | # Although "package_data" is the preferred approach, in some case you may 170 | # need to place data files outside of your packages. See: 171 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files 172 | # 173 | # In this case, "data_file" will be installed into "/my_data" 174 | data_files=[ 175 | # ("my_data", ["data/data_file"]), 176 | ], # Optional 177 | 178 | # To provide executable scripts, use entry points in preference to the 179 | # "scripts" keyword. Entry points provide cross-platform support and allow 180 | # `pip` to create the appropriate form of executable for the target 181 | # platform. 182 | # 183 | # For example, the following would provide a command called `sample` which 184 | # executes the function `main` from this package when invoked: 185 | entry_points={ # Optional 186 | "console_scripts": [ 187 | "wavedrompy=wavedrom:main", 188 | ], 189 | }, 190 | ) 191 | -------------------------------------------------------------------------------- /test/brick_regressions.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | Regression = namedtuple("Regression", ["wave", "hscale", "period", "phase", "expected"]) 4 | Regression.__new__.__defaults__ = ("", 1, 1, 0, []) 5 | 6 | 7 | basic = [ 8 | Regression(wave="P", expected=['Pclk', 'nclk']), 9 | Regression(wave="P", hscale=2, expected=['Pclk', '111', 'nclk', '000']), 10 | Regression(wave="P", period=2, expected=['Pclk', '111', 'nclk', '000']), 11 | Regression(wave="P", hscale=2, period=0.5, expected=['Pclk', 'nclk']), 12 | Regression(wave="P", phase=1, expected=['nclk']), 13 | Regression(wave="P", hscale=2, phase=2, expected=['nclk', '000']), 14 | Regression(wave="P.", expected=['Pclk', 'nclk', 'Pclk', 'nclk']), 15 | Regression(wave="P..", expected=['Pclk', 'nclk', 'Pclk', 'nclk', 'Pclk', 'nclk']), 16 | Regression(wave="P.", hscale=2, expected=['Pclk', '111', 'nclk', '000', 'Pclk', '111', 'nclk', '000']), 17 | Regression(wave="P..", hscale=2, expected=['Pclk', '111', 'nclk', '000', 'Pclk', '111', 'nclk', '000', 18 | 'Pclk', '111', 'nclk', '000']), 19 | Regression(wave="px", expected=['pclk', 'nclk', '0mx', 'xxx']), 20 | Regression(wave="hx", expected=['111', '111', '1mx', 'xxx']), 21 | Regression(wave="nx", expected=['nclk', 'pclk', '1mx', 'xxx']), 22 | Regression(wave="lx", expected=['000', '000', '0mx', 'xxx']), 23 | Regression(wave="Px", expected=['Pclk', 'nclk', '0mx', 'xxx']), 24 | Regression(wave="Hx", expected=['111', '111', '1mx', 'xxx']), 25 | Regression(wave="Nx", expected=['Nclk', 'pclk', '1mx', 'xxx']), 26 | Regression(wave="Lx", expected=['000', '000', '0mx', 'xxx']), 27 | Regression(wave="xpx", expected=['xxx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx']), 28 | Regression(wave="xhx", expected=['xxx', 'xxx', 'pclk', '111', '1mx', 'xxx']), 29 | Regression(wave="xnx", expected=['xxx', 'xxx', 'nclk', 'pclk', '1mx', 'xxx']), 30 | Regression(wave="xlx", expected=['xxx', 'xxx', 'nclk', '000', '0mx', 'xxx']), 31 | Regression(wave="xPx", expected=['xxx', 'xxx', 'Pclk', 'nclk', '0mx', 'xxx']), 32 | Regression(wave="xHx", expected=['xxx', 'xxx', 'Pclk', '111', '1mx', 'xxx']), 33 | Regression(wave="xNx", expected=['xxx', 'xxx', 'Nclk', 'pclk', '1mx', 'xxx']), 34 | Regression(wave="xLx", expected=['xxx', 'xxx', 'Nclk', '000', '0mx', 'xxx']), 35 | Regression(wave="0", expected=['000', '000']), 36 | Regression(wave="1", expected=['111', '111']), 37 | Regression(wave="01", expected=['000', '000', '0m1', '111']), 38 | Regression(wave="01", hscale=2, expected=['000', '000', '000', '000', '0m1', '111', '111', '111']), 39 | Regression(wave="01.0", expected=['000', '000', '0m1', '111', '111', '111', '1m0', '000']), 40 | Regression(wave="01.zx=ud.23.45", 41 | expected=['000', '000', '0m1', '111', '111', '111', '1mz', 'zzz', 'zmx', 'xxx', 'xmv-2', 'vvv-2', 42 | 'vmu-2', 'uuu', 'umd', 'ddd', 'ddd', 'ddd', 'dmv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vvv-3', 43 | 'vvv-3', 'vmv-3-4', 'vvv-4', 'vmv-4-5', 'vvv-5']), 44 | Regression(wave="01.zx=ud.23.45", hscale=2, 45 | expected=['000', '000', '000', '000', '0m1', '111', '111', '111', '111', '111', '111', '111', '1mz', 46 | 'zzz', 'zzz', 'zzz', 'zmx', 'xxx', 'xxx', 'xxx', 'xmv-2', 'vvv-2', 'vvv-2', 'vvv-2', 'vmu-2', 47 | 'uuu', 'uuu', 'uuu', 'umd', 'ddd', 'ddd', 'ddd', 'ddd', 'ddd', 'ddd', 'ddd', 'dmv-2', 'vvv-2', 48 | 'vvv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vvv-3', 'vvv-3', 'vvv-3', 'vvv-3', 'vvv-3', 49 | 'vvv-3', 'vmv-3-4', 'vvv-4', 'vvv-4', 'vvv-4', 'vmv-4-5', 'vvv-5', 'vvv-5', 'vvv-5']), 50 | Regression(wave="phnlPHNL", expected=['pclk', 'nclk', 'pclk', '111', 'nclk', 'pclk', 'nclk', '000', 'Pclk', 'nclk', 51 | 'Pclk', '111', 'Nclk', 'pclk', 'Nclk', '000']), 52 | Regression(wave="xhlhLHl.", expected=['xxx', 'xxx', 'pclk', '111', 'nclk', '000', 'pclk', '111', 'Nclk', '000', 53 | 'Pclk', '111', 'nclk', '000', '000', '000']), 54 | Regression(wave="hpHplnLn", expected=['111', '111', '111', 'nclk', 'Pclk', '111', '111', 'nclk', '000', '000', 55 | '000', 'pclk', 'Nclk', '000', '000', 'pclk']), 56 | Regression(wave="nhNhplPl", expected=['nclk', 'pclk', '111', '111', 'Nclk', 'pclk', '111', '111', '111', 'nclk', 57 | '000', '000', 'Pclk', 'nclk', '000', '000']), 58 | Regression(wave="xlh.L.Hx", expected=['xxx', 'xxx', 'nclk', '000', 'pclk', '111', '111', '111', 'Nclk', '000', 59 | '000', '000', 'Pclk', '111', '1mx', 'xxx']) 60 | ] 61 | 62 | period = [ 63 | Regression(wave="h.nh.nh.l..hlx.hlx.hlx.hlx.hlx.hlx.hlx.hlx.hnh.l..hlx.hlx.h", period=0.5, 64 | expected=['111', '111', 'nclk', 'pclk', '111', '111', 'nclk', 'pclk', '111', '111', 'nclk', '000', '000', 65 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 66 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 67 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', 'pclk', '111', 68 | '111', 'nclk', '000', '000', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 69 | 'pclk']), 70 | Regression(wave="h.lh..lh..l..hlx.hlx.hlx.hlx.hlx.hlx.hlx.hlx.hnh.l..hlx.hlx.h", period=0.5, 71 | expected=['111', '111', 'nclk', 'pclk', '111', '111', 'nclk', 'pclk', '111', '111', 'nclk', '000', '000', 72 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 73 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 74 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', 'pclk', '111', 75 | '111', 'nclk', '000', '000', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 76 | 'pclk']), 77 | Regression(wave="0..............................................50.............", period=0.5, 78 | expected=['000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', 79 | '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', 80 | '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', 81 | '000', '000', '000', '000', '000', '000', '000', '000', '0mv-5', 'vm0-5', '000', '000', '000', 82 | '000', '000', '000', '000', '000', '000', '000', '000', '000', '000']), 83 | ] 84 | 85 | subcycle = [ 86 | Regression(wave="0<10>1", expected=['000', '000', '0m1', '1m0', '0m1', '111']), 87 | Regression(wave="<10>10", expected=['111', '1m0', '0m1', '111', '1m0', '000']), 88 | Regression(wave="1x", expected=['xxx', 'xm0', '0m1', '111', '1mx', 'xxx']), 89 | Regression(wave="<01>", expected=['000', '0m1']), 90 | Regression(wave="x.<01...0>x", expected=['xxx', 'xxx', 'xxx', 'xxx', 'xm0', '0m1', '111', '111', '111', '1m0', 91 | '0mx', 'xxx']), 92 | Regression(wave="==2<30>2<0xx1>2333444555", 93 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vmv-2-3', 'vm0-3', '0mv-2', 'vvv-2', 94 | 'vm0-2', '0mx', 'xmx', 'xm1', '1mv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vmv-3-3', 'vvv-3', 95 | 'vmv-3-3', 'vvv-3', 'vmv-3-4', 'vvv-4', 'vmv-4-4', 'vvv-4', 'vmv-4-4', 'vvv-4', 'vmv-4-5', 96 | 'vvv-5', 'vmv-5-5', 'vvv-5', 'vmv-5-5', 'vvv-5']), 97 | Regression(wave="=2<15.1>2<01>2355", 98 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vm1-2', '1mv-5', 'vvv-5', 'vm1-5', '1mv-2', 'vvv-2', 99 | 'vm0-2', '0m1', '1mv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vmv-3-5', 'vvv-5', 'vmv-5-5', 'vvv-5']), 100 | Regression(wave="=2<1001.1>2<01.5>3", 101 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vm1-2', '1m0', '0m0', '0m1', '111', '1m1', '1mv-2', 102 | 'vvv-2', 'vm0-2', '0m1', '111', '1mv-5', 'vmv-5-3', 'vvv-3']), 103 | Regression(wave="=2<10zuzd1x.1>2", 104 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vm1-2', '1m0', '0mz', 'zmu', 'umz', 'zmd', 'dm1', '1mx', 105 | 'xxx', 'xm1', '1mv-2', 'vvv-2']), 106 | Regression(wave="<=|>.x", expected=['vvv-2', 'vvv-2', 'vvv-2', 'vvv-2', 'vmx-2', 'xxx']), 107 | Regression(wave="x<=|>.x", expected=['xxx', 'xxx', 'xmv-2', 'vvv-2', 'vvv-2', 'vvv-2', 'vmx-2', 'xxx']), 108 | Regression(wave="x<.|>0x", expected=['xxx', 'xxx', 'xxx', 'xxx', 'xm0', '000', '0mx', 'xxx']), 109 | Regression(wave="hnhnhlnnhln", 110 | expected=['111', '111', 'nclk', 'pclk', '111', '111', 'nclk', 'pclk', '111', '111', 'nclk', '000', '000', 111 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 112 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 113 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', 'pclk', '111', 114 | '111', 'nclk', '000', '000', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 115 | 'pclk']), 116 | Regression(wave="0.h<.>l.", expected=['000', '000', '000', '000', 'pclk', '111', '111', 'nclk', '000', '000', '000']), 117 | Regression(wave="0..l.", expected=['000', '000', '000', '000', 'pclk', '111', '111', 'nclk', '000', '000', '000']), 118 | Regression(wave="0.l.", expected=['000', '000', '000', '000', 'pclk', '111', '111', 'nclk', '000', '000', '000']), 119 | ] 120 | 121 | all = basic + period + subcycle -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | collect_ignore = ["single_test.py"] 4 | def pytest_report_header(config): 5 | if "WAVEDROMDIR" in os.environ: 6 | print("Using wavedrom in WAVEDROMDIR ({})".format(os.environ["WAVEDROMDIR"])) -------------------------------------------------------------------------------- /test/diff.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import namedtuple 3 | 4 | import xmldiff 5 | import xmldiff.main 6 | from lxml import etree 7 | 8 | from PIL import Image, ImageChops 9 | import cairosvg 10 | import io 11 | 12 | UpdateAttribx = namedtuple("UpdateAttribx", 'node name value old') 13 | MoveNodex = namedtuple("MoveNodex", 'node target position nodex targetx') 14 | 15 | 16 | def main(f_out, f_out_py): 17 | parser = etree.XMLParser(remove_blank_text=True) 18 | orig_tree = etree.parse(f_out, parser) 19 | py_tree = etree.parse(f_out_py, parser) 20 | 21 | diff = xmldiff.main.diff_trees(orig_tree, py_tree) 22 | unknown = [] 23 | 24 | for action in diff: 25 | if isinstance(action, xmldiff.actions.UpdateAttrib): 26 | node = orig_tree.xpath(action.node)[0] 27 | if node.tag[-3:] == "svg" and action.name == "viewBox": 28 | # The viewBox format differs, both are legal notations (space vs. comma-separated) 29 | if re.sub(r"\s+", ",", node.attrib[action.name]) == action.value: 30 | continue 31 | elif re.sub(r"\s+", "", node.attrib[action.name]) == re.sub(r"\s+", "", action.value): 32 | # Whitespace differences are okay 33 | continue 34 | else: 35 | py = action.value 36 | js = node.attrib[action.name] 37 | if action.name in ["transform", "d", "x"]: 38 | # Floating point and int differences 39 | pattern_float = re.compile(r'(?3.5, remove once we are over 2.7.. 48 | # action = UpdateAttribx(**{ **action._asdict(), "old": node.attrib[action.name]}) 49 | action_dict = action._asdict() 50 | action_dict["old"] = node.attrib[action.name] 51 | action = UpdateAttribx(**action_dict) 52 | elif isinstance(action, xmldiff.actions.InsertAttrib): 53 | node = orig_tree.xpath(action.node)[0] 54 | if node.tag[-3:] == "svg" and action.name in ["baseProfile", "version"]: 55 | # svgwrite adds more info to the svg element 56 | continue 57 | elif isinstance(action, xmldiff.actions.MoveNode): 58 | node = orig_tree.xpath(action.node)[0] 59 | node.getparent().remove(node) 60 | target = orig_tree.xpath(action.target)[0] 61 | target.insert(action.position, node) 62 | if node.tag.endswith("}style"): 63 | # This is okay 64 | continue 65 | action_dict = action._asdict() 66 | action_dict["nodex"] = etree.tostring(node) 67 | action_dict["targetx"] = etree.tostring(target) 68 | action = MoveNodex(**action_dict) 69 | elif isinstance(action, xmldiff.actions.UpdateTextIn): 70 | node = orig_tree.xpath(action.node)[0] 71 | if action.text is None: 72 | if node.tag[-2:] == "}g" and node.attrib["id"] == "groups_0": 73 | # Upstream bug, reported: https://github.com/wavedrom/wavedrom/issues/251 74 | continue 75 | elif node.text is None: 76 | pass 77 | elif re.sub(r"\s+", "", node.text) == re.sub(r"\s+", "", action.text): 78 | # Whitespace differences are okay 79 | continue 80 | action = action._replace(node=etree.tostring(node)) 81 | elif isinstance(action, xmldiff.actions.InsertNode): 82 | # Not okay, but we must do the same to preserve the valid tree for further checks 83 | target = orig_tree.xpath(action.target)[0] 84 | node = target.makeelement(action.tag) 85 | target.insert(action.position, node) 86 | action = action._replace(target=etree.tostring(orig_tree.xpath(action.target)[0])) 87 | elif isinstance(action, xmldiff.actions.DeleteNode): 88 | node = orig_tree.xpath(action.node)[0] 89 | node.getparent().remove(node) 90 | action = action._replace(node=etree.tostring(node)) 91 | 92 | unknown.append(action) 93 | return unknown 94 | 95 | def diff_raster(f_out_js, f_out_py): 96 | with open(f_out_js, encoding="utf-8") as fileObj_svg_js: 97 | svg_js = fileObj_svg_js.read() 98 | 99 | with open(f_out_py, encoding="utf-8") as fileObj_svg_py: 100 | svg_py = fileObj_svg_py.read() 101 | 102 | png_js = cairosvg.svg2png(svg_js) 103 | png_py = cairosvg.svg2png(svg_py) 104 | 105 | image_js = Image.open(io.BytesIO(png_js)) 106 | image_py = Image.open(io.BytesIO(png_py)) 107 | 108 | return ImageChops.difference(image_js, image_py) 109 | -------------------------------------------------------------------------------- /test/files/assign_74ls688.json: -------------------------------------------------------------------------------- 1 | { "assign":[ 2 | ["z", ["~&", 3 | ["~^", ["~", "p0"], ["~", "q0"]], 4 | ["~^", ["~", "p1"], ["~", "q1"]], 5 | ["~^", ["~", "p2"], ["~", "q2"]], 6 | "...", 7 | ["~^", ["~", "p7"], ["~", "q7"]], 8 | ["~","~en"] 9 | ]] 10 | ]} 11 | -------------------------------------------------------------------------------- /test/files/assign_binary2gray.json: -------------------------------------------------------------------------------- 1 | { "assign":[ 2 | ["g0", ["^", "b0", "b1"]], 3 | ["g1", ["^", "b1", "b2"]], 4 | ["g2", ["^", "b2", "b3"]], 5 | ["g3", ["=", "b3"]] 6 | ]} -------------------------------------------------------------------------------- /test/files/assign_gray2binary.json: -------------------------------------------------------------------------------- 1 | { "assign":[ 2 | ["b3", "g3"], 3 | ["b2", ["^", "b3", "g2"]], 4 | ["b1", ["^", "b2", "g1"]], 5 | ["b0", ["^", "b1", "g0"]] 6 | ]} 7 | -------------------------------------------------------------------------------- /test/files/assign_iec60617.json: -------------------------------------------------------------------------------- 1 | { "assign":[ 2 | ["out", 3 | ["XNOR", 4 | ["NAND", 5 | ["INV", "a"], 6 | ["NOR", "b", ["BUF","c"]] 7 | ], 8 | ["AND", 9 | ["XOR", "d", "e", ["OR","f","g"]], 10 | "h" 11 | ] 12 | ] 13 | ] 14 | ]} 15 | -------------------------------------------------------------------------------- /test/files/assign_xor.json: -------------------------------------------------------------------------------- 1 | { "assign":[ 2 | ["out", 3 | ["|", 4 | ["&", ["~", "a"], "b"], 5 | ["&", ["~", "b"], "a"] 6 | ] 7 | ] 8 | ]} -------------------------------------------------------------------------------- /test/files/bitfield_0.json: -------------------------------------------------------------------------------- 1 | {"reg": [ 2 | { "name": "IPO", "bits": 8, "attr": "RO" }, 3 | { "bits": 7 }, 4 | { "name": "BRK", "bits": 5, "attr": "RW", "type": 4 }, 5 | { "name": "CPK", "bits": 1, "attr": 0 }, 6 | { "name": "Clear", "bits": 3, "attr": ["001", "110"]}, 7 | { "bits": 8 } 8 | ] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/files/issue_10.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "clk", "wave": "p..Pp..P"}, 3 | ["Master", 4 | ["ctrl", 5 | {"name": "write", "wave": "01.0...."}, 6 | {"name": "read", "wave": "0...1..0"} 7 | ], 8 | { "name": "addr", "wave": "x3.x4..x", "data": "A1 A2"}, 9 | { "name": "wdata", "wave": "x3.x....", "data": "D1" } 10 | ], 11 | {}, 12 | ["Slave", 13 | ["ctrl", 14 | {"name": "ack", "wave": "x01x0.1x"} 15 | ], 16 | { "name": "rdata", "wave": "x.....4x", "data": "Q2"} 17 | ] 18 | ]} -------------------------------------------------------------------------------- /test/files/issue_11.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "0", "wave": "lh", "node": ".a", "phase":0}, 3 | { "name": "0.1", "wave": "lh", "node": ".b", "phase":-0.1}, 4 | { "name": "0.2", "wave": "lh", "node": ".c", "phase":-0.2}, 5 | { "name": "0.3", "wave": "lh", "node": ".d", "phase":-0.3}, 6 | { "name": "0.4", "wave": "lh", "node": ".e", "phase":-0.4}, 7 | { "name": "0.5", "wave": "lh", "node": ".f", "phase":-0.5}, 8 | { "name": "0.6", "wave": "lh", "node": ".g", "phase":-0.6}, 9 | { "name": "0.7", "wave": "lh", "node": ".h", "phase":-0.7}, 10 | { "name": "0.8", "wave": "lh", "node": ".i", "phase":-0.8}, 11 | { "name": "0.9", "wave": "lh", "node": ".j", "phase":-0.9}, 12 | { "name": "1", "wave": "lh", "node": ".k", "phase":-1} 13 | ], 14 | "edge": [ 15 | "a-b", 16 | "b-c", 17 | "c-d", 18 | "d-e", 19 | "e-f", 20 | "f-g", 21 | "g-h", 22 | "h-i", 23 | "i-j", 24 | "j-k" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/files/issue_13.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "10", "wave": "lh===...........", "data": ["abcd", "1234"], "phase":-10} 3 | ] 4 | } -------------------------------------------------------------------------------- /test/files/issue_14.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "trig", "wave": "lh......|.........", "node": ".t................"}, 3 | { "name": "0", "wave": "l..====4|=0.......", "node": "...a..............", "data": [ "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]"], "phase":0}, 4 | { "name": "1", "wave": "l..====4|=0.......", "node": "...b..............", "data": [ "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]"], "phase":-1}, 5 | { "name": "2", "wave": "x.3====4|==30.....", "node": "...c..............", "data": ["SoT", "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]", "CRC", "EoT"], "phase":-1.5}, 6 | { "name": "3", "wave": "x35====4|=5=30....", "node": "...d..............", "data": ["SoT", "PH", "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]", "PF", "CRC", "EoT"], "phase":-5} 7 | ], 8 | "edge": [ 9 | "t~>a", 10 | "a~>b", 11 | "b~>c", 12 | "c~>d" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/files/issue_16.json: -------------------------------------------------------------------------------- 1 | { 2 | "signal": [ 3 | { 4 | "wave": "|nh....nh.nhn.xnhnh..|." 5 | }, 6 | { 7 | "wave": "x=.....=..=..x|=.=...=.", 8 | "data": [ 9 | "Calib.", 10 | "Stat./Comm.", 11 | "N data nibbles", 12 | "CRC", 13 | "Pause", 14 | "Calib." 15 | ] 16 | }, 17 | { 18 | "node": "...................1.2." 19 | } 20 | ], 21 | "edge": [ 22 | "1<->2 Interrupt" 23 | ], 24 | "head": { 25 | "text": [ 26 | "tspan", 27 | [ 28 | "tspan", 29 | { 30 | "font-weight": "bold" 31 | }, 32 | "SENT message signal timing diagram" 33 | ] 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /test/files/issue_37.json: -------------------------------------------------------------------------------- 1 | {signal: [ 2 | {name: 'e', wave: '0.30.30..', data: ['τ', 'τ+1']}, 3 | {name: 'x', wave: 'x.=x.=x..', data: ['x₀', 'x₁']}, 4 | {name: 'δₑx', wave: 'x..=..=..', data: ['x₀', 'x₁']}, 5 | {name: 'δe', wave: '0..30.30.', data: ['τ', 'τ+1']}, 6 | ]} -------------------------------------------------------------------------------- /test/files/issue_7.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "CK", "wave": "P.......", "period": 2 }, 3 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 }, 4 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 }, 5 | { "name": "DQS", "wave": "z.......0.1010z." }, 6 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" } 7 | ], 8 | "head":{ 9 | "text": "WaveDrom example" 10 | }, 11 | "foot":{ 12 | "text": "Figure 100" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/files/signal_0.json: -------------------------------------------------------------------------------- 1 | { "signal": [{ "name": "sig", "wave": "1" }] } 2 | -------------------------------------------------------------------------------- /test/files/subcycle_0.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { 3 | "name": "P1", 4 | "wave": "==2<30>2<0xx1>2333444555", 5 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8" 6 | }, 7 | { 8 | "name": "P2", 9 | "wave": "=2<15.1>2<01>2355", 10 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8", 11 | "period": 2 12 | }, 13 | { 14 | "name": "P3", 15 | "wave": "=2<1001.1>2<01.5>3", 16 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8", 17 | "period": 3 18 | }, 19 | { 20 | "name": "P4", 21 | "wave": "=2<10zuzd1x.1>2", 22 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8", 23 | "period": 4 24 | } 25 | ]} 26 | -------------------------------------------------------------------------------- /test/files/subcycle_1.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "clk", "wave": "p....."}, 3 | { "name": "A1", "wave": "x=x|=x|=.", "data": "a1 b1" }, 4 | { "name": "A2", "wave": "x=x|=x|=.", "data": "a2 b2", "period": 2 }, 5 | { "name": "A3", "wave": "x=x|=x|=.", "data": "a3 b3", "period": 3 }, 6 | { "name": "A4", "wave": "x=x|=x|=.", "data": "a4 b4", "period": 4 }, 7 | { "name": "B1", "wave": "x===.", "data": "a1 b1" }, 8 | { "name": "B2", "wave": "x===.", "data": "a2 b2", "period": 2 }, 9 | { "name": "B3", "wave": "x===.", "data": "a3 b3", "period": 3 }, 10 | { "name": "B4", "wave": "x===.", "data": "a4 b4", "period": 4 } 11 | ]} 12 | -------------------------------------------------------------------------------- /test/files/tutorial_0.json: -------------------------------------------------------------------------------- 1 | { "signal": [{ "name": "Alfa", "wave": "01.zx=ud.23.45" }] } -------------------------------------------------------------------------------- /test/files/tutorial_0n.json: -------------------------------------------------------------------------------- 1 | { 2 | "signal": [{ "name": "Alfa", "wave": "01.zx=ud.23.45" }], 3 | "config": { "skin": "narrow" } 4 | } 5 | -------------------------------------------------------------------------------- /test/files/tutorial_1.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "pclk", "wave": "p......." }, 3 | { "name": "Pclk", "wave": "P......." }, 4 | { "name": "nclk", "wave": "n......." }, 5 | { "name": "Nclk", "wave": "N......." }, 6 | {}, 7 | { "name": "clk0", "wave": "phnlPHNL" }, 8 | { "name": "clk1", "wave": "xhlhLHl." }, 9 | { "name": "clk2", "wave": "hpHplnLn" }, 10 | { "name": "clk3", "wave": "nhNhplPl" }, 11 | { "name": "clk4", "wave": "xlh.L.Hx" } 12 | ]} -------------------------------------------------------------------------------- /test/files/tutorial_10.json: -------------------------------------------------------------------------------- 1 | {"signal": [ 2 | {"name":"clk", "wave": "p.....PPPPp...." }, 3 | {"name":"dat", "wave": "x....2345x.....", "data": "a b c d" }, 4 | {"name":"req", "wave": "0....1...0....." } 5 | ], 6 | "head": {"text": 7 | ["tspan", 8 | ["tspan", {"class":"error h1"}, "error "], 9 | ["tspan", {"class":"warning h2"}, "warning "], 10 | ["tspan", {"class":"info h3"}, "info "], 11 | ["tspan", {"class":"success h4"}, "success "], 12 | ["tspan", {"class":"muted h5"}, "muted "], 13 | ["tspan", {"class":"h6"}, "h6 "], 14 | "default ", 15 | ["tspan", {"fill":"pink", "font-weight":"bold", "font-style":"italic"}, "pink-bold-italic"] 16 | ] 17 | }, 18 | "foot": {"text": 19 | ["tspan", "E=mc", 20 | ["tspan", {"dy":"-5"}, "2"], 21 | ["tspan", {"dy": "5"}, ". "], 22 | ["tspan", {"font-size":"25"}, "B "], 23 | ["tspan", {"text-decoration":"overline"},"over "], 24 | ["tspan", {"text-decoration":"underline"},"under "], 25 | ["tspan", {"baseline-shift":"sub"}, "sub "], 26 | ["tspan", {"baseline-shift":"super"}, "super "] 27 | ],"tock":-5 28 | } 29 | } -------------------------------------------------------------------------------- /test/files/tutorial_11.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "A", "wave": "01........0....", "node": ".a........j" }, 3 | { "name": "B", "wave": "0.1.......0.1..", "node": "..b.......i" }, 4 | { "name": "C", "wave": "0..1....0...1..", "node": "...c....h.." }, 5 | { "name": "D", "wave": "0...1..0.....1.", "node": "....d..g..." }, 6 | { "name": "E", "wave": "0....10.......1", "node": ".....ef...." } 7 | ], 8 | "edge": [ 9 | "a~b t1", "c-~a t2", "c-~>d time 3", "d~-e", 10 | "e~>f", "f->g", "g-~>h", "h~>i some text", "h~->j" 11 | ] 12 | } -------------------------------------------------------------------------------- /test/files/tutorial_12.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "A", "wave": "01..0..", "node": ".a..e.." }, 3 | { "name": "B", "wave": "0.1..0.", "node": "..b..d.", "phase":0.5 }, 4 | { "name": "C", "wave": "0..1..0", "node": "...c..f" }, 5 | { "node": "...g..h" } 6 | ], 7 | "edge": [ 8 | "b-|a t1", "a-|c t2", "b-|-c t3", "c-|->e t4", "e-|>f more text", 9 | "e|->d t6", "c-g", "f-h", "g<->h 3 ms" 10 | ] 11 | } -------------------------------------------------------------------------------- /test/files/tutorial_1n.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "pclk", "wave": "p......." }, 3 | { "name": "Pclk", "wave": "P......." }, 4 | { "name": "nclk", "wave": "n......." }, 5 | { "name": "Nclk", "wave": "N......." }, 6 | {}, 7 | { "name": "clk0", "wave": "phnlPHNL" }, 8 | { "name": "clk1", "wave": "xhlhLHl." }, 9 | { "name": "clk2", "wave": "hpHplnLn" }, 10 | { "name": "clk3", "wave": "nhNhplPl" }, 11 | { "name": "clk4", "wave": "xlh.L.Hx" } 12 | ], 13 | "config": { "skin": "narrow" } 14 | } -------------------------------------------------------------------------------- /test/files/tutorial_2.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "clk", "wave": "P......" }, 3 | { "name": "bus", "wave": "x.==.=x", "data": ["head", "body", "tail", "data"] }, 4 | { "name": "wire", "wave": "0.1..0." } 5 | ]} -------------------------------------------------------------------------------- /test/files/tutorial_3.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "clk", "wave": "p.....|..." }, 3 | { "name": "data", "wave": "x.345x|=.x", "data": ["head", "body", "tail", "data"] }, 4 | { "name": "Request", "wave": "0.1..0|1.0" }, 5 | {}, 6 | { "name": "Acknowledge", "wave": "1.....|01." } 7 | ]} -------------------------------------------------------------------------------- /test/files/tutorial_4.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "clk", "wave": "p..Pp..P"}, 3 | ["Master", 4 | ["ctrl", 5 | {"name": "write", "wave": "01.0...."}, 6 | {"name": "read", "wave": "0...1..0"} 7 | ], 8 | { "name": "addr", "wave": "x3.x4..x", "data": "A1 A2"}, 9 | { "name": "wdata", "wave": "x3.x....", "data": "D1" } 10 | ], 11 | {}, 12 | ["Slave", 13 | ["ctrl", 14 | {"name": "ack", "wave": "x01x0.1x"} 15 | ], 16 | { "name": "rdata", "wave": "x.....4x", "data": "Q2"} 17 | ] 18 | ]} -------------------------------------------------------------------------------- /test/files/tutorial_5.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "CK", "wave": "P.......", "period": 2 }, 3 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 }, 4 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 }, 5 | { "name": "DQS", "wave": "z.......0.1010z." }, 6 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" } 7 | ]} -------------------------------------------------------------------------------- /test/files/tutorial_6.json: -------------------------------------------------------------------------------- 1 | { "signal": [ 2 | { "name": "clk", "wave": "p...." }, 3 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] }, 4 | { "name": "Request", "wave": "01..0" } 5 | ], 6 | "config": { "hscale": 1 } 7 | } -------------------------------------------------------------------------------- /test/files/tutorial_7.json: -------------------------------------------------------------------------------- 1 | { "signal" : [ 2 | { "name": "clk", "wave": "p...." }, 3 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] }, 4 | { "name": "Request", "wave": "01..0" } 5 | ], 6 | "config" : { "hscale" : 2 } 7 | } -------------------------------------------------------------------------------- /test/files/tutorial_8.json: -------------------------------------------------------------------------------- 1 | { "signal" : [ 2 | { "name": "clk", "wave": "p...." }, 3 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] }, 4 | { "name": "Request", "wave": "01..0" } 5 | ], 6 | "config" : { "hscale" : 3 } 7 | } -------------------------------------------------------------------------------- /test/files/tutorial_9.json: -------------------------------------------------------------------------------- 1 | {"signal": [ 2 | {"name":"clk", "wave": "p...." }, 3 | {"name":"data", "wave": "x345x", "data": "a b c" }, 4 | {"name":"Request", "wave": "01..0" } 5 | ], 6 | "head":{ 7 | "text":"WaveDrom example", 8 | "tick":0 9 | }, 10 | "foot":{ 11 | "text":"Figure 100", 12 | "tock":9 13 | } 14 | } -------------------------------------------------------------------------------- /test/single_test.py: -------------------------------------------------------------------------------- 1 | #%% [markdown] 2 | # The current working directory must be in 3 | # the `test` folder. Even though this file resides in the `test` folder, VSCode starts the kernel in the "opened" folder by default, which is the root of the GitHub project. 4 | #%% 5 | import os 6 | 7 | if not os.getcwd()[-4:] == 'test': 8 | os.chdir('./test') 9 | 10 | #%% [markdown] 11 | # # Test Case Selection and Configuration 12 | # Change `file` to the .json that should be rendered and tested 13 | #%% 14 | #The json file to render with `wavedrompy` and `wavedrom`, then compare the results 15 | file = './files/issue_14.json' 16 | 17 | #The directory where the SVG and PNG files will be saved 18 | tmpdir = './tmp' 19 | wavedromdir = './tmp/wavedrom' 20 | 21 | #%% from `test_render.py` 22 | import subprocess 23 | from os.path import splitext, basename 24 | 25 | import wavedrom 26 | from diff import diff_raster 27 | from diff import main as diff 28 | 29 | #%% [markdown] 30 | # # Upstream Setup 31 | # Uncomment these two lines if the environment needs to have wavedrom downloaded and installed 32 | #%% from `test_render.py`, `wavedromdir(tmpdir_factory)` 33 | 34 | #subprocess.check_call("git clone https://github.com/wavedrom/wavedrom.git {}".format(wavedromdir), shell=True) 35 | #subprocess.check_call("npm install", cwd=str(wavedromdir), shell=True) 36 | 37 | #%% [markdown] 38 | # # Generate SVGs, Determine XML Differences, and Calculate Raster Difference 39 | #%% from `test_render.py`, `test_upstream(tmpdir,wavedromdir,file)` 40 | from IPython.display import SVG, display 41 | 42 | test_name = splitext(basename(file))[0] 43 | f_out_js = "{}/{}_js.svg".format(tmpdir, test_name) 44 | f_out_py = "{}/{}_py.svg".format(tmpdir, test_name) 45 | 46 | subprocess.check_call("node {}/bin/cli.js -i {} > {}".format(wavedromdir, file, f_out_js), shell=True) 47 | wavedrom.render_file(file, f_out_py, strict_js_features=True) 48 | 49 | display('wavedrom:') 50 | display(SVG(f_out_js)) 51 | display('wavedrompy:') 52 | display(SVG(f_out_py)) 53 | 54 | unknown = diff(f_out_js, f_out_py) 55 | 56 | if len(unknown) > 0: 57 | msg = "{} mismatch(es)\n".format(len(unknown)) 58 | msg += "js file: {}\npy file: {}\n".format(f_out_js, f_out_py) 59 | msg += "\n".join([str(action) for action in unknown]) 60 | #pytest.fail(msg) 61 | display(msg) 62 | 63 | raster_difference = diff_raster(f_out_js, f_out_py) 64 | 65 | #%% [markdown] 66 | # # Analyze Raster Difference 67 | # This cell does some analysis on the difference between the two images generarted by rasterizing the SVG output by *wavedrom* and *wavedrompy*. The cell saves several channel-specific differences to images so the differences can be visualized. 68 | # 69 | # The process should work with differences in all four channels of an RGBA image. It will not work with L (grayscale) images. This has only been tested with RGBA images that have differences in either: 70 | # - The RGB channels only 71 | # - The alpha channel only 72 | #%% 73 | from IPython.display import display 74 | from PIL import Image, ImageChops, ImageOps 75 | 76 | differentBands = '' #To be appened with bands that have at least one different pixel 77 | 78 | if raster_difference.getbbox() is None: #The images are identical 79 | print('Wavedrom and Wavedrompy rendered to indentical PNG images') 80 | else: 81 | #Check which individual bands are different, and add bands with differences to `differentBands` 82 | for bandName, band in zip(raster_difference.getbands(), raster_difference.split()): 83 | if band.getbbox() is not None: 84 | differentBands += bandName 85 | print('Difference in ' + bandName + ' channel') 86 | 87 | #Display differences in color bands, ignoring alpha 88 | if 'R' in differentBands or 'G' in differentBands or 'B' in differentBands: 89 | print('Difference of RGB:') 90 | noAlpha = Image.merge('RGB', raster_difference.split()[0:3]) 91 | noAlphaEnhanced = ImageOps.autocontrast(noAlpha) 92 | display(noAlphaEnhanced) 93 | noAlphaEnhanced.save('./tmp/' + test_name + '_noAlphaDiff.png') 94 | 95 | #Display differences in alpha band as grayscale image 96 | if 'A' in differentBands: 97 | print('Difference of alpha:') 98 | alphaOnly = raster_difference.split()[-1] 99 | alphaOnlyEnhanced = ImageOps.autocontrast(alphaOnly) 100 | display(alphaOnlyEnhanced) 101 | alphaOnlyEnhanced.save('./tmp/' + test_name + '_alphaOnlyDiff.png') 102 | 103 | #%% Compose the original and difference images 104 | import cairosvg, io 105 | 106 | noAlphaCopy = noAlphaEnhanced.copy() 107 | 108 | with open(f_out_js, encoding="utf-8") as fileObj_svg_js: 109 | svg_js = fileObj_svg_js.read() 110 | 111 | png_js = cairosvg.svg2png(svg_js) 112 | orig = Image.open(io.BytesIO(png_js)) 113 | origCopy = orig.copy() 114 | 115 | #Compose the two images, 116 | noAlphaCopy.putalpha(255) 117 | origCopy.putalpha(64) 118 | noAlphaCopy.alpha_composite(origCopy) 119 | 120 | #Use original transparency from wavedrom image 121 | noAlphaCopy.putalpha(orig.split()[-1]) 122 | 123 | display(noAlphaCopy) 124 | noAlphaCopy.save('./tmp/' + test_name + '_differenceAndOriginalComposed.png') 125 | -------------------------------------------------------------------------------- /test/test_brick_regression.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from wavedrom import WaveDrom 4 | from brick_regressions import all 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | metafunc.parametrize("test", all) 9 | 10 | 11 | def test_regression(test): 12 | w = WaveDrom() 13 | w.lane.period = test.period 14 | w.lane.hscale = test.hscale 15 | w.lane.phase = test.phase 16 | output = w.parse_wave_lane(test.wave, test.period * test.hscale - 1) 17 | assert(output == test.expected) -------------------------------------------------------------------------------- /test/test_render.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | from glob import glob 5 | from os.path import splitext, basename 6 | 7 | import wavedrom 8 | import pytest 9 | from diff import diff_raster 10 | from diff import main as diff 11 | 12 | files_basic = glob("test/files/signal_*.json") 13 | files_subcycle = glob("test/files/subcycle_*.json") 14 | files_assign = glob("test/files/assign_*.json") 15 | files_bitfield = glob("test/files/bitfield_*.json") 16 | files_tutorial = glob("test/files/tutorial_*.json") 17 | files_issues = glob("test/files/issue_*.json") 18 | 19 | files = files_basic + files_tutorial + files_issues 20 | 21 | 22 | def pytest_generate_tests(metafunc): 23 | metafunc.parametrize("file", files) 24 | 25 | 26 | def test_render(file): 27 | jinput = open(file).read() 28 | wavedrom.render(jinput) 29 | 30 | 31 | @pytest.fixture(scope="session") 32 | def wavedromdir(tmpdir_factory): 33 | if "WAVEDROMDIR" in os.environ: 34 | return os.environ["WAVEDROMDIR"] 35 | else: 36 | wavedromdir = tmpdir_factory.mktemp("wavedrom") 37 | subprocess.check_call("git clone https://github.com/wavedrom/wavedrom.git {}".format(wavedromdir), shell=True) 38 | subprocess.check_call("git reset --hard 1d4d25181d6660b5d069defcf04583158b51aa5c~1", cwd=str(wavedromdir), shell=True) 39 | subprocess.check_call("npm install", cwd=str(wavedromdir), shell=True) 40 | return wavedromdir 41 | 42 | 43 | @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher") 44 | def test_upstream(tmpdir,wavedromdir,file): 45 | base = splitext(basename(file))[0] 46 | f_out = "{}/{}.svg".format(tmpdir, base) 47 | f_out_py = "{}/{}_py.svg".format(tmpdir, base) 48 | 49 | subprocess.check_call("node {}/bin/cli.js -i {} > {}".format(wavedromdir, file, f_out), shell=True) 50 | wavedrom.render_file(file, f_out_py, strict_js_features=True) 51 | 52 | unknown = diff(f_out, f_out_py) 53 | 54 | if len(unknown) > 0: 55 | msg = "{} mismatch(es)\n".format(len(unknown)) 56 | msg += "js file: {}\npy file: {}\n".format(f_out, f_out_py) 57 | msg += "\n".join([str(action) for action in unknown]) 58 | pytest.fail(msg) 59 | 60 | img = diff_raster(f_out, f_out_py) 61 | 62 | if img.getbbox() is not None: 63 | pytest.fail("Raster image comparison failed for " + file) 64 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py37, py38, py39, p310 3 | 4 | [testenv] 5 | deps = pytest 6 | extras = test 7 | 8 | commands = 9 | pytest -v {posargs} 10 | -------------------------------------------------------------------------------- /wavedrom/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright wavedrompy contributors. 2 | # SPDX-License-Identifier: MIT 3 | 4 | 5 | import argparse 6 | import json 7 | import yaml 8 | import sys 9 | 10 | from .waveform import WaveDrom 11 | from .assign import Assign 12 | from .version import version 13 | from .bitfield import BitField 14 | 15 | import json 16 | 17 | 18 | def fixQuotes(inputString): 19 | # fix double quotes in the input file. opening with yaml and dumping with json fix the issues. 20 | yamlCode = yaml.load(inputString, Loader=yaml.FullLoader) 21 | fixedString = json.dumps(yamlCode, indent=4) 22 | return fixedString 23 | 24 | 25 | def render(source="", output=[], strict_js_features=False): 26 | source = json.loads(fixQuotes(source)) 27 | if source.get("signal"): 28 | return WaveDrom().render_waveform(0, source, output, strict_js_features) 29 | elif source.get("assign"): 30 | return Assign().render(0, source, output) 31 | elif source.get("reg"): 32 | return BitField().renderJson(source) 33 | 34 | 35 | def render_write(source, output, strict_js_features=False): 36 | jinput = source.read() 37 | out = render(jinput, strict_js_features=strict_js_features) 38 | out.write(output) 39 | 40 | 41 | def render_file(source, output, strict_js_features=False): 42 | out = open(output, "w") 43 | render_write(open(source, "r"), out, strict_js_features=strict_js_features) 44 | out.close() 45 | 46 | 47 | def main(): 48 | parser = argparse.ArgumentParser(description="") 49 | parser.add_argument( 50 | "--input", 51 | "-i", 52 | help="", 53 | required=True, 54 | type=argparse.FileType("r"), 55 | ) 56 | parser.add_argument( 57 | "--svg", 58 | "-s", 59 | help="", 60 | nargs="?", 61 | type=argparse.FileType("w"), 62 | default=sys.stdout, 63 | ) 64 | args = parser.parse_args() 65 | 66 | render_write(args.input, args.svg, False) 67 | -------------------------------------------------------------------------------- /wavedrom/assign.py: -------------------------------------------------------------------------------- 1 | # Copyright wavedrompy contributors. 2 | # SPDX-License-Identifier: MIT 3 | 4 | # Translated to Python from original file: 5 | # https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js 6 | 7 | from collections import namedtuple 8 | import svgwrite 9 | 10 | from .base import SVGBase 11 | 12 | 13 | class RenderState: 14 | def __init__(self, x=0, y=0, xmax=0): 15 | self.x = x 16 | self.y = y 17 | self.xmax = xmax 18 | 19 | def __str__(self): 20 | return "x={} y={}, xmax={}".format(self.x, self.y, self.xmax) 21 | 22 | 23 | RenderObject = namedtuple("RenderObject", "name x y") 24 | 25 | 26 | class Assign(SVGBase): 27 | def render_tree(self, tree, state): 28 | state.xmax = max(state.xmax, state.x) 29 | y = state.y 30 | for i in range(1, len(tree)): 31 | if isinstance(tree[i], list): 32 | state = self.render_tree( 33 | tree[i], RenderState(x=state.x + 1, y=state.y, xmax=state.xmax) 34 | ) 35 | else: 36 | tree[i] = RenderObject(name=tree[i], x=state.x + 1, y=state.y) 37 | state.y += 2 38 | tree[0] = RenderObject(name=tree[0], x=state.x, y=round((y + state.y - 2) / 2)) 39 | state.x -= 1 40 | 41 | return state 42 | 43 | def draw_body(self, type, ymin, ymax): 44 | circle = " M 4,0 C 4,1.1 3.1,2 2,2 0.9,2 0,1.1 0,0 c 0,-1.1 0.9,-2 2,-2 1.1,0 2,0.9 2,2 z" 45 | gates = { 46 | "~": "M -11,-6 -11,6 0,0 z m -5,6 5,0" + circle, 47 | "=": "M -11,-6 -11,6 0,0 z m -5,6 5,0", 48 | "&": "m -16,-10 5,0 c 6,0 11,4 11,10 0,6 -5,10 -11,10 l -5,0 z", 49 | "~&": "m -16,-10 5,0 c 6,0 11,4 11,10 0,6 -5,10 -11,10 l -5,0 z" + circle, 50 | "|": "m -18,-10 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 2.5,-5 2.5,-15 0,-20 z", 51 | "~|": "m -18,-10 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 2.5,-5 2.5,-15 0,-20 z" 52 | + circle, 53 | "^": "m -21,-10 c 1,3 2,6 2,10 m 0,0 c 0,4 -1,7 -2,10 m 3,-20 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 1,-3 2,-6 2,-10 0,-4 -1,-7 -2,-10 z", 54 | "~^": "m -21,-10 c 1,3 2,6 2,10 m 0,0 c 0,4 -1,7 -2,10 m 3,-20 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 1,-3 2,-6 2,-10 0,-4 -1,-7 -2,-10 z" 55 | + circle, 56 | "+": "m -8,5 0,-10 m -5,5 10,0 m 3,0 c 0,4.418278 -3.581722,8 -8,8 -4.418278,0 -8,-3.581722 -8,-8 0,-4.418278 3.581722,-8 8,-8 4.418278,0 8,3.581722 8,8 z", 57 | "*": "m -4,4 -8,-8 m 0,8 8,-8 m 4,4 c 0,4.418278 -3.581722,8 -8,8 -4.418278,0 -8,-3.581722 -8,-8 0,-4.418278 3.581722,-8 8,-8 4.418278,0 8,3.581722 8,8 z", 58 | } 59 | iec = { 60 | "BUF": 1, 61 | "INV": 1, 62 | "AND": "&", 63 | "NAND": "&", 64 | "OR": "\u22651", 65 | "NOR": "\u22651", 66 | "XOR": "=1", 67 | "XNOR": "=1", 68 | "box": "", 69 | } 70 | circled = {"INV", "NAND", "NOR", "XNOR"} 71 | 72 | if ymax == ymin: 73 | ymax = 4 74 | ymin = -4 75 | 76 | if type in gates: 77 | return self.element.path(class_="gate", d=gates[type]) 78 | elif type in iec: 79 | g = self.container.g() 80 | if type in circled: 81 | path = self.element.path( 82 | class_="gate", 83 | d="m -16,{} 16,0 0,{} -16,0 z {}".format( 84 | ymin - 3, ymax - ymin + 6, circle 85 | ), 86 | ) 87 | else: 88 | path = self.element.path( 89 | class_="gate", 90 | d="m -16,{} 16,0 0,{} -16,0 z".format(ymin - 3, ymax - ymin + 6), 91 | ) 92 | g.add(path) 93 | tspan = self.element.tspan(iec[type], x=[-14], y=[4], class_="wirename") 94 | text = self.element.text("") 95 | text.add(tspan) 96 | g.add(text) 97 | return g 98 | else: 99 | tspan = self.element.tspan(type, x=[-14], y=[4], class_="wirename") 100 | text = self.element.text("") 101 | text.add(tspan) 102 | return text 103 | 104 | def draw_gate(self, spec): 105 | ret = self.container.g() 106 | 107 | ys = [s[1] for s in spec[2:]] 108 | 109 | ymin = min(ys) 110 | ymax = max(ys) 111 | 112 | g = self.container.g(transform="translate(16,0)") 113 | g.add( 114 | self.element.path( 115 | d="M {},{} {},{}".format(spec[2][0], ymin, spec[2][0], ymax), 116 | class_="wire", 117 | ) 118 | ) 119 | ret.add(g) 120 | 121 | for s in spec[2:]: 122 | path = self.element.path(d="m {},{} 16,0".format(s[0], s[1]), class_="wire") 123 | ret.add(self.container.g().add(path)) 124 | 125 | g = self.container.g( 126 | transform="translate({},{})".format(spec[1][0], spec[1][1]) 127 | ) 128 | g.add(self.element.title(spec[0])) 129 | g.add(self.draw_body(spec[0], ymin - spec[1][1], ymax - spec[1][1])) 130 | ret.add(g) 131 | 132 | return ret 133 | 134 | def draw_boxes(self, tree, xmax): 135 | ret = self.container.g() 136 | spec = [] 137 | 138 | if isinstance(tree, list): 139 | spec.append(tree[0].name) 140 | spec.append([32 * (xmax - tree[0].x), 8 * tree[0].y]) 141 | for t in tree[1:]: 142 | if isinstance(t, list): 143 | spec.append([32 * (xmax - t[0].x), 8 * t[0].y]) 144 | else: 145 | spec.append([32 * (xmax - t.x), 8 * t.y]) 146 | 147 | ret.add(self.draw_gate(spec)) 148 | 149 | for t in tree[1:]: 150 | ret.add(self.draw_boxes(t, xmax)) 151 | else: 152 | fname = tree.name 153 | fx = 32 * (xmax - tree.x) 154 | fy = 8 * tree.y 155 | g = self.container.g(transform="translate({},{})".format(fx, fy)) 156 | g.add(self.element.title(fname)) 157 | g.add(self.element.path(d="M 2,0 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z")) 158 | tspan = self.element.tspan(fname, x=[-4], y=[4], class_="pinname") 159 | text = self.element.text("") 160 | text.add(tspan) 161 | g.add(text) 162 | ret.add(g) 163 | 164 | return ret 165 | 166 | def render(self, index=0, source={}, output=[]): 167 | STYLE = ".pinname {font-size:12px; font-style:normal; font-variant:normal; font-weight:500; font-stretch:normal; text-align:center; text-anchor:end; font-family:Helvetica} .wirename {font-size:12px; font-style:normal; font-variant:normal; font-weight:500; font-stretch:normal; text-align:center; text-anchor:start; font-family:Helvetica} .wirename:hover {fill:blue} .gate {color:#000; fill:#ffc; fill-opacity: 1;stroke:#000; stroke-width:1; stroke-opacity:1} .gate:hover {fill:red !important; } .wire {fill:none; stroke:#000; stroke-width:1; stroke-opacity:1} .grid {fill:#fff; fill-opacity:1; stroke:none}" 168 | 169 | tree = source.get("assign") 170 | state = RenderState(x=0, y=2, xmax=0) 171 | 172 | for t in tree: 173 | state = self.render_tree(t, state) 174 | state.x += 1 175 | 176 | xmax = state.xmax + 3 177 | 178 | width = 32 * (xmax + 1) + 1 179 | height = 8 * (state.y + 1) - 7 180 | ilen = 4 * (xmax + 1) 181 | jlen = state.y + 1 182 | 183 | grid = self.container.g() 184 | 185 | for i in range(ilen + 1): 186 | for j in range(jlen + 1): 187 | grid.add( 188 | self.element.rect( 189 | height=1, 190 | width=1, 191 | x=(i * 8 - 0.5), 192 | y=(j * 8 - 0.5), 193 | class_="grid", 194 | ) 195 | ) 196 | 197 | for t in tree: 198 | content = self.draw_boxes(t, xmax) 199 | 200 | attr = {"viewBox": "0 0 {} {}".format(width, height)} 201 | template = svgwrite.Drawing( 202 | id="svgcontent_{index}".format(index=index), size=[width, height], **attr 203 | ) 204 | template.defs.add(svgwrite.container.Style(content=STYLE)) 205 | g = self.container.g(transform="translate(0.5,0.5)") 206 | g.add(grid) 207 | g.add(content) 208 | template.add(g) 209 | return template 210 | -------------------------------------------------------------------------------- /wavedrom/attrdict.py: -------------------------------------------------------------------------------- 1 | class AttrDict(dict): 2 | def __init__(self, *args, **kwargs): 3 | super(AttrDict, self).__init__(*args, **kwargs) 4 | self.__dict__ = self 5 | -------------------------------------------------------------------------------- /wavedrom/base.py: -------------------------------------------------------------------------------- 1 | # Copyright wavedrompy contributors. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import svgwrite 5 | from .attrdict import AttrDict 6 | 7 | 8 | class SVGBase(object): 9 | container = AttrDict( 10 | { 11 | "defs": svgwrite.container.Defs, 12 | "g": svgwrite.container.Group, 13 | "marker": svgwrite.container.Marker, 14 | "use": svgwrite.container.Use, 15 | } 16 | ) 17 | element = AttrDict( 18 | { 19 | "rect": svgwrite.shapes.Rect, 20 | "path": svgwrite.path.Path, 21 | "text": svgwrite.text.Text, 22 | "tspan": svgwrite.text.TSpan, 23 | "title": svgwrite.base.Title, 24 | "line": svgwrite.shapes.Line, 25 | } 26 | ) 27 | -------------------------------------------------------------------------------- /wavedrom/bitfield.py: -------------------------------------------------------------------------------- 1 | # Copyright wavedrompy contributors. 2 | # SPDX-License-Identifier: MIT 3 | 4 | # Translated to Python from original file: 5 | # https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js 6 | 7 | from math import floor 8 | 9 | import svgwrite 10 | 11 | from .base import SVGBase 12 | from .tspan import TspanParser 13 | 14 | 15 | class Options: 16 | def __init__( 17 | self, 18 | vspace=80, 19 | hspace=800, 20 | lanes=1, 21 | bits=32, 22 | hflip=False, 23 | vflip=False, 24 | fontsize=14, 25 | fontfamily="sans-serif", 26 | fontweight="normal", 27 | ): 28 | self.vspace = vspace if vspace > 19 else 80 29 | self.hspace = hspace if hspace > 39 else 800 30 | self.lanes = lanes if lanes > 0 else 1 31 | self.bits = bits if bits > 4 else 32 32 | self.hflip = hflip 33 | self.vflip = vflip 34 | self.fontsize = fontsize if fontsize > 5 else 14 35 | self.fontfamily = fontfamily 36 | self.fontweight = fontweight 37 | 38 | 39 | colors = {2: "FF0000", 3: "AAFF00", 4: "00FFD5", 5: "FFBF00", 6: "00FF1A", 7: "006AFF"} 40 | 41 | 42 | def type_style(t): 43 | if t in colors.keys(): 44 | return ";fill:#{}".format(colors[t]) 45 | else: 46 | return "" 47 | 48 | 49 | class BitField(SVGBase): 50 | def tspan_parse(self, text): 51 | parser = TspanParser() 52 | parser.feed(text) 53 | return parser.get_text() 54 | 55 | def hline(self, len, x=0, y=0): 56 | return self.element.line(start=(x, y), end=(x + len, y)) 57 | 58 | def vline(self, len, x=0, y=0): 59 | return self.element.line(start=(x, y), end=(x, y + len)) 60 | 61 | def get_text(self, body, x, y=None): 62 | x_list = None 63 | if x: 64 | x_list = [x] 65 | y_list = None 66 | if y: 67 | y_list = [y] 68 | text = self.element.text("", x=x_list, y=y_list) 69 | for t in self.tspan_parse(str(body)): 70 | text.add(t) 71 | return text 72 | 73 | def get_label(self, attr, x, y, step=0, length=0): 74 | if isinstance(attr, int): 75 | attr = int(attr) 76 | res = [] 77 | for i in range(length): 78 | val = (attr >> i) & 1 79 | xi = x + step * (length / 2 - i - 0.5) 80 | res.append(self.get_text(val, xi, y)) 81 | return res 82 | else: 83 | if "\n" in attr: 84 | names = attr.split("\n") 85 | count = len(names) 86 | return [ 87 | self.get_text( 88 | name, x, y + (-(count - 1) / 2 + i) * self.opt.fontsize 89 | ) 90 | for (i, name) in enumerate(names) 91 | ] 92 | return [self.get_text(attr, x, y)] 93 | 94 | def get_attrs(self, e, step, lsbm, msbm): 95 | if self.opt.vflip: 96 | x = step * (msbm + lsbm) / 2 97 | else: 98 | x = step * (self.mod - ((msbm + lsbm) / 2) - 1) 99 | attr = e["attr"] 100 | bits = e["bits"] 101 | attrs = [attr] 102 | # 'attr' supports both a scalar and a list. 103 | if isinstance(attr, list): 104 | attrs = attr 105 | return [self.get_label(a, x, 16 * i, step, bits) for (i, a) in enumerate(attrs)] 106 | 107 | def labelArr(self, desc): 108 | step = self.opt.hspace / self.mod 109 | bits = self.container.g( 110 | transform="translate({},{})".format(step / 2, self.opt.vspace / 5) 111 | ) 112 | names = self.container.g( 113 | transform="translate({},{})".format(step / 2, self.opt.vspace / 2 + 4) 114 | ) 115 | attrs = self.container.g( 116 | transform="translate({},{})".format(step / 2, self.opt.vspace) 117 | ) 118 | blanks = self.container.g( 119 | transform="translate(0,{})".format(self.opt.vspace / 4) 120 | ) 121 | 122 | for e in desc: 123 | lsbm = 0 124 | msbm = self.mod - 1 125 | lsb = self.index * self.mod 126 | msb = (self.index + 1) * self.mod - 1 127 | 128 | if floor(e["lsb"] / self.mod) == self.index: 129 | lsbm = e["lsbm"] 130 | lsb = e["lsb"] 131 | if floor(e["msb"] / self.mod) == self.index: 132 | msb = e["msb"] 133 | msbm = e["msbm"] 134 | else: 135 | if floor(e["msb"] / self.mod) == self.index: 136 | msb = e["msb"] 137 | msbm = e["msbm"] 138 | else: 139 | continue 140 | 141 | if self.opt.vflip: 142 | bits.add(self.get_text(lsb, x=[step * lsbm])) 143 | else: 144 | bits.add(self.get_text(lsb, x=[step * (self.mod - lsbm - 1)])) 145 | if lsbm != msbm: 146 | if self.opt.vflip: 147 | bits.add(self.get_text(msb, x=[step * msbm])) 148 | else: 149 | bits.add(self.get_text(msb, x=[step * (self.mod - msbm - 1)])) 150 | if e.get("name"): 151 | if self.opt.vflip: 152 | x = step * (msbm + lsbm) / 2 153 | else: 154 | x = step * (self.mod - ((msbm + lsbm) / 2) - 1) 155 | for n in self.get_label(e["name"], x, 0): 156 | names.add(n) 157 | 158 | if not e.get("name") or e.get("type"): 159 | style = "fill-opacity:0.1" + type_style(e.get("type", 0)) 160 | if self.opt.vflip: 161 | insert_x = lsbm 162 | else: 163 | insert_x = self.mod - msbm - 1 164 | insert = [step * insert_x, 0] 165 | size = [step * (msbm - lsbm + 1), self.opt.vspace / 2] 166 | blanks.add(self.element.rect(insert=insert, size=size, style=style)) 167 | if e.get("attr") is not None: 168 | for attr in self.get_attrs(e, step, lsbm, msbm): 169 | for a in attr: 170 | attrs.add(a) 171 | 172 | g = self.container.g() 173 | g.add(blanks) 174 | g.add(bits) 175 | g.add(names) 176 | g.add(attrs) 177 | return g 178 | 179 | def labels(self, desc): 180 | g = self.container.g(text_anchor="middle") 181 | g.add(self.labelArr(desc)) 182 | return g 183 | 184 | def cage(self, desc): 185 | hspace = self.opt.hspace 186 | vspace = self.opt.vspace 187 | mod = self.mod 188 | 189 | g = self.container.g( 190 | stroke="black", 191 | stroke_width=1, 192 | stroke_linecap="round", 193 | transform="translate(0,{})".format(vspace / 4), 194 | ) 195 | 196 | g.add(self.hline(hspace)) 197 | if self.opt.vflip: 198 | g.add(self.vline(0)) 199 | else: 200 | g.add(self.vline(vspace / 2)) 201 | g.add(self.hline(hspace, 0, vspace / 2)) 202 | 203 | i = self.index * mod 204 | if self.opt.vflip: 205 | r = range(0, mod + 1) 206 | else: 207 | r = range(mod, 0, -1) 208 | for j in r: 209 | if j == mod or any([(e["lsb"] == i) for e in desc]): 210 | g.add(self.vline((vspace / 2), j * (hspace / mod))) 211 | else: 212 | g.add(self.vline((vspace / 16), j * (hspace / mod))) 213 | g.add(self.vline((vspace / 16), j * (hspace / mod), vspace * 7 / 16)) 214 | i += 1 215 | 216 | return g 217 | 218 | def lane(self, desc): 219 | x = 4.5 220 | if self.opt.hflip: 221 | i = self.index 222 | else: 223 | i = self.opt.lanes - self.index - 1 224 | y = i * self.opt.vspace + 0.5 225 | g = self.container.g( 226 | transform="translate({},{})".format(x, y), 227 | text_anchor="middle", 228 | font_size=self.opt.fontsize, 229 | font_family=self.opt.fontfamily, 230 | font_weight=self.opt.fontweight, 231 | ) 232 | 233 | g.add(self.cage(desc)) 234 | g.add(self.labels(desc)) 235 | return g 236 | 237 | def get_max_attrs(self, desc): 238 | max_count = 0 239 | for e in desc: 240 | if "attr" in e: 241 | if isinstance(e["attr"], list): 242 | max_count = max(max_count, len(e["attr"])) 243 | else: 244 | max_count = max(max_count, 1) 245 | return max_count 246 | 247 | def render(self, desc, opt=Options()): 248 | self.opt = opt 249 | 250 | # Compute extra per-lane space needed if there are more than one attr 251 | # for any field. This spaces all lanes uniformly, matching the lane 252 | # with the most attr's. 253 | extra_attrs = 0 254 | max_attrs = self.get_max_attrs(desc) 255 | if max_attrs > 1: 256 | extra_attrs = max_attrs - 1 257 | self.extra_attr_space = extra_attrs * 16 258 | 259 | width = opt.hspace + 9 260 | height = (opt.vspace + self.extra_attr_space) * opt.lanes + 5 261 | 262 | template = svgwrite.Drawing() 263 | template["width"] = width 264 | template["height"] = height 265 | template["class"] = "WaveDrom" 266 | template.viewbox(0, 0, width, height) 267 | 268 | lsb = 0 269 | self.mod = int(opt.bits / opt.lanes) 270 | 271 | for e in desc: 272 | e["lsb"] = lsb 273 | e["lsbm"] = lsb % self.mod 274 | lsb += e["bits"] 275 | e["msb"] = lsb - 1 276 | e["msbm"] = e["msb"] % self.mod 277 | 278 | for i in range(opt.lanes): 279 | self.index = i 280 | template.add(self.lane(desc)) 281 | 282 | return template 283 | 284 | def renderJson(self, source): 285 | opt = Options() 286 | if source.get("config"): 287 | opt = Options(**source["config"]) 288 | if source.get("reg"): 289 | return self.render(source["reg"], opt) 290 | -------------------------------------------------------------------------------- /wavedrom/css.py: -------------------------------------------------------------------------------- 1 | # Copyright wavedrompy contributors. 2 | # SPDX-License-Identifier: MIT 3 | 4 | # Translated to Python from original file: 5 | # https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js 6 | 7 | from .attrdict import AttrDict 8 | 9 | css = AttrDict({}) 10 | css.default = """ 11 | text{font-size:11pt; 12 | font-style:normal; 13 | font-variant:normal; 14 | font-weight:normal; 15 | font-stretch:normal; 16 | text-align:center; 17 | fill-opacity:1; 18 | font-family:Helvetica} 19 | .h1{font-size:33pt; 20 | font-weight:bold} 21 | .h2{font-size:27pt; 22 | font-weight:bold} 23 | .h3{font-size:20pt; 24 | font-weight:bold} 25 | .h4{font-size:14pt; 26 | font-weight:bold} 27 | .h5{font-size:11pt; 28 | font-weight:bold} 29 | .h6{font-size:8pt; 30 | font-weight:bold} 31 | .muted{fill:#aaa} 32 | .warning{fill:#f6b900} 33 | .error{fill:#f60000} 34 | .info{fill:#0041c4} 35 | .success{fill:#00ab00} 36 | .s1{fill:none; 37 | stroke:#000; 38 | stroke-width:1; 39 | stroke-linecap:round; 40 | stroke-linejoin:miter; 41 | stroke-miterlimit:4; 42 | stroke-opacity:1; 43 | stroke-dasharray:none} 44 | .s2{fill:none; 45 | stroke:#000; 46 | stroke-width:0.5; 47 | stroke-linecap:round; 48 | stroke-linejoin:miter; 49 | stroke-miterlimit:4; 50 | stroke-opacity:1; 51 | stroke-dasharray:none} 52 | .s3{color:#000; 53 | fill:none; 54 | stroke:#000; 55 | stroke-width:1; 56 | stroke-linecap:round; 57 | stroke-linejoin:miter; 58 | stroke-miterlimit:4; 59 | stroke-opacity:1; 60 | stroke-dasharray:1, 3; 61 | stroke-dashoffset:0; 62 | marker:none; 63 | visibility:visible; 64 | display:inline; 65 | overflow:visible} 66 | .s4{color:#000; 67 | fill:none; 68 | stroke:#000; 69 | stroke-width:1; 70 | stroke-linecap:round; 71 | stroke-linejoin:miter; 72 | stroke-miterlimit:4; 73 | stroke-opacity:1; 74 | stroke-dasharray:none; 75 | stroke-dashoffset:0; 76 | marker:none; 77 | visibility:visible; 78 | display:inline; 79 | overflow:visible} 80 | .s5{fill:#fff; 81 | stroke:none} 82 | .s6{fill:#000; 83 | fill-opacity:1; 84 | stroke:none} 85 | .s7{color:#000; 86 | fill:#fff; 87 | fill-opacity:1; 88 | fill-rule:nonzero; 89 | stroke:none; 90 | stroke-width:1px; 91 | marker:none; 92 | visibility:visible; 93 | display:inline; 94 | overflow:visible} 95 | .s8{color:#000; 96 | fill:#ffffb4; 97 | fill-opacity:1; 98 | fill-rule:nonzero; 99 | stroke:none; 100 | stroke-width:1px; 101 | marker:none; 102 | visibility:visible; 103 | display:inline; 104 | overflow:visible} 105 | .s9{color:#000; 106 | fill:#ffe0b9; 107 | fill-opacity:1; 108 | fill-rule:nonzero; 109 | stroke:none; 110 | stroke-width:1px; 111 | marker:none; 112 | visibility:visible; 113 | display:inline; 114 | overflow:visible} 115 | .s10{color:#000; 116 | fill:#b9e0ff; 117 | fill-opacity:1; 118 | fill-rule:nonzero; 119 | stroke:none; 120 | stroke-width:1px; 121 | marker:none; 122 | visibility:visible; 123 | display:inline; 124 | overflow:visible} 125 | .s11{color:#000; 126 | fill:#ccfdfe; 127 | fill-opacity:1; 128 | fill-rule:nonzero; 129 | stroke:none; 130 | stroke-width:1px; 131 | marker:none; 132 | visibility:visible; 133 | display:inline; 134 | overflow:visible} 135 | .s12{color:#000; 136 | fill:#cdfdc5; 137 | fill-opacity:1; 138 | fill-rule:nonzero; 139 | stroke:none; 140 | stroke-width:1px; 141 | marker:none; 142 | visibility:visible; 143 | display:inline; 144 | overflow:visible} 145 | .s13{color:#000; 146 | fill:#f0c1fb; 147 | fill-opacity:1; 148 | fill-rule:nonzero; 149 | stroke:none; 150 | stroke-width:1px; 151 | marker:none; 152 | visibility:visible; 153 | display:inline; 154 | overflow:visible} 155 | .s14{color:#000; 156 | fill:#f5c2c0; 157 | fill-opacity:1; 158 | fill-rule:nonzero; 159 | stroke:none; 160 | stroke-width:1px; 161 | marker:none; 162 | visibility:visible; 163 | display:inline; 164 | overflow:visible} 165 | .s15{fill:#0041c4; 166 | fill-opacity:1; 167 | stroke:none} 168 | .s16{fill:none; 169 | stroke:#0041c4; 170 | stroke-width:1; 171 | stroke-linecap:round; 172 | stroke-linejoin:miter; 173 | stroke-miterlimit:4; 174 | stroke-opacity:1; 175 | stroke-dasharray:none} 176 | """ 177 | 178 | css.narrow = """ 179 | text{font-size:11pt; 180 | font-style:normal; 181 | font-variant:normal; 182 | font-weight:normal; 183 | font-stretch:normal; 184 | text-align:center; 185 | fill-opacity:1; 186 | font-family:Helvetica} 187 | .muted{fill:#aaa} 188 | .warning{fill:#f6b900} 189 | .error{fill:#f60000} 190 | .info{fill:#0041c4} 191 | .success{fill:#00ab00} 192 | .h1{font-size:33pt;font-weight:bold} 193 | .h2{font-size:27pt;font-weight:bold} 194 | .h3{font-size:20pt;font-weight:bold} 195 | .h4{font-size:14pt;font-weight:bold} 196 | .h5{font-size:11pt;font-weight:bold} 197 | .h6{font-size:8pt;font-weight:bold} 198 | .s1{fill:none; 199 | stroke:#000000; 200 | stroke-width:1; 201 | stroke-linecap:round; 202 | stroke-linejoin:miter; 203 | stroke-miterlimit:4; 204 | stroke-opacity:1; 205 | stroke-dasharray:none} 206 | .s2{fill:none; 207 | stroke:#000000; 208 | stroke-width:0.5; 209 | stroke-linecap:round; 210 | stroke-linejoin:miter; 211 | stroke-miterlimit:4; 212 | stroke-opacity:1; 213 | stroke-dasharray:none} 214 | .s3{color:#000000; 215 | fill:none; 216 | stroke:#000000; 217 | stroke-width:1; 218 | stroke-linecap:round; 219 | stroke-linejoin:miter; 220 | stroke-miterlimit:4; 221 | stroke-opacity:1; 222 | stroke-dasharray:1, 3; 223 | stroke-dashoffset:0; 224 | marker:none; 225 | visibility:visible; 226 | display:inline; 227 | overflow:visible; 228 | enable-background:accumulate} 229 | .s4{color:#000000; 230 | fill:none; 231 | stroke:#000000; 232 | stroke-width:1; 233 | stroke-linecap:round; 234 | stroke-linejoin:miter; 235 | stroke-miterlimit:4; 236 | stroke-opacity:1; 237 | stroke-dasharray:none; 238 | stroke-dashoffset:0; 239 | marker:none; 240 | visibility:visible; 241 | display:inline; 242 | overflow:visible} 243 | .s5{fill:#ffffff;stroke:none} 244 | .s6{color:#000000; 245 | fill:#ffffb4; 246 | fill-opacity:1; 247 | fill-rule:nonzero; 248 | stroke:none; 249 | stroke-width:1px; 250 | marker:none; 251 | visibility:visible; 252 | display:inline; 253 | overflow:visible; 254 | enable-background:accumulate} 255 | .s7{color:#000000; 256 | fill:#ffe0b9; 257 | fill-opacity:1; 258 | fill-rule:nonzero; 259 | stroke:none; 260 | stroke-width:1px; 261 | marker:none; 262 | visibility:visible; 263 | display:inline; 264 | overflow:visible; 265 | enable-background:accumulate} 266 | .s8{color:#000000; 267 | fill:#b9e0ff; 268 | fill-opacity:1; 269 | fill-rule:nonzero; 270 | stroke:none; 271 | stroke-width:1px; 272 | marker:none; 273 | visibility:visible; 274 | display:inline; 275 | overflow:visible; 276 | enable-background:accumulate} 277 | .s9{fill:#000000;fill-opacity:1;stroke:none} 278 | .s10{color:#000000; 279 | fill:#ffffff; 280 | fill-opacity:1; 281 | fill-rule:nonzero; 282 | stroke:none; 283 | stroke-width:1px; 284 | marker:none; 285 | visibility:visible; 286 | display:inline; 287 | overflow:visible; 288 | enable-background:accumulate} 289 | """ 290 | 291 | css.lowkey = """ 292 | text{font-size:11pt; 293 | font-style:normal; 294 | font-variant:normal; 295 | font-weight:normal; 296 | font-stretch:normal; 297 | text-align:center; 298 | fill-opacity:1; 299 | font-family:Helvetica} 300 | .muted{fill:#aaa} 301 | .warning{fill:#f6b900} 302 | .error{fill:#f60000} 303 | .info{fill:#0041c4} 304 | .success{fill:#00ab00} 305 | .h1{font-size:33pt; 306 | font-weight:bold} 307 | .h2{font-size:27pt; 308 | font-weight:bold} 309 | .h3{font-size:20pt; 310 | font-weight:bold} 311 | .h4{font-size:14pt; 312 | font-weight:bold} 313 | .h5{font-size:11pt; 314 | font-weight:bold} 315 | .h6{font-size:8pt; 316 | font-weight:bold} 317 | .s1{fill:none; 318 | stroke:#606060; 319 | stroke-width:0.75px; 320 | stroke-linecap:round; 321 | stroke-linejoin:miter; 322 | stroke-miterlimit:4; 323 | stroke-opacity:1; 324 | stroke-dasharray:none} 325 | .s2{fill:none; 326 | stroke:#606060; 327 | stroke-width:0.5; 328 | stroke-linecap:round; 329 | stroke-linejoin:miter; 330 | stroke-miterlimit:4; 331 | stroke-opacity:1; 332 | stroke-dasharray:none} 333 | .s3{color:#000; 334 | fill:none; 335 | stroke:#606060; 336 | stroke-width:0.75px; 337 | stroke-linecap:round; 338 | stroke-linejoin:miter; 339 | stroke-miterlimit:4; 340 | stroke-opacity:1; 341 | stroke-dasharray:1, 3; 342 | stroke-dashoffset:0; 343 | marker:none; 344 | visibility:visible; 345 | display:inline; 346 | overflow:visible; 347 | enable-background:accumulate} 348 | .s4{color:#000; 349 | fill:none; 350 | stroke:#606060; 351 | stroke-width:0.75px; 352 | stroke-linecap:round; 353 | stroke-linejoin:miter; 354 | stroke-miterlimit:4; 355 | stroke-opacity:1; 356 | stroke-dasharray:none; 357 | stroke-dashoffset:0; 358 | marker:none; 359 | visibility:visible; 360 | display:inline; 361 | overflow:visible} 362 | .s5{fill:#ffffff; 363 | stroke:none} 364 | .s6{color:#000; 365 | fill:#eaeaea; 366 | fill-opacity:1; 367 | fill-rule:nonzero; 368 | stroke:none; 369 | stroke-width:0.25px; 370 | marker:none; 371 | visibility:visible; 372 | display:inline; 373 | overflow:visible; 374 | enable-background:accumulate} 375 | .s7{color:#000; 376 | fill:#d7d7d7; 377 | fill-opacity:1; 378 | fill-rule:nonzero; 379 | stroke:none; 380 | stroke-width:0.25px; 381 | marker:none; 382 | visibility:visible; 383 | display:inline; 384 | overflow:visible; 385 | enable-background:accumulate} 386 | .s8{color:#000; 387 | fill:#c0c0c0; 388 | fill-opacity:1; 389 | fill-rule:nonzero; 390 | stroke:none; 391 | stroke-width:0.25px; 392 | marker:none; 393 | visibility:visible; 394 | display:inline; 395 | overflow:visible; 396 | enable-background:accumulate} 397 | .s9{fill:#606060; 398 | fill-opacity:1; 399 | stroke:none} 400 | .s10{color:#000; 401 | fill:#fff; 402 | fill-opacity:1; 403 | fill-rule:nonzero; 404 | stroke:none; 405 | stroke-width:0.25px; 406 | marker:none; 407 | visibility:visible; 408 | display:inline; 409 | overflow:visible; 410 | enable-background:accumulate} 411 | .s11{fill:#0041c4; 412 | fill-opacity:1; 413 | stroke:none} 414 | .s12{fill:none; 415 | stroke:#0041c4; 416 | stroke-width:0.75px; 417 | stroke-linecap:round; 418 | stroke-linejoin:miter; 419 | stroke-miterlimit:4; 420 | stroke-opacity:1; 421 | stroke-dasharray:none} 422 | """ 423 | -------------------------------------------------------------------------------- /wavedrom/tspan.py: -------------------------------------------------------------------------------- 1 | # Copyright wavedrompy contributors. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import sys 5 | 6 | import svgwrite 7 | from six import string_types 8 | from svgwrite.base import BaseElement 9 | from svgwrite.etree import etree 10 | from .attrdict import AttrDict 11 | 12 | if sys.version_info < (3, 0): 13 | from HTMLParser import HTMLParser 14 | else: 15 | from html.parser import HTMLParser 16 | 17 | 18 | class TspanParser(HTMLParser, object): 19 | tags = { 20 | "o": {"text_decoration": "overline"}, 21 | "ins": {"text_decoration": "underline"}, 22 | "sub": {"baseline_shift": "sub"}, 23 | "sup": {"baseline_shift": "super"}, 24 | "b": {"font_weight": "bold"}, 25 | "i": {"font_style": "italic"}, 26 | "s": {"text_decoration": "line-through"}, 27 | "tt": {"font_family": "monospace"}, 28 | } 29 | 30 | def __init__(self): 31 | super(TspanParser, self).__init__() 32 | self.text = [] 33 | self.state = [] 34 | 35 | def handle_starttag(self, tag, attrs): 36 | self.state.append(tag) 37 | 38 | def handle_endtag(self, tag): 39 | if self.state.pop() != tag: 40 | raise RuntimeError("Unexpected closing tag: {}".format(tag)) 41 | 42 | def get_style(self): 43 | return {k: v for d in [self.tags[t] for t in self.state] for k, v in d.items()} 44 | 45 | def handle_data(self, data): 46 | if len(self.state) == 0: 47 | self.text.append(svgwrite.text.TSpan(data)) 48 | else: 49 | self.text.append(svgwrite.text.TSpan(data, **self.get_style())) 50 | 51 | def get_text(self): 52 | return self.text 53 | 54 | 55 | class JsonMLElement(BaseElement): 56 | """Class that generates xml elements from jsonml.""" 57 | 58 | def __init__(self, source, **extra): 59 | """Constructs from jsonml source.""" 60 | self._jsonml = self.extract_element(source) 61 | self.elementname = self._jsonml.tagname 62 | self._jsonml.attributes.update(extra) 63 | super(JsonMLElement, self).__init__(**extra) 64 | 65 | def extract_element(self, e): 66 | """Extract AttrDict from jsonml 67 | 68 | This function non-recursively extracts an AttrDict from jsonml. 69 | This AttrDict has the three elements tagname, attributes and 70 | element_list according to the jsonml specification. 71 | 72 | :param e: element as jsonml list/tuple 73 | :return: AttrDict 74 | """ 75 | if not isinstance(e, (list, tuple)): 76 | raise ValueError("JsonML must be a list") 77 | if len(e) == 0: 78 | raise ValueError("JsonML cannot be an empty list") 79 | if not isinstance(e[0], string_types): 80 | raise ValueError("JsonML tagname must be string") 81 | ret = AttrDict({"tagname": e[0], "attributes": {}, "element-list": []}) 82 | if len(e) > 1: 83 | if isinstance(e[1], dict): 84 | ret.attributes = e[1] 85 | if len(e) > 2: 86 | ret.element_list = e[2:] 87 | else: 88 | ret.element_list = e[1:] 89 | return ret 90 | 91 | def get_xml_element(self, e): 92 | """Generate xml element from jsonml AttrDict 93 | 94 | Recursively generates xml element from jsonml AttrDict. 95 | 96 | :param e: jsonml AttrDict 97 | :return: xml element 98 | """ 99 | 100 | # create element 101 | element = etree.Element(e.tagname) 102 | # set element attributes 103 | for attribute, value in sorted(e.attributes.items()): 104 | # filter 'None' values 105 | if value is not None: 106 | value = self.value_to_string(value) 107 | if value: # just add not empty attributes 108 | element.set(attribute, value) 109 | # store the last xml sibling, because we may need to add 110 | # text to it's tail. This is to support the tagged text 111 | # style ("abc") 112 | last = None 113 | for c in e.element_list: 114 | if isinstance(c, string_types): 115 | # Strings need special treatment for insertion 116 | # as those are not elements 117 | if last is None: 118 | # No non-text element seen so far 119 | if element.text is None: 120 | # No other element seen so far 121 | element.text = c 122 | else: 123 | # Append to other texts 124 | element.text += c 125 | else: 126 | # There was an element already 127 | if last.tail is None: 128 | # No text after that so far 129 | last.tail = c 130 | else: 131 | # Append to other text 132 | last.tail += c 133 | else: 134 | # Recurse 135 | last = self.get_xml_element(self.extract_element(c)) 136 | element.append(last) 137 | 138 | return element 139 | 140 | def get_xml(self): 141 | return self.get_xml_element(self._jsonml) 142 | --------------------------------------------------------------------------------