From 8f25427fd71397ca230f4202cf0435381520013f Mon Sep 17 00:00:00 2001 From: randogoth Date: Mon, 20 Oct 2025 01:11:29 +0300 Subject: [PATCH] figlet fix, code block margin --- .gitignore | 2 + README.md | 5 +- examples/loremipsum.md | 2 + loremipsum.txt | 121 ------------------------------ md2txt.py | 105 +++++++++++++++++++++++--- output.txt | 163 ----------------------------------------- 6 files changed, 100 insertions(+), 298 deletions(-) delete mode 100644 loremipsum.txt delete mode 100644 output.txt diff --git a/.gitignore b/.gitignore index 505a3b1..ac1c839 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ build/ dist/ wheels/ *.egg-info +*.txt # Virtual environments .venv + diff --git a/README.md b/README.md index d22824f..04bdcf3 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ h2_font: standard h3_font: small margin_left: 4 margin_right: 4 -paragraph_spacing: 1 hyphenate: true hyphen_lang: en_US --- @@ -51,9 +50,7 @@ hyphen_lang: en_US ### Subsection ``` -Font names also accept the special keywords `caps` (force uppercase) and `title` (title case) for any heading. `margin_left` and `margin_right` add leading/trailing spaces to body text, `paragraph_spacing` (or `lines_between_paragraphs`) inserts blank lines between paragraphs, and enabling `hyphenate` activates PyHyphen-based wrapping (optional `hyphen_lang` defaults to `en_US`). - -If the rendered FIGlet text would exceed the configured width, the converter automatically falls back to the H4-style uppercase heading with a dashed underline. +Font names also accept the special keywords `caps` (force uppercase) and `title` (title case) for any heading. `margin_left` and `margin_right` add leading/trailing spaces to body text, `paragraph_spacing` (or `lines_between_paragraphs`) inserts blank lines between paragraphs, and enabling `hyphenate` activates PyHyphen-based wrapping (optional `hyphen_lang` defaults to `en_US`). Set `figlet_fallback: true` to force an H4-style fallback when a FIGlet banner would overflow; when omitted (the default), the banner is kept even if it extends beyond the width. ## Styling via Attribute Blocks diff --git a/examples/loremipsum.md b/examples/loremipsum.md index 998f544..d1769af 100644 --- a/examples/loremipsum.md +++ b/examples/loremipsum.md @@ -2,6 +2,8 @@ h1_font: big h2_font: small h3_font: caps +margin_left: 8 +margin_right: 8 paragraph_spacing: 2 hyphenate: true --- diff --git a/loremipsum.txt b/loremipsum.txt deleted file mode 100644 index dcc85c3..0000000 --- a/loremipsum.txt +++ /dev/null @@ -1,121 +0,0 @@ - _____ _ _ _ _ - / ____| | (_) | | (_) - | (___ __ _| |_ ___ _ __ | |_ _ __ _ - \___ \ / _` | | |/ _ \ '_ \| __| |/ _` | - ____) | (_| | | | __/ | | | |_| | (_| | - |_____/ \__,_|_|_|\___|_| |_|\__|_|\__,_| - - - - ___ _ _ _ _ _ _ _ - / __| |_ _ _ _ _ __| |_ __ _ | |_ (_)_ _ __ __ _____| |_(_) |_ _ _ ___ - \__ \ _| '_| || / _| _/ _` | | ' \| | ' \/ _| \ V / -_) _| | _| || (_-< - |___/\__|_| \_,_\__|\__\__,_| |_||_|_|_||_\__| \_/\___|\__|_|\__|\_,_/__/ - - __ _ - / _|__ _ __| |_ _ _ _ __ - | _/ _` / _| _| || | ' \ - |_| \__,_\__|\__|\_,_|_|_|_| - - -Lorem markdownum omnes tellus, in, Herculeis `reciprocalPower` neque promissa e- -st Latiae caput umero talia fidissima, nymphae. Foedera et sagittas tenetur adde -matri esse: talia manibus. - - - ___ _ _ _ _ _ _ - | __| _(_) |_ __ _(_)__| |___ ___ | |___ _ _ __ _(_)__ _ _ _ ___ - | _| || | | _| \ V / / _` / -_) _ \ | / _ \ ' \/ _` | / _` | || / -_) - |_| \_,_|_|\__| \_/|_\__,_\___\___/ |_\___/_||_\__, |_\__, |\_,_\___| - |___/ |_| - _ _ _ - __| |___ __| (_)______ ___ - / _` / -_) _` | (_-<_- None: @@ -681,14 +685,60 @@ class MarkdownToTxtConverter: if style_key in {"caps", "title"}: return self._render_h4_plus(text, style, transform=style_key) if level <= 3: - figlet_lines = self._figlet_render(level, text) - if figlet_lines: - max_figlet_width = max((len(line.rstrip()) for line in figlet_lines), default=0) - if max_figlet_width <= self._effective_width(style): - styled = self._apply_style_to_lines(figlet_lines, style) - return styled + [""] + figlet_lines = self._render_figlet_heading(level, text, style) + if figlet_lines is not None: + return figlet_lines + [""] return self._render_h4_plus(text, style) + def _render_figlet_heading(self, level: int, text: str, style: BlockStyle) -> Optional[List[str]]: + if Figlet is None: + return None + font_name = getattr(self.frontmatter, f"h{level}_font", "standard") + cache_key = (font_name, "wide") + figlet = self.figlets.get(cache_key) + if figlet is None: + try: + figlet = Figlet(font=font_name, width=max(self.width, 100000)) + except (FontNotFound, TypeError): + return None + self.figlets[cache_key] = figlet + + words = text.split() + if not words: + return [] + + available_width = self._effective_width(style) + justify = self._figlet_justify(style.align) + + render_key = (font_name, available_width, justify) + render_figlet = self.figlets.get(render_key) + if render_figlet is None: + try: + render_figlet = Figlet(font=font_name, width=available_width, justify=justify) + except (FontNotFound, TypeError): + return None + self.figlets[render_key] = render_figlet + + rendered = render_figlet.renderText(text).rstrip("\n").splitlines() + if not rendered: + return [] + + lines = [line.rstrip("\n") for line in rendered] + overflow_detected = any(len(line.rstrip()) > available_width for line in lines) + if overflow_detected and self.figlet_fallback: + return None + + margin_left = min(max(style.margin_left, 0), self.width - 1) + indent_str = " " * margin_left + return [indent_str + line.rstrip() for line in lines] + + def _figlet_justify(self, align: str) -> str: + if align == "center": + return "center" + if align == "right": + return "right" + return "left" + def _render_horizontal_rule(self, style: BlockStyle) -> str: margin_left = min(max(style.margin_left, 0), self.width - 1) available_width = max(1, self._effective_width(style)) @@ -920,7 +970,7 @@ class MarkdownToTxtConverter: self.link_indices[url] = index return index - def _figlet_render(self, level: int, text: str) -> Optional[List[str]]: + def _figlet_render(self, level: int, text: str, allow_overflow: bool = False) -> Optional[List[str]]: if Figlet is None: return None font_name = getattr(self.frontmatter, f"h{level}_font", "standard") @@ -934,10 +984,44 @@ class MarkdownToTxtConverter: rendered = figlet.renderText(text).rstrip("\n") lines = rendered.splitlines() trimmed_lines = [line.rstrip("\n") for line in lines] - if not trimmed_lines or max(len(line.rstrip()) for line in trimmed_lines) > self.width: + if not trimmed_lines: + return None + if not allow_overflow and max(len(line.rstrip()) for line in trimmed_lines) > self.width: return None return [line.rstrip() for line in trimmed_lines] + def _align_figlet_block(self, lines: List[str], style: BlockStyle) -> List[str]: + margin_left = min(max(style.margin_left, 0), self.width - 1) + margin_right = max(style.margin_right, 0) + available_width = max(1, self.width - margin_left - margin_right) + + raw = [line.rstrip() for line in lines] + non_empty = [line for line in raw if line.strip()] + if non_empty: + leading = min(len(line) - len(line.lstrip(" ")) for line in non_empty) + else: + leading = 0 + stripped = [line[leading:] if line.strip() else "" for line in raw] + block_width = max((len(line) for line in stripped if line.strip()), default=0) + extra_space = max(0, available_width - block_width) + if style.align == "center": + align_offset = extra_space // 2 + elif style.align == "right": + align_offset = extra_space + else: + align_offset = 0 + max_indent = max(0, self.width - block_width) + indent = min(margin_left + align_offset, max_indent) + indent_str = " " * indent + + aligned: List[str] = [] + for line in stripped: + if not line.strip(): + aligned.append("") + continue + aligned.append(indent_str + line) + return aligned + def _render_h4_plus(self, text: str, style: BlockStyle, transform: str = "caps") -> List[str]: if transform == "title": processed = self._to_title_case(text) @@ -1179,6 +1263,7 @@ def parse_frontmatter(lines: List[str]) -> Tuple[FrontMatter, List[str]]: paragraph_spacing=max(0, _parse_int(paragraph_spacing_value, 0)), hyphenate=_parse_bool(frontmatter.get("hyphenate"), False), hyphen_lang=(frontmatter.get("hyphen_lang") or "en_US").strip() or "en_US", + figlet_fallback=_parse_bool(frontmatter.get("figlet_fallback"), False), ) return fm, remaining diff --git a/output.txt b/output.txt deleted file mode 100644 index 549e49c..0000000 --- a/output.txt +++ /dev/null @@ -1,163 +0,0 @@ - _ _ _ _ _ _ ___ __ -| |__ / | | | | | ___ __ _ __| (_)_ __ __ _ ( _ ) \ \ -| '_ \| | | |_| |/ _ \/ _` |/ _` | | '_ \ / _` | / _ \ _____| | -| | | | | | _ | __/ (_| | (_| | | | | | (_| | | (_) |_____| | -|_| |_|_| |_| |_|\___|\__,_|\__,_|_|_| |_|\__, | \___/ | | - |___/ /_/ - - _ ____ _ _ _ _ -| |__ |___ \ | | | | ___ __ _ __| (_)_ __ __ _ -| '_ \ __) | | |_| |/ _ \/ _` |/ _` | | '_ \ / _` | -| | | |/ __/ | _ | __/ (_| | (_| | | | | | (_| | -|_| |_|_____| |_| |_|\___|\__,_|\__,_|_|_| |_|\__, | - |___/ - - _ _____ _ _ _ _ -| |__ |___ / | | | | ___ __ _ __| (_)_ __ __ _ -| '_ \ |_ \ | |_| |/ _ \/ _` |/ _` | | '_ \ / _` | -| | | |___) | | _ | __/ (_| | (_| | | | | | (_| | -|_| |_|____/ |_| |_|\___|\__,_|\__,_|_|_| |_|\__, | - |___/ - -H4 HEADING ----------- - -H5 HEADING ----------- - -H6 HEADING ----------- - - - _ _ _ _ _ ____ _ -| | | | ___ _ __(_)_______ _ __ | |_ __ _| | | _ \ _ _| | ___ ___ -| |_| |/ _ \| '__| |_ / _ \| '_ \| __/ _` | | | |_) | | | | |/ _ \/ __| -| _ | (_) | | | |/ / (_) | | | | || (_| | | | _ <| |_| | | __/\__ \ -|_| |_|\___/|_| |_/___\___/|_| |_|\__\__,_|_| |_| \_\\__,_|_|\___||___/ - - - --------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- - - _____ _ _ -| ____|_ __ ___ _ __ | |__ __ _ ___(_)___ -| _| | '_ ` _ \| '_ \| '_ \ / _` / __| / __| -| |___| | | | | | |_) | | | | (_| \__ \ \__ \ -|_____|_| |_| |_| .__/|_| |_|\__,_|___/_|___/ - |_| - - -T H I S I S B O L D T E X T _T_H_I_S___I_S___B_O_L_D___T_E_X_T_ T h i s -i s i t a l i c t e x t _T_h_i_s___i_s___i_t_a_l_i_c___t_e_x_t_ --S-t-r-i-k-e-t-h-r-o-u-g-h- - - ____ _ _ _ -| __ )| | ___ ___| | ____ _ _ _ ___ | |_ ___ ___ -| _ \| |/ _ \ / __| |/ / _` | | | |/ _ \| __/ _ \/ __| -| |_) | | (_) | (__| < (_| | |_| | (_) | || __/\__ \ -|____/|_|\___/ \___|_|\_\__, |\__,_|\___/ \__\___||___/ - |_| - - - | Blockquotes can also be nested... - | | by using additional greater-than signs right next to each other... - | | | or with spaces between arrows. - - _ _ _ -| | (_)___| |_ ___ -| | | / __| __/ __| -| |___| \__ \ |_\__ \ -|_____|_|___/\__|___/ - - - - _ _ _ _ -| | | |_ __ ___ _ __ __| | ___ _ __ ___ __| | -| | | | '_ \ / _ \| '__/ _` |/ _ \ '__/ _ \/ _` | -| |_| | | | | (_) | | | (_| | __/ | | __/ (_| | - \___/|_| |_|\___/|_| \__,_|\___|_| \___|\__,_| - - - -+ Create a list by starting a line with `+`, `-`, or `*` -- Marker character change forces new list start: -* Ac tristique libero volutpat at -+ Facilisis in pretium nisl aliquet -- Nulla volutpat aliquam velit -+ Very easy - - ___ _ _ - / _ \ _ __ __| | ___ _ __ ___ __| | -| | | | '__/ _` |/ _ \ '__/ _ \/ _` | -| |_| | | | (_| | __/ | | __/ (_| | - \___/|_| \__,_|\___|_| \___|\__,_| - - - -1. Lorem ipsum dolor sit amet -2. Consectetur adipiscing elit -3. Integer molestie lorem at massa - - ____ _ - / ___|___ __| | ___ -| | / _ \ / _` |/ _ \ -| |__| (_) | (_| | __/ - \____\___/ \__,_|\___| - - - -Inline `code` - -Indented code - - 01 | // Some comments - 02 | line 1 of code - 03 | line 2 of code - 04 | line 3 of code - - - -Block code "fences" - - 01 | Sample text here... - - -Syntax highlighting - - 01 | var foo = function (bar) { - 02 | return bar++; - 03 | }; - 04 | - 05 | console.log(foo(5)); - - - _ _ _ -| | (_)_ __ | | _____ -| | | | '_ \| |/ / __| -| |___| | | | | <\__ \ -|_____|_|_| |_|_|\_\___/ - - - -[link text](1) - -[link with title](2) - - ___ -|_ _|_ __ ___ __ _ __ _ ___ ___ - | || '_ ` _ \ / _` |/ _` |/ _ \/ __| - | || | | | | | (_| | (_| | __/\__ \ -|___|_| |_| |_|\__,_|\__, |\___||___/ - |___/ - - -[Image: Minion](3) [Image: Stormtroopocat](4) - -[1] http://dev.nodeca.com -[2] http://nodeca.github.io/pica/demo/ -[3] https://octodex.github.com/images/minion.png -[4] https://octodex.github.com/images/stormtroopocat.jpg