diff --git a/md2pdf.py b/md2pdf.py index 7a982bf..c59b88f 100755 --- a/md2pdf.py +++ b/md2pdf.py @@ -12,7 +12,7 @@ Usage: md2pdf.py docs/ -o pdf_output/ # Creates PDFs in pdf_output/ # With custom style - md2pdf.py input.md --style minimal # Use minimal style (no colors) + md2pdf.py input.md --style mono # Use monospace style Requires: pip install markdown weasyprint """ @@ -26,6 +26,107 @@ from weasyprint.text.fonts import FontConfiguration # Style templates STYLES = { + "elegant": """ + @import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap'); + @page { + size: A4; + margin: 1.8cm 2cm; + } + body { + font-family: 'Lato', 'Helvetica Neue', Helvetica, sans-serif; + font-weight: 300; + font-size: 9.5pt; + line-height: 1.55; + color: #2c2c2c; + } + h1 { + font-weight: 300; + font-size: 22pt; + color: #1a1a1a; + margin: 0 0 0.3em 0; + letter-spacing: 0.5pt; + } + h2 { + font-weight: 400; + font-size: 11pt; + color: #444; + margin: 1.2em 0 0.4em 0; + padding-bottom: 0.2em; + border-bottom: 1px solid #e0e0e0; + text-transform: uppercase; + letter-spacing: 1pt; + } + h3 { + font-weight: 400; + font-size: 10pt; + color: #333; + margin: 0.9em 0 0.3em 0; + } + h4 { + font-weight: 400; + font-size: 9.5pt; + color: #555; + margin: 0.7em 0 0.2em 0; + } + p { + margin: 0.4em 0; + } + code { + font-family: 'SF Mono', Menlo, monospace; + font-size: 8.5pt; + background: #f8f8f8; + padding: 1px 4px; + border-radius: 2px; + } + pre { + background: #f8f8f8; + padding: 0.8em; + font-size: 8pt; + border-left: 2px solid #ddd; + } + pre code { background: none; padding: 0; } + table { + width: 100%; + border-collapse: collapse; + font-size: 9pt; + margin: 0.8em 0; + } + th, td { + padding: 0.4em; + text-align: left; + border-bottom: 1px solid #eee; + } + th { font-weight: 400; color: #666; } + ul, ol { + margin: 0.3em 0; + padding-left: 0; + list-style-position: inside; + list-style-type: disc; + } + ol { list-style-type: decimal; } + li { + margin: 0.15em 0; + } + blockquote { + margin: 0.8em 0; + padding-left: 1em; + border-left: 2px solid #ccc; + color: #666; + font-style: italic; + } + a { + color: #2c2c2c; + text-decoration: none; + border-bottom: 1px solid #ccc; + } + strong { font-weight: 400; } + em { font-style: italic; } + hr { + border: none; + border-top: 1px solid #e5e5e5; + margin: 1.2em 0; + } + """, "default": """ @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600&display=swap'); @page { @@ -108,7 +209,9 @@ STYLES = { margin: 0.5em 0; padding-left: 0; list-style-position: inside; + list-style-type: disc; } + ol { list-style-type: decimal; } li { margin: 0.2em 0; } @@ -132,49 +235,6 @@ STYLES = { margin: 1.5em 0; } """, - "minimal": """ - @page { - size: A4; - margin: 2cm; - } - body { - font-family: Georgia, 'Times New Roman', serif; - font-size: 11pt; - line-height: 1.7; - color: #000; - } - h1, h2, h3, h4 { - font-family: -apple-system, BlinkMacSystemFont, Arial, sans-serif; - color: #000; - } - h1 { font-size: 22pt; margin-top: 0; } - h2 { font-size: 16pt; margin-top: 1.5em; } - h3 { font-size: 13pt; margin-top: 1.2em; } - h4 { font-size: 11pt; margin-top: 1em; } - code { - font-family: Menlo, Monaco, monospace; - font-size: 10pt; - background: #f5f5f5; - padding: 1px 4px; - } - pre { - background: #f5f5f5; - padding: 1em; - font-size: 9pt; - border: 1px solid #ddd; - } - pre code { background: none; padding: 0; } - table { border-collapse: collapse; width: 100%; margin: 1em 0; } - th, td { border: 1px solid #000; padding: 0.4em; text-align: left; } - th { font-weight: bold; } - blockquote { - margin: 1em 2em; - font-style: italic; - color: #555; - } - ul, ol { padding-left: 0; list-style-position: inside; } - a { color: #000; } - """, "dark": """ @page { size: A4; @@ -223,113 +283,119 @@ STYLES = { padding: 0.5em 1em; margin: 1em 0; } - ul, ol { padding-left: 0; list-style-position: inside; } + ul, ol { padding-left: 0; list-style-position: inside; list-style-type: disc; } + ol { list-style-type: decimal; } a { color: #818cf8; } hr { border: none; border-top: 1px solid #374151; margin: 2em 0; } """, - "elegant": """ - @import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap'); + "mono": """ + @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&display=swap'); @page { size: A4; - margin: 1.8cm 2cm; + margin: 2cm; } body { - font-family: 'Lato', 'Helvetica Neue', Helvetica, sans-serif; + font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', Consolas, monospace; font-weight: 300; - font-size: 9.5pt; - line-height: 1.55; - color: #2c2c2c; + font-size: 9pt; + line-height: 1.6; + color: #1a1a1a; + } + h1, h2, h3, h4 { + font-weight: 500; + color: #000; } h1 { - font-weight: 300; - font-size: 22pt; - color: #1a1a1a; - margin: 0 0 0.3em 0; - letter-spacing: 0.5pt; + font-size: 16pt; + margin-top: 0; + margin-bottom: 0.8em; + padding-bottom: 0.3em; + border-bottom: 2px solid #000; } h2 { - font-weight: 400; - font-size: 11pt; - color: #444; - margin: 1.2em 0 0.4em 0; - padding-bottom: 0.2em; - border-bottom: 1px solid #e0e0e0; + font-size: 12pt; + margin-top: 1.5em; + margin-bottom: 0.5em; text-transform: uppercase; letter-spacing: 1pt; } h3 { - font-weight: 400; font-size: 10pt; - color: #333; - margin: 0.9em 0 0.3em 0; + margin-top: 1.2em; + margin-bottom: 0.4em; } h4 { - font-weight: 400; - font-size: 9.5pt; - color: #555; - margin: 0.7em 0 0.2em 0; + font-size: 9pt; + margin-top: 1em; } p { - margin: 0.4em 0; + margin: 0.5em 0; } code { - font-family: 'SF Mono', Menlo, monospace; - font-size: 8.5pt; - background: #f8f8f8; + background-color: #f0f0f0; padding: 1px 4px; border-radius: 2px; } pre { - background: #f8f8f8; - padding: 0.8em; - font-size: 8pt; - border-left: 2px solid #ddd; + background-color: #f5f5f5; + padding: 1em; + border: 1px solid #ddd; + font-size: 8.5pt; + line-height: 1.5; + } + pre code { + background: none; + padding: 0; } - pre code { background: none; padding: 0; } table { - width: 100%; border-collapse: collapse; - font-size: 9pt; - margin: 0.8em 0; + width: 100%; + margin: 1em 0; + font-size: 8.5pt; } th, td { + border: 1px solid #ccc; padding: 0.4em; text-align: left; - border-bottom: 1px solid #eee; } - th { font-weight: 400; color: #666; } + th { + background: #f0f0f0; + font-weight: 500; + } ul, ol { - margin: 0.3em 0; + margin: 0.5em 0; padding-left: 0; list-style-position: inside; + list-style-type: disc; } + ol { list-style-type: decimal; } li { - margin: 0.15em 0; + margin: 0.2em 0; } blockquote { - margin: 0.8em 0; - padding-left: 1em; - border-left: 2px solid #ccc; - color: #666; + border-left: 3px solid #999; + margin: 1em 0; + padding: 0.5em 1em; + color: #555; font-style: italic; } a { - color: #2c2c2c; - text-decoration: none; - border-bottom: 1px solid #ccc; + color: #000; + text-decoration: underline; + } + strong { + font-weight: 500; } - strong { font-weight: 400; } - em { font-style: italic; } hr { border: none; - border-top: 1px solid #e5e5e5; - margin: 1.2em 0; + border-top: 1px solid #ccc; + margin: 1.5em 0; } """ } -def convert_md_to_pdf(md_file: Path, output_file: Path, style: str = "default") -> Path: +def convert_md_to_pdf(md_file: Path, output_file: Path, style: str = "elegant") -> Path: """Convert a single markdown file to PDF""" with open(md_file, 'r', encoding='utf-8') as f: @@ -346,7 +412,7 @@ def convert_md_to_pdf(md_file: Path, output_file: Path, style: str = "default") ] ) - css = STYLES.get(style, STYLES["default"]) + css = STYLES.get(style, STYLES["elegant"]) full_html = f""" @@ -372,22 +438,21 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: - %(prog)s document.md Convert single file + %(prog)s document.md Convert single file (elegant style) %(prog)s document.md -o report.pdf Convert with custom output name %(prog)s docs/ Convert all .md files in directory %(prog)s docs/ -o pdf/ Convert directory to different output - %(prog)s doc.md --style minimal Use minimal style + %(prog)s doc.md --style mono Use monospace font %(prog)s doc.md --style dark Use dark theme - %(prog)s doc.md --style elegant Use elegant style (ideal for CVs) -Available styles: default, minimal, dark, elegant +Available styles: elegant (default), default, dark, mono """ ) parser.add_argument('input', help='Input markdown file or directory') parser.add_argument('-o', '--output', help='Output PDF file or directory') - parser.add_argument('--style', choices=list(STYLES.keys()), default='default', - help='Style template (default: default)') + parser.add_argument('--style', choices=list(STYLES.keys()), default='elegant', + help='Style template (default: elegant)') parser.add_argument('-q', '--quiet', action='store_true', help='Suppress output') args = parser.parse_args()