Repository: kickingvegas/regfmt (language: python)

Issue:
Fix codebase to be PEP 8 comformant
Fix codebase to be PEP 8 comformant
Prepare for launch
- Clean up README.org
- Bump to version 0.1.10


Discussion / hints:

Fix patch:
diff --git a/.github/README.org b/.github/README.org
new file mode 100644
index 0000000..83de6a0
--- /dev/null
+++ b/.github/README.org
@@ -0,0 +1,294 @@
+* regfmt
+This project is a *work in progress*.
+
+*regfmt* is a command line utility to generate SVG diagrams for control register-style data formats. It is inspired by the /dformat/ command from the /troff/ family of tools, however re-imagined using contemporary (circa 2022) file formats.
+
+Example output of *regfmt*:
+
+[[../tests/control/example_0001-github.svg]]
+
+* Features
+- SVG output
+- Modern configuration input file formats
+  - YAML for register configuration
+  - CSS for styling SVG output
+
+* Usage
+#+begin_src text
+  usage: regfmt [-h] [-v] [-o OUTPUT] [-s STYLE] [input]
+
+  regfmt - generate SVG diagrams of control register-style data formats
+
+  positional arguments:
+    input                 input register format YAML file (default: input.yaml)
+
+  optional arguments:
+    -h, --help            show this help message and exit
+    -v, --version         print version information and exit
+    -o OUTPUT, --output OUTPUT
+                          output file (default: '-' for stdout)
+    -s STYLE, --style STYLE
+                          CSS style file
+  
+#+end_src
+
+* Examples
+
+Generate the output file ~register.svg~ by invoking ~regfmt~ with the input file ~register.yaml~:
+
+#+begin_src shell
+  $ regfmt -o register.svg register.yaml
+#+end_src
+
+The file content of ~register.yaml~:
+
+#+begin_src yaml :tangle tests/data/register.yaml
+  ##
+  # Example YAML file for regfmt
+
+  width: 32
+  endian: bigBit
+
+  registers:
+    - name: R1
+      fields:
+        - width: 8
+          name: A3
+
+        - width: 8
+          name: A2
+
+        - width: 8
+          name: A1
+
+        - width: 8
+          name: A0
+  layout:
+    field-name-align: center
+#+end_src
+
+The corresponding output SVG file ~register.svg~ file would be rendered as follows:
+
+[[../tests/control/register.svg]]
+
+There are times when field names are physically too long to center align inside a field. Using the configuration setting ~layout.field-name-align: stair-left~ will layout the field names in staircase, left-aligned fashion as illustrated below.
+
+#+begin_src yaml :tangle tests/data/register-stair-left.yaml
+  ##
+  # Example YAML file for regfmt
+
+  width: 8
+  endian: bigBit
+
+  registers:
+    - name: R1
+      fields:
+        - width: 1
+          name: Coprocessor Usable
+
+        - width: 1
+          name: Boostrap Exception Vectors
+
+        - width: 2
+          name: Parity Errors
+
+        - width: 4
+          name: Interrupt
+  layout:
+    field-name-align: stair-left
+#+end_src
+
+[[../tests/control/register-stair-left.svg]]
+** CSS Styling
+The following example CSS file illustrates how to style the output SVG diagrams as shown in this document. They are invoked using the ~-s, --style~ command line options.
+
+#+begin_src css :tangle tests/data/github.css
+  /**
+   regfmt example CSS file
+
+   Copyright © 2022 Charles Y. Choi
+   */
+
+  body {
+      font-family: Futura, Helvetica, Arial, Verdana;
+      font-size: 12pt;
+      font-style: normal;
+      font-weight: normal;
+      fill: white;
+      stroke: black;
+      stroke-width: 1.0;
+      stroke-linecap: butt;
+  }
+
+  register-name {
+      fill: green;
+  }
+
+  field-name {
+      fill: teal;
+  }
+
+  field-index {
+      fill: darkslategrey;
+  }
+
+  field-name-line: {
+      stroke: purple;
+  }
+#+end_src
+
+* Installation
+*regfmt* supports installation via [[https://pypi.org/project/regfmt/][PyPi]]. It is recommended that you do this in a Python virtual environment.
+
+#+begin_src sh
+  $ python3 -m venv .venv
+  $ source .venv/bin/activate
+  (.venv) pip install regfmt
+#+end_src
+
+** Installing for Development
+
+*regfmt* uses a Python virtual environment to support its execution. From the top-level project directory, run the Makefile target ~install~ to create the virtual environment ~.venv~ and install the /pip/ modules required by *regfmt*. 
+
+#+begin_src sh
+  $ make install
+#+end_src
+
+To initialize the environment invoke the following command:
+#+begin_src sh
+  $ source .venv/bin/activate 
+#+end_src
+
+* regfmt YAML Specification
+
+** Top Level
+Top level schema for the *regfmt* input file.
+
+| Property  | Type                    | Required | Description                             |
+|-----------+-------------------------+----------+-----------------------------------------|
+| width     | integer                 | Y        | Global register bit width. Must be ≧ 1. |
+| registers | [[#Register][Register]][1..N]{ordered} | Y        | Array of ~Register~ instances.            |
+| endian    | [[#Endian][Endian]]                  | Y        | Endianness.                             |
+| layout    | [[#Layout][Layout]]                  |          | Layout directives.                      |
+
+** Endian
+This enumeration captures the supported [[https://en.wikipedia.org/wiki/Endianness][endianness]] types for a register. Selection of this value will determine how fields are to be indexed by *regfmt*. 
+
+| Value      | Description                                                              |
+|------------+--------------------------------------------------------------------------|
+| bigBit     | Bitwise endianness where the most significant bit is transmitted first.  |
+| littleBit  | Bitwise endianness where the least significant bit is transmitted first. |
+| bigByte    | Byte-wise big endian order.                                              |
+| littleByte | Byte-wise little endian order.                                           |
+
+** Register
+Schema describing register configuration.
+
+| Property | Type                 | Required | Description                                                                      |
+|----------+----------------------+----------+----------------------------------------------------------------------------------|
+| name     | string               |          | Register name. Can be ~null~.                                                      |
+| width    | integer              |          | Register bit width. If not specified will inherit from [[#Top-Level][Top Level]].                |
+| endian   | [[#Endian][Endian]]               |          | Endianness. If not specified will inherit from [[#Top-Level][Top Level]].                        |
+| fields   | [[#Field][Field]][1..N]{ordered} | Y        | Array of Field instances. Sum of field widths must equal the register bit width. |
+
+** Field
+Schema describing field configuration within a register.
+
+| Property | Type    | Required | Description                   |
+|----------+---------+----------+-------------------------------|
+| name     | string  |          | Field name. Can be null.      |
+| width    | integer | Y        | Field bit width. Must be ≧ 1. |
+
+** Layout
+Schema for layout configuration 
+
+| Property         | Type           | Required | Description                      |
+|------------------+----------------+----------+----------------------------------|
+| field-name-align | [[#FieldNameAlign][FieldNameAlign]] |          | Alignment directive for a field. |
+
+** FieldNameAlign
+This enumeration captures the supported alignment options for a field name.
+
+| Value      | Description                                                            |
+|------------+------------------------------------------------------------------------|
+| center     | Align field name in the center of the field.                           |
+| stair-left | Align field name below the field in staircase fashion, left-justified. |
+
+
+* regfmt CSS Specification
+
+** Selectors
+
+The following CSS selectors supported by *regfmt* are described below.
+
+*** body
+| Property       | Type          | Default Value | Description                                                                                                                                                                                          |
+|----------------+---------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| font-family    | <[[https://w3c.github.io/csswg-drafts/css-fonts/#family-name-value][family name]]> | Helvetica     | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family][font-family]] attribute indicates which font family will be used to render the text, specified as a prioritized list of font family names and/or generic family names.                             |
+| font-size      | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length][length]]>      | 12pt          | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size][font-size]] attribute refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment. Note only 'pt' is supported by *regfmt*. |
+| font-style     | [[#FontStyle][FontStyle]]     | normal        | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size][font-style]] attribute specifies whether the text is to be rendered using a normal, italic, or oblique face.                                                                                       |
+| font-weight    | [[#FontWeight][FontWeight]]    | normal        | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight][font-weight]] attribute refers to the boldness or lightness of the glyphs used to render the text, relative to other fonts in the same font family.                                                |
+| fill           | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#paint][paint]]>       | black         | Presentation attribute that [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill][defines]] the color used to paint the element.                                                                                                                             |
+| stroke         | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#paint][paint]]>       | black         | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke][stroke]] attribute is a presentation attribute defining the color (or any SVG paint servers like gradients or patterns) used to paint the outline of the shape.                                    |
+| stroke-width   | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length][length]]>      | 1px           | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width][stroke-width]] attribute is a presentation attribute defining the width of the stroke to be applied to the shape.                                                                                  |
+| stroke-linecap | [[#StrokeLinecap][StrokeLinecap]] | butt          | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap][stroke-linecap]] attribute is a presentation attribute defining the shape to be used at the end of open subpaths when they are stroked.                                                            |
+
+
+
+*** register, field
+| Property       | Type          | Default Value | Description                                                                                                                                                       |
+|----------------+---------------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| fill           | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#paint][paint]]>       | black         | Presentation attribute that [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill][defines]] the color used to paint the element.                                                                                          |
+| stroke         | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#paint][paint]]>       | black         | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke][stroke]] attribute is a presentation attribute defining the color (or any SVG paint servers like gradients or patterns) used to paint the outline of the shape. |
+| stroke-width   | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length][length]]>      | 1px           | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width][stroke-width]] attribute is a presentation attribute defining the width of the stroke to be applied to the shape.                                               |
+| stroke-linecap | [[#StrokeLinecap][StrokeLinecap]] | butt          | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap][stroke-linecap]] attribute is a presentation attribute defining the shape to be used at the end of open subpaths when they are stroked.                         |
+
+*** register-name, field-name, field-index
+| Property    | Type          | Default Value | Description                                                                                                                                                                                          |
+|-------------+---------------+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| font-family | <[[https://w3c.github.io/csswg-drafts/css-fonts/#family-name-value][family name]]> | Helvetica     | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-family][font-family]] attribute indicates which font family will be used to render the text, specified as a prioritized list of font family names and/or generic family names.                             |
+| font-size   | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length][length]]>      | 12pt          | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size][font-size]] attribute refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment. Note only 'pt' is supported by *regfmt*. |
+| font-style  | [[#FontStyle][FontStyle]]     | normal        | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size][font-style]] attribute specifies whether the text is to be rendered using a normal, italic, or oblique face.                                                                                       |
+| font-weight | [[#FontWeight][FontWeight]]    | normal        | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-weight][font-weight]] attribute refers to the boldness or lightness of the glyphs used to render the text, relative to other fonts in the same font family.                                                |
+| fill        | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#paint][paint]]>       | black         | Presentation attribute that [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill][defines]] the color used to paint the element.                                                                                                                             |
+
+*** field-name-line
+| Property       | Type          | Default Value | Description                                                                                                                                                       |
+|----------------+---------------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| stroke         | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#paint][paint]]>       | black         | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke][stroke]] attribute is a presentation attribute defining the color (or any SVG paint servers like gradients or patterns) used to paint the outline of the shape. |
+| stroke-width   | <[[https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length][length]]>      | 1px           | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width][stroke-width]] attribute is a presentation attribute defining the width of the stroke to be applied to the shape.                                               |
+| stroke-linecap | [[#StrokeLinecap][StrokeLinecap]] | butt          | The [[https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap][stroke-linecap]] attribute is a presentation attribute defining the shape to be used at the end of open subpaths when they are stroked.                         |
+
+** Enumerations
+*** StrokeLinecap
+| Value  | Description                                                                                                                                                                                             |
+|--------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| butt   | The butt value indicates that the stroke for each subpath does not extend beyond its two endpoints.                                                                                                     |
+| square | The square value indicates that at the end of each subpath the stroke will be extended by a rectangle with a width equal to half the width of the stroke and a height equal to the width of the stroke. |
+| round  | The round value indicates that at the end of each subpath the stroke will be extended by a half circle with a diameter equal to the stroke width.                                                       |
+
+*** FontStyle
+
+| Value   |
+|---------+
+| normal  |
+| italic  |
+| oblique |
+
+*** FontWeight
+
+| Value   |
+|---------+
+| normal  |
+| bold    |
+| bolder  |
+| lighter |
+
+* License
+Copyright © 2022 Charles Y. Choi
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..91bb24d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+Copyright © 2022 Charles Y. Choi
+
+Licensed under the Apache License, Version 2.0 (the "License"); you
+may not use this file except in compliance with the License. You may
+obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the License for the specific language governing
+permissions and limitations under the License.
+
diff --git a/Makefile b/Makefile
index f598831..8a6a9a9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,96 @@
+##
+# Copyright 2022 Charles Y. Choi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+.EXPORT_ALL_VARIABLES:
+
 EXEC=regfmt
 EXEC_SRC=${EXEC}.py
+PYTHON_EXEC=python3
+PYTHONPATH=$(shell pwd)/src
 
 help:
-	./${EXEC_SRC} -h
+	./scripts/${EXEC} -h
 
 test:
 	python -m unittest discover
 
+coverage: 
+	python -m coverage run -m unittest discover
+
+coverage-html: htmlcov/index.html
+
+htmlcov/index.html: coverage
+	python -m coverage html
+
+view-coverage-html: htmlcov/index.html
+	open htmlcov/index.html
+
+coverage-report: coverage
+	python -m coverage report
+
 clean:
 	find . -name '*.*~' -print -exec rm {} \;
+	- rm .coverage
+	- rm -rf htmlcov
+
+install: .venv
+	./scripts/install.sh
 
+.venv:
+	${PYTHON_EXEC} -m venv .venv
 
-deep-clean: clean
-	find . -name '__pycache__' -not -path "./.venv/*" -print | xargs rm -rf 
+install-pip-requirements:
+	pip install -r src/regfmt/requirements.txt
+
+freeze-pip-requirements:
+	pip freeze > src/regfmt/requirements.txt
+
+deepclean: clean
+	find . -name '__pycache__' -not -path "./.venv/*" -print | xargs rm -rf
+	find . -name '*.egg-info' -not -path "./.venv/*" -print | xargs rm -rf
+	rm -rf dist
 
 clean-tests:
 	make -C tests clean
 
-.PHONY: help test clean deep-clean clean-tests
+readme-examples:
+	regfmt -s tests/data/github.css -o tests/control/example_0001-github.svg tests/data/example_0001.yaml
+	regfmt -s tests/data/github.css -o tests/control/register.svg tests/data/register.yaml
+	regfmt -s tests/data/github.css -o tests/control/register-stair-left.svg tests/data/register-stair-left.yaml
+
+lint:
+	flake8 src/regfmt/*.py
+
+package:
+	${PYTHON_EXEC} -m build
+
+upload:
+	twine upload --repository pypi dist/*
+
+.PHONY: help \
+test \
+clean \
+deepclean \
+clean-tests \
+install-pip-requirements \
+freeze-pip-requirements \
+readme-examples \
+package \
+upload \
+coverage \
+coverage-html \
+view-coverage-html \
+lint
+
diff --git a/NOTES.org b/NOTES.org
deleted file mode 100644
index f308ba0..0000000
--- a/NOTES.org
+++ /dev/null
@@ -1,168 +0,0 @@
-* regfmt
-
-Command line utility to render to SVG a data register with multiple bit fields specified. This utility is inspired by the ~troff~ tool ~dformat~.
-
-This project is a *work in progress*.
-
-* Configuration File Schema
-
-** Top Level
-| Property  | Type                    | Description                                                       |
-|-----------+-------------------------+-------------------------------------------------------------------|
-|           |                         | <40>                                                              |
-| width     | Int                     | Global register bit width. If not specified, default value is ~32~. |
-| registers | ~Register~[1..N]{ordered} | Array of ~Register~ instances.                                      |
-| style     | ~Style~                   | Style instance                                                    |
-
-** Register
-| Property     | Type                 | Description                                          |
-|--------------+----------------------+------------------------------------------------------|
-|              |                      | <40>                                                 |
-| name         | String               | Register name.                                       |
-| width        | Int                  | Register bit width. Overrides top level ~width~ value. |
-| layout-order | ~LayoutOrder~          | Field layout order.                                  |
-| fields       | Field[1..N]{ordered} | Array of ~Field~ instances.                            |
-
-** LayoutOrder
-| Values          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
-|-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-|                 | <60>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
-| memory          | Interpret the element order of ~Register.fields~ such that the first element has the least significant bit (/lsb/) of the register and the last element has the most significant bit (/msb/) of the register. When rendered, the first element will be placed /right-most/ and the last element placed /left-most/. Within a field, bit ordering always renders the /msb/ index on the left and the /lsb/ on the right. If the field is only one bit long, then the bit index is rendered in the center. |
-| transmit-big    | Interpret the element order of ~Register.fields~ such that the first element contains the /msb/ of the data payload and the last element contains the /lsb/.                                                                                                                                                                                                                                                                                                                                    |
-| transmit-little | Interpret the element order of ~Register.fields~ such that the first element contains the /lsb/ of the data payload and the last element contains the /msb/.                                                                                                                                                                                                                                                                                                                                    |
-
-** BitOrder
-String enum value to describe the layout order of a bit field.
-| Values   | Description                                                              |
-|----------+--------------------------------------------------------------------------|
-| msb..lsb | Render most significant bit on the left, least significant bit on right. |
-| lsb..msb | Render least significant bit on the left, most significant bit on right. |
-
-** Field
-| Property | Type   | Description           |
-|----------+--------+-----------------------|
-| name     | String | Field name.           |
-| width    | Int    | ~Register~ field width. |
-
-** Style
-| Property       | Type            | Description |
-|----------------+-----------------+-------------|
-| font-family    | CSS Font Family |             |
-| font-size      | CSS Font Size   |             |
-| font-color     | CSS Font Color  |             |
-| stroke         |                 |             |
-| stroke-width   |                 |             |
-| stroke-linecap |                 |             |
-| field-layout   | ~FieldLayout~     |             |
-
-** FieldLayout
-
-| Values | Description                                                       |
-|--------+-------------------------------------------------------------------|
-| left   | Render left bit field value only.                                 |
-| right  | Render right bit field value only.                                |
-| both   | Render both. (Default)                                            |
-| none   | Do not render any bit field value. Can also specify a ~null~ value. |
- 
-* Punchlist [1/4]
-- [-] Define Configuration Schema
-  - [X] Register Definition
-  - [ ] Style Definition
-- [X] Process configuration file 
-  - [X] Validate Configuration
-  - [X] DRC configuration
-- [-] SVG Rendering
-- [ ] Tests
-
-https://stackoverflow.com/questions/65721909/how-to-get-bounding-box-coordinates-around-entire-text
-
-https://github.com/python-pillow/Pillow/issues/3921
-
-#+begin_src
-from PIL import ImageFont
-f2 = ImageFont.truetype('Futura.ttc', 12)
->>> f2.getsize('M')
-(12, 14)
-
->>> 28.0 / 72
-0.3888888888888889
->>> 0.3888888888888889
-0.3888888888888889
->>> 14.0/72
-0.19444444444444445
->>>   
-#+end_src
-
-https://stackoverflow.com/questions/15356641/how-to-write-xml-declaration-using-xml-etree-elementtree
-
-tinycss2 is a CSS parser.
-
-Fuck you really want to define a netlist.
-
-#+begin_src python
-  class BaseField: 
-      def __init__(self):
-          self.name = 'string'
-          self.width = 'int'
-
-  class Field: BaseField
-      def __init__(self):
-          pass
-
-  class Register: BaseField
-      def __init__(self):
-          self.direction = 
-          self.fields = [field]
-          
-#+end_src
-
-
-#+begin_src yaml
-  width: 64
-  registers: 
-    - name: TEQ
-      width: 32
-      endian: littleByte
-      fields:
-        - name: TEQ
-          width: 6
-
-        - name: code
-          width: 10
-
-        - name: rt
-          width: 5
-
-        - name: rs
-          width: 5
-
-        - name: SPECIAL
-          width: 6
-
-          
-
-  netlist:
-    - layout: col2, tb, bt, lr, rl
-      registers:
-        - TEQ
-        - mary
-
-#+end_src
-
-
-``(left, top, right, bottom)`` bounding box
-
-
-* References
- - https://json-schema.org/understanding-json-schema/reference/numeric.html
- - https://json-schema.org/learn/miscellaneous-examples.html
- - https://python-jsonschema.readthedocs.io/en/stable/
- - https://stackoverflow.com/questions/3262569/validating-a-yaml-document-in-python
- - https://python-jsonschema.readthedocs.io/en/latest/api/jsonschema/validators/#jsonschema.validators.Draft202012Validator
- - https://python-jsonschema.readthedocs.io/en/latest/
- - https://pynative.com/python-json-validation/
- - http://donofden.com/blog/2020/03/15/How-to-Validate-JSON-Schema-using-Python
- - https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Positions
- - https://pillow.readthedocs.io/en/stable/reference/ImageFont.html
-
-   
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..80aaf9b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# regfmt
+
+This project is a **work in progress**.
+
+**regfmt** is a command line utility to generate SVG diagrams for control register-style data formats. It is inspired by the *dformat* command from the *troff* family of tools, however re-imagined using contemporary (circa 2022) file formats.
+
+Example output SVG:
+
+![example svg](http://yummymelon.com/images/example_0001-github.svg)
+
+# Features
+
+-   SVG output
+-   Modern configuration input file formats
+    -   YAML for register configuration
+    -   CSS for styling SVG output
+
+# Usage
+
+    usage: regfmt [-h] [-v] [-o OUTPUT] [-s STYLE] [input]
+    
+    regfmt - generate SVG diagrams of control register-style data formats
+    
+    positional arguments:
+      input                 input register format YAML file (default: input.yaml)
+    
+    optional arguments:
+      -h, --help            show this help message and exit
+      -v, --version         print version information and exit
+      -o OUTPUT, --output OUTPUT
+                            output file (default: '-' for stdout)
+      -s STYLE, --style STYLE
+                            CSS style file
+
diff --git a/README.org b/README.org
deleted file mode 100644
index c75862a..0000000
--- a/README.org
+++ /dev/null
@@ -1,13 +0,0 @@
-* regfmt
-
-This is a work in progress. 
-
-* License
-Copyright © 2022 Charles Y. Choi
-
-Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..eb7a6d4
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,36 @@
+[build-system]
+requires = ["setuptools>=61.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "regfmt"
+version = "0.1.10"
+authors = [
+  { name="Charles Choi", email="kickingvegas@gmail.com"}
+]
+
+description = "A command line utility to generate SVG diagrams for control register-style data formats."
+readme = "README.md"
+requires-python = ">=3.7"
+classifiers = [
+    "Programming Language :: Python :: 3",
+    "License :: OSI Approved :: Apache Software License",
+    "Operating System :: OS Independent",
+]
+
+dependencies = [
+    "attrs>=22.1.0",
+    "jsonschema>=4.16.0",
+    "Pillow>=9.2.0",
+    "pyrsistent>=0.18.1",
+    "PyYAML>=6.0",
+    "tinycss2>=1.1.1",
+    "webencodings>=0.5.1"
+]
+
+[project.urls]
+"Homepage" = "https://github.com/kickingvegas/regfmt"
+"Issue Tracker" = "https://github.com/kickingvegas/regfmt/issues"
+
+[project.scripts]
+regfmt = "regfmt:main"
diff --git a/regfmt.py b/regfmt.py
deleted file mode 100755
index 58866c5..0000000
--- a/regfmt.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python3
-##
-# Copyright 2022 Charles Y. Choi
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import sys
-from regfmtlib import CommandLineParser
-from regfmtlib import InputLoadAndValidate
-from regfmtlib import TopLevel
-from regfmtlib import DRCChecker
-from regfmtlib import SVGWriter
-
-VERSION = '0.1.0'
-
-class RegisterFormat:
-    def __init__(self, parsedArguments):
-        self.version = VERSION
-        self.stdout = sys.stdout
-        self.stdin = sys.stdin
-        self.stderr = sys.stderr
-        
-        self.parsedArguments = parsedArguments
-
-        if parsedArguments.version:
-            sys.stdout.write('{0}\n'.format(self.version))
-            sys.exit(0)
-
-        if parsedArguments.output != '-':
-            outfile = open('{0}'.format(parsedArguments.output), 'w')
-            self.stdout = outfile
-
-        if not os.path.exists(parsedArguments.input):
-            sys.stderr.write('ERROR: file "{0}" does not exist. Please specify an input file.\n'.format(parsedArguments.input))
-            sys.exit(1)
-                    
-    def run(self):
-        # Load and validate input YAML
-        loader = InputLoadAndValidate(self.parsedArguments)
-        inputYAML = loader.loadAndValidate()
-        #print(inputYAML)
-        
-        # Deserialize input YAML into native object DB
-        registerDB = TopLevel(config=inputYAML)
-
-        # DRC Check native object DB
-        drcChecker = DRCChecker()
-        drcChecker.check(registerDB)
-        drcChecker.subIndexFields(registerDB)
-
-        # Render SVG
-        svgWriter = SVGWriter(registerDB, self.stdout, configFileName=self.parsedArguments.config)
-        svgWriter.writeSVG()
-
-        # wrap up
-        if self.stdout != sys.stdout:
-            self.stdout.close()
-
-if __name__ == '__main__':
-    app = RegisterFormat(CommandLineParser().run())
-    app.run()
diff --git a/regfmtlib/svgwriter.py b/regfmtlib/svgwriter.py
deleted file mode 100644
index 66b5d5f..0000000
--- a/regfmtlib/svgwriter.py
+++ /dev/null
@@ -1,188 +0,0 @@
-##
-# Copyright 2022 Charles Y. Choi
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import sys
-import os
-import errno
-from xml.dom.minidom import getDOMImplementation
-from regfmtlib import TopLevel
-from regfmtlib import Register
-from PIL import ImageFont
-from regfmtlib.svggeometry import *
-from regfmtlib.cssparser import parseCSS, cascadeStyles
-
-BASE_FONT_SIZE = 12
-
-def cssFontToImageFont(fontFamily, fontSize):
-    baseFontname = fontFamily
-    baseFontSize = fontSize
-
-    # !!!: only pt is supported for geometry calculations
-
-    if 'pt' in baseFontSize:
-        baseFontSize = int(float(baseFontSize.replace('pt', '')))
-
-    else:
-        try:
-            baseFontSize = int(float(round(baseFontSize)))
-        except:
-            message = ('WARNING: body font-size specification of "{}" is unsupported in '
-                       'CSS file for sizing the geometry of register fields. '
-                       'Coercing font size value to {}pt.\n')
-            sys.stderr.write(message.format(fontSize, BASE_FONT_SIZE))
-            baseFontSize = BASE_FONT_SIZE
-
-    result = ImageFont.truetype(baseFontname, baseFontSize)
-    return result
-
-class SVGWriter:
-    def __init__(self, registerDB: TopLevel, outfile, configFileName=None):
-        self.registerDB = registerDB
-        self.outfile = outfile
-        self.styleSheet = StyleSheet()
-        try:
-            parseCSS(configFileName=configFileName, styleSheet=self.styleSheet)
-        except FileNotFoundError as err:
-            message = 'ERROR: {}: "{}"  Exiting…\n'.format(err.strerror, err.filename)
-            sys.stderr.write(message)
-            sys.exit(err.errno)
-
-        except ValueError as err:
-            message = 'ERROR: {} Exiting…\n'.format(err.args[0])
-            sys.stderr.write(message)
-            sys.exit(1)
-
-
-        cascadeStyles(self.styleSheet)
-        self.font = cssFontToImageFont(fontFamily=self.styleSheet.body.fontFamily[0],
-                                       fontSize=self.styleSheet.body.fontSize)
-
-    def createDocument(self):
-        dom = getDOMImplementation()
-        doc = dom.createDocument(None, 'svg', None)
-        topElement = doc.documentElement
-        topElement.setAttribute('version', '1.1')
-        topElement.setAttribute('xmlns', "http://www.w3.org/2000/svg")
-        return doc, topElement
-
-    def writeSVG(self):
-        doc, topElement = self.createDocument()
-        children = []
-
-        registers: [Register] = self.registerDB.registers
-        bitFieldSize = self.getBitFieldSize(self.font)
-
-        # Initialize container for geometry
-        topGroup = Group()
-
-        # Generate groups, each group holding a single register
-        # layoutGeometry, transformGeometry, writeDOM, compositeSVG
-
-        # Layout Geometry - all decisions on the graphical layout of a register instance are done here.
-        for register in registers:
-            # define untransformed geometry for register
-            registerHeight = bitFieldSize.height
-            registerWidth = bitFieldSize.width * register.width
-
-            registerGroup = Group()
-            topGroup.append(registerGroup)
-
-            if register.name is not None:
-                registerNameBbox = self.font.getbbox(register.name, anchor='lt')
-                registerNameHeight = bboxHeight(registerNameBbox)
-                registerGroup.append(Text(register.name,
-                                          x = registerWidth + 10,
-                                          y = registerNameHeight + (registerHeight/2.0) - (registerNameHeight / 2.0),
-                                          style=self.styleSheet.registerName
-                                          ))
-
-            displacementX = 0
-            for field in register.fields:
-                fieldWidth = field.width * bitFieldSize.width
-                fieldHeight = registerHeight
-
-                fieldX = 0
-                fieldY = 0
-
-                T = translateTransform(displacementX, 0.0)
-                V = vector2D(fieldX, fieldY)
-                transformedV = matrixMult(T, V)
-                fieldX, fieldY = coordinateFromVector2D(transformedV)
-
-                registerGroup.append(Rect(x=fieldX,
-                                          y=fieldY,
-                                          width=fieldWidth,
-                                          height=fieldHeight,
-                                          style=self.styleSheet.field))
-
-                if field.name is not None:
-                    fieldNameBbox = self.font.getbbox(field.name, anchor='la')
-                    fieldNameHeight = bboxHeight(fieldNameBbox)
-                    fieldNameWidth = bboxWidth(fieldNameBbox)
-
-                    registerGroup.append(Text(field.name,
-                                              x=fieldNameBbox[0] + (fieldWidth/2.0) + displacementX,
-                                              y=fieldNameBbox[3] + (fieldHeight/2.0) - (fieldNameHeight/2.0),
-                                              textAnchor='middle',
-                                              style=self.styleSheet.fieldName
-                                              ))
-
-                registerGroup.append(Text(str(field.leftIndex),
-                                          x=fieldX + 3,
-                                          y=fieldY + fieldHeight - 3,
-                                          textAnchor='start',
-                                          style=self.styleSheet.fieldIndex
-                                          ))
-
-                registerGroup.append(Text(str(field.rightIndex),
-                                          x=fieldX + fieldWidth - 3,
-                                          y=fieldY + fieldHeight - 3,
-                                          textAnchor='end',
-                                          style=self.styleSheet.fieldIndex
-                                          ))
-
-                displacementX += fieldWidth
-
-            #break
-
-        # Transform Geometry
-        displacementY = 0
-        for group in topGroup:
-            group.translate(0, displacementY)
-            displacementY += (bitFieldSize.height + 5)
-
-        # Write DOM and composite to SVG
-        self.compositeSVG(doc, topElement, topGroup, self.outfile)
-
-    def getBitFieldSize(self, font):
-        """
-        Return unit size of a bit field to be rendered in SVG.
-
-        :param font:
-        :return: instance of Size()
-        """
-        bbox = font.getbbox('M', anchor='lt')
-        x = bbox[0]
-        y = bbox[1]
-        width = bboxWidth(bbox) * 2
-        height = bboxHeight(bbox) * 4
-        return Size(width, height)
-
-    def compositeSVG(self, doc, topElement, topGroup, outfile):
-        children = []
-        topGroup.writeDOM(children, doc)
-        for child in children:
-            topElement.appendChild(child)
-        doc.writexml(outfile, encoding='utf-8', addindent="  ", newl="\n")
diff --git a/scripts/devinit.sh b/scripts/devinit.sh
new file mode 100644
index 0000000..9ea8ec0
--- /dev/null
+++ b/scripts/devinit.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+export PYTHONPATH=$PWD/src
diff --git a/scripts/install.sh b/scripts/install.sh
new file mode 100755
index 0000000..c161661
--- /dev/null
+++ b/scripts/install.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+
+source .venv/bin/activate && pip install -r src/regfmt/requirements.txt
+cd .venv/bin && ln -s ../../scripts/regfmt . 
diff --git a/scripts/regfmt b/scripts/regfmt
new file mode 100755
index 0000000..a268960
--- /dev/null
+++ b/scripts/regfmt
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import re
+import sys
+from regfmt import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())
diff --git a/regfmtlib/CommandLineParser.py b/src/regfmt/CommandLineParser.py
similarity index 59%
rename from regfmtlib/CommandLineParser.py
rename to src/regfmt/CommandLineParser.py
index a36ea13..d0e0aa1 100644
--- a/regfmtlib/CommandLineParser.py
+++ b/src/regfmt/CommandLineParser.py
@@ -15,25 +15,37 @@
 
 import argparse
 
+
 class CommandLineParser:
     def __init__(self):
-        self.parser = argparse.ArgumentParser(description="regfmt - generate SVG")
+
+        description = ("%(prog)s - generate SVG diagrams of control "
+                       "register-style data formats")
+
+        epilog = ("Input file details can be found at "
+                  "<https://github.com/kickingvegas/regfmt>. "
+                  "Please report issues to "
+                  "<https://github.com/kickingvegas/regfmt/issues>.")
+
+        self.parser = argparse.ArgumentParser(description=description,
+                                              epilog=epilog)
         self.parser.add_argument('-v', '--version',
                                  action='store_true',
                                  help='print version information and exit')
-        
+
         self.parser.add_argument('-o', '--output',
                                  action='store',
                                  default='-',
-                                 help='output file')
+                                 help="output file (default: '-' for stdout)")
 
-        self.parser.add_argument('-c', '--config',
+        self.parser.add_argument('-s', '--style',
                                  action='store',
                                  default=None,
-                                 help='CSS configuration file')
+                                 help='CSS style file')
 
         self.parser.add_argument('input', nargs='?', default='input.yaml',
-                                 help='input register format YAML file (default: input.yaml)')
-                
+                                 help=('input register format YAML file '
+                                       '(default: input.yaml)'))
+
     def run(self):
         return self.parser.parse_args()
diff --git a/regfmtlib/Constants.py b/src/regfmt/Constants.py
similarity index 100%
rename from regfmtlib/Constants.py
rename to src/regfmt/Constants.py
diff --git a/regfmtlib/DRCChecker.py b/src/regfmt/DRCChecker.py
similarity index 77%
rename from regfmtlib/DRCChecker.py
rename to src/regfmt/DRCChecker.py
index 176cef4..d49ed85 100644
--- a/regfmtlib/DRCChecker.py
+++ b/src/regfmt/DRCChecker.py
@@ -13,20 +13,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from regfmtlib import Register
-from regfmtlib import Field
-from regfmtlib import TopLevel
-from regfmtlib import Endian
+from regfmt import Register
+from regfmt import Field
+from regfmt import TopLevel
+from regfmt import Endian
 from operator import add
 from functools import reduce
 import sys
 
+
 class DRCChecker:
     def __init__(self):
         self.foo = None
         self.errors = []
 
-
     def check(self, registerDB: TopLevel):
         # TODO: this should be a throwing method
         self.checkFieldWidths(registerDB)
@@ -39,9 +39,14 @@ def checkFieldWidths(self, registerDB: TopLevel):
             widths = [x.width for x in fields]
             sum = reduce(add, widths)
             if sum != register.width:
-                message = 'DRC Violation: sum total of widths of fields ({0}) must equal the register width ({1}) for register {2}\n'.format(sum, register.width, register.name)
-                self.errors.append(message)
-
+                message = ('DRC Violation: sum total of widths of '
+                           'fields ({0}) must equal the register width '
+                           '({1}) for register {2}\n')
+                self.errors.append(message.format(sum,
+                                                  register.width,
+                                                  register.name))
+
+        # TODO: this should be thrown
         for message in self.errors:
             sys.stderr.write(message)
 
@@ -67,21 +72,18 @@ def subIndexFields(self, registerDB: TopLevel):
                     field.leftIndex = count
                     count -= (field.width - 1)
                     field.rightIndex = count
-                    #print('{0}:{1} {2}'.format(field.leftIndex, field.rightIndex, field.name))
+# print('{0}:{1} {2}'.format(field.leftIndex, field.rightIndex, field.name))
 
                 elif endian == Endian.littleBit.value:
                     count += 1
                     field.leftIndex = count
                     count += (field.width - 1)
                     field.rightIndex = count
-                    #print('{0}:{1} {2}'.format(field.leftIndex, field.rightIndex, field.name))
+# print('{0}:{1} {2}'.format(field.leftIndex, field.rightIndex, field.name))
 
                 elif endian == Endian.littleByte.value:
                     count += 1
                     field.rightIndex = count
                     count += (field.width - 1)
                     field.leftIndex = count
-                    #print('{0}:{1} {2}'.format(field.leftIndex, field.rightIndex, field.name))
-
-
-
+# print('{0}:{1} {2}'.format(field.leftIndex, field.rightIndex, field.name))
diff --git a/regfmtlib/Endian.py b/src/regfmt/Endian.py
similarity index 99%
rename from regfmtlib/Endian.py
rename to src/regfmt/Endian.py
index 12d477c..baa25b4 100644
--- a/regfmtlib/Endian.py
+++ b/src/regfmt/Endian.py
@@ -15,6 +15,7 @@
 
 from enum import Enum
 
+
 class Endian(Enum):
     littleByte = "littleByte"
     bigByte = "bigByte"
diff --git a/regfmtlib/Field.py b/src/regfmt/Field.py
similarity index 84%
rename from regfmtlib/Field.py
rename to src/regfmt/Field.py
index c6da972..70bf9fa 100644
--- a/regfmtlib/Field.py
+++ b/src/regfmt/Field.py
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+
 class Field:
     def __init__(self, config=None):
         self.name: str = None
@@ -22,5 +23,7 @@ def __init__(self, config=None):
 
         # !!!: Note config should already be validated.
         if config:
-            self.name = config['name']
-            self.width = config['width']
+            if 'name' in config:
+                self.name = config['name']
+            if 'width' in config:
+                self.width = config['width']
diff --git a/regfmtlib/InputLoadAndValidate.py b/src/regfmt/InputLoadAndValidate.py
similarity index 57%
rename from regfmtlib/InputLoadAndValidate.py
rename to src/regfmt/InputLoadAndValidate.py
index ee09486..c14d1d8 100644
--- a/regfmtlib/InputLoadAndValidate.py
+++ b/src/regfmt/InputLoadAndValidate.py
@@ -13,12 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from jsonschema import validate, exceptions
-from yaml import Loader, Dumper, load, safe_load
-from yaml.scanner import ScannerError
-import sys
+from jsonschema import validate
+from yaml import Loader, load, safe_load
 
-INPUT_SCHEMA_YAML="""
+INPUT_SCHEMA_YAML = """
 type: object
 properties: 
   width:
@@ -36,23 +34,10 @@
   layout:
     type: object
     properties:
-      registers:
-        enum:
-          - tb
-          - lr
-
-      register_name:
-        enum:
-          - left
-          - right
-          - top
-          - bottom
-          - null
-      field_name:
+      field-name-align:
         enum:
           - center
-          - left
-          - right
+          - stair-left
 
 required:
   - width
@@ -101,6 +86,7 @@
       - fields
 """
 
+
 class InputLoadAndValidate:
     def __init__(self, parsedArguments):
         self.parsedArguments = parsedArguments
@@ -108,45 +94,14 @@ def __init__(self, parsedArguments):
     def loadAndValidate(self):
         inputYAML = self.loadInput(self.parsedArguments)
         schemaYAML = self.loadInputSchema(INPUT_SCHEMA_YAML)
-
-        try:
-            result = validate(inputYAML, schemaYAML)
-            if result is not None:
-                # this should never happen
-                sys.stderr.write('ERROR: unexpected result from validate()\n')
-                sys.exit(1)
-
-        except exceptions.ValidationError as err:
-            print(err.json_path)
-            print(err.message)
-            print(err.path)
-            print(err.relative_path)
-            print(err.absolute_path)
-            print(err.context)
-            print(err.cause)
-            print(err.instance)
-            print(err.validator)
-            print(err.schema_path)
-            print(dir(err))
-
+        validate(inputYAML, schemaYAML)
         return inputYAML
         
     def loadInput(self, parsedArguments):
         with open(parsedArguments.input, 'r') as infile:
-            try:
-                yamlConfig = load(infile, Loader=Loader)
-            except ScannerError as err:
-                sys.stderr.write('ERROR: {0}\n'.format(err))
-                sys.exit(1)
-
+            yamlConfig = load(infile, Loader=Loader)
         return yamlConfig
 
     def loadInputSchema(self, schemaString):
-        try:
-            yamlConfig = safe_load(schemaString)
-        except ScannerError as err:
-            sys.stderr.write('ERROR: {0}\n'.format(err))
-            sys.exit(1)
-
-
+        yamlConfig = safe_load(schemaString)
         return yamlConfig
diff --git a/regfmtlib/Register.py b/src/regfmt/Register.py
similarity index 87%
rename from regfmtlib/Register.py
rename to src/regfmt/Register.py
index 5bedc94..b11586b 100644
--- a/regfmtlib/Register.py
+++ b/src/regfmt/Register.py
@@ -13,14 +13,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from regfmtlib import Constants
-from regfmtlib import Endian
-from regfmtlib import Field
+from regfmt import Constants
+from regfmt import Endian
+from regfmt import Field
 
 DEFAULT_WIDTH = Constants.DEFAULT_WIDTH
 
+
 class Register:
-    def __init__(self, parent, config=None, width: int=DEFAULT_WIDTH, endian: Endian=Endian.bigByte):
+    def __init__(self, parent, config=None, width: int = DEFAULT_WIDTH, endian: Endian = Endian.bigBit):
         self.name: str = None
         self.width: int = width
         self.endian: Endian = endian
diff --git a/src/regfmt/RegisterFormat.py b/src/regfmt/RegisterFormat.py
new file mode 100755
index 0000000..7bfba48
--- /dev/null
+++ b/src/regfmt/RegisterFormat.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+##
+# Copyright 2022 Charles Y. Choi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import errno
+import os
+import sys
+from regfmt import VERSION
+from regfmt import CommandLineParser
+from regfmt import InputLoadAndValidate
+from regfmt import TopLevel
+from regfmt import DRCChecker
+from regfmt import SVGWriter
+from jsonschema.exceptions import ValidationError
+from yaml.scanner import ScannerError
+from tinycss2.ast import Declaration, ParseError
+
+
+class RegisterFormat:
+    def __init__(self, parsedArguments):
+        self.version = VERSION
+        self.stdout = sys.stdout
+        self.stdin = sys.stdin
+        self.stderr = sys.stderr
+        self.parsedArguments = parsedArguments
+
+    def run(self):
+        parsedArguments = self.parsedArguments
+
+        if parsedArguments.version:
+            self.stdout.write('{0}\n'.format(self.version))
+            return 0
+
+        if parsedArguments.output != '-':
+            outfile = open('{0}'.format(parsedArguments.output), 'w')
+            self.stdout = outfile
+
+        if not os.path.exists(parsedArguments.input):
+            message = 'ERROR: file "{0}" not found.\n'
+            self.stderr.write(message.format(parsedArguments.input))
+            return errno.ENOENT
+
+        # Load and validate input YAML
+        loader = InputLoadAndValidate(self.parsedArguments)
+
+        try:
+            inputYAML = loader.loadAndValidate()
+            # print(inputYAML)
+        except ValidationError as err:
+            message = 'ERROR: YAML file "{}" in path "{}": {}'
+            self.stderr.write(message.format(self.parsedArguments.input,
+                                             err.json_path,
+                                             err.message))
+
+            # print(err.json_path)
+            # print(err.message)
+            # print(err.path)
+            # print(err.relative_path)
+            # print(err.absolute_path)
+            # print(err.context)
+            # print(err.cause)
+            # print(err.instance)
+            # print(err.validator)
+            # print(err.schema_path)
+            # print(dir(err))
+
+            return errno.EINVAL
+        except ScannerError as err:
+            self.stderr.write('ERROR: {0}\n'.format(err))
+            return errno.EINVAL
+
+        except:
+            print('wtf')
+
+        # Deserialize input YAML into native object DB
+        registerDB = TopLevel(config=inputYAML)
+
+        # DRC Check native object DB
+        drcChecker = DRCChecker()
+        drcChecker.check(registerDB)
+        drcChecker.subIndexFields(registerDB)
+
+        # Render SVG
+        try:
+            svgWriter = SVGWriter(registerDB,
+                                  self.stdout,
+                                  configFileName=self.parsedArguments.style)
+        except FileNotFoundError as err:
+            message = 'ERROR: {}: "{}"  Exiting…\n'.format(err.strerror, err.filename)
+            self.stderr.write(message)
+            return err.errno
+        except ValueError as err:
+            if len(err.args) and isinstance(err.args[0], Declaration):
+                errorDeclaration: Declaration = err.args[0]
+                errorMessage: str = err.args[1]
+                message = 'ERROR: {} (line {}, row {}): {}\n'.format(self.parsedArguments.style,
+                                                                     errorDeclaration.source_line,
+                                                                     errorDeclaration.source_column,
+                                                                     errorMessage)
+                self.stderr.write(message)
+
+            elif len(err.args) and isinstance(err.args[0], ParseError):
+                parseError: ParseError = err.args[0]
+                errorMessage: str = err.args[1]
+                self.stderr.write(errorMessage)
+
+            else:
+                message = 'ERROR: {} Exiting…\n'.format(err.args[0])
+                self.stderr.write(message)
+
+            return errno.EINVAL
+
+        svgWriter.writeSVG()
+
+        # wrap up
+        if self.stdout != sys.stdout:
+            self.stdout.close()
+
+        return 0
+
+
+if __name__ == '__main__':
+    app = RegisterFormat(CommandLineParser().run())
+    app.run()
diff --git a/src/regfmt/TopLevel.py b/src/regfmt/TopLevel.py
new file mode 100644
index 0000000..7f6cbc0
--- /dev/null
+++ b/src/regfmt/TopLevel.py
@@ -0,0 +1,61 @@
+##
+# Copyright 2022 Charles Y. Choi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from regfmt import Constants
+from regfmt import Register
+from regfmt import Endian
+from enum import Enum
+
+DEFAULT_WIDTH = Constants.DEFAULT_WIDTH
+
+
+class FieldNameAlign(Enum):
+    center = "center"
+    stairLeft = 'stair-left'
+
+
+class RegisterLayout(Enum):
+    tb = "tb"
+
+
+class Layout:
+    def __init__(self,
+                 fieldNameAlign: FieldNameAlign = FieldNameAlign.center,
+                 registerLayout: RegisterLayout = RegisterLayout.tb):
+        self.fieldNameAlign: FieldNameAlign = fieldNameAlign
+        self.registerLayout: RegisterLayout = registerLayout
+
+
+class TopLevel:
+    def __init__(self, config=None, width: int = DEFAULT_WIDTH, endian: Endian = Endian.bigByte):
+        self.width: int = width
+        self.endian: Endian = endian
+        self.registers: [Register] = []
+        self.layout = Layout()
+
+        if config:
+            self.width = config['width']
+            self.endian = config['endian']
+
+            if 'registers' in config:
+                for registerConfig in config['registers']:
+                    self.registers.append(Register(self, registerConfig))
+
+            if 'layout' in config:
+                if 'field-name-align' in config['layout']:
+                    self.layout.fieldNameAlign = FieldNameAlign(config['layout']['field-name-align'])
+
+                if 'register-layout' in config['layout']:
+                    self.layout.registerLayout = RegisterLayout(config['layout']['register-layout'])
diff --git a/regfmtlib/__init__.py b/src/regfmt/__init__.py
similarity index 65%
rename from regfmtlib/__init__.py
rename to src/regfmt/__init__.py
index f963a17..870b88d 100644
--- a/regfmtlib/__init__.py
+++ b/src/regfmt/__init__.py
@@ -12,17 +12,32 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+from os import uname
+VERSION = '0.1.10'
+BASE_FONT_NAME = 'Helvetica'
+
+unameObj = uname()
+if unameObj.sysname == 'Linux':
+    BASE_FONT_NAME = 'FreeSans'
+elif unameObj.sysname == 'Windows':
+    BASE_FONT_NAME = 'Arial'
 
 from .InputLoadAndValidate import InputLoadAndValidate
-from .CommandLineParser import CommandLineParser
 from .Constants import Constants
 from .Endian import Endian
 from .Field import Field
 from .Register import Register
-from .TopLevel import TopLevel
+from .TopLevel import FieldNameAlign, RegisterLayout, Layout, TopLevel
 from .DRCChecker import DRCChecker
 from .cssstyles import *
 from .svggeometry import *
 from .svgwriter import SVGWriter
+from .centeralignlayout import *
+from .stairleftlayout import *
+from .CommandLineParser import CommandLineParser
+from .RegisterFormat import RegisterFormat
 
 
+def main():
+    app = RegisterFormat(CommandLineParser().run())
+    return app.run()
diff --git a/src/regfmt/centeralignlayout.py b/src/regfmt/centeralignlayout.py
new file mode 100644
index 0000000..752dc5c
--- /dev/null
+++ b/src/regfmt/centeralignlayout.py
@@ -0,0 +1,94 @@
+from regfmt.svggeometry import *
+from regfmt import Register
+
+
+def writeFieldNameCenterSVG(registerDB, topGroup, styleSheet, font, registerSpacing=5):
+    bitFieldSize = getBitFieldSize(font=font)
+
+    registers: [Register] = registerDB.registers
+    parentOrigin = Point()
+    parentSize = Size(height=(bitFieldSize.height + registerSpacing) * len(registers))
+
+    displacementY = 0
+
+    for register in registers:
+        registerGroup = Group()
+        topGroup.append(registerGroup)
+
+        registerHeight = bitFieldSize.height
+        registerWidth = bitFieldSize.width * register.width
+
+        if register.name:
+            registerNameFrame = getTextFrame(text=register.name, font=styleSheet.registerName.truetype())
+
+            x = registerWidth + 10
+            y = registerNameFrame.size.height + (registerHeight/2.0) - (registerNameFrame.size.height/2.0)
+
+            registerGroup.append(Text(text=register.name,
+                                      x=x,
+                                      y=y,
+                                      style=styleSheet.registerName))
+
+            newParentWidth = x + registerNameFrame.size.width
+            if parentSize.width < newParentWidth:
+                parentSize.width = newParentWidth
+
+        displacementX = 0
+
+        for field in register.fields:
+            fieldWidth = field.width * bitFieldSize.width
+            fieldHeight = registerHeight
+
+            T = translateTransform(displacementX, 0.0)
+            V = vector2D(0.0, 0.0)
+            transformedV = matrixMult(T, V)
+            fieldX, fieldY = coordinateFromVector2D(transformedV)
+
+            registerGroup.append(Rect(x=fieldX,
+                                      y=fieldY,
+                                      width=fieldWidth,
+                                      height=fieldHeight,
+                                      style=styleSheet.field))
+
+            if field.name:
+                fieldNameFrame = getTextFrame(text=field.name, font=styleSheet.fieldName.truetype())
+
+                fieldNameX = (fieldWidth / 2.0) + displacementX
+                fieldNameY = fieldNameFrame.origin.y + (fieldHeight / 2.0) - (fieldNameFrame.size.height / 2.0)
+
+                registerGroup.append(Text(text=field.name,
+                                          x=fieldNameX,
+                                          y=fieldNameY,
+                                          textAnchor='middle',
+                                          style=styleSheet.fieldName))
+
+            if field.width > 1:
+                registerGroup.append(Text(str(field.leftIndex),
+                                          x=fieldX + 3,
+                                          y=fieldY + fieldHeight - 3,
+                                          textAnchor='start',
+                                          style=styleSheet.fieldIndex
+                                          ))
+
+                registerGroup.append(Text(str(field.rightIndex),
+                                          x=fieldX + fieldWidth - 3,
+                                          y=fieldY + fieldHeight - 3,
+                                          textAnchor='end',
+                                          style=styleSheet.fieldIndex
+                                          ))
+            else:
+                registerGroup.append(Text(str(field.leftIndex),
+                                          x=fieldX + (fieldWidth / 2.0),
+                                          y=fieldY + fieldHeight - 3,
+                                          textAnchor='middle',
+                                          style=styleSheet.fieldIndex
+                                          ))
+
+            displacementX += fieldWidth
+
+    for group in topGroup:
+        group.translate(0, displacementY)
+        displacementY += (bitFieldSize.height + registerSpacing)
+
+    return (parentOrigin, parentSize)
+
diff --git a/regfmtlib/cssparser.py b/src/regfmt/cssparser.py
similarity index 61%
rename from regfmtlib/cssparser.py
rename to src/regfmt/cssparser.py
index 3ca2afa..2d78793 100644
--- a/regfmtlib/cssparser.py
+++ b/src/regfmt/cssparser.py
@@ -14,18 +14,18 @@
 # limitations under the License.
 
 import os
-import sys
 import errno
-
 import tinycss2
-from regfmtlib.cssstyles import *
+from regfmt.cssstyles import *
 
 classNameMap = {
                 'register-name': 'registerName',
                 'field-name': 'fieldName',
-                'field-index': 'fieldIndex'
+                'field-index': 'fieldIndex',
+                'field-name-line': 'fieldNameLine'
                 }
 
+
 def parseCSS(configFileName: str, styleSheet: StyleSheet):
     if configFileName is None:
         return
@@ -50,9 +50,9 @@ def parseCSS(configFileName: str, styleSheet: StyleSheet):
         filterDeclarationErrors(configFileName, declarations)
 
         className = rule.prelude[0].value
-        #print(className)
 
-        if className not in ('body', 'register', 'field', 'register-name', 'field-name', 'field-index'):
+        if className not in ('body', 'register', 'field', 'register-name',
+                             'field-name', 'field-index', 'field-name-line'):
             # list of legal names
             continue
 
@@ -61,48 +61,34 @@ def parseCSS(configFileName: str, styleSheet: StyleSheet):
                                'fill',
                                'stroke', 'stroke-width', 'stroke-linecap')
 
-            try:
-                for declaration in declarations:
-                    if declaration.name not in legalAttributes:
-                        continue
-
-                    elif declaration.name == 'font-family':
-                        styleSheet.body.fontFamily = extractFontFamily(declaration)
+            for declaration in declarations:
+                if declaration.name not in legalAttributes:
+                    continue
 
-                    elif declaration.name == 'font-size':
-                        styleSheet.body.fontSize = extractDimensionalValue(declaration)
-                        #styleSheet.registerName.fontSize = styleSheet.body.fontSize
-                        #styleSheet.fieldName.fontSize = styleSheet.body.fontSize
-                        # TODO: deal with fieldIndex
+                elif declaration.name == 'font-family':
+                    styleSheet.body.fontFamily = extractFontFamily(declaration)
 
-                    elif declaration.name == 'font-style':
-                        styleSheet.body.fontStyle = extractEnum(declaration, FontStyle)
+                elif declaration.name == 'font-size':
+                    styleSheet.body.fontSize = extractDimensionalValue(declaration)
+                    # TODO: deal with fieldIndex
 
-                    elif declaration.name == 'font-weight':
-                        styleSheet.body.fontWeight = extractEnum(declaration, FontWeight)
+                elif declaration.name == 'font-style':
+                    styleSheet.body.fontStyle = extractEnum(declaration, FontStyle)
 
-                    elif declaration.name == 'fill':
-                        styleSheet.body.fill = extractColor(declaration)
+                elif declaration.name == 'font-weight':
+                    styleSheet.body.fontWeight = extractEnum(declaration, FontWeight)
 
-                    elif declaration.name == 'stroke':
-                        styleSheet.body.stroke = extractColor(declaration)
+                elif declaration.name == 'fill':
+                    styleSheet.body.fill = extractColor(declaration)
 
-                    elif declaration.name == 'stroke-width':
-                        styleSheet.body.strokeWidth = extractDimensionalValue(declaration)
+                elif declaration.name == 'stroke':
+                    styleSheet.body.stroke = extractColor(declaration)
 
-                    elif declaration.name == 'stroke-linecap':
-                        styleSheet.body.strokeLinecap = extractEnum(declaration, StrokeLinecap)
+                elif declaration.name == 'stroke-width':
+                    styleSheet.body.strokeWidth = extractDimensionalValue(declaration)
 
-            except ValueError as err:
-                if len(err.args) and isinstance(err.args[0], tinycss2.ast.Declaration):
-                    errorDeclaration: tinycss2.ast.Declaration = err.args[0]
-                    errorMessage: str = err.args[1]
-                    message = 'ERROR: {} (line {}, row {}): {}\n'.format(configFileName,
-                                                                       errorDeclaration.source_line,
-                                                                       errorDeclaration.source_column,
-                                                                       errorMessage)
-                    sys.stderr.write(message)
-                    sys.exit(1)
+                elif declaration.name == 'stroke-linecap':
+                    styleSheet.body.strokeLinecap = extractEnum(declaration, StrokeLinecap)
 
         elif className in ('register', 'field'):
             for declaration in declarations:
@@ -118,6 +104,17 @@ def parseCSS(configFileName: str, styleSheet: StyleSheet):
                 elif declaration.name == 'stroke-linecap':
                     getattr(styleSheet, className).strokeLinecap = extractEnum(declaration, StrokeLinecap)
 
+        elif className == 'field-name-line':
+            for declaration in declarations:
+                if declaration.name == 'stroke':
+                    getattr(styleSheet, classNameMap[className]).stroke = extractColor(declaration)
+
+                elif declaration.name == 'stroke-width':
+                    getattr(styleSheet, classNameMap[className]).strokeWidth = extractDimensionalValue(declaration)
+
+                elif declaration.name == 'stroke-linecap':
+                    getattr(styleSheet, classNameMap[className]).strokeLinecap = extractEnum(declaration, StrokeLinecap)
+
         elif className in ('register-name', 'field-name', 'field-index'):
             for declaration in declarations:
                 if declaration.name == 'font-family':
@@ -132,34 +129,69 @@ def parseCSS(configFileName: str, styleSheet: StyleSheet):
                 elif declaration.name == 'font-weight':
                     getattr(styleSheet, classNameMap[className]).fontWeight = extractEnum(declaration, FontWeight)
 
+                elif declaration.name == 'fill':
+                    getattr(styleSheet, classNameMap[className]).fill = extractColor(declaration)
+
 
 def cascadeStyles(styleSheet):
-    ## Cascade styles in StyleSheet
+    # Cascade styles in StyleSheet
     # body > register, field if property.value is None
     # body > register-name, field-name, field-index if property.value is None
     # cascade rect style
-    for obj in [styleSheet.register, styleSheet.field]:
-        if getattr(obj, 'fill') is None:
-            setattr(obj, 'fill', styleSheet.body.fill)
-        if getattr(obj, 'stroke') is None:
-            setattr(obj, 'stroke', styleSheet.body.stroke)
-        if getattr(obj, 'strokeWidth') is None:
-            setattr(obj, 'strokeWidth', styleSheet.body.strokeWidth)
-        if getattr(obj, 'strokeLinecap') is None:
-            setattr(obj, 'strokeLinecap', styleSheet.body.strokeLinecap)
-    # cascade body text styles to child styles
-    for obj in [styleSheet.registerName, styleSheet.fieldName, styleSheet.fieldIndex]:
+
+    obj = styleSheet.register
+    if getattr(obj, 'fill') is None:
+        setattr(obj, 'fill', styleSheet.body.fill)
+    if getattr(obj, 'stroke') is None:
+        setattr(obj, 'stroke', styleSheet.body.stroke)
+    if getattr(obj, 'strokeWidth') is None:
+        setattr(obj, 'strokeWidth', styleSheet.body.strokeWidth)
+    if getattr(obj, 'strokeLinecap') is None:
+        setattr(obj, 'strokeLinecap', styleSheet.body.strokeLinecap)
+
+    obj = styleSheet.field
+    if getattr(obj, 'fill') is None:
+        setattr(obj, 'fill', styleSheet.register.fill)
+    if getattr(obj, 'stroke') is None:
+        setattr(obj, 'stroke', styleSheet.register.stroke)
+    if getattr(obj, 'strokeWidth') is None:
+        setattr(obj, 'strokeWidth', styleSheet.register.strokeWidth)
+    if getattr(obj, 'strokeLinecap') is None:
+        setattr(obj, 'strokeLinecap', styleSheet.register.strokeLinecap)
+
+    obj = styleSheet.fieldNameLine
+    if getattr(obj, 'stroke') is None:
+        setattr(obj, 'stroke', styleSheet.field.stoke)
+    if getattr(obj, 'strokeWidth') is None:
+        setattr(obj, 'strokeWidth', styleSheet.field.strokeWidth)
+    if getattr(obj, 'strokeLinecap') is None:
+        setattr(obj, 'strokeLinecap', styleSheet.field.xstrokeLinecap)
+
+    obj = styleSheet.registerName
+    if getattr(obj, 'fontFamily') is None:
+        setattr(obj, 'fontFamily', styleSheet.body.fontFamily)
+    if getattr(obj, 'fontStyle') is None:
+        setattr(obj, 'fontStyle', styleSheet.body.fontStyle)
+    if getattr(obj, 'fontWeight') is None:
+        setattr(obj, 'fontWeight', styleSheet.body.fontWeight)
+    if getattr(obj, 'fontSize') is None:
+        setattr(obj, 'fontSize', styleSheet.body.fontSize)
+    # !!!: Because fill attribute used for both text and rect with
+    # different semantics, using rect.stroke value for text.fill
+    if getattr(obj, 'fill') is None:
+        setattr(obj, 'fill', styleSheet.body.stroke)
+
+    for obj in [styleSheet.fieldName, styleSheet.fieldIndex]:
         if getattr(obj, 'fontFamily') is None:
-            setattr(obj, 'fontFamily', styleSheet.body.fontFamily)
+            setattr(obj, 'fontFamily', styleSheet.registerName.fontFamily)
         if getattr(obj, 'fontStyle') is None:
-            setattr(obj, 'fontStyle', styleSheet.body.fontStyle)
+            setattr(obj, 'fontStyle', styleSheet.registerName.fontStyle)
         if getattr(obj, 'fontWeight') is None:
-            setattr(obj, 'fontWeight', styleSheet.body.fontWeight)
+            setattr(obj, 'fontWeight', styleSheet.registerName.fontWeight)
         if getattr(obj, 'fontSize') is None:
-            setattr(obj, 'fontSize', styleSheet.body.fontSize)
-        # !!!: Because fill attribute used for both text and rect with different semantics, using rect.stroke value for text.fill
+            setattr(obj, 'fontSize', styleSheet.registerName.fontSize)
         if getattr(obj, 'fill') is None:
-            setattr(obj, 'fill', styleSheet.body.stroke)
+            setattr(obj, 'fill', styleSheet.registerName.fill)
 
 
 def extractEnum(declaration, enumClass):
@@ -181,6 +213,7 @@ def extractEnum(declaration, enumClass):
 
     return enumClass[token.value]
 
+
 def extractDimensionalValue(declaration):
     value = None
     tokens = list(
@@ -200,6 +233,7 @@ def extractDimensionalValue(declaration):
         value = str(token.value)
     return value
 
+
 def extractColor(declaration):
     tokens = list(
         filter(lambda x: (isinstance(x, tinycss2.ast.IdentToken) or
@@ -215,6 +249,7 @@ def extractColor(declaration):
         value = '#{}'.format(token.value)
     return value
 
+
 def extractFontSize(declaration):
     tokens = list(
         filter(lambda x: (isinstance(x, tinycss2.ast.DimensionToken) or
@@ -230,10 +265,12 @@ def extractFontSize(declaration):
         value = str(token.value)
     return value
 
+
 def extractFontFamily(declaration):
     tokens = filter(lambda x: isinstance(x, tinycss2.ast.IdentToken), declaration.value)
     return [x.value for x in tokens]
 
+
 def filterDeclarationErrors(configFileName, declarations):
     parseErrors = filter(lambda x: isinstance(x, tinycss2.ast.ParseError), declarations)
     for parseError in parseErrors:
@@ -241,6 +278,4 @@ def filterDeclarationErrors(configFileName, declarations):
                                                                       parseError.source_line,
                                                                       parseError.source_column,
                                                                       parseError.message)
-
-        sys.stderr.write(message)
-        sys.exit(1)
+        raise ValueError(parseError, message)
diff --git a/regfmtlib/cssstyles.py b/src/regfmt/cssstyles.py
similarity index 59%
rename from regfmtlib/cssstyles.py
rename to src/regfmt/cssstyles.py
index 62c3291..bd14075 100644
--- a/regfmtlib/cssstyles.py
+++ b/src/regfmt/cssstyles.py
@@ -14,41 +14,91 @@
 # limitations under the License.
 
 from enum import Enum
+from PIL import ImageFont
+import sys
+from regfmt import BASE_FONT_NAME
+
 
 class StrokeLinecap(Enum):
     butt = 'butt'
     round = 'round'
     square = 'square'
 
+
 class FontStyle(Enum):
     normal = 'normal'
     italic = 'italic'
     oblique = 'oblique'
 
+
 class FontWeight(Enum):
     normal = 'normal'
     bold = 'bold'
     bolder = 'bolder'
     lighter = 'lighter'
 
+
 class TextStyle:
     def __init__(self,
                  fontFamily: [str] = None,
                  fontSize: str = None,
-                 fontStyle: FontStyle=None,
-                 fontWeight: str = None,
+                 fontStyle: FontStyle = None,
+                 fontWeight: FontWeight = None,
                  fill: str = None):
         self.fontFamily = fontFamily
         self.fontSize = fontSize
         self.fontStyle = fontStyle
         self.fontWeight = fontWeight
         self.fill = fill
+        self.font = None
+
+    def truetype(self):
+        if self.font:
+            return self.font
+
+        baseFontSize = self.fontSize
+
+        if 'pt' in baseFontSize:
+            baseFontSize = int(round(float(baseFontSize.replace('pt', ''))))
+
+        else:
+            try:
+                baseFontSize = int(float(round(baseFontSize)))
+            except:
+                message = ('WARNING: body font-size specification of "{}" is unsupported in '
+                           'CSS file for sizing the geometry of register fields. '
+                           'Coercing font size value to {}pt.\n')
+                sys.stderr.write(message.format(self.fontSize, 12))
+                baseFontSize = 12
+
+        for fontName in self.fontFamily:
+            try:
+                font = ImageFont.truetype(fontName, baseFontSize)
+                continue
+            except OSError:
+                
+                font = ImageFont.truetype(BASE_FONT_NAME, baseFontSize)
+                break
+
+        return font
+
+
+class LineStyle:
+    def __init__(self,
+                 stroke: str = 'grey',
+                 strokeWidth: str = '0.8',
+                 strokeLinecap: StrokeLinecap = StrokeLinecap.butt
+                 ):
+        self.stroke: str = stroke
+        self.strokeWidth: str = strokeWidth
+        self.strokeLinecap: StrokeLinecap = strokeLinecap
+
 
 class RectStyle:
     def __init__(self,
-                 fill: str=None,
-                 stroke: str=None,
-                 strokeWidth: str=None,
+                 fill: str = None,
+                 stroke: str = None,
+                 strokeWidth: str = None,
                  strokeLinecap: StrokeLinecap = None
                  ):
         # TODO: support fill opacity
@@ -59,6 +109,7 @@ def __init__(self,
         self.strokeWidth: str = strokeWidth
         self.strokeLinecap: StrokeLinecap = strokeLinecap
 
+
 class BaseStyle(TextStyle, RectStyle):
     def __init__(self):
         TextStyle.__init__(self,
@@ -74,6 +125,7 @@ def __init__(self):
                            strokeWidth='0.5',
                            strokeLinecap=StrokeLinecap.butt)
 
+
 class StyleSheet:
     def __init__(self):
         self.body = BaseStyle()
@@ -82,3 +134,4 @@ def __init__(self):
         self.registerName = TextStyle()
         self.fieldName = TextStyle()
         self.fieldIndex = TextStyle(fontSize='10pt')
+        self.fieldNameLine = LineStyle()
diff --git a/src/regfmt/requirements.txt b/src/regfmt/requirements.txt
new file mode 100644
index 0000000..cac3b48
--- /dev/null
+++ b/src/regfmt/requirements.txt
@@ -0,0 +1,7 @@
+attrs==22.1.0
+jsonschema==4.16.0
+Pillow==9.2.0
+pyrsistent==0.18.1
+PyYAML==6.0
+tinycss2==1.1.1
+webencodings==0.5.1
diff --git a/src/regfmt/stairleftlayout.py b/src/regfmt/stairleftlayout.py
new file mode 100644
index 0000000..2ba62c6
--- /dev/null
+++ b/src/regfmt/stairleftlayout.py
@@ -0,0 +1,126 @@
+from regfmt.svggeometry import *
+from regfmt import Register
+
+
+def writeStairLeftSVG(registerDB, topGroup, styleSheet, font, registerSpacing=5):
+    bitFieldSize = getBitFieldSize(font=font)
+
+    registers: [Register] = registerDB.registers
+    parentOrigin = Point()
+    parentSize = Size(height=(bitFieldSize.height + registerSpacing) * len(registers))
+
+    maxFieldDisplacementYs = []
+    displacementY = 0
+    for register in registers:
+        registerGroup = Group()
+        topGroup.append(registerGroup)
+
+        registerHeight = bitFieldSize.height
+        registerWidth = bitFieldSize.width * register.width
+
+        if register.name:
+            registerNameFrame = getTextFrame(text=register.name, font=styleSheet.registerName.truetype())
+
+            x = registerWidth + 10
+            y = registerNameFrame.size.height + (registerHeight/2.0) - (registerNameFrame.size.height/2.0)
+
+            registerGroup.append(Text(text=register.name,
+                                      x=x,
+                                      y=y,
+                                      style=styleSheet.registerName))
+
+            newParentWidth = x + registerNameFrame.size.width
+            if parentSize.width < newParentWidth:
+                parentSize.width = newParentWidth
+
+        displacementX = 0
+
+        fieldNames = list(filter(lambda x: x is not None, [field.name for field in register.fields]))
+        fieldCount = len(fieldNames)
+
+        fieldFrames = tuple(map(lambda x: getTextFrame(text=x, font=styleSheet.fieldName.truetype()), fieldNames))
+        fieldHeights = tuple(map(lambda x: x.size.height, fieldFrames))
+        maxFieldHeight = max(fieldHeights)
+
+        fieldDisplacementY = (maxFieldHeight + 10.0) * fieldCount
+        maxFieldDisplacementYs.append(fieldDisplacementY)
+
+        counter = 0
+        for field in register.fields:
+            fieldWidth = field.width * bitFieldSize.width
+            fieldHeight = registerHeight
+
+            T = translateTransform(displacementX, 0.0)
+            V = vector2D(0.0, 0.0)
+            transformedV = matrixMult(T, V)
+            fieldX, fieldY = coordinateFromVector2D(transformedV)
+
+            registerGroup.append(Rect(x=fieldX,
+                                      y=fieldY,
+                                      width=fieldWidth,
+                                      height=fieldHeight,
+                                      style=styleSheet.field))
+
+            if field.width > 1:
+                registerGroup.append(Text(str(field.leftIndex),
+                                          x=fieldX + 3,
+                                          y=fieldY + fieldHeight - 3,
+                                          textAnchor='start',
+                                          style=styleSheet.fieldIndex
+                                          ))
+
+                registerGroup.append(Text(str(field.rightIndex),
+                                          x=fieldX + fieldWidth - 3,
+                                          y=fieldY + fieldHeight - 3,
+                                          textAnchor='end',
+                                          style=styleSheet.fieldIndex
+                                          ))
+            else:
+                registerGroup.append(Text(str(field.leftIndex),
+                                          x=fieldX + (fieldWidth / 2.0),
+                                          y=fieldY + fieldHeight - 3,
+                                          textAnchor='middle',
+                                          style=styleSheet.fieldIndex
+                                          ))
+
+            if field.name:
+                fieldNameFrame = fieldFrames[counter]
+
+                fieldNameX = (fieldWidth / 2.0) + displacementX + 3.0
+                fieldNameY = fieldHeight + fieldNameFrame.origin.y + fieldDisplacementY + 10.0
+
+                newParentWidth = fieldNameX + fieldNameFrame.size.width
+                if parentSize.width < newParentWidth:
+                    parentSize.width = newParentWidth
+
+                registerGroup.append(Text(text=field.name,
+                                          x=fieldNameX,
+                                          y=fieldNameY,
+                                          textAnchor='left',
+                                          style=styleSheet.fieldName))
+
+                registerGroup.append(Line(x1=displacementX + (fieldWidth / 2.0),
+                                          y1=registerHeight,
+                                          x2=displacementX + (fieldWidth / 2.0),
+                                          y2=fieldNameY,
+                                          style=styleSheet.fieldNameLine))
+
+                fieldDisplacementY -= (maxFieldHeight + 10)
+
+                if parentSize.height < fieldNameY:
+                    parentSize.height = fieldNameY
+
+                counter += 1
+
+            displacementX += fieldWidth
+
+    counter2 = 0
+    for group in topGroup:
+        group.translate(0, displacementY)
+        displacementY += (bitFieldSize.height * 2.0 + registerSpacing + maxFieldDisplacementYs[counter2])
+        if parentSize.height < displacementY:
+            parentSize.height = displacementY
+        counter2 += 1
+
+    return (parentOrigin, parentSize)
+
diff --git a/regfmtlib/svggeometry.py b/src/regfmt/svggeometry.py
similarity index 54%
rename from regfmtlib/svggeometry.py
rename to src/regfmt/svggeometry.py
index 8bc3989..10b6e60 100644
--- a/regfmtlib/svggeometry.py
+++ b/src/regfmt/svggeometry.py
@@ -13,41 +13,113 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from regfmtlib.cssstyles import *
-
+from regfmt.cssstyles import *
+from xml.dom.minidom import parseString
+from PIL import ImageFont
 from collections import UserList
+from regfmt import BASE_FONT_NAME
+
+BASE_FONT_SIZE = 12
+
 
 def bboxWidth(bbox):
     return (bbox[2] - bbox[0])
 
+
 def bboxHeight(bbox):
     return (bbox[3] - bbox[1])
 
+
 def matrixMult(X, Y):
-    result = [[sum(a*b for a,b in zip(X_row,Y_col)) for Y_col in zip(*Y)] for X_row in X]
+    result = [[sum(a*b for a, b in zip(X_row, Y_col)) for Y_col in zip(*Y)] for X_row in X]
     return result
 
+
 def translateTransform(x, y):
     result = [[1.0, 0.0, x],
               [0.0, 1.0, y],
               [0.0, 0.0, 1.0]]
     return result
 
+
 def vector2D(x, y):
     result = [[x], [y], [1.0]]
     return result
 
+
 def coordinateFromVector2D(V):
     result = (V[0][0], V[1][0])
     return result
 
-def numToUnitString(num, unit: str=''):
+
+def numToUnitString(num, unit: str = ''):
     if unit == '':
         result = '{0}'.format(num)
     else:
         result = '{0}{1}'.format(num, unit)
     return result
 
+
+def cssFontToImageFont(fontFamily, fontSize):
+    result = None
+    for baseFontname in fontFamily:
+        baseFontSize = fontSize
+
+        # !!!: only pt is supported for geometry calculations
+
+        if 'pt' in baseFontSize:
+            baseFontSize = int(round(float(baseFontSize.replace('pt', ''))))
+        else:
+            try:
+                baseFontSize = int(float(round(baseFontSize)))
+            except:
+                message = ('WARNING: body font-size specification of "{}" is unsupported in '
+                           'CSS file for sizing the geometry of register fields. '
+                           'Coercing font size value to {}pt.\n')
+                sys.stderr.write(message.format(fontSize, BASE_FONT_SIZE))
+                baseFontSize = BASE_FONT_SIZE
+
+        try:
+            result = ImageFont.truetype(baseFontname, baseFontSize)
+            break
+        except OSError:
+            continue
+
+    if result is None:
+        result = ImageFont.truetype(BASE_FONT_NAME, baseFontSize)
+
+    return result
+
+
+def getTextFrame(text, font, anchor='ls', baseDPI=96.0):
+    """
+    use baseline by default
+
+    scalingFactor adjusts for the screen resolution which presumes 96 DPI.
+
+    :param text:
+    :param font:
+    :param anchor:
+    :return:
+    """
+    scalingFactor = baseDPI / 64.0
+    bbox64 = font.getbbox(text=text, anchor=anchor)
+    bbox = tuple(map(lambda x: x * scalingFactor, bbox64))
+    length = font.getlength(text=text)
+    frame = Frame(x=bbox[0],
+                  y=(bbox[1] * -1),
+                  width=bboxWidth(bbox),
+                  height=bboxHeight(bbox))
+    return frame
+
+
+def getBitFieldSize(font):
+    emFrame = getTextFrame(text='M', font=font)
+    emFrame.size.width *= 2
+    emFrame.size.height *= 4
+    return emFrame.size
+
+
 class Group(UserList):
     def writeDOM(self, children, doc):
         """
@@ -65,32 +137,47 @@ def writeDOM(self, children, doc):
                 shape: Shape = element
                 children.append(shape.writeDOM(doc))
 
-    def translate(self, dx: float=0.0, dy: float=0.0):
+    def translate(self, dx: float = 0.0, dy: float = 0.0):
         for element in self.data:
             if isinstance(element, Group):
                 element.translate(dx, dy)
             else:
-                #print('translating {0} by {1}, {2}'.format(element, dx, dy))
+                # print('translating {0} by {1}, {2}'.format(element, dx, dy))
                 shape: Shape = element
                 shape.translate(dx, dy)
 
+
 class Point:
-    def __init__(self, x: float=0.0, y: float=0.0):
+    def __init__(self, x: float = 0.0, y: float = 0.0):
         self.x: float = x
         self.y: float = y
 
+
 class Size:
-    def __init__(self, width: float=0.0, height: float=0.0):
+    def __init__(self, width: float = 0.0, height: float = 0.0):
         self.width: float = width
         self.height: float = height
 
+
 class Frame:
     def __init__(self, x: float = 0.0, y: float = 0.0, width: float = 0.0, height: float = 0.0):
         self.origin: Point = Point(x, y)
         self.size: Size = Size(width, height)
 
-class Shape:
+
+class Geometry:
+    def writeDOM(self, doc):
+        # virtual method; intended to be overridden
+        pass
+
+    def translate(self, dx: float = 0.0, dy: float = 0.0):
+        # virutal method; indented to be overwritten
+        pass
+
+
+class Shape(Geometry):
     def __init__(self, x: float = 0.0, y: float = 0.0, width: float = 0.0, height: float = 0.0):
+        Geometry.__init__(self)
         self.frame: Frame = Frame(x, y, width, height)
 
     def origin(self):
@@ -99,25 +186,62 @@ def origin(self):
     def size(self):
         return self.frame.size
 
-    def setOrigin(self, x: float=0.0, y: float=0.0):
+    def setOrigin(self, x: float = 0.0, y: float = 0.0):
         self.frame.origin.x = x
         self.frame.origin.y = y
 
-    def setSize(self, width: float=0.0, height: float=0.0):
+    def setSize(self, width: float = 0.0, height: float = 0.0):
         self.frame.size.width = width
         self.frame.size.height = height
 
-    def writeDOM(self, doc):
-        # virtual method; intended to be overridden
-        pass
-
-    def translate(self, dx: float=0.0, dy: float=0.0):
+    def translate(self, dx: float = 0.0, dy: float = 0.0):
         T = translateTransform(dx, dy)
         V = vector2D(self.origin().x, self.origin().y)
         transformedV = matrixMult(T, V)
         newX, newY = coordinateFromVector2D(transformedV)
         self.setOrigin(newX, newY)
 
+
+class Line(Geometry):
+    def __init__(self,
+                 x1: float = 0.0,
+                 y1: float = 0.0,
+                 x2: float = 0.0,
+                 y2: float = 0.0,
+                 style: LineStyle = LineStyle()):
+        Geometry.__init__(self)
+        self.x1 = x1
+        self.y1 = y1
+        self.x2 = x2
+        self.y2 = y2
+        self.style: LineStyle = style
+
+    def writeDOM(self, doc):
+        lineElement = doc.createElement('line')
+        lineElement.setAttribute('x1', str(self.x1))
+        lineElement.setAttribute('y1', str(self.y1))
+        lineElement.setAttribute('x2', str(self.x2))
+        lineElement.setAttribute('y2', str(self.y2))
+        lineElement.setAttribute('stroke', self.style.stroke)
+        lineElement.setAttribute('stroke-width', self.style.strokeWidth)
+        lineElement.setAttribute('stroke-linecap', self.style.strokeLinecap.value)
+        return lineElement
+
+    def translate(self, dx: float = 0.0, dy: float = 0.0):
+        T = translateTransform(dx, dy)
+        V = vector2D(self.x1, self.y1)
+        transformedV = matrixMult(T, V)
+        newX, newY = coordinateFromVector2D(transformedV)
+        self.x1 = newX
+        self.y1 = newY
+
+        V = vector2D(self.x2, self.y2)
+        transformedV = matrixMult(T, V)
+        newX, newY = coordinateFromVector2D(transformedV)
+        self.x2 = newX
+        self.y2 = newY
+
+
 class Rect(Shape):
     def __init__(self,
                  x: float = 0.0, y: float = 0.0, width: float = 0.0, height: float = 0.0,
@@ -138,11 +262,12 @@ def writeDOM(self, doc):
         rectElement.setAttribute('stroke-linecap', self.style.strokeLinecap.value)
         return rectElement
 
+
 class Text(Shape):
     def __init__(self,
                  text="something",
                  x: float = 0.0, y: float = 0.0, width: float = 0.0, height: float = 0.0,
-                 textAnchor: str='start',
+                 textAnchor: str = 'start',
                  style: TextStyle = TextStyle()
                  ):
 
@@ -162,13 +287,19 @@ def writeDOM(self, doc):
         textElement.setAttribute('text-anchor', self.textAnchor)
         textElement.setAttribute('fill', self.style.fill)
 
-        textNode = doc.createTextNode(self.text)
+        if '<sub>' in self.text:
+            # don't think this is going to work.
+            newText = '<p>{}</p>'.format(self.text)
+            xmlFragment = parseString(newText)
+            textNode = xmlFragment.documentElement
+
+        else:
+            textNode = doc.createTextNode(self.text)
         textElement.appendChild(textNode)
         return textElement
 
 
 if __name__ == '__main__':
-    
     group = Group()
     group.append(Rect((0, 0, 10, 10)))
     group.append(Rect((0, 5, 29, 12)))
@@ -177,7 +308,3 @@ def writeDOM(self, doc):
     topGroup = Group()
     
     topGroup.append(group)
-    
-
-
-        
diff --git a/src/regfmt/svgwriter.py b/src/regfmt/svgwriter.py
new file mode 100644
index 0000000..1077634
--- /dev/null
+++ b/src/regfmt/svgwriter.py
@@ -0,0 +1,84 @@
+##
+# Copyright 2022 Charles Y. Choi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xml.dom.minidom import getDOMImplementation
+from regfmt import TopLevel, FieldNameAlign, VERSION
+from regfmt.svggeometry import *
+from regfmt.cssparser import parseCSS, cascadeStyles
+from regfmt.centeralignlayout import writeFieldNameCenterSVG
+from regfmt.stairleftlayout import writeStairLeftSVG
+
+
+class SVGWriter:
+    def __init__(self, registerDB: TopLevel, outfile, configFileName=None):
+        self.registerDB = registerDB
+        self.outfile = outfile
+        self.styleSheet = StyleSheet()
+
+        parseCSS(configFileName=configFileName, styleSheet=self.styleSheet)
+        cascadeStyles(self.styleSheet)
+        self.font = cssFontToImageFont(fontFamily=self.styleSheet.body.fontFamily,
+                                       fontSize=self.styleSheet.body.fontSize)
+
+    def createDocument(self, origin=None, size=None):
+        dom = getDOMImplementation()
+        doc = dom.createDocument(None, 'svg', None)
+        topElement = doc.documentElement
+        topElement.setAttribute('version', '1.1')
+        topElement.setAttribute('xmlns', "http://www.w3.org/2000/svg")
+        watermark = 'diagram generated by regfmt {} https://github.com/kickingvegas/regfmt'.format(VERSION)
+        topElement.appendChild(doc.createComment(watermark))
+        if origin:
+            topElement.setAttribute('x', str(origin.x))
+            topElement.setAttribute('y', str(origin.y))
+
+        if size:
+            topElement.setAttribute('width', str(size.width))
+            topElement.setAttribute('height', str(size.height))
+
+        return doc, topElement
+
+    def compositeSVG(self, doc, topElement, topGroup, outfile):
+        children = []
+        topGroup.writeDOM(children, doc)
+        for child in children:
+            topElement.appendChild(child)
+        doc.writexml(outfile, encoding='utf-8', addindent="  ", newl="\n")
+
+    def writeSVG(self, registerDB: TopLevel = None):
+        if registerDB is None:
+            registerDB = self.registerDB
+
+        # Layout Geometry - all decisions on the graphical layout of a register instance are done here.
+
+        # Initialize container for geometry
+        topGroup = Group()
+        if registerDB.layout.fieldNameAlign == FieldNameAlign.center:
+            origin, size = writeFieldNameCenterSVG(registerDB=registerDB,
+                                                   topGroup=topGroup,
+                                                   styleSheet=self.styleSheet,
+                                                   font=self.font)
+
+        elif registerDB.layout.fieldNameAlign == FieldNameAlign.stairLeft:
+            origin, size = writeStairLeftSVG(registerDB=registerDB,
+                                             topGroup=topGroup,
+                                             styleSheet=self.styleSheet,
+                                             font=self.font)
+            # origin, size = self.writeFieldNameStairLeftSVG(registerDB, topGroup)
+
+        doc, topElement = self.createDocument(origin=origin, size=size)
+
+        # Write DOM and composite to SVG
+        self.compositeSVG(doc, topElement, topGroup, self.outfile)


Test patch:
diff --git a/tests/control/example_0001-github.svg b/tests/control/example_0001-github.svg
new file mode 100644
index 0000000..08b5348
--- /dev/null
+++ b/tests/control/example_0001-github.svg
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0.0" y="0.0" width="1186.0" height="213.0">
+  <!--diagram generated by regfmt 0.1.10 https://github.com/kickingvegas/regfmt-->
+  <text x="1162.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R1</text>
+  <rect x="0.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="144.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A4</text>
+  <text x="3.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">31</text>
+  <text x="285.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">24</text>
+  <rect x="288.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="432.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A3</text>
+  <text x="291.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">23</text>
+  <text x="573.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">16</text>
+  <rect x="576.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="720.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A2</text>
+  <text x="579.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">15</text>
+  <text x="861.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">8</text>
+  <rect x="864.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="1008.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A1</text>
+  <text x="867.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">7</text>
+  <text x="1149.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+  <text x="1162.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R2</text>
+  <rect x="0.0" y="71.0" width="144.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="72.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">B7</text>
+  <text x="3.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">31</text>
+  <text x="141.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">28</text>
+  <rect x="144.0" y="71.0" width="144.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="216.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">B0</text>
+  <text x="147.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">27</text>
+  <text x="285.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">24</text>
+  <rect x="288.0" y="71.0" width="432.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="504.0" y="109.25" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">B_I/O</text>
+  <text x="291.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">23</text>
+  <text x="717.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">12</text>
+  <rect x="720.0" y="71.0" width="144.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="792.0" y="109.25" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">C_I/O</text>
+  <text x="723.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">11</text>
+  <text x="861.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">8</text>
+  <rect x="864.0" y="71.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="1008.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">RESERVED</text>
+  <text x="867.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">7</text>
+  <text x="1149.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+  <text x="1162.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R3</text>
+  <rect x="0.0" y="142.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="144.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">Control</text>
+  <text x="3.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">31</text>
+  <text x="285.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">24</text>
+  <rect x="288.0" y="142.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="432.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">Data</text>
+  <text x="291.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">23</text>
+  <text x="573.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">16</text>
+  <rect x="576.0" y="142.0" width="576.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="864.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">Address</text>
+  <text x="579.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">15</text>
+  <text x="1149.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+</svg>
diff --git a/tests/control/example_0001.svg b/tests/control/example_0001.svg
new file mode 100644
index 0000000..08b5348
--- /dev/null
+++ b/tests/control/example_0001.svg
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0.0" y="0.0" width="1186.0" height="213.0">
+  <!--diagram generated by regfmt 0.1.10 https://github.com/kickingvegas/regfmt-->
+  <text x="1162.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R1</text>
+  <rect x="0.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="144.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A4</text>
+  <text x="3.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">31</text>
+  <text x="285.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">24</text>
+  <rect x="288.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="432.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A3</text>
+  <text x="291.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">23</text>
+  <text x="573.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">16</text>
+  <rect x="576.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="720.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A2</text>
+  <text x="579.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">15</text>
+  <text x="861.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">8</text>
+  <rect x="864.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="1008.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A1</text>
+  <text x="867.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">7</text>
+  <text x="1149.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+  <text x="1162.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R2</text>
+  <rect x="0.0" y="71.0" width="144.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="72.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">B7</text>
+  <text x="3.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">31</text>
+  <text x="141.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">28</text>
+  <rect x="144.0" y="71.0" width="144.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="216.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">B0</text>
+  <text x="147.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">27</text>
+  <text x="285.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">24</text>
+  <rect x="288.0" y="71.0" width="432.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="504.0" y="109.25" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">B_I/O</text>
+  <text x="291.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">23</text>
+  <text x="717.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">12</text>
+  <rect x="720.0" y="71.0" width="144.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="792.0" y="109.25" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">C_I/O</text>
+  <text x="723.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">11</text>
+  <text x="861.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">8</text>
+  <rect x="864.0" y="71.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="1008.0" y="110.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">RESERVED</text>
+  <text x="867.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">7</text>
+  <text x="1149.0" y="134.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+  <text x="1162.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R3</text>
+  <rect x="0.0" y="142.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="144.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">Control</text>
+  <text x="3.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">31</text>
+  <text x="285.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">24</text>
+  <rect x="288.0" y="142.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="432.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">Data</text>
+  <text x="291.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">23</text>
+  <text x="573.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">16</text>
+  <rect x="576.0" y="142.0" width="576.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="864.0" y="181.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">Address</text>
+  <text x="579.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">15</text>
+  <text x="1149.0" y="205.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+</svg>
diff --git a/tests/control/example_0002.svg b/tests/control/example_0002.svg
new file mode 100644
index 0000000..1a1f773
--- /dev/null
+++ b/tests/control/example_0002.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0.0" y="0.0" width="446.5" height="71.0">
+  <!--diagram generated by regfmt 0.1.10 https://github.com/kickingvegas/regfmt-->
+  <text x="298.0" y="42.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">Control Register 1</text>
+  <rect x="0.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="18.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">7</text>
+  <rect x="36.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="54.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">6</text>
+  <rect x="72.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="90.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">5</text>
+  <rect x="108.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="126.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">4</text>
+  <rect x="144.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="162.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">3</text>
+  <rect x="180.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="198.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">2</text>
+  <rect x="216.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="234.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">1</text>
+  <rect x="252.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="270.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">0</text>
+</svg>
diff --git a/tests/control/example_0003.svg b/tests/control/example_0003.svg
new file mode 100644
index 0000000..d7497ab
--- /dev/null
+++ b/tests/control/example_0003.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0.0" y="0.0" width="482.5" height="851.0">
+  <!--diagram generated by regfmt 0.1.10 https://github.com/kickingvegas/regfmt-->
+  <text x="298.0" y="42.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">Output Configuration</text>
+  <rect x="0.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="18.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">7</text>
+  <text x="21.0" y="285.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">OBF_f</text>
+  <line x1="18.0" y1="66.0" x2="18.0" y2="285.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="36.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="54.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">6</text>
+  <text x="57.0" y="257.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">INTE_A</text>
+  <line x1="54.0" y1="66.0" x2="54.0" y2="257.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="72.0" y="0.0" width="72.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="75.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">5</text>
+  <text x="141.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="end" fill="black">4</text>
+  <text x="111.0" y="229.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">I/O</text>
+  <line x1="108.0" y1="66.0" x2="108.0" y2="229.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="144.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="162.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">3</text>
+  <text x="165.0" y="201.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">INTR_A (Interrupt A)</text>
+  <line x1="162.0" y1="66.0" x2="162.0" y2="201.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="180.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="198.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">2</text>
+  <text x="201.0" y="173.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">INTE_B</text>
+  <line x1="198.0" y1="66.0" x2="198.0" y2="173.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="216.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="234.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">1</text>
+  <text x="237.0" y="145.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">OBF_B (Output Buffer B)</text>
+  <line x1="234.0" y1="66.0" x2="234.0" y2="145.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="252.0" y="0.0" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="270.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">0</text>
+  <text x="273.0" y="117.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">INTR_B (Interrupt B)</text>
+  <line x1="270.0" y1="66.0" x2="270.0" y2="117.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <text x="298.0" y="375.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">Output Configuration 2</text>
+  <rect x="0.0" y="333.0" width="144.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="3.0" y="396.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">7</text>
+  <text x="141.0" y="396.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="end" fill="black">4</text>
+  <text x="75.0" y="502.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">OBF_f</text>
+  <line x1="72.0" y1="399.0" x2="72.0" y2="502.0" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="144.0" y="333.0" width="72.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="147.0" y="396.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">3</text>
+  <text x="213.0" y="396.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="end" fill="black">2</text>
+  <text x="183.0" y="475.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">INTE_A</text>
+  <line x1="180.0" y1="399.0" x2="180.0" y2="475.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="216.0" y="333.0" width="72.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="219.0" y="396.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">1</text>
+  <text x="285.0" y="396.0" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="end" fill="black">0</text>
+  <text x="255.0" y="449.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">I/O</text>
+  <line x1="252.0" y1="399.0" x2="252.0" y2="449.0" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <text x="298.0" y="591.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="start" fill="black">Output Configuration 3</text>
+  <rect x="0.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="18.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">7</text>
+  <text x="21.0" y="803.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">A1</text>
+  <line x1="18.0" y1="615.5" x2="18.0" y2="803.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="36.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="54.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">6</text>
+  <text x="57.0" y="780.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">A2</text>
+  <line x1="54.0" y1="615.5" x2="54.0" y2="780.0" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="72.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="90.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">5</text>
+  <text x="93.0" y="756.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">A3</text>
+  <line x1="90.0" y1="615.5" x2="90.0" y2="756.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="108.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="126.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">4</text>
+  <text x="129.0" y="733.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">A4</text>
+  <line x1="126.0" y1="615.5" x2="126.0" y2="733.0" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="144.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="162.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">3</text>
+  <rect x="180.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="198.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">2</text>
+  <text x="201.0" y="709.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">A6</text>
+  <line x1="198.0" y1="615.5" x2="198.0" y2="709.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="216.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="234.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">1</text>
+  <text x="237.0" y="686.0" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">A7</text>
+  <line x1="234.0" y1="615.5" x2="234.0" y2="686.0" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="252.0" y="549.5" width="36.0" height="66.0" fill="none" stroke="black" stroke-width="0.5" stroke-linecap="butt"/>
+  <text x="270.0" y="612.5" font-size="10pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="middle" fill="black">0</text>
+  <text x="273.0" y="662.5" font-size="12pt" font-family="Futura,Helvetica,Arial,sans-serif" font-style="normal" font-weight="normal" text-anchor="left" fill="black">A7</text>
+  <line x1="270.0" y1="615.5" x2="270.0" y2="662.5" stroke="grey" stroke-width="0.8" stroke-linecap="butt"/>
+</svg>
diff --git a/tests/control/register-stair-left.svg b/tests/control/register-stair-left.svg
new file mode 100644
index 0000000..0286a0e
--- /dev/null
+++ b/tests/control/register-stair-left.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0.0" y="0.0" width="352.5" height="249.0">
+  <!--diagram generated by regfmt 0.1.10 https://github.com/kickingvegas/regfmt-->
+  <text x="298.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R1</text>
+  <rect x="0.0" y="0.0" width="36.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="18.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="darkslategrey">7</text>
+  <text x="21.0" y="201.5" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="left" fill="teal">Coprocessor Usable</text>
+  <line x1="18.0" y1="66.0" x2="18.0" y2="201.5" stroke="purple" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="36.0" y="0.0" width="36.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="54.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="darkslategrey">6</text>
+  <text x="57.0" y="173.5" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="left" fill="teal">Bootstrap Exception Vectors</text>
+  <line x1="54.0" y1="66.0" x2="54.0" y2="173.5" stroke="purple" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="72.0" y="0.0" width="72.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="75.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">5</text>
+  <text x="141.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">4</text>
+  <text x="111.0" y="145.5" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="left" fill="teal">Parity Errors</text>
+  <line x1="108.0" y1="66.0" x2="108.0" y2="145.5" stroke="purple" stroke-width="0.8" stroke-linecap="butt"/>
+  <rect x="144.0" y="0.0" width="144.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="147.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">3</text>
+  <text x="285.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+  <text x="219.0" y="117.5" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="left" fill="teal">Interrupt Waiting</text>
+  <line x1="216.0" y1="66.0" x2="216.0" y2="117.5" stroke="purple" stroke-width="0.8" stroke-linecap="butt"/>
+</svg>
diff --git a/tests/control/register.svg b/tests/control/register.svg
new file mode 100644
index 0000000..24cb297
--- /dev/null
+++ b/tests/control/register.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0.0" y="0.0" width="1186.0" height="71.0">
+  <!--diagram generated by regfmt 0.1.10 https://github.com/kickingvegas/regfmt-->
+  <text x="1162.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="green">R1</text>
+  <rect x="0.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="144.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A3</text>
+  <text x="3.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">31</text>
+  <text x="285.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">24</text>
+  <rect x="288.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="432.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A2</text>
+  <text x="291.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">23</text>
+  <text x="573.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">16</text>
+  <rect x="576.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="720.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A1</text>
+  <text x="579.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">15</text>
+  <text x="861.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">8</text>
+  <rect x="864.0" y="0.0" width="288.0" height="66.0" fill="white" stroke="black" stroke-width="1.0" stroke-linecap="butt"/>
+  <text x="1008.0" y="39.75" font-size="12.0pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="middle" fill="teal">A0</text>
+  <text x="867.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="start" fill="darkslategrey">7</text>
+  <text x="1149.0" y="63.0" font-size="10pt" font-family="Futura,Helvetica,Arial,Verdana,FreeSans" font-style="normal" font-weight="normal" text-anchor="end" fill="darkslategrey">0</text>
+</svg>
diff --git a/tests/data/clean.css b/tests/data/clean.css
new file mode 100644
index 0000000..1833aa4
--- /dev/null
+++ b/tests/data/clean.css
@@ -0,0 +1,57 @@
+/**
+ regfmt example CSS file
+
+ Copyright © 2022 Charles Y. Choi
+ */
+
+body {
+    font-family: Futura, Helvetica, Arial, Verdana;
+    font-size: 12pt;
+    font-style: normal;
+    font-weight: normal;
+    fill: none;
+    stroke: green
+    stroke-width: 0.5;
+    stroke-linecap: butt;
+}
+
+register {
+    fill: chiffon;
+    stroke: magenta;
+    stroke-width: 0.1;
+    stroke-linecap: butt;
+}
+
+field {
+    fill: cyan;
+    stroke: black;
+    stroke-width: 0.3;
+    stroke-linecap: butt;
+}
+
+field-name-line {
+    stroke: orange;
+    stroke-width: 1;
+    stroke-linecap: butt;
+}
+
+register-name {
+    font-size: 12pt;
+    fill: black;
+}
+
+
+field-name {
+    font-family: Helvetica;
+    font-style: normal;
+    font-weight: normal;
+    font-size: 12pt;
+    fill: blue;
+}
+
+field-index {
+    font-size: 10pt;
+    font-style: normal;
+    font-weight: bold;
+    fill: blue;
+}
diff --git a/tests/data/example_0001.yaml b/tests/data/example_0001.yaml
index 0819c4e..6e90a2f 100644
--- a/tests/data/example_0001.yaml
+++ b/tests/data/example_0001.yaml
@@ -22,33 +22,31 @@ registers:
 
   - name: R2
     fields:
-      - name: A1
-        width: 8
+      - name: B7
+        width: 4
+      - name: B0
+        width: 4
 
-      - name: A2
-        width: 8
+      - width: 12
+        name: B_I/O
 
-      - name: A3
-        width: 8
-
-      - name: A4
-        width: 8
+      - width: 4
+        name: C_I/O
+        
+      - width: 8
+        name: RESERVED
 
   - name: R3
     fields:
-      - name: A1
-        width: 8
-
-      - name: A2
+      - name: Control
         width: 8
 
-      - name: A3
+      - name: Data
         width: 8
 
-      - name: A4
-        width: 8
+      - name: Address
+        width: 16
 
 layout:
-   registers: lr
-   register_name: left
-   field_name: center
+  field-name-align: center
+  register-layout: tb
diff --git a/tests/data/example_0002.yaml b/tests/data/example_0002.yaml
new file mode 100644
index 0000000..b968ba6
--- /dev/null
+++ b/tests/data/example_0002.yaml
@@ -0,0 +1,40 @@
+##
+# Example YAML file for regfmt
+# Copyright © 2022 Charles Y. Choi
+#
+# Single 8-bit register with each bit a field, no field names assigned.
+
+width: 8
+endian: bigBit
+
+registers:
+  - name: Control Register 1
+    fields:
+      - width: 1
+        name: null
+
+      - width: 1
+        name: null
+
+      - width: 1
+        name: null
+
+      - width: 1
+        name: null
+        
+      - width: 1
+        name: null
+
+      - width: 1
+        name: null
+
+      - width: 1
+        name: null
+
+      - width: 1
+        name: null
+        
+
+layout:
+  field-name-align: center
+  register-layout: tb
diff --git a/tests/data/example_0003.yaml b/tests/data/example_0003.yaml
new file mode 100644
index 0000000..b55fdc7
--- /dev/null
+++ b/tests/data/example_0003.yaml
@@ -0,0 +1,73 @@
+##
+# Example YAML file for regfmt
+# Copyright © 2022 Charles Y. Choi
+#
+# Single 8-bit register with 8 1-bit fields. 
+
+width: 8
+endian: bigBit
+
+registers:
+  - name: Output Configuration
+    fields:
+      - width: 1
+        name: OBF_f
+
+      - width: 1
+        name: INTE_A
+
+      - width: 2
+        name: I/O
+
+      - width: 1
+        name: INTR_A (Interrupt A)
+
+      - width: 1
+        name: INTE_B 
+
+      - width: 1
+        name: OBF_B (Output Buffer B)
+
+      - width: 1
+        name: INTR_B (Interrupt B)
+
+  - name: Output Configuration 2
+    fields:
+      - width: 4
+        name: OBF_f
+
+      - width: 2
+        name: INTE_A
+
+      - width: 2
+        name: I/O
+
+  - name: Output Configuration 3
+    fields:
+      - width: 1
+        name: A1
+
+      - width: 1
+        name: A2
+
+      - width: 1
+        name: A3
+
+      - width: 1
+        name: A4
+
+      - width: 1
+        name: null
+
+      - width: 1
+        name: A6
+
+      - width: 1
+        name: A7
+        
+      - width: 1
+        name: A7
+
+
+layout:
+  field-name-align: stair-left
diff --git a/tests/data/github.css b/tests/data/github.css
new file mode 100644
index 0000000..0e0102f
--- /dev/null
+++ b/tests/data/github.css
@@ -0,0 +1,32 @@
+/**
+ regfmt example CSS file
+
+ Copyright © 2022 Charles Y. Choi
+ */
+
+body {
+    font-family: Futura, Helvetica, Arial, Verdana, FreeSans;
+    font-size: 12pt;
+    font-style: normal;
+    font-weight: normal;
+    fill: white;
+    stroke: black;
+    stroke-width: 1.0;
+    stroke-linecap: butt;
+}
+
+register-name {
+    fill: green;
+}
+
+field-name {
+    fill: teal;
+}
+
+field-index {
+    fill: darkslategrey;
+}
+
+field-name-line: {
+    stroke: purple;
+}
diff --git a/tests/data/main.css b/tests/data/main.css
index 8017bfd..a054fbf 100644
--- a/tests/data/main.css
+++ b/tests/data/main.css
@@ -7,46 +7,22 @@
 body {
     font-family: Futura, Helvetica, Arial, Verdana;
     font-size: 12pt;
-    font-style: italic;
+    font-style: normal;
     font-weight: normal;
-    fill: yellow;
-    stroke: red;
-    stroke-width: 0.5;
-    stroke-linecap: butt
-}
-
-register {
-    fill: cyan;
-    stroke: black;
-    stroke-width: 0.1;
-    stroke-linecap: butt
-}
-
-field {
-    fill: cyan;
+    fill: white;
     stroke: black;
-    stroke-width: 0.3;
-    stroke-linecap: butt
+    stroke-width: 1.0;
+    stroke-linecap: butt;
 }
 
 register-name {
-    font-family: Times, serif;
-    font-size: 12pt;
-    font-style: italic;
-    fill: black;
+    fill: green;
 }
 
 field-name {
-    font-style: normal;
-    font-weight: normal;
-    font-size: 12pt;
     fill: black;
 }
 
 field-index {
-    font-size: 9pt;
-    font-family: Times;
-    font-style: italic;
-    font-weight: normal;
-    fill: blue;
+    fill: darkslategrey;
 }
diff --git a/tests/data/register-stair-left.yaml b/tests/data/register-stair-left.yaml
new file mode 100644
index 0000000..e5daf4a
--- /dev/null
+++ b/tests/data/register-stair-left.yaml
@@ -0,0 +1,23 @@
+##
+# Example YAML file for regfmt
+
+width: 8
+endian: bigBit
+
+registers:
+  - name: R1
+    fields:
+      - width: 1
+        name: Coprocessor Usable
+
+      - width: 1
+        name: Bootstrap Exception Vectors
+
+      - width: 2
+        name: Parity Errors
+
+      - width: 4
+        name: Interrupt Waiting
+
+layout:
+  field-name-align: stair-left
diff --git a/tests/data/register.yaml b/tests/data/register.yaml
new file mode 100644
index 0000000..43a69df
--- /dev/null
+++ b/tests/data/register.yaml
@@ -0,0 +1,22 @@
+##
+# Example YAML file for regfmt
+
+width: 32
+endian: bigBit
+
+registers:
+  - name: R1
+    fields:
+      - width: 8
+        name: A3
+
+      - width: 8
+        name: A2
+
+      - width: 8
+        name: A1
+
+      - width: 8
+        name: A0
+layout:
+  field-name-align: center
diff --git a/tests/test_CommandLineParser.py b/tests/test_CommandLineParser.py
index e1ebe68..138d7b3 100644
--- a/tests/test_CommandLineParser.py
+++ b/tests/test_CommandLineParser.py
@@ -14,10 +14,10 @@
 # limitations under the License.
 
 import unittest
-
-from regfmtlib import CommandLineParser
+from regfmt import CommandLineParser
 from regfmt import RegisterFormat
 
+
 class TestCommandLineParser(unittest.TestCase):
     def test_version(self):
         clp = CommandLineParser()
@@ -41,5 +41,6 @@ def test_run(self):
         parsedArgs = clp.parser.parse_args(['-o', 'tests/output/example_0001.svg', 'tests/data/example_0001.yaml'])
         RegisterFormat(parsedArgs).run()
 
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_RegisterFormat.py b/tests/test_RegisterFormat.py
new file mode 100644
index 0000000..8182539
--- /dev/null
+++ b/tests/test_RegisterFormat.py
@@ -0,0 +1,107 @@
+##
+# Copyright 2022 Charles Y. Choi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+from regfmt import CommandLineParser
+from regfmt import RegisterFormat
+from io import StringIO
+from regfmt import VERSION
+import difflib
+import errno
+
+
+class TestRegisterFormat(unittest.TestCase):
+    def test_example_files(self):
+        examples = ['example_0001', 'example_0002', 'example_0003']
+
+        for example in examples:
+            clp = CommandLineParser()
+            outfileName = 'tests/output/{}.svg'.format(example)
+            infileName = 'tests/data/{}.yaml'.format(example)
+            controlName = 'tests/control/{}.svg'.format(example)
+            parsedArgs = clp.parser.parse_args(['-o', outfileName, infileName])
+            result = RegisterFormat(parsedArgs).run()
+            self.assertEqual(0, result)
+
+            with open(outfileName) as infile:
+                testLines = infile.readlines()
+
+            with open(controlName) as infile:
+                controlLines = infile.readlines()
+
+            diff = list(difflib.unified_diff(testLines, controlLines, fromfile=outfileName, tofile=controlName))
+            self.assertEqual(0, len(diff))
+
+    def test_version(self):
+        clp = CommandLineParser()
+        parsedArgs = clp.parser.parse_args(['--version'])
+        # !!!: for some reason version isn't set by argparse
+        parsedArgs.version = True
+
+        registerFormat = RegisterFormat(parsedArgs)
+        registerFormat.stdout = StringIO()
+        result = registerFormat.run()
+        testValue = registerFormat.stdout.getvalue()
+
+        self.assertEqual(0, result)
+        self.assertEqual(testValue.strip(), VERSION)
+
+    def test_missingFile(self):
+        controlFileName = 'dummyfile.yaml'
+        clp = CommandLineParser()
+        parsedArgs = clp.parser.parse_args([controlFileName])
+
+        registerFormat = RegisterFormat(parsedArgs)
+        registerFormat.stderr = StringIO()
+        result = registerFormat.run()
+        testValue = registerFormat.stderr.getvalue()
+        self.assertEqual(errno.ENOENT, result)
+        message = 'ERROR: file "{0}" not found.\n'
+        self.assertEqual(testValue, message.format(controlFileName))
+
+    def test_missing_css(self):
+        clp = CommandLineParser()
+        controlFileName = "foo.css"
+        message = 'ERROR: No such file or directory: "{}"  Exiting…\n'
+
+        parsedArgs = clp.parser.parse_args(['-s', controlFileName, 'tests/data/example_0001.yaml'])
+        registerFormat = RegisterFormat(parsedArgs)
+        registerFormat.stderr = StringIO()
+        result = registerFormat.run()
+        testValue = registerFormat.stderr.getvalue()
+        self.assertEqual(result, errno.ENOENT)
+        self.assertEqual(testValue, message.format(controlFileName))
+
+    def test_css_main(self):
+        clp = CommandLineParser()
+        controlFileName = "clean.css"
+        message = 'ERROR: No such file or directory: "{}"  Exiting…\n'
+
+        parsedArgs = clp.parser.parse_args(['-s',
+                                            'tests/data/{}'.format(controlFileName),
+                                            '-o',
+                                            'tests/output/example_0001-clean.svg',
+                                            'tests/data/example_0001.yaml'])
+        registerFormat = RegisterFormat(parsedArgs)
+        # registerFormat.stderr = StringIO()
+        result = registerFormat.run()
+        self.assertEqual(result, 0)
+        # testValue = registerFormat.stderr.getvalue()
+        # self.assertEqual(result, errno.ENOENT)
+        # self.assertEqual(testValue, message.format(controlFileName))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/regfmtlib/tests/__init__.py b/tests/test_constants.py
similarity index 71%
rename from regfmtlib/tests/__init__.py
rename to tests/test_constants.py
index 707304a..b621b13 100644
--- a/regfmtlib/tests/__init__.py
+++ b/tests/test_constants.py
@@ -13,3 +13,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import unittest
+from regfmt import Constants
+
+
+class TestConstants(unittest.TestCase):
+    def test_default_width(self):
+        self.assertEqual(Constants.DEFAULT_WIDTH, 32)
+
+
+if __name__ == '__main__':
+    unittest.main()
+    
diff --git a/regfmtlib/tests/test_endian.py b/tests/test_endian.py
similarity index 97%
rename from regfmtlib/tests/test_endian.py
rename to tests/test_endian.py
index fcda1dc..f31b72b 100644
--- a/regfmtlib/tests/test_endian.py
+++ b/tests/test_endian.py
@@ -14,7 +14,8 @@
 # limitations under the License.
 
 import unittest
-from regfmtlib import Endian
+from regfmt import Endian
+
 
 class TestEndian(unittest.TestCase):
     def test_littleByte(self):
@@ -29,6 +30,7 @@ def test_littleBit(self):
     def test_bigBit(self):
         self.assertEqual(Endian.bigBit.value, "bigBit")
 
+
 if __name__ == '__main__':
     unittest.main()
     
diff --git a/tests/test_field.py b/tests/test_field.py
new file mode 100644
index 0000000..944c996
--- /dev/null
+++ b/tests/test_field.py
@@ -0,0 +1,47 @@
+##
+# Copyright 2022 Charles Y. Choi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import random
+import unittest
+from regfmt import Field
+from tests.testutils import auditAttributeExistence, randomAsciiString
+
+
+class TestField(unittest.TestCase):
+    def test_attributes(self):
+        field = Field()
+        attributes = ['name', 'width', 'leftIndex', 'rightIndex']
+        auditAttributeExistence(self, field, attributes)
+
+    def test_init_config(self):
+        config = {'name': randomAsciiString(k=10),
+                  'width': random.randint(0, 512)}
+        field = Field(config=config)
+
+        self.assertEqual(field.name, config['name'])
+        self.assertEqual(field.width, config['width'])
+
+        controlLeftIndex = random.randint(0, 512)
+        controlRightIndex = random.randint(0, 512)
+
+        field.leftIndex = controlLeftIndex
+        field.rightIndex = controlRightIndex
+
+        self.assertEqual(field.leftIndex, controlLeftIndex)
+        self.assertEqual(field.rightIndex, controlRightIndex)
+
+
+if __name__ == '__main__':
+    unittest.main()
+    
diff --git a/tests/test_register.py b/tests/test_register.py
new file mode 100644
index 0000000..1d0d18e
--- /dev/null
+++ b/tests/test_register.py
@@ -0,0 +1,54 @@
+##
+# Copyright 2022 Charles Y. Choi
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import random
+import unittest
+from regfmt import Register, Field, TopLevel, Constants, Endian
+from tests.testutils import auditAttributeExistence, randomAsciiString
+
+
+class TestRegister(unittest.TestCase):
+    def test_attributes(self):
+        topLevel = TopLevel()
+        register = Register(parent=topLevel)
+
+        attributes = ['name', 'width', 'endian', 'fields', 'parent']
+        auditAttributeExistence(self, register, attributes)
+
+        self.assertEqual(register.width, Constants.DEFAULT_WIDTH)
+        self.assertEqual(register.endian, Endian.bigBit)
+        self.assertEqual(register.parent, topLevel)
+
+    def test_init_config(self):
+        config = {'name': randomAsciiString(k=10),
+                  'width': random.randint(0, 512),
+                  'endian': Endian.littleBit}
+
+        fields = [{'name': randomAsciiString(k=random.randint(1, 8)),
+                   'width': random.randint(1, 512)}]
+        config['fields'] = fields
+
+        register = Register(parent=TopLevel(), config=config)
+
+        self.assertEqual(register.name, config['name'])
+        self.assertEqual(register.width, config['width'])
+        self.assertEqual(register.endian, config['endian'])
+
+        for field in register.fields:
+            self.assertIsInstance(field, Field)
+
+
+if __name__ == '__main__':
+    unittest.main()
+    
diff --git a/regfmtlib/TopLevel.py b/tests/testutils.py
similarity index 50%
rename from regfmtlib/TopLevel.py
rename to tests/testutils.py
index b5d9ceb..29fe9d5 100644
--- a/regfmtlib/TopLevel.py
+++ b/tests/testutils.py
@@ -13,22 +13,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from regfmtlib import Constants
-from regfmtlib import Register
-from regfmtlib import Endian
+import unittest
+import random
+import string
 
-DEFAULT_WIDTH = Constants.DEFAULT_WIDTH
 
-class TopLevel:
-    def __init__(self, config=None):
-        self.width: int = DEFAULT_WIDTH
-        self.endian: Endian = Endian.bigByte
-        self.registers: [Register] = []
+def auditAttributeExistence(testbench: unittest.TestCase, obj, attributes):
+    for attribute in attributes:
+        testbench.assertTrue(hasattr(obj, attribute), '{} missing attribute: {}'.format(obj, attribute))
 
-        if config:
-            self.width = config['width']
-            self.endian = config['endian']
 
-            if 'registers' in config:
-                for registerConfig in config['registers']:
-                    self.registers.append(Register(self, registerConfig))
+def randomAsciiString(k: int = 5):
+    data = string.ascii_letters + string.digits
+    result = ''.join(random.choices(data, k=k))
+    return result