engrapha-notes 0.1.0__tar.gz → 0.1.1__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.
Files changed (21) hide show
  1. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/PKG-INFO +20 -4
  2. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/README.md +19 -3
  3. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/pyproject.toml +2 -2
  4. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/cli.py +457 -6
  5. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/helpers.py +33 -12
  6. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes.egg-info/PKG-INFO +20 -4
  7. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes.egg-info/requires.txt +1 -1
  8. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/setup.cfg +0 -0
  9. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/__init__.py +0 -0
  10. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/document.py +0 -0
  11. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/packet.py +0 -0
  12. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/palette.py +0 -0
  13. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/styles.py +0 -0
  14. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes/theme.py +0 -0
  15. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes.egg-info/SOURCES.txt +0 -0
  16. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes.egg-info/dependency_links.txt +0 -0
  17. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes.egg-info/entry_points.txt +0 -0
  18. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/src/engrapha_notes.egg-info/top_level.txt +0 -0
  19. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/tests/test_exports.py +0 -0
  20. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/tests/test_markdown_compiler.py +0 -0
  21. {engrapha_notes-0.1.0 → engrapha_notes-0.1.1}/tests/test_math.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engrapha-notes
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Themed ReportLab notes toolkit with markdown, HTML, PPTX, and flashcard exports
5
5
  Author: Bharat Dangi
6
6
  License-Expression: MIT
@@ -11,7 +11,7 @@ Description-Content-Type: text/markdown
11
11
  Requires-Dist: reportlab>=4.5.1
12
12
  Requires-Dist: pygments>=2.20.0
13
13
  Requires-Dist: matplotlib>=3.10
14
- Requires-Dist: engrapha-diagrams>=0.1.0
14
+ Requires-Dist: engrapha-diagrams>=0.1.1
15
15
  Provides-Extra: flashcards
16
16
  Requires-Dist: genanki>=0.13.1; extra == "flashcards"
17
17
  Provides-Extra: split
@@ -315,6 +315,14 @@ en.formula(r"E = mc^2", color="#fbbf24", fontsize=14.0)
315
315
  en.formula_block(r"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}", color="#38bdf8")
316
316
  ```
317
317
 
318
+ #### Matplotlib Mathtext Limitations
319
+ Since inline math uses matplotlib's internal mathtext parser (rather than a full-scale LaTeX compiler), there are a few syntax constraints:
320
+ * **No short-form inequality macros**: Use `\geq` and `\leq` instead of `\ge` and `\le`.
321
+ * **No `\iff` and extensible arrows**: Use `\Leftrightarrow` instead of `\iff`, and use `\overset{label}{\rightarrow}` instead of extensible arrows like `\xrightarrow{label}`.
322
+ * **No `\pmod`**: Use `\ (\mathrm{mod}\ N)` instead of `\pmod{N}` or `\pmod N`.
323
+ * **Set vertical bars**: Use `\vert` or `\mid` for vertical bars in sets.
324
+
325
+
318
326
  ### 6. Advanced Styling, Custom Layouts & Overrides
319
327
 
320
328
  ```python
@@ -442,10 +450,18 @@ en.packet_format(
442
450
 
443
451
  ## 💻 Markdown CLI Compiler
444
452
 
445
- You can compile markdown notes files directly to PDF via the command line interface using either `Engrapha` or `pdfnotes`:
453
+ You can compile markdown notes files directly to PDF via the command line interface using either `engrapha` or `pdfnotes`:
454
+
455
+ ```bash
456
+ engrapha input.md --output output.pdf --theme catppuccin-mocha
457
+ ```
458
+
459
+ ### Display Help & Documentation
460
+ To print the comprehensive API reference, theme listing, and Markdown syntax guide directly in your terminal, run:
446
461
 
447
462
  ```bash
448
- Engrapha input.md --output output.pdf --theme catppuccin-mocha
463
+ engrapha --info
464
+ # or: pdfnotes --info
449
465
  ```
450
466
 
451
467
  ### Options:
@@ -87,7 +87,7 @@ en.cover_card(
87
87
  banner_svg="asset_images/network_topology.svg",
88
88
  banner_width=400.0,
89
89
  banner_align="center",
90
- )
90
+ )
91
91
 
92
92
  # Add a faint SVG background illustration (optional svglib for native vector)
