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.
@@ -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
+ ]
@@ -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