mkdocs-dbml-plugin 1.0.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZhuchkaTriplesix
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ include setup.py
5
+ recursive-include mkdocs_dbml_plugin *.py *.pyx
6
+ global-exclude __pycache__ *.py[cod] *.so *.pyd
7
+ global-exclude *.c
8
+ prune example
9
+ prune .github
10
+ prune tests
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: mkdocs-dbml-plugin
3
+ Version: 1.0.0
4
+ Summary: MkDocs plugin to render DBML as interactive ERD diagrams
5
+ Author-email: ZhuchkaTriplesix <mrlololoshka94@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ZhuchkaTriplesix/mkdocs-dbml
8
+ Project-URL: Documentation, https://github.com/ZhuchkaTriplesix/mkdocs-dbml#readme
9
+ Project-URL: Repository, https://github.com/ZhuchkaTriplesix/mkdocs-dbml
10
+ Project-URL: Issues, https://github.com/ZhuchkaTriplesix/mkdocs-dbml/issues
11
+ Keywords: mkdocs,plugin,dbml,erd,diagram,documentation
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Documentation
15
+ Classifier: Topic :: Text Processing :: Markup
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: mkdocs>=1.0.0
26
+ Requires-Dist: numpy>=1.20.0
27
+ Requires-Dist: pydbml>=1.0.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: cython>=0.29; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # MkDocs DBML Plugin
33
+
34
+ MkDocs plugin that renders [DBML](https://dbml.dbdiagram.io/) code blocks as interactive, visual ERD (Entity-Relationship Diagram) directly in your documentation.
35
+
36
+ ## Features
37
+
38
+ - **Interactive SVG diagrams** — drag tables, zoom with mouse wheel, pan the canvas
39
+ - **Field-to-field connections** — relationship lines go from the exact FK field to the exact PK field
40
+ - **Orthogonal routing** — lines use right-angle paths and automatically avoid overlapping tables
41
+ - **Click-to-select** — click any relationship line to highlight it and its connected fields
42
+ - **Crow's foot notation** — classic ERD markers for one-to-one, one-to-many, many-to-many
43
+ - **Material Design 3 icons** — PK, FK, NOT NULL, UNIQUE badges on fields
44
+ - **7 color themes** — default, ocean, sunset, forest, dark, dark_gray, black
45
+ - **High performance** — optional Cython-compiled routing engine; Numba JIT fallback
46
+
47
+ ## Installation
48
+
49
+ ### From source (local project)
50
+
51
+ ```bash
52
+ git clone https://github.com/ZhuchkaTriplesix/mkdocs-dbml.git
53
+ cd mkdocs-dbml
54
+ pip install -e .
55
+ ```
56
+
57
+ ### From source with Cython (optional, for best performance)
58
+
59
+ If you have a C compiler available (MSVC on Windows, gcc/clang on Linux/macOS):
60
+
61
+ ```bash
62
+ pip install cython
63
+ python setup.py build_ext --inplace
64
+ pip install -e .
65
+ ```
66
+
67
+ Without Cython the plugin works fine — it falls back to a pure-Python router.
68
+
69
+ ### Dependencies
70
+
71
+ Installed automatically by `pip install`:
72
+
73
+ | Package | Purpose |
74
+ |---------|---------|
75
+ | `mkdocs >= 1.0` | Documentation framework |
76
+ | `pydbml >= 1.0` | DBML parser |
77
+
78
+ ## Quick start
79
+
80
+ ### 1. Enable the plugin in `mkdocs.yml`
81
+
82
+ ```yaml
83
+ plugins:
84
+ - search
85
+ - dbml
86
+ ```
87
+
88
+ ### 2. Write DBML in any Markdown file
89
+
90
+ ````markdown
91
+ ```dbml
92
+ Table users {
93
+ id integer [primary key]
94
+ username varchar(50) [not null, unique]
95
+ email varchar(100) [not null]
96
+ created_at timestamp
97
+ }
98
+
99
+ Table posts {
100
+ id integer [primary key]
101
+ user_id integer [ref: > users.id]
102
+ title varchar(200) [not null]
103
+ content text
104
+ published boolean [default: false]
105
+ created_at timestamp
106
+ }
107
+
108
+ Table comments {
109
+ id integer [primary key]
110
+ post_id integer [ref: > posts.id]
111
+ user_id integer [ref: > users.id]
112
+ content text [not null]
113
+ created_at timestamp
114
+ }
115
+ ```
116
+ ````
117
+
118
+ ### 3. Build or serve
119
+
120
+ ```bash
121
+ mkdocs serve # live preview at http://127.0.0.1:8000
122
+ mkdocs build # static output in site/
123
+ ```
124
+
125
+ The plugin converts every `` ```dbml `` code block into an interactive SVG diagram.
126
+
127
+ ### Including native `.dbml` files
128
+
129
+ You can embed the contents of a `.dbml` file instead of pasting DBML inline. Paths are relative to your `docs_dir`.
130
+
131
+ **Option 1 — single line with path (must end with `.dbml`, no spaces):**
132
+
133
+ ````markdown
134
+ ```dbml
135
+ schema.dbml
136
+ ```
137
+ ````
138
+
139
+ **Option 2 — explicit `file:` or `include:` prefix:**
140
+
141
+ ````markdown
142
+ ```dbml
143
+ file: schemas/public.dbml
144
+ ```
145
+ ````
146
+
147
+ The file is read from `docs_dir` (e.g. `docs/schema.dbml` or `docs/schemas/public.dbml`). Path traversal (`../`) outside `docs_dir` is not allowed.
148
+
149
+ ## Configuration
150
+
151
+ All options are set under the `dbml` plugin entry in `mkdocs.yml`:
152
+
153
+ ```yaml
154
+ plugins:
155
+ - dbml:
156
+ theme: default # color theme (see below)
157
+ show_indexes: true # display index information
158
+ show_notes: true # display table notes
159
+ ```
160
+
161
+ ### Themes
162
+
163
+ | Theme | Header gradient | Best for |
164
+ |-------|----------------|----------|
165
+ | `default` | Purple | Light backgrounds |
166
+ | `ocean` | Deep blue → cyan | Light backgrounds |
167
+ | `sunset` | Pink → blue | Light backgrounds |
168
+ | `forest` | Dark teal → green | Light backgrounds |
169
+ | `dark` | Dark violet | Dark backgrounds |
170
+ | `dark_gray` | Gray → slate | Dark backgrounds, neutral |
171
+ | `black` | Near-black → gray | Dark backgrounds, high contrast |
172
+
173
+ Example with the dark theme:
174
+
175
+ ```yaml
176
+ plugins:
177
+ - dbml:
178
+ theme: dark
179
+ ```
180
+
181
+ ## DBML syntax cheat-sheet
182
+
183
+ ```dbml
184
+ Table table_name {
185
+ column_name column_type [attributes]
186
+ }
187
+ ```
188
+
189
+ ### Column attributes
190
+
191
+ | Attribute | Syntax |
192
+ |-----------|--------|
193
+ | Primary key | `[primary key]` or `[pk]` |
194
+ | Not null | `[not null]` |
195
+ | Unique | `[unique]` |
196
+ | Default value | `[default: value]` |
197
+ | Foreign key | `[ref: > other_table.column]` |
198
+
199
+ ### Relationship types
200
+
201
+ | Syntax | Meaning | Markers |
202
+ |--------|---------|---------|
203
+ | `ref: > table.col` | Many-to-one | Circle → Crow's foot |
204
+ | `ref: < table.col` | One-to-many | Crow's foot → Bar |
205
+ | `ref: - table.col` | One-to-one | Bar → Bar |
206
+ | `ref: <> table.col` | Many-to-many | Crow's foot → Crow's foot |
207
+
208
+ ### Indexes and notes
209
+
210
+ ```dbml
211
+ Table example {
212
+ id integer [pk]
213
+ user_id integer
214
+ post_id integer
215
+
216
+ indexes {
217
+ user_id
218
+ (user_id, post_id) [unique]
219
+ }
220
+
221
+ Note: 'Description of this table'
222
+ }
223
+ ```
224
+
225
+ Full DBML specification: [dbml.dbdiagram.io/docs](https://dbml.dbdiagram.io/docs/)
226
+
227
+ ## Interaction guide
228
+
229
+ | Action | How |
230
+ |--------|-----|
231
+ | **Move a table** | Click and drag the table |
232
+ | **Pan the canvas** | Click and drag empty space |
233
+ | **Zoom** | Mouse wheel (10% – 300%) |
234
+ | **Fullscreen** | Click the expand button (top-right), Esc to exit |
235
+ | **Highlight connections** | Hover over a table |
236
+ | **Select a line** | Click on any relationship line |
237
+ | **Field tooltip** | Hover over a field row |
238
+
239
+ ## Project structure
240
+
241
+ ```
242
+ mkdocs-dbml/
243
+ ├── setup.py # package config
244
+ ├── requirements.txt
245
+ ├── mkdocs_dbml_plugin/
246
+ │ ├── __init__.py
247
+ │ ├── plugin.py # MkDocs hook + interactive JS
248
+ │ ├── renderer.py # DBML → SVG rendering + CSS
249
+ │ ├── layout.py # BFS graph layout engine
250
+ │ ├── config.py # theme definitions
251
+ │ ├── routing.py # import wrapper (Cython → Python)
252
+ │ ├── _routing.pyx # Cython routing (optional)
253
+ │ └── _routing_py.py # pure-Python routing fallback
254
+ └── example/ # demo MkDocs site
255
+ ├── mkdocs.yml
256
+ └── docs/
257
+ └── *.md
258
+ ```
259
+
260
+ ## Publishing to PyPI (maintainers)
261
+
262
+ ```bash
263
+ pip install build twine
264
+ python -m build
265
+ twine upload dist/*
266
+ ```
267
+
268
+ Use an API token from [pypi.org/manage/account](https://pypi.org/manage/account/) (env `TWINE_USERNAME=__token__`, `TWINE_PASSWORD=<token>`).
269
+
270
+ ## License
271
+
272
+ MIT
@@ -0,0 +1,241 @@
1
+ # MkDocs DBML Plugin
2
+
3
+ MkDocs plugin that renders [DBML](https://dbml.dbdiagram.io/) code blocks as interactive, visual ERD (Entity-Relationship Diagram) directly in your documentation.
4
+
5
+ ## Features
6
+
7
+ - **Interactive SVG diagrams** — drag tables, zoom with mouse wheel, pan the canvas
8
+ - **Field-to-field connections** — relationship lines go from the exact FK field to the exact PK field
9
+ - **Orthogonal routing** — lines use right-angle paths and automatically avoid overlapping tables
10
+ - **Click-to-select** — click any relationship line to highlight it and its connected fields
11
+ - **Crow's foot notation** — classic ERD markers for one-to-one, one-to-many, many-to-many
12
+ - **Material Design 3 icons** — PK, FK, NOT NULL, UNIQUE badges on fields
13
+ - **7 color themes** — default, ocean, sunset, forest, dark, dark_gray, black
14
+ - **High performance** — optional Cython-compiled routing engine; Numba JIT fallback
15
+
16
+ ## Installation
17
+
18
+ ### From source (local project)
19
+
20
+ ```bash
21
+ git clone https://github.com/ZhuchkaTriplesix/mkdocs-dbml.git
22
+ cd mkdocs-dbml
23
+ pip install -e .
24
+ ```
25
+
26
+ ### From source with Cython (optional, for best performance)
27
+
28
+ If you have a C compiler available (MSVC on Windows, gcc/clang on Linux/macOS):
29
+
30
+ ```bash
31
+ pip install cython
32
+ python setup.py build_ext --inplace
33
+ pip install -e .
34
+ ```
35
+
36
+ Without Cython the plugin works fine — it falls back to a pure-Python router.
37
+
38
+ ### Dependencies
39
+
40
+ Installed automatically by `pip install`:
41
+
42
+ | Package | Purpose |
43
+ |---------|---------|
44
+ | `mkdocs >= 1.0` | Documentation framework |
45
+ | `pydbml >= 1.0` | DBML parser |
46
+
47
+ ## Quick start
48
+
49
+ ### 1. Enable the plugin in `mkdocs.yml`
50
+
51
+ ```yaml
52
+ plugins:
53
+ - search
54
+ - dbml
55
+ ```
56
+
57
+ ### 2. Write DBML in any Markdown file
58
+
59
+ ````markdown
60
+ ```dbml
61
+ Table users {
62
+ id integer [primary key]
63
+ username varchar(50) [not null, unique]
64
+ email varchar(100) [not null]
65
+ created_at timestamp
66
+ }
67
+
68
+ Table posts {
69
+ id integer [primary key]
70
+ user_id integer [ref: > users.id]
71
+ title varchar(200) [not null]
72
+ content text
73
+ published boolean [default: false]
74
+ created_at timestamp
75
+ }
76
+
77
+ Table comments {
78
+ id integer [primary key]
79
+ post_id integer [ref: > posts.id]
80
+ user_id integer [ref: > users.id]
81
+ content text [not null]
82
+ created_at timestamp
83
+ }
84
+ ```
85
+ ````
86
+
87
+ ### 3. Build or serve
88
+
89
+ ```bash
90
+ mkdocs serve # live preview at http://127.0.0.1:8000
91
+ mkdocs build # static output in site/
92
+ ```
93
+
94
+ The plugin converts every `` ```dbml `` code block into an interactive SVG diagram.
95
+
96
+ ### Including native `.dbml` files
97
+
98
+ You can embed the contents of a `.dbml` file instead of pasting DBML inline. Paths are relative to your `docs_dir`.
99
+
100
+ **Option 1 — single line with path (must end with `.dbml`, no spaces):**
101
+
102
+ ````markdown
103
+ ```dbml
104
+ schema.dbml
105
+ ```
106
+ ````
107
+
108
+ **Option 2 — explicit `file:` or `include:` prefix:**
109
+
110
+ ````markdown
111
+ ```dbml
112
+ file: schemas/public.dbml
113
+ ```
114
+ ````
115
+
116
+ The file is read from `docs_dir` (e.g. `docs/schema.dbml` or `docs/schemas/public.dbml`). Path traversal (`../`) outside `docs_dir` is not allowed.
117
+
118
+ ## Configuration
119
+
120
+ All options are set under the `dbml` plugin entry in `mkdocs.yml`:
121
+
122
+ ```yaml
123
+ plugins:
124
+ - dbml:
125
+ theme: default # color theme (see below)
126
+ show_indexes: true # display index information
127
+ show_notes: true # display table notes
128
+ ```
129
+
130
+ ### Themes
131
+
132
+ | Theme | Header gradient | Best for |
133
+ |-------|----------------|----------|
134
+ | `default` | Purple | Light backgrounds |
135
+ | `ocean` | Deep blue → cyan | Light backgrounds |
136
+ | `sunset` | Pink → blue | Light backgrounds |
137
+ | `forest` | Dark teal → green | Light backgrounds |
138
+ | `dark` | Dark violet | Dark backgrounds |
139
+ | `dark_gray` | Gray → slate | Dark backgrounds, neutral |
140
+ | `black` | Near-black → gray | Dark backgrounds, high contrast |
141
+
142
+ Example with the dark theme:
143
+
144
+ ```yaml
145
+ plugins:
146
+ - dbml:
147
+ theme: dark
148
+ ```
149
+
150
+ ## DBML syntax cheat-sheet
151
+
152
+ ```dbml
153
+ Table table_name {
154
+ column_name column_type [attributes]
155
+ }
156
+ ```
157
+
158
+ ### Column attributes
159
+
160
+ | Attribute | Syntax |
161
+ |-----------|--------|
162
+ | Primary key | `[primary key]` or `[pk]` |
163
+ | Not null | `[not null]` |
164
+ | Unique | `[unique]` |
165
+ | Default value | `[default: value]` |
166
+ | Foreign key | `[ref: > other_table.column]` |
167
+
168
+ ### Relationship types
169
+
170
+ | Syntax | Meaning | Markers |
171
+ |--------|---------|---------|
172
+ | `ref: > table.col` | Many-to-one | Circle → Crow's foot |
173
+ | `ref: < table.col` | One-to-many | Crow's foot → Bar |
174
+ | `ref: - table.col` | One-to-one | Bar → Bar |
175
+ | `ref: <> table.col` | Many-to-many | Crow's foot → Crow's foot |
176
+
177
+ ### Indexes and notes
178
+
179
+ ```dbml
180
+ Table example {
181
+ id integer [pk]
182
+ user_id integer
183
+ post_id integer
184
+
185
+ indexes {
186
+ user_id
187
+ (user_id, post_id) [unique]
188
+ }
189
+
190
+ Note: 'Description of this table'
191
+ }
192
+ ```
193
+
194
+ Full DBML specification: [dbml.dbdiagram.io/docs](https://dbml.dbdiagram.io/docs/)
195
+
196
+ ## Interaction guide
197
+
198
+ | Action | How |
199
+ |--------|-----|
200
+ | **Move a table** | Click and drag the table |
201
+ | **Pan the canvas** | Click and drag empty space |
202
+ | **Zoom** | Mouse wheel (10% – 300%) |
203
+ | **Fullscreen** | Click the expand button (top-right), Esc to exit |
204
+ | **Highlight connections** | Hover over a table |
205
+ | **Select a line** | Click on any relationship line |
206
+ | **Field tooltip** | Hover over a field row |
207
+
208
+ ## Project structure
209
+
210
+ ```
211
+ mkdocs-dbml/
212
+ ├── setup.py # package config
213
+ ├── requirements.txt
214
+ ├── mkdocs_dbml_plugin/
215
+ │ ├── __init__.py
216
+ │ ├── plugin.py # MkDocs hook + interactive JS
217
+ │ ├── renderer.py # DBML → SVG rendering + CSS
218
+ │ ├── layout.py # BFS graph layout engine
219
+ │ ├── config.py # theme definitions
220
+ │ ├── routing.py # import wrapper (Cython → Python)
221
+ │ ├── _routing.pyx # Cython routing (optional)
222
+ │ └── _routing_py.py # pure-Python routing fallback
223
+ └── example/ # demo MkDocs site
224
+ ├── mkdocs.yml
225
+ └── docs/
226
+ └── *.md
227
+ ```
228
+
229
+ ## Publishing to PyPI (maintainers)
230
+
231
+ ```bash
232
+ pip install build twine
233
+ python -m build
234
+ twine upload dist/*
235
+ ```
236
+
237
+ Use an API token from [pypi.org/manage/account](https://pypi.org/manage/account/) (env `TWINE_USERNAME=__token__`, `TWINE_PASSWORD=<token>`).
238
+
239
+ ## License
240
+
241
+ MIT
@@ -0,0 +1,4 @@
1
+ from .plugin import DbmlPlugin
2
+
3
+ __version__ = "1.0.0"
4
+ __all__ = ["DbmlPlugin"]
@@ -0,0 +1,136 @@
1
+ # cython: boundscheck=False, wraparound=False, cdivision=True
2
+ from libc.math cimport fabs
3
+ from libc.stdlib cimport malloc, free
4
+
5
+ cdef struct Rect:
6
+ double x, y, w, h
7
+
8
+
9
+ cdef int _overlaps_v(double vx, double y_lo, double y_hi,
10
+ Rect *rects, int n, int skip1, int skip2) noexcept nogil:
11
+ cdef int i
12
+ cdef Rect r
13
+ cdef double pad = 5.0
14
+ for i in range(n):
15
+ if i == skip1 or i == skip2:
16
+ continue
17
+ r = rects[i]
18
+ if r.x - pad <= vx <= r.x + r.w + pad:
19
+ if not (y_hi < r.y - pad or y_lo > r.y + r.h + pad):
20
+ return i
21
+ return -1
22
+
23
+
24
+ cdef double _path_cost(list waypoints):
25
+ cdef double cost = 0.0
26
+ cdef int i
27
+ cdef double x1, y1, x2, y2
28
+ for i in range(len(waypoints) - 1):
29
+ x1, y1 = waypoints[i]
30
+ x2, y2 = waypoints[i + 1]
31
+ cost += fabs(x2 - x1) + fabs(y2 - y1)
32
+ return cost
33
+
34
+
35
+ cdef list _route_one(double sx, double sy, double ex, double ey,
36
+ int skip1, int skip2, Rect *rects, int n, double gap):
37
+ cdef double mid_x = (sx + ex) / 2.0
38
+ cdef double y_lo = sy if sy < ey else ey
39
+ cdef double y_hi = sy if sy > ey else ey
40
+ cdef int blocker, dummy
41
+ cdef double left_x, right_x, jog_y, safe_x, stub
42
+ cdef bint left_ok, right_ok
43
+
44
+ blocker = _overlaps_v(mid_x, y_lo, y_hi, rects, n, skip1, skip2)
45
+ if blocker < 0:
46
+ return [(sx, sy), (mid_x, sy), (mid_x, ey), (ex, ey)]
47
+
48
+ left_x = rects[blocker].x - gap
49
+ right_x = rects[blocker].x + rects[blocker].w + gap
50
+
51
+ left_ok = _overlaps_v(left_x, y_lo, y_hi, rects, n, skip1, skip2) < 0
52
+ right_ok = _overlaps_v(right_x, y_lo, y_hi, rects, n, skip1, skip2) < 0
53
+
54
+ if left_ok and right_ok:
55
+ mid_x = left_x if fabs(left_x - sx) < fabs(right_x - sx) else right_x
56
+ elif left_ok:
57
+ mid_x = left_x
58
+ elif right_ok:
59
+ mid_x = right_x
60
+ else:
61
+ if sy < rects[blocker].y:
62
+ jog_y = rects[blocker].y - gap
63
+ else:
64
+ jog_y = rects[blocker].y + rects[blocker].h + gap
65
+ safe_x = left_x if fabs(left_x - sx) < fabs(right_x - sx) else right_x
66
+ stub = gap if ex < sx else -gap
67
+ return [
68
+ (sx, sy), (safe_x, sy), (safe_x, jog_y),
69
+ (ex + stub, jog_y), (ex + stub, ey), (ex, ey),
70
+ ]
71
+
72
+ return [(sx, sy), (mid_x, sy), (mid_x, ey), (ex, ey)]
73
+
74
+
75
+ def route_connection(from_rect, to_rect, field_y_from, field_y_to,
76
+ from_idx, to_idx, table_rects, gap=20.0):
77
+ cdef int n = len(table_rects)
78
+ cdef Rect *rects = <Rect *>malloc(n * sizeof(Rect))
79
+ if rects == NULL:
80
+ return [(0, 0), (0, 0)], 'right', 'left'
81
+
82
+ cdef int i
83
+ for i in range(n):
84
+ rects[i].x = table_rects[i][0]
85
+ rects[i].y = table_rects[i][1]
86
+ rects[i].w = table_rects[i][2]
87
+ rects[i].h = table_rects[i][3]
88
+
89
+ cdef double fx = from_rect[0], fy = from_rect[1], fw = from_rect[2], fh = from_rect[3]
90
+ cdef double tx = to_rect[0], ty = to_rect[1], tw = to_rect[2], th = to_rect[3]
91
+ cdef double sx, ex, cost, best_cost
92
+ cdef list wp, best_wp
93
+ cdef str best_sf, best_st
94
+
95
+ best_cost = 1e18
96
+ best_wp = []
97
+ best_sf = 'right'
98
+ best_st = 'left'
99
+
100
+ for sf in ('right', 'left'):
101
+ for st in ('right', 'left'):
102
+ sx = (fx + fw + 12.0) if sf == 'right' else (fx - 12.0)
103
+ ex = (tx - 12.0) if st == 'left' else (tx + tw + 12.0)
104
+ wp = _route_one(sx, field_y_from, ex, field_y_to,
105
+ from_idx, to_idx, rects, n, gap)
106
+ cost = _path_cost(wp)
107
+
108
+ for j in range(1, len(wp) - 1):
109
+ mx, my = wp[j]
110
+ for k in range(n):
111
+ if k == from_idx or k == to_idx:
112
+ continue
113
+ if rects[k].x <= mx <= rects[k].x + rects[k].w and rects[k].y <= my <= rects[k].y + rects[k].h:
114
+ cost += 100000
115
+ break
116
+
117
+ if cost < best_cost:
118
+ best_cost = cost
119
+ best_wp = wp
120
+ best_sf = sf
121
+ best_st = st
122
+
123
+ free(rects)
124
+ return best_wp, best_sf, best_st
125
+
126
+
127
+ def build_table_rects(positions, dimensions):
128
+ names = sorted(positions.keys())
129
+ idx_map = {}
130
+ rects = []
131
+ for i, name in enumerate(names):
132
+ idx_map[name] = i
133
+ px, py = positions[name]
134
+ w, h = dimensions[name]
135
+ rects.append((px, py, w, h))
136
+ return names, idx_map, rects