engrapha-notes 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- engrapha_notes/__init__.py +236 -0
- engrapha_notes/cli.py +883 -0
- engrapha_notes/document.py +2691 -0
- engrapha_notes/helpers.py +4386 -0
- engrapha_notes/packet.py +282 -0
- engrapha_notes/palette.py +61 -0
- engrapha_notes/styles.py +28 -0
- engrapha_notes/theme.py +348 -0
- engrapha_notes-0.1.0.dist-info/METADATA +530 -0
- engrapha_notes-0.1.0.dist-info/RECORD +13 -0
- engrapha_notes-0.1.0.dist-info/WHEEL +5 -0
- engrapha_notes-0.1.0.dist-info/entry_points.txt +3 -0
- engrapha_notes-0.1.0.dist-info/top_level.txt +1 -0
engrapha_notes/packet.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
engrapha_notes.packet -- Frame/packet format diagram helpers.
|
|
3
|
+
|
|
4
|
+
Provides two complementary frame display functions:
|
|
5
|
+
|
|
6
|
+
frame_format(caption, fields) -- Horizontal field-name / field-size row table.
|
|
7
|
+
packet_format(caption, fields, bit_ruler) -- 32-bit RFC-style bit-grid.
|
|
8
|
+
|
|
9
|
+
Both write to the engrapha_notes.helpers.story list by default.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from reportlab.lib.enums import TA_CENTER
|
|
17
|
+
from reportlab.platypus import Paragraph, Spacer, Table, TableStyle
|
|
18
|
+
|
|
19
|
+
from .theme import get_theme
|
|
20
|
+
from .styles import S
|
|
21
|
+
|
|
22
|
+
# Counters ensure unique ReportLab style names within a document session.
|
|
23
|
+
_ff_counter = 0
|
|
24
|
+
_pf_counter = 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def frame_format(caption: str, fields: list[tuple[str, str]]) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Render a horizontal frame diagram.
|
|
30
|
+
|
|
31
|
+
Row 1: field names (themed accent, bold).
|
|
32
|
+
Row 2: field sizes (muted text_dim italic).
|
|
33
|
+
Column widths are proportional to the longest text in each field.
|
|
34
|
+
"""
|
|
35
|
+
from .helpers import add, sp
|
|
36
|
+
|
|
37
|
+
global _ff_counter
|
|
38
|
+
_ff_counter += 1
|
|
39
|
+
uid = f"FF{_ff_counter}"
|
|
40
|
+
|
|
41
|
+
t_theme = get_theme()
|
|
42
|
+
|
|
43
|
+
th = S(
|
|
44
|
+
f"PN_FF_H_{uid}",
|
|
45
|
+
fontSize=8.5,
|
|
46
|
+
textColor=t_theme.rl(t_theme.accent),
|
|
47
|
+
fontName="Helvetica-Bold",
|
|
48
|
+
alignment=TA_CENTER,
|
|
49
|
+
leading=11,
|
|
50
|
+
)
|
|
51
|
+
td = S(
|
|
52
|
+
f"PN_FF_D_{uid}",
|
|
53
|
+
fontSize=7.5,
|
|
54
|
+
textColor=t_theme.rl(t_theme.text_dim),
|
|
55
|
+
fontName="Helvetica-Oblique",
|
|
56
|
+
alignment=TA_CENTER,
|
|
57
|
+
leading=10,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
headers = [Paragraph(f"<b>{name}</b>", th) for name, _ in fields]
|
|
61
|
+
sizes = [Paragraph(size, td) for _, size in fields]
|
|
62
|
+
data = [headers, sizes]
|
|
63
|
+
|
|
64
|
+
total_chars = sum(max(len(name), len(size)) for name, size in fields)
|
|
65
|
+
col_widths = [
|
|
66
|
+
max(28.0, 493.0 * max(len(name), len(size)) / total_chars)
|
|
67
|
+
for name, size in fields
|
|
68
|
+
]
|
|
69
|
+
scale = 493.0 / sum(col_widths)
|
|
70
|
+
col_widths = [w * scale for w in col_widths]
|
|
71
|
+
|
|
72
|
+
t = Table(data, colWidths=col_widths)
|
|
73
|
+
t.setStyle(
|
|
74
|
+
TableStyle(
|
|
75
|
+
[
|
|
76
|
+
("BACKGROUND", (0, 0), (-1, -1), t_theme.rl(t_theme.surface)),
|
|
77
|
+
("ALIGN", (0, 0), (-1, -1), "CENTER"),
|
|
78
|
+
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
|
79
|
+
("GRID", (0, 0), (-1, -1), 0.75, t_theme.rl(t_theme.table_bdr)),
|
|
80
|
+
("TOPPADDING", (0, 0), (-1, -1), 5),
|
|
81
|
+
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
|
|
82
|
+
("LEFTPADDING", (0, 0), (-1, -1), 2),
|
|
83
|
+
("RIGHTPADDING", (0, 0), (-1, -1), 2),
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
setattr(t, "_is_frame_format", True)
|
|
88
|
+
add(t)
|
|
89
|
+
if caption:
|
|
90
|
+
add(Spacer(1, 4))
|
|
91
|
+
add(
|
|
92
|
+
Paragraph(
|
|
93
|
+
f"<i>{caption}</i>",
|
|
94
|
+
S(
|
|
95
|
+
f"PN_FF_CAP_{uid}",
|
|
96
|
+
fontSize=8.5,
|
|
97
|
+
textColor=t_theme.rl(t_theme.text_dim),
|
|
98
|
+
fontName="Helvetica-Oblique",
|
|
99
|
+
alignment=TA_CENTER,
|
|
100
|
+
leading=12,
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
sp(6)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def packet_format(
|
|
108
|
+
caption: str,
|
|
109
|
+
fields: list[tuple[str, int]],
|
|
110
|
+
bit_ruler: bool = True,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Render an RFC/Tanenbaum-style 32-bit-aligned packet header grid.
|
|
114
|
+
|
|
115
|
+
fields: list of (name, bit_width) tuples. Fields wider than 32 bits
|
|
116
|
+
span multiple rows (e.g. IP source address = 32 bits fills one row;
|
|
117
|
+
IPv6 source address = 128 bits fills four rows).
|
|
118
|
+
"""
|
|
119
|
+
from .helpers import add, sp
|
|
120
|
+
|
|
121
|
+
global _pf_counter
|
|
122
|
+
_pf_counter += 1
|
|
123
|
+
uid = f"PF{_pf_counter}"
|
|
124
|
+
|
|
125
|
+
t_theme = get_theme()
|
|
126
|
+
|
|
127
|
+
ruler_style = S(
|
|
128
|
+
f"PN_PF_RULER_{uid}",
|
|
129
|
+
fontSize=6.5,
|
|
130
|
+
textColor=t_theme.rl(t_theme.text_dim),
|
|
131
|
+
fontName="Helvetica-Bold",
|
|
132
|
+
alignment=TA_CENTER,
|
|
133
|
+
leading=8,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# -- Split fields into 32-bit grid rows -----------------------------------
|
|
137
|
+
grid_rows: list[list[tuple[str, int]]] = []
|
|
138
|
+
current_row: list[tuple[str, int]] = []
|
|
139
|
+
current_width = 0
|
|
140
|
+
|
|
141
|
+
for name, width in fields:
|
|
142
|
+
if width >= 32 and current_width > 0:
|
|
143
|
+
current_row.append(("", 32 - current_width))
|
|
144
|
+
grid_rows.append(current_row)
|
|
145
|
+
current_row = []
|
|
146
|
+
current_width = 0
|
|
147
|
+
if width >= 32:
|
|
148
|
+
grid_rows.append([(name, width)])
|
|
149
|
+
else:
|
|
150
|
+
if current_width + width > 32:
|
|
151
|
+
current_row.append(("", 32 - current_width))
|
|
152
|
+
grid_rows.append(current_row)
|
|
153
|
+
current_row = []
|
|
154
|
+
current_width = 0
|
|
155
|
+
current_row.append((name, width))
|
|
156
|
+
current_width += width
|
|
157
|
+
if current_width == 32:
|
|
158
|
+
grid_rows.append(current_row)
|
|
159
|
+
current_row = []
|
|
160
|
+
current_width = 0
|
|
161
|
+
|
|
162
|
+
if current_row:
|
|
163
|
+
current_row.append(("", 32 - current_width))
|
|
164
|
+
grid_rows.append(current_row)
|
|
165
|
+
|
|
166
|
+
# -- Build cell matrix -----------------------------------------------------
|
|
167
|
+
matrix: list[list[Any]] = []
|
|
168
|
+
spans: list[tuple[Any, ...]] = []
|
|
169
|
+
|
|
170
|
+
if bit_ruler:
|
|
171
|
+
ruler_cells: list[Any] = [""] * 32
|
|
172
|
+
for col_idx in [0, 4, 8, 16, 24, 31]:
|
|
173
|
+
ruler_cells[col_idx] = Paragraph(str(col_idx), ruler_style)
|
|
174
|
+
matrix.append(ruler_cells)
|
|
175
|
+
ruler_offset = 1
|
|
176
|
+
else:
|
|
177
|
+
ruler_offset = 0
|
|
178
|
+
|
|
179
|
+
current_rl_row = ruler_offset
|
|
180
|
+
for r_idx, r_fields in enumerate(grid_rows):
|
|
181
|
+
max_height = 1
|
|
182
|
+
for name, width in r_fields:
|
|
183
|
+
if width > 32:
|
|
184
|
+
max_height = max(max_height, width // 32)
|
|
185
|
+
for _ in range(max_height):
|
|
186
|
+
matrix.append([""] * 32)
|
|
187
|
+
|
|
188
|
+
col_cursor = 0
|
|
189
|
+
for name, width in r_fields:
|
|
190
|
+
if not name and width == 0:
|
|
191
|
+
continue
|
|
192
|
+
if width == 0:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
if width > 32:
|
|
196
|
+
f_sz = 7.5
|
|
197
|
+
l_ing = 11.0
|
|
198
|
+
elif width >= 16:
|
|
199
|
+
f_sz = 8.0
|
|
200
|
+
l_ing = 12.0
|
|
201
|
+
elif width >= 8:
|
|
202
|
+
f_sz = 7.5
|
|
203
|
+
l_ing = 11.0
|
|
204
|
+
else:
|
|
205
|
+
f_sz = 6.0
|
|
206
|
+
l_ing = 9.0
|
|
207
|
+
|
|
208
|
+
if name:
|
|
209
|
+
cell_th = S(
|
|
210
|
+
f"PN_PF_H_{uid}_{r_idx}_{col_cursor}",
|
|
211
|
+
fontSize=f_sz,
|
|
212
|
+
textColor=t_theme.rl(t_theme.accent),
|
|
213
|
+
fontName="Helvetica-Bold",
|
|
214
|
+
alignment=TA_CENTER,
|
|
215
|
+
leading=l_ing,
|
|
216
|
+
)
|
|
217
|
+
dim_color = t_theme.text_dim
|
|
218
|
+
if width >= 8:
|
|
219
|
+
text = f"<b>{name}</b><br/><font size=5.5 color='{dim_color}'>{width} bits</font>"
|
|
220
|
+
else:
|
|
221
|
+
text = f"<b>{name}</b><br/><font size=5.0 color='{dim_color}'>{width}</font>"
|
|
222
|
+
text_p = Paragraph(text, cell_th)
|
|
223
|
+
else:
|
|
224
|
+
text_p = ""
|
|
225
|
+
|
|
226
|
+
matrix[current_rl_row][col_cursor] = text_p
|
|
227
|
+
start_col = col_cursor
|
|
228
|
+
end_col = col_cursor + min(width, 32) - 1
|
|
229
|
+
start_row = current_rl_row
|
|
230
|
+
end_row = current_rl_row + max_height - 1
|
|
231
|
+
if start_col != end_col or start_row != end_row:
|
|
232
|
+
spans.append(("SPAN", (start_col, start_row), (end_col, end_row)))
|
|
233
|
+
col_cursor += min(width, 32)
|
|
234
|
+
|
|
235
|
+
current_rl_row += max_height
|
|
236
|
+
|
|
237
|
+
# -- Render ---------------------------------------------------------------
|
|
238
|
+
CW_PTS = 493.0 # standard content width in points
|
|
239
|
+
col_widths_cells = [CW_PTS / 32.0] * 32
|
|
240
|
+
row_heights: list[float] = []
|
|
241
|
+
if bit_ruler:
|
|
242
|
+
row_heights.append(10)
|
|
243
|
+
for _ in range(len(matrix) - (1 if bit_ruler else 0)):
|
|
244
|
+
row_heights.append(20)
|
|
245
|
+
|
|
246
|
+
t = Table(matrix, colWidths=col_widths_cells, rowHeights=row_heights)
|
|
247
|
+
style_cmds: list[Any] = [
|
|
248
|
+
("ALIGN", (0, 0), (-1, -1), "CENTER"),
|
|
249
|
+
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
|
250
|
+
("TOPPADDING", (0, 0), (-1, -1), 2),
|
|
251
|
+
("BOTTOMPADDING", (0, 0), (-1, -1), 2),
|
|
252
|
+
("LEFTPADDING", (0, 0), (-1, -1), 1),
|
|
253
|
+
("RIGHTPADDING", (0, 0), (-1, -1), 1),
|
|
254
|
+
]
|
|
255
|
+
start_grid_row = 1 if bit_ruler else 0
|
|
256
|
+
style_cmds += [
|
|
257
|
+
("GRID", (0, start_grid_row), (-1, -1), 0.75, t_theme.rl(t_theme.table_bdr)),
|
|
258
|
+
("BACKGROUND", (0, start_grid_row), (-1, -1), t_theme.rl(t_theme.surface)),
|
|
259
|
+
]
|
|
260
|
+
style_cmds.extend(spans)
|
|
261
|
+
t.setStyle(TableStyle(style_cmds))
|
|
262
|
+
setattr(t, "_is_packet_format", True)
|
|
263
|
+
add(t)
|
|
264
|
+
if caption:
|
|
265
|
+
add(Spacer(1, 4))
|
|
266
|
+
add(
|
|
267
|
+
Paragraph(
|
|
268
|
+
f"<i>{caption}</i>",
|
|
269
|
+
S(
|
|
270
|
+
f"PN_PF_CAP_{uid}",
|
|
271
|
+
fontSize=8.5,
|
|
272
|
+
textColor=t_theme.rl(t_theme.text_dim),
|
|
273
|
+
fontName="Helvetica-Oblique",
|
|
274
|
+
alignment=TA_CENTER,
|
|
275
|
+
leading=12,
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
sp(6)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
__all__ = ["frame_format", "packet_format"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
engrapha_notes.palette -- Shared dark-theme color palette for ReportLab notes PDFs.
|
|
3
|
+
|
|
4
|
+
Import everything you need:
|
|
5
|
+
from engrapha_notes.palette import BG, CYAN, GREEN, YELLOW, WHITE, TABLE_HDR, ...
|
|
6
|
+
Or simply use:
|
|
7
|
+
import engrapha_notes as en # en.BG, en.CYAN, etc.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from reportlab.lib import colors
|
|
11
|
+
|
|
12
|
+
# -- Page background & cards --------------------------------------------------
|
|
13
|
+
BG = colors.HexColor("#0d1117")
|
|
14
|
+
CARD_DARK = colors.HexColor("#161b22")
|
|
15
|
+
CARD_MID = colors.HexColor("#1c2333")
|
|
16
|
+
|
|
17
|
+
# -- Accent colors ------------------------------------------------------------
|
|
18
|
+
CYAN = colors.HexColor("#79c0ff")
|
|
19
|
+
GREEN = colors.HexColor("#3fb950")
|
|
20
|
+
GREEN_CARD = colors.HexColor("#0d2119")
|
|
21
|
+
YELLOW = colors.HexColor("#d29922")
|
|
22
|
+
YELLOW_CARD = colors.HexColor("#1f1a0a")
|
|
23
|
+
RED = colors.HexColor("#f85149")
|
|
24
|
+
RED_CARD = colors.HexColor("#1e0d0d")
|
|
25
|
+
PURPLE = colors.HexColor("#bc8cff")
|
|
26
|
+
PURPLE_CARD = colors.HexColor("#180d2b")
|
|
27
|
+
|
|
28
|
+
# -- Text ---------------------------------------------------------------------
|
|
29
|
+
WHITE = colors.HexColor("#f0f6fc")
|
|
30
|
+
WHITE_DIM = colors.HexColor("#9da7b3")
|
|
31
|
+
CODE_GREEN = colors.HexColor("#7ee787")
|
|
32
|
+
|
|
33
|
+
# -- Table --------------------------------------------------------------------
|
|
34
|
+
TABLE_HDR = colors.HexColor("#1f6feb")
|
|
35
|
+
TABLE_R1 = colors.HexColor("#161b22")
|
|
36
|
+
TABLE_R2 = colors.HexColor("#1b2230")
|
|
37
|
+
TABLE_BDR = colors.HexColor("#30363d")
|
|
38
|
+
CODE_BG = colors.HexColor("#161b22")
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"BG",
|
|
42
|
+
"CARD_DARK",
|
|
43
|
+
"CARD_MID",
|
|
44
|
+
"CYAN",
|
|
45
|
+
"GREEN",
|
|
46
|
+
"GREEN_CARD",
|
|
47
|
+
"YELLOW",
|
|
48
|
+
"YELLOW_CARD",
|
|
49
|
+
"RED",
|
|
50
|
+
"RED_CARD",
|
|
51
|
+
"PURPLE",
|
|
52
|
+
"PURPLE_CARD",
|
|
53
|
+
"WHITE",
|
|
54
|
+
"WHITE_DIM",
|
|
55
|
+
"CODE_GREEN",
|
|
56
|
+
"TABLE_HDR",
|
|
57
|
+
"TABLE_R1",
|
|
58
|
+
"TABLE_R2",
|
|
59
|
+
"TABLE_BDR",
|
|
60
|
+
"CODE_BG",
|
|
61
|
+
]
|
engrapha_notes/styles.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from reportlab.lib.styles import ParagraphStyle as S
|
|
2
|
+
|
|
3
|
+
BODY_ST = S("Body")
|
|
4
|
+
SECT_ST = S("Section")
|
|
5
|
+
CODE_ST = S("Code")
|
|
6
|
+
COVER_H1 = S("Cover_H1", fontSize=24, alignment=1, spaceAfter=20)
|
|
7
|
+
COVER_H2 = S("Cover_H2", fontSize=18, alignment=1, spaceAfter=20)
|
|
8
|
+
COVER_SUB = S("Cover_Sub", fontSize=14, alignment=1, spaceAfter=10)
|
|
9
|
+
CHAP_H1 = S("Chap_H1", fontSize=20, alignment=0, spaceAfter=15)
|
|
10
|
+
H1 = S("Heading1", fontSize=16, spaceBefore=12, spaceAfter=6)
|
|
11
|
+
H2 = S("Heading2", fontSize=14, spaceBefore=10, spaceAfter=6)
|
|
12
|
+
H3 = S("Heading3", fontSize=12, spaceBefore=8, spaceAfter=4)
|
|
13
|
+
H4 = S("Heading4", fontSize=11, spaceBefore=6, spaceAfter=4)
|
|
14
|
+
H5 = S("Heading5", fontSize=10, spaceBefore=4, spaceAfter=2)
|
|
15
|
+
H6 = S("Heading6", fontSize=10, spaceBefore=4, spaceAfter=2)
|
|
16
|
+
TOC_H1 = S("TOC_Heading1", fontSize=14, spaceBefore=6)
|
|
17
|
+
TOC_H2 = S("TOC_Heading2", fontSize=12, leftIndent=20, spaceBefore=4)
|
|
18
|
+
TOC_H3 = S("TOC_Heading3", fontSize=11, leftIndent=40, spaceBefore=2)
|
|
19
|
+
BULLET_ST = S("Bullet", leftIndent=20, spaceBefore=2, spaceAfter=2)
|
|
20
|
+
|
|
21
|
+
# Backward-compatible style aliases used by the theme application layer.
|
|
22
|
+
PART_ST = H1
|
|
23
|
+
CHAP_ST = CHAP_H1
|
|
24
|
+
SUB_ST = H3
|
|
25
|
+
DEF_ST = BODY_ST
|
|
26
|
+
TIP_ST = BODY_ST
|
|
27
|
+
NOTE_ST = BODY_ST
|
|
28
|
+
CAP_ST = BODY_ST
|