markdocx 3.0.0__tar.gz → 4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdocx
3
- Version: 3.0.0
3
+ Version: 4.0.0
4
4
  Summary: Convert AI-generated Markdown textbooks to polished DOCX with native math equations and syntax-highlighted code
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "markdocx"
3
- version = "3.0.0"
3
+ version = "4.0.0"
4
4
  description = "Convert AI-generated Markdown textbooks to polished DOCX with native math equations and syntax-highlighted code"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -7,5 +7,5 @@ with native math equations and syntax-highlighted code.
7
7
 
8
8
  from markdocx.core import convert_file, convert_directory
9
9
 
10
- __version__ = "3.0.0"
10
+ __version__ = "4.0.0"
11
11
  __all__ = ["convert_file", "convert_directory"]
@@ -731,42 +731,76 @@ def _render_workflow(code: str):
731
731
  n = len(steps)
732
732
  is_vertical = direction.startswith("v")
733
733
 
734
- # Calculate figure size
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
- fig_w = max(5, 6)
737
- fig_h = max(3, n * 1.5 + 1.5)
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
- fig_w = max(4, n * 2.5 + 1)
740
- fig_h = max(3, 4)
769
+ # Horizontal: limited width per box, allow wrapping
770
+ max_chars_per_line = 14
771
+ box_w = max(2.2, min(3.5, max_text_len * 0.15 + 0.8))
772
+ box_h = 1.0
773
+ fontsize_normal = 11
774
+ fontsize_decision = 10
775
+ spacing_x = box_w + 1.0 # center-to-center horizontal distance
776
+
777
+ fig_w = max(6, n * spacing_x + 2.0)
778
+ fig_h = max(3.5, 4.0)
741
779
 
742
780
  fig, ax = plt.subplots(figsize=(fig_w, fig_h))
743
- ax.set_aspect("equal")
781
+ # Do NOT use set_aspect("equal") — it distorts boxes
744
782
  ax.axis("off")
745
783
 
746
- # Shape dimensions
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.0 - i * spacing
789
+ y = fig_h - 1.5 - i * spacing_y
757
790
  else:
758
- x = 1.5 + i * (box_w + 0.8)
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
- margin = 2.0
766
- ax.set_xlim(min(all_x) - margin, max(all_x) + margin)
767
- ax.set_ylim(min(all_y) - margin, max(all_y) + margin)
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 for step types
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
- text = step.get("text", "")
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
- # Diamond shape
788
- diamond_w = box_w * 0.7
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, text, ha="center", va="center",
799
- fontsize=9, fontweight="bold", color=text_color, zorder=4,
800
- wrap=True)
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 - box_h / 2), box_w, box_h,
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, text, ha="center", va="center",
811
- fontsize=10, fontweight="bold", color=text_color, zorder=4)
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
- # Parallelogram
815
- skew = 0.2
852
+ skew = 0.25
816
853
  pgram = plt.Polygon([
817
- (x - box_w / 2 + skew, y + box_h / 2),
818
- (x + box_w / 2 + skew, y + box_h / 2),
819
- (x + box_w / 2 - skew, y - box_h / 2),
820
- (x - box_w / 2 - skew, y - box_h / 2)
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, text, ha="center", va="center",
825
- fontsize=10, fontweight="bold", color=text_color, zorder=4)
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 - box_h / 2), box_w, box_h,
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, text, ha="center", va="center",
836
- fontsize=10, fontweight="bold", color=text_color, zorder=4)
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
- step_type = steps[i].get("type", "process")
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
- start_y = y1 - box_h / 2 - 0.05
847
- end_y = y2 + box_h / 2 + 0.05
848
- if step_type == "decision":
849
- start_y = y1 - box_h * 1.2 - 0.05
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 + box_h * 1.2 + 0.05
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
- start_x = x1 + box_w / 2 + 0.05
859
- end_x = x2 - box_w / 2 - 0.05
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),
@@ -158,7 +158,7 @@ wheels = [
158
158
 
159
159
  [[package]]
160
160
  name = "markdocx"
161
- version = "2.0.0"
161
+ version = "3.0.0"
162
162
  source = { editable = "." }
163
163
  dependencies = [
164
164
  { name = "latex2mathml" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes