3@brief Draws a bit field diagram from a JSON schema and outputs it in various formats.
4This script reads a JSON schema file and generates a visual representation of the bit fields defined in the schema.
5It supports resolving internal and external JSON references and can handle different schema versions.
6The output includes a bit field diagram in PDF, PNG, and SVG formats, as well as a Markdown file with property details.
9python draw_bit_field.py <input_file> [--output_dir <output_dir>] [--version <version>]
11@param input_file Path to the input JSON schema file.
12@param output_dir Path to the output directory (default: 'readme-header').
13@param version Schema version to use (e.g., v0, v1) (optional).
14@section dependencies Dependencies
20Shinsuke OTA <ota@rcnp.osaka-u.ac.jp>
26import matplotlib.pyplot
as plt
27import matplotlib.patches
as patches
32parser = argparse.ArgumentParser(description=
'Draw bit field diagram from JSON schema.')
33parser.add_argument(
'input_file', type=str, help=
'Path to the input JSON schema file')
34parser.add_argument(
'--output_dir', type=str, help=
'Path to the output directory', default=
'readme-header')
37parser.add_argument(
'--version', type=str, help=
'Schema version to use (e.g., v0, v1)', default=
None)
38args = parser.parse_args()
41with open(args.input_file,
'r')
as f:
46 properties = schema[
'definitions'][args.version][
'properties']
48 properties = schema[
'properties']
55 Resolves a JSON reference to its corresponding property.
57 This function handles both internal references (starting with '#') and external references
58 (containing a file path and a reference path separated by '#').
61 ref (str): The JSON reference to resolve. It can be an internal reference (e.g., '#/definitions/Property')
62 or an external reference (e.g., 'file.json#/definitions/Property').
65 dict: The resolved property from the JSON schema.
68 KeyError: If the reference path does not exist in the properties.
69 FileNotFoundError: If the external reference file does not exist.
70 json.JSONDecodeError: If the external reference file is not a valid JSON.
72 if ref.startswith(
'#'):
73 ref_path = ref.split(
'/')[-1]
74 return properties[ref_path]
76 ref_file, ref_path = ref.split(
'#')
77 ref_file = os.path.join(os.path.dirname(args.input_file), ref_file)
78 with open(ref_file,
'r')
as f:
79 ref_schema = json.load(f)
80 ref_properties = ref_schema[
'properties']
81 ref_path = ref_path.split(
'/')[-1]
82 return ref_properties[ref_path]
85def calculate_bit_length(details):
89 return ref_details[
'bitLength']
90 if details[
'type'] ==
'object':
92 if details[
'type'] ==
'array':
93 items_ref = details[
'items'].get(
'$ref')
96 return ref_details[
'bitLength']
98 return details[
'bitLength']
109for prop, details
in properties.items():
110 total_bits += calculate_bit_length(details)
112rows = (total_bits + 63) // 64
115fig_width = bit_width * 64 + margin * 2
116fig_height = (rows + 1) * bit_height + margin * 2
119fig, ax = plt.subplots(figsize=(fig_width / 10, fig_height / 10))
120ax.set_xlim(0, fig_width)
121ax.set_ylim(0, fig_height)
126y = fig_height - margin - bit_height
131 rect = patches.Rectangle((x, y), bit_width, bit_height, linewidth=1, edgecolor=
'black', facecolor=
'none')
134 fontsize = min(bit_width, bit_height) * 3
135 ax.text(x + bit_width / 2, y + bit_height / 2, text, ha=
'center', va=
'center', fontsize=fontsize)
142def draw_bit_field(x, y, bit_length, prop_name, bit_position, is_array=False):
143 remaining_bits = 64 - (bit_position % 64)
144 if bit_length > remaining_bits:
146 rect = patches.Rectangle((x, y), remaining_bits * bit_width, bit_height, linewidth=1, edgecolor=
'black', facecolor=
'none')
148 label = f
"{prop_name}[]" if is_array
else prop_name
149 fontsize = min(remaining_bits * bit_width, bit_height) * 3
150 ax.text(x + (remaining_bits * bit_width) / 2, y + bit_height / 2, label, ha=
'center', va=
'center', fontsize=fontsize)
155 bit_length -= remaining_bits
156 bit_position += remaining_bits
158 remaining_bits = bit_length
161 rect = patches.Rectangle((x, y), remaining_bits * bit_width, bit_height, linewidth=1, edgecolor=
'black', facecolor=
'none')
163 label = f
"{prop_name}[]" if is_array
else prop_name
164 fontsize = min(remaining_bits * bit_width, bit_height) * 3
165 ax.text(x + (remaining_bits * bit_width) / 2, y + bit_height / 2, label, ha=
'center', va=
'center', fontsize=fontsize)
167 return x + remaining_bits * bit_width, y, bit_position + remaining_bits
169for prop, details
in properties.items():
170 if '$ref' in details:
172 if details[
'type'] ==
'object':
174 if details[
'type'] ==
'array':
175 items_ref = details[
'items'].get(
'$ref')
178 bit_length = ref_details[
'bitLength']
179 x, y, bit_position =
draw_bit_field(x, y, bit_length, prop, bit_position, is_array=
True)
180 if bit_position % 64 == 0:
184 bit_length = details[
'bitLength']
185 x, y, bit_position =
draw_bit_field(x, y, bit_length, prop, bit_position)
186 if bit_position % 64 == 0:
191output_md_dir = args.output_dir
192output_pdf_dir = output_md_dir +
'/pdf'
193output_png_dir = output_md_dir +
'/png'
194output_svg_dir = output_md_dir +
'/svg'
195os.makedirs(output_pdf_dir, exist_ok=
True)
196os.makedirs(output_png_dir, exist_ok=
True)
197os.makedirs(output_svg_dir, exist_ok=
True)
198os.makedirs(output_md_dir, exist_ok=
True)
201 base_filename = os.path.splitext(os.path.basename(args.input_file))[0] + f
'-{args.version}'
203 base_filename = os.path.splitext(os.path.basename(args.input_file))[0]
205output_pdf_file = os.path.join(output_pdf_dir, base_filename +
'.pdf')
206output_svg_file = os.path.join(output_svg_dir, base_filename +
'.svg')
207output_png_file = os.path.join(output_png_dir, base_filename +
'.png')
208output_small_png_file = os.path.join(output_png_dir, base_filename +
'-s.png')
209output_md_file = os.path.join(output_md_dir, base_filename +
'.md')
212with open(output_md_file,
'w')
as md_file:
213 title = schema.get(
'title',
'No Title')
215 title = f
"{title} ({args.version})"
216 description = schema.get(
'description',
'No Description')
217 md_file.write(f
"# {title}\n\n")
218 md_file.write(f
"{description}\n\n")
219 md_file.write(
"| Property Name | Bit Length | Description |\n")
220 md_file.write(
"|---------------|------------|-------------|\n")
221 for prop, details
in properties.items():
222 description = details.get(
'description',
'')
223 bit_length = details.get(
'bitLength',
'')
224 if '$ref' in details:
226 bit_length = details.get(
'bitLength', bit_length)
227 if description ==
'':
228 description = details.get(
'description',
'')
229 md_file.write(f
"| {prop} | {bit_length} | {description} |\n")
231 relative_png_path = os.path.relpath(output_png_file, start=os.path.dirname(output_md_file))
232 relative_pdf_path = os.path.relpath(output_pdf_file, start=os.path.dirname(output_md_file))
233 relative_svg_path = os.path.relpath(output_svg_file, start=os.path.dirname(output_md_file))
234 md_file.write(f
"\n\n")
239plt.savefig(output_pdf_file, format=
'pdf')
240plt.savefig(output_png_file, format=
'png')
241plt.savefig(output_svg_file, format=
'svg')