93
93
  en.cover_image(
@@ -282,6 +282,14 @@ en.formula(r"E = mc^2", color="#fbbf24", fontsize=14.0)
282
282
  en.formula_block(r"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}", color="#38bdf8")
283
283
  ```
284
284
 
285
+ #### Matplotlib Mathtext Limitations
286
+ Since inline math uses matplotlib's internal mathtext parser (rather than a full-scale LaTeX compiler), there are a few syntax constraints:
287
+ * **No short-form inequality macros**: Use `\geq` and `\leq` instead of `\ge` and `\le`.
288
+ * **No `\iff` and extensible arrows**: Use `\Leftrightarrow` instead of `\iff`, and use `\overset{label}{\rightarrow}` instead of extensible arrows like `\xrightarrow{label}`.
289
+ * **No `\pmod`**: Use `\ (\mathrm{mod}\ N)` instead of `\pmod{N}` or `\pmod N`.
290
+ * **Set vertical bars**: Use `\vert` or `\mid` for vertical bars in sets.
291
+
292
+
285
293
  ### 6. Advanced Styling, Custom Layouts & Overrides
286
294
 
287
295
  ```python
@@ -409,10 +417,18 @@ en.packet_format(
409
417
 
410
418
  ## 💻 Markdown CLI Compiler
411
419
 
412
- You can compile markdown notes files directly to PDF via the command line interface using either `Engrapha` or `pdfnotes`:
420
+ You can compile markdown notes files directly to PDF via the command line interface using either `engrapha` or `pdfnotes`:
421
+
422
+ ```bash
423
+ engrapha input.md --output output.pdf --theme catppuccin-mocha
424
+ ```
425
+
426
+ ### Display Help & Documentation
427
+ To print the comprehensive API reference, theme listing, and Markdown syntax guide directly in your terminal, run:
413
428
 
414
429
  ```bash
415
- Engrapha input.md --output output.pdf --theme catppuccin-mocha
430
+ engrapha --info
431
+ # or: pdfnotes --info
416
432
  ```
417
433
 
418
434
  ### Options:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engrapha-notes"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Themed ReportLab notes toolkit with markdown, HTML, PPTX, and flashcard exports"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -20,7 +20,7 @@ dependencies = [
20
20
  "reportlab>=4.5.1",
21
21
  "pygments>=2.20.0",
22
22
  "matplotlib>=3.10",
23
- "engrapha-diagrams>=0.1.0",
23
+ "engrapha-diagrams>=0.1.1",
24
24
  ]
25
25
 
26
26
  [project.optional-dependencies]
@@ -64,6 +64,7 @@ def format_inline_markdown(text: str) -> str:
64
64
  escaped = escaped.replace("&lt;u&gt;", "<u>").replace("&lt;/u&gt;", "</u>")
65
65
 
66
66
  code_spans: list[str] = []
67
+
67
68
  def _code_repl(m: re.Match[str]) -> str:
68
69
  code_spans.append(m.group(1))
69
70
  return f"@@CODE{len(code_spans)-1}@@"
@@ -654,8 +655,7 @@ def parse_markdown_lines(content_lines: List[str]) -> None:
654
655
  if in_table and table_header:
655
656
  formatted_header = [format_inline_markdown(h) for h in table_header]
656
657
  formatted_rows = [
657
- [format_inline_markdown(cell) for cell in row]
658
- for row in table_rows
658
+ [format_inline_markdown(cell) for cell in row] for row in table_rows
659
659
  ]
660
660
  en.info_table(formatted_header, formatted_rows)
661
661
  table_rows.clear()
@@ -742,9 +742,7 @@ def parse_markdown_lines(content_lines: List[str]) -> None:
742
742
  if parts and parts[-1] == "":
743
743
  parts = parts[:-1]
744
744
  if len(parts) >= 2:
745
- is_separator = all(
746
- re.match(r"^:?-+:?$", p) for p in parts
747
- )
745
+ is_separator = all(re.match(r"^:?-+:?$", p) for p in parts)
748
746
  if is_separator:
749
747
  in_table = True
750
748
  idx += 1
@@ -838,12 +836,452 @@ def parse_markdown_lines(content_lines: List[str]) -> None:
838
836
  flush_table()
839
837
 
840
838
 
839
+ def print_package_help() -> None:
840
+ """Print comprehensive package usage guide and API documentation to stdout."""
841
+ guide = """================================================================================
842
+ ENGRAPHA SUITE DOCUMENTATION
843
+ ================================================================================
844
+
845
+ Welcome to the Engrapha Suite. This utility includes:
846
+ 1. engrapha_notes: A themed ReportLab notes generator API.
847
+ 2. engrapha_diagrams: A vector-native diagram builder API.
848
+ 3. engrapha/pdfnotes CLI: A markdown-to-PDF notes compiler.
849
+
850
+ For standalone diagrams API and DSL reference, run:
851
+ engrapha-diagram (or engrapha-diagrams, engrapha_diagram, pdfdiagram)
852
+
853
+ --------------------------------------------------------------------------------
854
+ 1. PYTHON NOTES API REFERENCE (engrapha_notes)
855
+ --------------------------------------------------------------------------------
856
+ import engrapha_notes as en
857
+
858
+ THEMES:
859
+ Preset themes: en.DARK, en.LIGHT, en.OCEAN_DARK, en.FOREST_DARK, en.SUNSET_DARK,
860
+ en.MIDNIGHT_DARK, en.OCEAN_LIGHT, en.SEPIA, en.CATPPUCCIN_LATTE, en.CATPPUCCIN_MOCHA,
861
+ en.LINEAR, en.NOTION, en.GITHUB, en.ACADEMIC, en.TEXTBOOK.
862
+
863
+ - en.set_theme(theme_obj)
864
+ Applies the theme. Custom themes can be created using `ThemeBuilder` or:
865
+ my_theme = en.LIGHT.copy_with(name="Custom", body_font="Times-Roman")
866
+ - en.get_theme() -> NotesTheme
867
+ Returns current theme metadata and styles.
868
+
869
+ GLOBAL FORMATTING & LAYOUT:
870
+ - en.set_global_header(left="", center="", right="")
871
+ Configures the page header running text.
872
+ - en.set_global_footer(left="", center="", right="", show_page_num=True)
873
+ Configures the page footer running text.
874
+ - en.suppress_header(page_only=True)
875
+ Hides page headers on the active page (or all pages if page_only=False).
876
+ - en.suppress_footer(page_only=True)
877
+ Hides page footers on the active page (or all pages if page_only=False).
878
+
879
+ COVER PAGES:
880
+ - en.cover_preset(preset_name, title, subtitle="", author="", date="")
881
+ Presets: 'engineering', 'research-paper', 'course-notes', 'networking', 'database', 'programming'.
882
+ - en.cover_card(title, subtitle="", author="", date="", cover_theme="linear", icon="", tags=[],
883
+ logo_svg=None, logo_width=120.0, banner_svg=None, banner_width=400.0, banner_align="left")
884
+ Themes: linear, notion, catppuccin, textbook, modern, minimal, corporate, academic, book.
885
+ - en.cover_image(path, opacity=0.06, placement="background")
886
+ Faint SVG background backdrop.
887
+
888
+ HEADINGS & PAGE NAVIGATION:
889
+ - en.toc(style="standard", col_widths=None)
890
+ Renders TOC. Style: 'standard' or 'index' (grid-based).
891
+ - en.part_box(title, subtitle="", topics=[])
892
+ Full-page part separator screen.
893
+ - en.chap_box(title, subtitle="")
894
+ Chapter title block.
895
+ - en.section(title)
896
+ Section header.
897
+ - en.subsection(title)
898
+ Subsection header.
899
+ - en.br()
900
+ Forces a page break.
901
+ - en.sp(height_in_points)
902
+ Inserts a vertical spacer.
903
+
904
+ BODY ELEMENTS & CALLOUTS:
905
+ - en.body(text, font_name=None, font_size=None, text_color=None, leading=None)
906
+ Normal paragraph text. Inline LaTeX '$...$' inside text is auto-compiled on the fly.
907
+ - en.bullet(items_list)
908
+ Bullet point list.
909
+ - en.formula(latex_str, color=None, fontsize=None) -> str
910
+ Inline LaTeX compiler. Returns string with XML image tag.
911
+ - en.formula_block(latex_str, color=None, fontsize=None)
912
+ Centers LaTeX formula block.
913
+ - en.code_block(code_text, lang="python", theme=en.DRACULA)
914
+ Syntax-highlighted code block. Themes: DRACULA, MONOKAI, GITHUB_DARK.
915
+ - en.info_table(headers, rows, col_widths=None)
916
+ Styled data table. Col widths can be percentages (e.g. ["40%", "60%"]).
917
+ - en.image(path, caption="", width=None, height=None, link=None, fallbacks=[])
918
+ Image loader with automatic URL caching, fallback lists, and missing placeholders.
919
+ - en.warning(text), en.note(text), en.tip(text), en.important(text)
920
+ Semantic callout cards.
921
+ - en.definition(title, text), en.theorem(title, text), en.proof(text)
922
+ Academic callout blocks.
923
+ - en.frame_format(title, fields)
924
+ Renders protocol frame segments. fields is a list of (label, bytes_desc).
925
+ - en.packet_format(title, fields, bit_ruler=True)
926
+ Renders network packet structures. fields is list of (label, bit_width).
927
+ - en.revision_card(title, items)
928
+ Revision checklist card.
929
+
930
+ ACTIVE RECALL & EXPORT:
931
+ - en.flashcard(question, answer)
932
+ Defines a flashcard study question. Gathered for Anki APKG compiler.
933
+ - en.question(text), en.answer(text), en.qbox(text)
934
+ Study questions layout.
935
+ - en.mcq(question, options, correct_index)
936
+ Multiple choice question layout.
937
+
938
+ CROSS-REFERENCING:
939
+ - en.label(name)
940
+ Attaches a target reference label to the preceding heading.
941
+ - en.ref(name) -> str
942
+ Resolves the target reference label page number.
943
+
944
+ DOCUMENT BUILD & EXPORT:
945
+ - en.build_doc(output_path, title=None, author=None)
946
+ Compiles everything into a PDF file.
947
+ - en.build_html(output_path)
948
+ Exports notes to HTML structure.
949
+ - en.build_pptx(output_path)
950
+ Exports notes to PowerPoint slides.
951
+ - en.build_split_doc(output_path, split_by="chapter")
952
+ Splits output PDF into multiple files by chapter boundaries or page ranges.
953
+
954
+ --------------------------------------------------------------------------------
955
+ 2. PYTHON DIAGRAMS API REFERENCE (engrapha_diagrams)
956
+ --------------------------------------------------------------------------------
957
+ import engrapha_diagrams as ed
958
+
959
+ To print standalone diagrams package reference guide, run:
960
+ engrapha-diagram (or engrapha-diagrams, engrapha_diagram, pdfdiagram)
961
+
962
+ All diagram constructors accept:
963
+ width, height, theme=None, caption=None
964
+
965
+ Available diagram classes:
966
+ - ed.Flowchart(width, height, theme=None, direction='TB', scale_factor=None)
967
+ * Methods:
968
+ .terminal(id, label) - Oval terminal node
969
+ .process(id, label) - Rectangular process node
970
+ .decision(id, label) - Diamond decision node
971
+ .io_box(id, label) - Parallelogram input/output node
972
+ .connector(id, label) - Circle connector
973
+ .predefined(id, label) - Predefined process box node
974
+ .edge(src, dst, label="", orthogonal=False, branch=None) - Link edge
975
+ * Example:
976
+ fc = ed.Flowchart(width=300, height=150, direction="LR")
977
+ fc.terminal("start", "START").process("calc", "Calc").terminal("end", "END")
978
+ fc.edge("start", "calc").edge("calc", "end")
979
+
980
+ - ed.SequenceDiagram(width, height, theme=None)
981
+ * Methods:
982
+ .actor(id, label) - Lifeline actor (person)
983
+ .participant(id, label) - Lifeline participant (box)
984
+ .activate(id) - Start activation bar
985
+ .deactivate(id) - End activation bar
986
+ .message(src, dst, text, arrow='solid') - Message arrow ('solid', 'dashed', etc.)
987
+ .divider(text) - Horizontal partition divider
988
+ * Example:
989
+ seq = ed.SequenceDiagram(width=400, height=220)
990
+ seq.actor("c", "Client").actor("s", "Server")
991
+ seq.activate("c").message("c", "s", "Request").activate("s")
992
+ seq.message("s", "c", "Response", arrow="dashed").deactivate("s")
993
+
994
+ - ed.LayeredStack(width, height, theme=None)
995
+ * Methods:
996
+ .layer(label, sublabel="") - Add a stack layer
997
+ .divider() - Insert thick border line
998
+ * Example:
999
+ stack = ed.LayeredStack(width=300, height=150)
1000
+ stack.layer("Application", "HTTP").layer("Transport", "TCP")
1001
+
1002
+ - ed.NetworkDiagram(width, height, theme=None)
1003
+ * Methods:
1004
+ .node(id, label, x, y, kind='host') - kinds: host, server, cloud, switch, database, router, firewall
1005
+ .link(id1, id2, label="")
1006
+ * Example:
1007
+ net = ed.NetworkDiagram(width=400, height=200)
1008
+ net.node("client", "PC", 50, 100).node("srv", "Server", 250, 100)
1009
+ net.link("client", "srv")
1010
+
1011
+ - ed.ClassDiagram(width, height, theme=None, class_w=120)
1012
+ * Methods:
1013
+ .uml_class(id, name, stereotype="", attributes=[], methods=[])
1014
+ .relate(src, dst, kind='association', label="") - kinds: inheritance, realization, composition, aggregation, association, dependency
1015
+ * Example:
1016
+ cd = ed.ClassDiagram(width=300, height=200)
1017
+ cd.uml_class("Shape", "Shape", stereotype="abstract")
1018
+ cd.uml_class("Circle", "Circle", attributes=["- r: double"])
1019
+ cd.relate("Circle", "Shape", kind="inheritance")
1020
+
1021
+ - ed.ERDiagram(width, height, theme=None)
1022
+ * Methods:
1023
+ .entity(id)
1024
+ .relationship(id)
1025
+ .entity_attributes(entity_id, attribute_list) - attribute_list elements: "Name" or ("ID", {"pk": True})
1026
+ .connect(node1, node2, card_from=None, card_to=None)
1027
+ * Example:
1028
+ er = ed.ERDiagram(width=400, height=180)
1029
+ er.entity("User").relationship("Owns").entity("Device")
1030
+ er.entity_attributes("User", [("ID", {"pk": True}), "Email"])
1031
+ er.connect("User", "Owns", card_from="1", card_to="N")
1032
+
1033
+ - ed.StateMachine(width, height, theme=None)
1034
+ * Methods:
1035
+ .state(id, label, x, y, initial=False, accepting=False)
1036
+ .transition(src, dst, label="")
1037
+ * Example:
1038
+ sm = ed.StateMachine(width=300, height=150)
1039
+ sm.state("s0", "Init", 50, 75, initial=True).state("s1", "Done", 250, 75, accepting=True)
1040
+ sm.transition("s0", "s1", "process")
1041
+
1042
+ - ed.TimingDiagram(width, height, theme=None)
1043
+ * Methods:
1044
+ .clock(label, period, cycles)
1045
+ .signal(label, transitions) - transitions: list of (time, state_0_or_1)
1046
+ * Example:
1047
+ td = ed.TimingDiagram(width=400, height=120)
1048
+ td.clock("CLK", 20.0, 5).signal("RESET", [(0, 1), (15, 0)])
1049
+
1050
+ - ed.SchemaDiagram(width, height, theme=None)
1051
+ * Methods:
1052
+ .table(name, columns, x=0, y=0) - columns: list of (col_name, col_type, {"pk": True/False, "fk": True/False})
1053
+ .relation(table1, col1, table2, col2)
1054
+ * Example:
1055
+ schema = ed.SchemaDiagram(width=400, height=200)
1056
+ schema.table("users", [("id", "INT", {"pk": True}), ("name", "VARCHAR", {})])
1057
+
1058
+ - ed.ArchitectureDiagram(width, height, theme=None, orientation='horizontal')
1059
+ * Methods:
1060
+ .client(id, label), .service(id, label), .database(id, label), .queue(id, label)
1061
+ .connect(src, dst, label="")
1062
+ * Example:
1063
+ arch = ed.ArchitectureDiagram(width=400, height=200)
1064
+ arch.client("client", "Browser").service("api", "API Gateway")
1065
+ arch.connect("client", "api", "HTTPS")
1066
+
1067
+ - ed.C4ContainerDiagram(width, height, theme=None)
1068
+ * Methods:
1069
+ .system(id, label, desc="")
1070
+ .container(id, label, technology, desc="")
1071
+ .relate(src, dst, label="")
1072
+ * Example:
1073
+ c4 = ed.C4ContainerDiagram(width=400, height=180)
1074
+ c4.system("user", "User").container("spa", "SPA", "React")
1075
+ c4.relate("user", "spa", "Uses")
1076
+
1077
+ - ed.GitDiagram(width, height, theme=None)
1078
+ * Methods:
1079
+ .commit(branch, label)
1080
+ .branch(parent, child)
1081
+ .merge(from_branch, to_branch, label)
1082
+ * Example:
1083
+ git = ed.GitDiagram(width=300, height=120)
1084
+ git.commit("main", "Init").branch("main", "dev").commit("dev", "Feature")
1085
+
1086
+ - ed.AWSDiagram(width, height, theme=None, orientation='horizontal')
1087
+ * Methods:
1088
+ .ec2(id, label), .rds(id, label), .s3(id, label), .lambda_fn(id, label), .sqs(id, label)
1089
+ .connect(src, dst, label="")
1090
+ * Example:
1091
+ aws = ed.AWSDiagram(width=400, height=200)
1092
+ aws.ec2("web", "Instance").rds("db", "RDS")
1093
+ aws.connect("web", "db", "SQL Connection")
1094
+
1095
+ To save standalone images:
1096
+ diagram.save("output.svg") # Supports SVG, PDF, PNG, JPG formats.
1097
+
1098
+ To embed inside engrapha_notes:
1099
+ # Calling .as_flowable() returns a list[Flowable] containing the drawing
1100
+ # and caption. This is fully accepted by en.add().
1101
+ en.add(diagram.as_flowable())
1102
+
1103
+ --------------------------------------------------------------------------------
1104
+ 3. CLI USAGE & COMPILER
1105
+ --------------------------------------------------------------------------------
1106
+ Compile a Markdown file into a themed PDF notes document:
1107
+ engrapha <input_file.md> [-o <output_file.pdf>] [-t <theme_name>]
1108
+
1109
+ Options:
1110
+ -o, --output Path to the output PDF (defaults to <input_name>.pdf).
1111
+ -t, --theme Theme name: dark (default), light, ocean-dark, forest-dark,
1112
+ sunset-dark, midnight-dark, ocean-light, sepia,
1113
+ catppuccin-latte, catppuccin-mocha.
1114
+ --title Metadata title.
1115
+ --author Metadata author.
1116
+ --info Display this detailed guide.
1117
+
1118
+ Markdown Syntax Supported:
1119
+ - Metadata Front-Matter:
1120
+ ---
1121
+ title: Note Title
1122
+ author: Author Name
1123
+ theme: midnight-dark
1124
+ ---
1125
+ - Headings:
1126
+ # Part Title -> Creates a Part Separator Box
1127
+ ## Chapter Title -> Creates a Chapter Title Box
1128
+ ### Section Title -> Creates a Section Title
1129
+ #### Subsection Title -> Creates a Subsection Title
1130
+ - Standard markdown paragraphs, **bold**, *italics*, `inline code`.
1131
+ - LaTeX Math: Inline LaTeX '$...$' is automatically compiled into inline vector math.
1132
+ (Note: Uses matplotlib's mathtext parser; use \\geq/\\leq instead of \\ge/\\le,
1133
+ \\Leftrightarrow instead of \\iff, \\ (\\mathrm{mod}\\ N) instead of \\pmod,
1134
+ and \\overset{label}{\\rightarrow} instead of \\xrightarrow).
1135
+ - Lists: Bullet lines starting with '-', '*' or '+' map to bullet lists.
1136
+ - Tables: Standard markdown tables map to styled Info Tables.
1137
+ - Alerts (GFM Alert blocks):
1138
+ > [!NOTE] Note text... (creates a Note callout box)
1139
+ > [!TIP] Tip text... (creates a Tip callout box)
1140
+ > [!WARNING] Warning text... (creates a Warning highlight box)
1141
+ > [!CAUTION] Caution text... (creates a Caution highlight box)
1142
+
1143
+ --------------------------------------------------------------------------------
1144
+ 4. MARKDOWN DIAGRAM DSL CODE BLOCKS
1145
+ --------------------------------------------------------------------------------
1146
+ You can embed vector diagrams directly in your markdown using fenced code blocks.
1147
+ Supported block types: flowchart, sequence, layeredstack, schema, er, git,
1148
+ architecture, c4, aws.
1149
+
1150
+ Valid configuration keys at the start of any block (no spaces around '='):
1151
+ width=VAL - Drawing width in points (default: 450)
1152
+ height=VAL - Drawing height in points (default: 240)
1153
+ direction=DIR - Flow direction: TB (Top-to-Bottom) or LR (Left-to-Right)
1154
+ scale_factor=VAL - Decimal scale factor (default: auto-computed)
1155
+ caption="TEXT" - Optional caption centered below diagram
1156
+
1157
+ - **flowchart**:
1158
+ * Syntax & Commands:
1159
+ - terminal <id> "<label>" - Oval shape node
1160
+ - process <id> "<label>" - Rectangular shape node
1161
+ - decision <id> "<label>" - Diamond shape node
1162
+ - io <id> "<label>" - Parallelogram input/output node
1163
+ - connector <id> "<label>" - Circle shape node
1164
+ - predefined <id> "<label>" - Predefined process box node
1165
+ - edge <src> <dst> ["label"] [orthogonal=true] - Connects two nodes
1166
+ * Example:
1167
+ ```flowchart
1168
+ width=400
1169
+ height=180
1170
+ direction=LR
1171
+ terminal s "START"
1172
+ process p "Compute Value"
1173
+ terminal e "END"
1174
+ edge s p
1175
+ edge p e
1176
+ ```
1177
+
1178
+ - **sequence**:
1179
+ * Syntax & Commands:
1180
+ - actor <id> "<label>" - Lifeline actor (person)
1181
+ - participant <id> "<label>" - Lifeline participant (box)
1182
+ - message <src> <dst> "<text>" [arrow=solid/dashed/solid_open/dashed_open]
1183
+ - divider ["label"] - Horizontal divider line
1184
+ * Example:
1185
+ ```sequence
1186
+ actor c "Client"
1187
+ participant s "Server"
1188
+ message c s "GET /index"
1189
+ divider "Processing"
1190
+ message s c "200 OK" arrow=dashed
1191
+ ```
1192
+
1193
+ - **layeredstack**:
1194
+ * Syntax & Commands:
1195
+ - layer "<label>" ["sublabel"] - Add a layer
1196
+ - divider - Thicker layer divider line
1197
+ * Example:
1198
+ ```layeredstack
1199
+ layer "Application" "HTTP"
1200
+ layer "Transport" "TCP"
1201
+ divider
1202
+ layer "Network" "IP"
1203
+ ```
1204
+
1205
+ - **schema**:
1206
+ * Syntax & Commands:
1207
+ - table <name> [x=coord] [y=coord] - Starts a database table definition
1208
+ - <col_name>: <type> [(pk)] [(fk)] [-> target_table.col] - Field entry
1209
+ - relation <table1>.<col1> <table2>.<col2> - FK link
1210
+ * Example:
1211
+ ```schema
1212
+ table users x=50 y=100
1213
+ id: INT (pk)
1214
+ email: VARCHAR
1215
+ table orders x=250 y=100
1216
+ id: INT (pk)
1217
+ user_id: INT (fk) -> users.id
1218
+ ```
1219
+
1220
+ - **git**:
1221
+ * Syntax & Commands:
1222
+ - commit <branch> "<message>" - Create a commit node
1223
+ - branch <parent> <child> - Spawn child branch lane
1224
+ - merge <from> <to> "<message>" - Merge two branch lanes
1225
+ * Example:
1226
+ ```git
1227
+ commit main "Init"
1228
+ branch main dev
1229
+ commit dev "Feature"
1230
+ merge dev main "Merge feature"
1231
+ ```
1232
+
1233
+ - **architecture**:
1234
+ * Syntax & Commands:
1235
+ - client <id> "<label>" - Client component box
1236
+ - service <id> "<label>" - Service component box
1237
+ - database <id> "<label>" - Database component cylinder
1238
+ - queue <id> "<label>" - Message queue component box
1239
+ - connect <src> <dst> "<label>" - Direct arrow connection
1240
+ * Example:
1241
+ ```architecture
1242
+ client web "Web App"
1243
+ service api "Backend"
1244
+ database db "DB"
1245
+ connect web api "HTTP"
1246
+ connect api db "SQL"
1247
+ ```
1248
+
1249
+ - **aws**:
1250
+ * Syntax & Commands:
1251
+ - ec2 <id> "<label>" - EC2 instance icon
1252
+ - rds <id> "<label>" - RDS database icon
1253
+ - s3 <id> "<label>" - S3 bucket icon
1254
+ - lambda <id> "<label>" - Lambda function icon
1255
+ - sqs <id> "<label>" - SQS queue icon
1256
+ - connect <src> <dst> "<label>" - Connection arrow
1257
+ * Example:
1258
+ ```aws
1259
+ ec2 web "Web App"
1260
+ rds db "Database"
1261
+ connect web db "SQL"
1262
+ ```
1263
+
1264
+ - **c4**:
1265
+ * Syntax & Commands:
1266
+ - system <id> "<label>" ["desc"] - System Context block
1267
+ - container <id> "<label>" "<tech>" ["desc"] - Container block
1268
+ - relate <src> <dst> "<label>" - Relationship arrow
1269
+ * Example:
1270
+ ```c4
1271
+ system user "Customer"
1272
+ container spa "SPA" "React" "Provides UI"
1273
+ relate user spa "Uses"
1274
+ ```
1275
+ ================================================================================"""
1276
+ print(guide)
1277
+
1278
+
841
1279
  def main() -> None:
842
1280
  """CLI entrypoint."""
843
1281
  parser = argparse.ArgumentParser(
844
1282
  description="Compile Markdown documents to themed ReportLab PDFs with native diagrams."
845
1283
  )
846
- parser.add_argument("input", help="Path to the input markdown file.")
1284
+ parser.add_argument("input", nargs="?", help="Path to the input markdown file.")
847
1285
  parser.add_argument(
848
1286
  "-o",
849
1287
  "--output",
@@ -857,9 +1295,22 @@ def main() -> None:
857
1295
  )
858
1296
  parser.add_argument("--title", help="Document title metadata.")
859
1297
  parser.add_argument("--author", help="Document author metadata.")
1298
+ parser.add_argument(
1299
+ "--info",
1300
+ action="store_true",
1301
+ help="Print comprehensive package usage guide and API documentation.",
1302
+ )
860
1303
 
861
1304
  args = parser.parse_args()
862
1305
 
1306
+ if args.info:
1307
+ print_package_help()
1308
+ sys.exit(0)
1309
+
1310
+ if not args.input:
1311
+ parser.print_help()
1312
+ sys.exit(1)
1313
+
863
1314
  try:
864
1315
  compile_markdown_to_pdf(
865
1316
  input_file=args.input,
@@ -15,7 +15,7 @@ from typing import Any, TYPE_CHECKING, Literal
15
15
  if TYPE_CHECKING:
16
16
  from reportlab.pdfgen.canvas import Canvas
17
17
 
18
- from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT
18
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT
19
19
  from reportlab.lib.styles import ParagraphStyle
20
20
  from reportlab.platypus import (
21
21
  Flowable,
@@ -645,6 +645,7 @@ def cover_card(
645
645
  banner_align: str = "left",
646
646
  logo_svg: str | None = None,
647
647
  logo_width: float = 120.0,
648
+ **extra: Any,
648
649
  ) -> None:
649
650
  """
650
651
  Add a styled cover-title card to the story.
@@ -657,10 +658,28 @@ def cover_card(
657
658
  tags_max_w: float = float(w_val) if isinstance(w_val, (int, float)) else float(CW)
658
659
  effective_style = cover_theme if cover_theme is not None else style
659
660
 
661
+ def _load_image_flowable(img_path: str, target_width: float) -> Any:
662
+ is_svg = img_path.lower().endswith(".svg")
663
+ if is_svg:
664
+ return InlineSVG(img_path, target_width=target_width)
665
+ else:
666
+ from reportlab.lib.utils import ImageReader
667
+ from reportlab.platypus import Image as RLImage
668
+ try:
669
+ reader = ImageReader(img_path)
670
+ iw, ih = reader.getSize()
671
+ aspect = ih / iw if iw > 0 else 1.0
672
+ target_height = target_width * aspect
673
+ return RLImage(img_path, width=target_width, height=target_height)
674
+ except Exception:
675
+ from reportlab.platypus import Spacer
676
+ return Spacer(1, 0)
677
+
660
678
  add(CoverBackgroundFlowable(effective_style, t_theme, bg_svg=bg_svg))
661
679
 
662
680
  if logo_svg:
663
- add(InlineSVG(logo_svg, target_width=logo_width))
681
+ logo_flowable = _load_image_flowable(logo_svg, target_width=logo_width)
682
+ add(logo_flowable)
664
683
  sp(16)
665
684
 
666
685
  on_dark_color = t_theme.rl(_contrast_text_color(t_theme))
@@ -788,7 +807,7 @@ def cover_card(
788
807
 
789
808
  if arch == "hero":
790
809
  # Massive typography, huge whitespace, left aligned
791
- sp(80)
810
+ sp(30 if logo_svg else 80)
792
811
  if icon:
793
812
  add(render_icon(icon, size=56.0))
794
813
  sp(24)
@@ -819,7 +838,7 @@ def cover_card(
819
838
 
820
839
  elif arch == "book":
821
840
  # Classic structured textbook aesthetic
822
- sp(120)
841
+ sp(40 if logo_svg else 120)
823
842
  st_title = ParagraphStyle(
824
843
  _uid(),
825
844
  parent=s.COVER_H1,
@@ -854,7 +873,7 @@ def cover_card(
854
873
 
855
874
  elif arch in ("academic_modern", "diagram"):
856
875
  # Clean university layout (diagram uses same text layout but relies on SVG background)
857
- sp(60)
876
+ sp(20 if logo_svg else 60)
858
877
  if icon:
859
878
  add(render_icon(icon, size=48.0))
860
879
  sp(20)
@@ -889,10 +908,10 @@ def cover_card(
889
908
  sp(24)
890
909
  from reportlab.platypus import Table, TableStyle, Indenter
891
910
 
892
- svg_flowable = InlineSVG(banner_svg, target_width=banner_width)
911
+ banner_flowable = _load_image_flowable(banner_svg, target_width=banner_width)
893
912
 
894
913
  if banner_align == "center":
895
- center_table = Table([[svg_flowable]], colWidths=["100%"])
914
+ center_table = Table([[banner_flowable]], colWidths=["100%"])
896
915
  center_table.setStyle(
897
916
  TableStyle(
898
917
  [
@@ -908,11 +927,12 @@ def cover_card(
908
927
  else:
909
928
  if base_indent > 0:
910
929
  add(Indenter(left=base_indent))
911
- add(svg_flowable)
930
+ add(banner_flowable)
912
931
  if base_indent > 0:
913
932
  add(Indenter(left=-base_indent))
914
933
 
915
- consumed += (banner_width * (640.0 / 1280.0)) + 24.0
934
+ banner_h = getattr(banner_flowable, "height", 0) or (banner_width * (640.0 / 1280.0))
935
+ consumed += banner_h + 24.0
916
936
 
917
937
  flexible_spacer(consumed)
918
938
 
@@ -1253,6 +1273,7 @@ def cover_preset(preset_name: str, **kwargs: Any) -> None:
1253
1273
  tags=tags,
1254
1274
  icon=icon,
1255
1275
  cover_theme=cover_theme,
1276
+ **merged,
1256
1277
  )
1257
1278
 
1258
1279
 
@@ -1462,7 +1483,7 @@ def body(
1462
1483
  fontName=f_name,
1463
1484
  leading=lead,
1464
1485
  spaceAfter=5,
1465
- alignment=TA_JUSTIFY,
1486
+ alignment=TA_LEFT,
1466
1487
  )
1467
1488
  add(Paragraph(text, st))
1468
1489
 
@@ -1481,7 +1502,7 @@ def definition(text: str, bg: Any = None, border: Any = None) -> None:
1481
1502
  leading=f_size * 1.6,
1482
1503
  spaceBefore=4,
1483
1504
  spaceAfter=6,
1484
- alignment=TA_JUSTIFY,
1505
+ alignment=TA_LEFT,
1485
1506
  )
1486
1507
 
1487
1508
  if getattr(t_theme, "plain_questions", False):
@@ -1865,7 +1886,7 @@ def proof(text: str) -> None:
1865
1886
  leftIndent=20,
1866
1887
  spaceBefore=6,
1867
1888
  spaceAfter=6,
1868
- alignment=TA_JUSTIFY,
1889
+ alignment=TA_LEFT,
1869
1890
  )
1870
1891
  p = Paragraph(f"<i>Proof.</i> {text} [Q.E.D.]", st)
1871
1892
  add(p)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engrapha-notes
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Themed ReportLab notes toolkit with markdown, HTML, PPTX, and flashcard exports
5
5
  Author: Bharat Dangi
6
6
  License-Expression: MIT
@@ -11,7 +11,7 @@ Description-Content-Type: text/markdown
11
11
  Requires-Dist: reportlab>=4.5.1
12
12
  Requires-Dist: pygments>=2.20.0
13
13
  Requires-Dist: matplotlib>=3.10
14
- Requires-Dist: engrapha-diagrams>=0.1.0
14
+ Requires-Dist: engrapha-diagrams>=0.1.1
15
15
  Provides-Extra: flashcards
16
16
  Requires-Dist: genanki>=0.13.1; extra == "flashcards"
17
17
  Provides-Extra: split
@@ -315,6 +315,14 @@ en.formula(r"E = mc^2", color="#fbbf24", fontsize=14.0)
315
315
  en.formula_block(r"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}", color="#38bdf8")
316
316
  ```
317
317
 
318
+ #### Matplotlib Mathtext Limitations
319
+ Since inline math uses matplotlib's internal mathtext parser (rather than a full-scale LaTeX compiler), there are a few syntax constraints:
320
+ * **No short-form inequality macros**: Use `\geq` and `\leq` instead of `\ge` and `\le`.
321
+ * **No `\iff` and extensible arrows**: Use `\Leftrightarrow` instead of `\iff`, and use `\overset{label}{\rightarrow}` instead of extensible arrows like `\xrightarrow{label}`.
322
+ * **No `\pmod`**: Use `\ (\mathrm{mod}\ N)` instead of `\pmod{N}` or `\pmod N`.
323
+ * **Set vertical bars**: Use `\vert` or `\mid` for vertical bars in sets.
324
+
325
+
318
326
  ### 6. Advanced Styling, Custom Layouts & Overrides
319
327
 
320
328
  ```python
@@ -442,10 +450,18 @@ en.packet_format(
442
450
 
443
451
  ## 💻 Markdown CLI Compiler
444
452
 
445
- You can compile markdown notes files directly to PDF via the command line interface using either `Engrapha` or `pdfnotes`:
453
+ You can compile markdown notes files directly to PDF via the command line interface using either `engrapha` or `pdfnotes`:
454
+
455
+ ```bash
456
+ engrapha input.md --output output.pdf --theme catppuccin-mocha
457
+ ```
458
+
459
+ ### Display Help & Documentation
460
+ To print the comprehensive API reference, theme listing, and Markdown syntax guide directly in your terminal, run:
446
461
 
447
462
  ```bash
448
- Engrapha input.md --output output.pdf --theme catppuccin-mocha
463
+ engrapha --info
464
+ # or: pdfnotes --info
449
465
  ```
450
466
 
451
467
  ### Options:
@@ -1,7 +1,7 @@
1
1
  reportlab>=4.5.1
2
2
  pygments>=2.20.0
3
3
  matplotlib>=3.10
4
- engrapha-diagrams>=0.1.0
4
+ engrapha-diagrams>=0.1.1
5
5
 
6
6
  [all]
7
7
  genanki>=0.13.1
File without changes