markdocx 3.0.0__tar.gz → 5.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.
- {markdocx-3.0.0 → markdocx-5.0.0}/PKG-INFO +1 -1
- {markdocx-3.0.0 → markdocx-5.0.0}/pyproject.toml +1 -1
- {markdocx-3.0.0 → markdocx-5.0.0}/rule/ai_gen_doc_rule.md +6 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/rule/ai_gen_doc_rule_en.md +6 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/__init__.py +1 -1
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/diagram_renderer.py +104 -52
- {markdocx-3.0.0 → markdocx-5.0.0}/uv.lock +1 -1
- {markdocx-3.0.0 → markdocx-5.0.0}/.gitignore +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/.python-version +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/LICENSE +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/README.md +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/main.py +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/requirements.txt +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/cli.py +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/code_renderer.py +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/core.py +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/docx_builder.py +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/math_renderer.py +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/md_parser.py +0 -0
- {markdocx-3.0.0 → markdocx-5.0.0}/src/markdocx/styles.py +0 -0
|
@@ -665,6 +665,12 @@ caption: Hình 8: Quy trình ETL
|
|
|
665
665
|
- Khuyến nghị dùng `[Bắt đầu]` và `[Kết thúc]` cho bước đầu và cuối
|
|
666
666
|
- Giữ quy trình tối đa **8 bước** để dễ đọc
|
|
667
667
|
- Dùng `title:` và `caption:` để đặt nhãn
|
|
668
|
+
- **Mỗi bước viết ngắn gọn, tối đa 10-12 ký tự** (ví dụ: `Thu nhận ảnh`, `Xử lý ảnh`)
|
|
669
|
+
- Nếu tên bước dài hơn 10 ký tự, hệ thống sẽ tự động xuống dòng
|
|
670
|
+
- **Bố cục ngang (`horizontal`):** viết tên bước **thật ngắn** (2-3 từ), không dùng câu dài
|
|
671
|
+
- ✅ Đúng: `(Thu nhận ảnh)`, `(Trích xuất)`, `(Phân loại)`
|
|
672
|
+
- ❌ Sai: `(Thu nhận và tiền xử lý ảnh đầu vào)`, `(Trích xuất đặc trưng từ dữ liệu)`
|
|
673
|
+
- **Bố cục dọc (`vertical`):** có thể viết dài hơn (tối đa 25-30 ký tự)
|
|
668
674
|
|
|
669
675
|
---
|
|
670
676
|
|
|
@@ -702,6 +702,12 @@ caption: Figure 8: ETL pipeline
|
|
|
702
702
|
- `[Start]` and `[End]` are recommended for the first and last steps
|
|
703
703
|
- Keep workflows to **8 steps or fewer** for readability
|
|
704
704
|
- Use `title:` and `caption:` for labeling
|
|
705
|
+
- **Keep each step label short — max 10-12 characters** (e.g., `Read Data`, `Transform`)
|
|
706
|
+
- Text longer than 10 characters will automatically wrap to 2 lines
|
|
707
|
+
- **Horizontal layout (`horizontal`):** use **very short** step labels (2-3 words max)
|
|
708
|
+
- ✅ Correct: `(Read Data)`, `(Transform)`, `(Classify)`
|
|
709
|
+
- ❌ Wrong: `(Read and preprocess input images)`, `(Extract features from data)`
|
|
710
|
+
- **Vertical layout (`vertical`):** may use longer labels (up to 25-30 characters)
|
|
705
711
|
|
|
706
712
|
---
|
|
707
713
|
|
|
@@ -731,42 +731,76 @@ def _render_workflow(code: str):
|
|
|
731
731
|
n = len(steps)
|
|
732
732
|
is_vertical = direction.startswith("v")
|
|
733
733
|
|
|
734
|
-
#
|
|
734
|
+
# ── Measure text to compute adaptive box sizes ──────────────
|
|
735
|
+
def _wrap_text(text, max_chars):
|
|
736
|
+
"""Wrap text into multiple lines if too long."""
|
|
737
|
+
if len(text) <= max_chars:
|
|
738
|
+
return text
|
|
739
|
+
words = text.split()
|
|
740
|
+
lines = []
|
|
741
|
+
current = ""
|
|
742
|
+
for word in words:
|
|
743
|
+
test = f"{current} {word}".strip() if current else word
|
|
744
|
+
if len(test) <= max_chars:
|
|
745
|
+
current = test
|
|
746
|
+
else:
|
|
747
|
+
if current:
|
|
748
|
+
lines.append(current)
|
|
749
|
+
current = word
|
|
750
|
+
if current:
|
|
751
|
+
lines.append(current)
|
|
752
|
+
return "\n".join(lines) if lines else text
|
|
753
|
+
|
|
754
|
+
# Find longest text to size boxes appropriately
|
|
755
|
+
max_text_len = max(len(s.get("text", "")) for s in steps)
|
|
756
|
+
|
|
735
757
|
if is_vertical:
|
|
736
|
-
|
|
737
|
-
|
|
758
|
+
# Vertical: boxes can be wide
|
|
759
|
+
max_chars_per_line = 30
|
|
760
|
+
box_w = max(3.0, min(6.0, max_text_len * 0.18 + 1.0))
|
|
761
|
+
box_h = 0.9
|
|
762
|
+
fontsize_normal = 12
|
|
763
|
+
fontsize_decision = 11
|
|
764
|
+
spacing_y = 1.8 # center-to-center vertical distance
|
|
765
|
+
|
|
766
|
+
fig_w = max(6, box_w + 3.0)
|
|
767
|
+
fig_h = max(4, n * spacing_y + 2.0)
|
|
738
768
|
else:
|
|
739
|
-
|
|
740
|
-
|
|
769
|
+
# Horizontal: wider boxes, bigger font, wrap early for readability
|
|
770
|
+
max_chars_per_line = 10
|
|
771
|
+
box_w = max(2.8, min(4.0, max_text_len * 0.18 + 1.2))
|
|
772
|
+
box_h = 1.2
|
|
773
|
+
fontsize_normal = 13
|
|
774
|
+
fontsize_decision = 12
|
|
775
|
+
spacing_x = box_w + 1.2 # center-to-center horizontal distance
|
|
776
|
+
|
|
777
|
+
fig_w = max(8, n * spacing_x + 2.5)
|
|
778
|
+
fig_h = max(4.0, 5.0)
|
|
741
779
|
|
|
742
780
|
fig, ax = plt.subplots(figsize=(fig_w, fig_h))
|
|
743
|
-
|
|
781
|
+
# Do NOT use set_aspect("equal") — it distorts boxes
|
|
744
782
|
ax.axis("off")
|
|
745
783
|
|
|
746
|
-
#
|
|
747
|
-
box_w = 2.0
|
|
748
|
-
box_h = 0.7
|
|
749
|
-
spacing = 1.5 # center-to-center distance
|
|
750
|
-
|
|
751
|
-
# Calculate positions
|
|
784
|
+
# ── Calculate positions ─────────────────────────────────────
|
|
752
785
|
positions = []
|
|
753
786
|
for i in range(n):
|
|
754
787
|
if is_vertical:
|
|
755
788
|
x = fig_w / 2
|
|
756
|
-
y = fig_h - 1.
|
|
789
|
+
y = fig_h - 1.5 - i * spacing_y
|
|
757
790
|
else:
|
|
758
|
-
x = 1.5 + i *
|
|
791
|
+
x = 1.5 + i * spacing_x
|
|
759
792
|
y = fig_h / 2
|
|
760
793
|
positions.append((x, y))
|
|
761
794
|
|
|
762
795
|
# Adjust axes limits
|
|
763
796
|
all_x = [p[0] for p in positions]
|
|
764
797
|
all_y = [p[1] for p in positions]
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
ax.
|
|
798
|
+
pad_x = box_w / 2 + 1.5
|
|
799
|
+
pad_y = box_h + 1.5
|
|
800
|
+
ax.set_xlim(min(all_x) - pad_x, max(all_x) + pad_x)
|
|
801
|
+
ax.set_ylim(min(all_y) - pad_y, max(all_y) + pad_y)
|
|
768
802
|
|
|
769
|
-
# Color scheme
|
|
803
|
+
# ── Color scheme ────────────────────────────────────────────
|
|
770
804
|
type_colors = {
|
|
771
805
|
"terminal": ("#34A853", "white"),
|
|
772
806
|
"start": ("#34A853", "white"),
|
|
@@ -776,17 +810,21 @@ def _render_workflow(code: str):
|
|
|
776
810
|
"io": ("#7B61FF", "white"),
|
|
777
811
|
}
|
|
778
812
|
|
|
779
|
-
# Draw steps
|
|
813
|
+
# ── Draw steps ──────────────────────────────────────────────
|
|
780
814
|
for i, step in enumerate(steps):
|
|
781
815
|
x, y = positions[i]
|
|
782
816
|
step_type = step.get("type", "process")
|
|
783
|
-
|
|
817
|
+
raw_text = step.get("text", "")
|
|
818
|
+
display_text = _wrap_text(raw_text, max_chars_per_line)
|
|
784
819
|
bg_color, text_color = type_colors.get(step_type, ("#4285F4", "white"))
|
|
785
820
|
|
|
821
|
+
# Count lines for height adjustment
|
|
822
|
+
n_lines = display_text.count("\n") + 1
|
|
823
|
+
effective_h = max(box_h, box_h + (n_lines - 1) * 0.3)
|
|
824
|
+
|
|
786
825
|
if step_type == "decision":
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
diamond_h = box_h * 1.2
|
|
826
|
+
diamond_w = box_w * 0.55
|
|
827
|
+
diamond_h = effective_h * 0.9
|
|
790
828
|
diamond = plt.Polygon([
|
|
791
829
|
(x, y + diamond_h),
|
|
792
830
|
(x + diamond_w, y),
|
|
@@ -795,68 +833,82 @@ def _render_workflow(code: str):
|
|
|
795
833
|
], closed=True, facecolor=bg_color, edgecolor="#333333",
|
|
796
834
|
linewidth=1.5, zorder=3)
|
|
797
835
|
ax.add_patch(diamond)
|
|
798
|
-
ax.text(x, y,
|
|
799
|
-
fontsize=
|
|
800
|
-
|
|
836
|
+
ax.text(x, y, display_text, ha="center", va="center",
|
|
837
|
+
fontsize=fontsize_decision, fontweight="bold",
|
|
838
|
+
color=text_color, zorder=4, linespacing=1.2)
|
|
801
839
|
|
|
802
840
|
elif step_type in ("terminal", "start", "end"):
|
|
803
|
-
# Rounded rectangle (stadium shape)
|
|
804
841
|
patch = FancyBboxPatch(
|
|
805
|
-
(x - box_w / 2, y -
|
|
842
|
+
(x - box_w / 2, y - effective_h / 2), box_w, effective_h,
|
|
806
843
|
boxstyle="round,pad=0.15", facecolor=bg_color,
|
|
807
844
|
edgecolor="#333333", linewidth=1.5, zorder=3
|
|
808
845
|
)
|
|
809
846
|
ax.add_patch(patch)
|
|
810
|
-
ax.text(x, y,
|
|
811
|
-
fontsize=
|
|
847
|
+
ax.text(x, y, display_text, ha="center", va="center",
|
|
848
|
+
fontsize=fontsize_normal, fontweight="bold",
|
|
849
|
+
color=text_color, zorder=4, linespacing=1.2)
|
|
812
850
|
|
|
813
851
|
elif step_type == "io":
|
|
814
|
-
|
|
815
|
-
skew = 0.2
|
|
852
|
+
skew = 0.25
|
|
816
853
|
pgram = plt.Polygon([
|
|
817
|
-
(x - box_w / 2 + skew, y +
|
|
818
|
-
(x + box_w / 2 + skew, y +
|
|
819
|
-
(x + box_w / 2 - skew, y -
|
|
820
|
-
(x - box_w / 2 - skew, y -
|
|
854
|
+
(x - box_w / 2 + skew, y + effective_h / 2),
|
|
855
|
+
(x + box_w / 2 + skew, y + effective_h / 2),
|
|
856
|
+
(x + box_w / 2 - skew, y - effective_h / 2),
|
|
857
|
+
(x - box_w / 2 - skew, y - effective_h / 2)
|
|
821
858
|
], closed=True, facecolor=bg_color, edgecolor="#333333",
|
|
822
859
|
linewidth=1.5, zorder=3)
|
|
823
860
|
ax.add_patch(pgram)
|
|
824
|
-
ax.text(x, y,
|
|
825
|
-
fontsize=
|
|
861
|
+
ax.text(x, y, display_text, ha="center", va="center",
|
|
862
|
+
fontsize=fontsize_normal, fontweight="bold",
|
|
863
|
+
color=text_color, zorder=4, linespacing=1.2)
|
|
826
864
|
|
|
827
865
|
else: # process
|
|
828
|
-
# Rectangle
|
|
829
866
|
patch = FancyBboxPatch(
|
|
830
|
-
(x - box_w / 2, y -
|
|
867
|
+
(x - box_w / 2, y - effective_h / 2), box_w, effective_h,
|
|
831
868
|
boxstyle="square,pad=0.1", facecolor=bg_color,
|
|
832
869
|
edgecolor="#333333", linewidth=1.5, zorder=3
|
|
833
870
|
)
|
|
834
871
|
ax.add_patch(patch)
|
|
835
|
-
ax.text(x, y,
|
|
836
|
-
fontsize=
|
|
872
|
+
ax.text(x, y, display_text, ha="center", va="center",
|
|
873
|
+
fontsize=fontsize_normal, fontweight="bold",
|
|
874
|
+
color=text_color, zorder=4, linespacing=1.2)
|
|
837
875
|
|
|
838
|
-
# Draw arrows between steps
|
|
876
|
+
# ── Draw arrows between steps ───────────────────────────────
|
|
839
877
|
for i in range(n - 1):
|
|
840
878
|
x1, y1 = positions[i]
|
|
841
879
|
x2, y2 = positions[i + 1]
|
|
842
880
|
|
|
843
|
-
|
|
881
|
+
cur_type = steps[i].get("type", "process")
|
|
882
|
+
next_type = steps[i + 1].get("type", "process")
|
|
883
|
+
|
|
884
|
+
# Recompute effective heights for arrow endpoints
|
|
885
|
+
cur_text = _wrap_text(steps[i].get("text", ""), max_chars_per_line)
|
|
886
|
+
next_text = _wrap_text(steps[i + 1].get("text", ""), max_chars_per_line)
|
|
887
|
+
cur_h = max(box_h, box_h + (cur_text.count("\n")) * 0.3)
|
|
888
|
+
next_h = max(box_h, box_h + (next_text.count("\n")) * 0.3)
|
|
844
889
|
|
|
845
890
|
if is_vertical:
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
start_y = y1 -
|
|
850
|
-
next_type = steps[i + 1].get("type", "process")
|
|
891
|
+
if cur_type == "decision":
|
|
892
|
+
start_y = y1 - cur_h * 0.9 - 0.08
|
|
893
|
+
else:
|
|
894
|
+
start_y = y1 - cur_h / 2 - 0.08
|
|
851
895
|
if next_type == "decision":
|
|
852
|
-
end_y = y2 +
|
|
896
|
+
end_y = y2 + next_h * 0.9 + 0.08
|
|
897
|
+
else:
|
|
898
|
+
end_y = y2 + next_h / 2 + 0.08
|
|
853
899
|
ax.annotate("", xy=(x2, end_y), xytext=(x1, start_y),
|
|
854
900
|
arrowprops=dict(arrowstyle="-|>", color="#555555",
|
|
855
901
|
lw=2, mutation_scale=15),
|
|
856
902
|
zorder=2)
|
|
857
903
|
else:
|
|
858
|
-
|
|
859
|
-
|
|
904
|
+
if cur_type == "decision":
|
|
905
|
+
start_x = x1 + box_w * 0.55 + 0.08
|
|
906
|
+
else:
|
|
907
|
+
start_x = x1 + box_w / 2 + 0.08
|
|
908
|
+
if next_type == "decision":
|
|
909
|
+
end_x = x2 - box_w * 0.55 - 0.08
|
|
910
|
+
else:
|
|
911
|
+
end_x = x2 - box_w / 2 - 0.08
|
|
860
912
|
ax.annotate("", xy=(end_x, y2), xytext=(start_x, y1),
|
|
861
913
|
arrowprops=dict(arrowstyle="-|>", color="#555555",
|
|
862
914
|
lw=2, mutation_scale=15),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|