figlet fix, code block margin

This commit is contained in:
randogoth 2025-10-20 01:11:29 +03:00
parent 70196134d6
commit 8f25427fd7
6 changed files with 100 additions and 298 deletions

2
.gitignore vendored
View file

@ -5,6 +5,8 @@ build/
dist/ dist/
wheels/ wheels/
*.egg-info *.egg-info
*.txt
# Virtual environments # Virtual environments
.venv .venv

View file

@ -41,7 +41,6 @@ h2_font: standard
h3_font: small h3_font: small
margin_left: 4 margin_left: 4
margin_right: 4 margin_right: 4
paragraph_spacing: 1
hyphenate: true hyphenate: true
hyphen_lang: en_US hyphen_lang: en_US
--- ---
@ -51,9 +50,7 @@ hyphen_lang: en_US
### Subsection ### 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`). 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.
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.
## Styling via Attribute Blocks ## Styling via Attribute Blocks

View file

@ -2,6 +2,8 @@
h1_font: big h1_font: big
h2_font: small h2_font: small
h3_font: caps h3_font: caps
margin_left: 8
margin_right: 8
paragraph_spacing: 2 paragraph_spacing: 2
hyphenate: true hyphenate: true
--- ---

View file

@ -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 / / _` / -_) _ \ | / _ \ ' \/ _` | / _` | || / -_)
|_| \_,_|_|\__| \_/|_\__,_\___\___/ |_\___/_||_\__, |_\__, |\_,_\___|
|___/ |_|
_ _ _
__| |___ __| (_)______ ___
/ _` / -_) _` | (_-<_-</ -_)
\__,_\___\__,_|_/__/__/\___|
Ille stupet, ultra I N C U N A B U L A A G E N D U M U R B E S mirer A P E
R, metu. Aegides novissima sunt turis quatiebant umbrosaque [putares](1). Metu-
unt eruiturque maximus velit, ater Vesta vulnus rustica verba! Mei vincite nul-
la, in Iovem: fama exurunt vernum!
De instat ubi induit fugientia inertes columbas tarda t e r g a P a r n a s i
a s e n t e n t i a omnia inmotusque, est et aetas sonus. Exhibuit coeperunt,
canis. Suis minora, tempore fateor neve moderamine erit. Non h o c r a d i i
s, Aenea etiamnum cogeris superinposita origine nec `hardening_search` dixit vi-
cina. Heros subito populifer adeunt quicquam cum A L E S Ixion angulus placet r-
eddere.
_____ _
|_ _| _ __ _ __| |_ __ _ __ _ _ _ ___ ___ ___ _ _ _ __ ___ _ _ ___
| || '_/ _` / _| _/ _` / _` | || / -_) (_-</ -_) '_| '_ \/ -_) ' \(_-<
|_||_| \__,_\__|\__\__,_\__, |\_,_\___| /__/\___|_| | .__/\___|_||_/__/
|_| |_|
__ _ _ ___ ____ _ __ _ _ _ ___ __ _ _ _ ___
/ _` | '_\ V / _` | / _` | || / _ \/ _` | || / -_)
\__,_|_| \_/\__,_| \__, |\_,_\___/\__, |\_,_\___|
|_| |_|
Inprudens dum memor alma, casses dedi Sinuessa iam quid adicit `gate_truncate_w-
ave`. [Geratur de](2), insolida temptat `open` carentia; genitorem solet, poten-
s. Adit vos ramos mundi castris quodque plangoremque harum profugi nulla aures
petiit liventia aere qua Hodites ruunt. Achaidos dilexisse `redundancy` prae-
sagia tenuatus necis C A P Y S R E C O R D O R tenebras actis signis, reliq-
uit, placidissime iram liquefactis longe nervi famem. Cura pars natique velleris
semel et at `printerWildcardParallel` inquit positosque comites ultra.
___ _ _
/ _ \ _ _ __ _ _ _| |_ __ _ __ _ _ _ ___ __ _ _ _ __ _ ___ _ _| |_
| (_) | || / _` | ' \ _/ _` / _` | || / -_) / _` | || / _` / -_) ' \ _|
\__\_\\_,_\__,_|_||_\__\__,_\__, |\_,_\___| \__,_|\_,_\__, \___|_||_\__|
|_| |___/
Acuti ignotis ignis, ostendit nostras terras nondum, [petit utque utero](3) sep-
ulcrales, pluma reseratis Ismario. Tria silentia duae n y m p h e: certe parva
cernam et dixit. [Verborum sedisti](1).
01 | if (firewall(4, 3) == 1 * device_media_parity) {
02 | system_copy_class += systray + oasis_wi_clean + lpi;
03 | modelLdap.search_secondary_socket *= bounce.proxy_text(cableMyspacePerl,
04 | scanner(spam));
05 | text_class_traceroute(vga, hdtv);
06 | }
07 | if (commerceStack) {
08 | wrapSafe(pum(itunesRosettaCache, sdramOpticalWorm));
09 | } else {
10 | minisite_worm(2, ssdString(84), gnuFriendly);
11 | state.qwerty(moduleJavascriptFloating + ugc, koffice,
12 | mail_system.prebinding_service_teraflops(mediaIcmpBoot));
13 | }
14 | memoryHexadecimalMultitasking += oop_android;
15 | var passive_vdu = bar;
16 | var ddrJava = leaf(unc_web);
MILLE PARVA
-----------
M I N E T U R E T aurum! Charaxi sanguine quo; vires subnectite P I G N O R A,
omnique hostibus loquerentur aquae Solis ferox? C U M N E, putat utar ex ad a-
ede mane. Vitalesque geminas exstimulat, iam vivunt dat artes et quem! Iam vio-
las molire ipse, spoliabitur dente nervis regis, factos quae.
| Adit et, aquaticus te dona effodiuntur tellus, fer totidem inquit faciat
| carchesia nectar mille? Indestrictus vestes ut et offensasque intibaque deum
| agitur linguae maiestas te rictus t e c u m l a n i a t q u e d i x i t
| ad datas `adsl`.
| Vino u t quod iaculi mutua armis uno est vos totidemque
| [exhortor](4). Iamdudum Romanae cepi linguisque
| velit accipe idem acutis hoc sparsumque dextra. Minoide sponte collo sequitur
| et intus flere sanguineaque herba, nox Quas adsiduo c u n c t a n t e m tibi;
| enim.
[1] #mille-parva
[2] #salientia-iungit-contra-et
[3] #structa-hinc-vetitus-factum
[4] #tractaque-serpens-arva-quoque

105
md2txt.py
View file

@ -113,6 +113,7 @@ class FrontMatter:
paragraph_spacing: int = 0 paragraph_spacing: int = 0
hyphenate: bool = False hyphenate: bool = False
hyphen_lang: str = "en_US" hyphen_lang: str = "en_US"
figlet_fallback: bool = False
class MarkdownToTxtConverter: class MarkdownToTxtConverter:
@ -127,6 +128,7 @@ class MarkdownToTxtConverter:
self.paragraph_spacing = max(0, self.frontmatter.paragraph_spacing) self.paragraph_spacing = max(0, self.frontmatter.paragraph_spacing)
self.hyphenate = self.frontmatter.hyphenate self.hyphenate = self.frontmatter.hyphenate
self.hyphen_lang = self.frontmatter.hyphen_lang or "en_US" self.hyphen_lang = self.frontmatter.hyphen_lang or "en_US"
self.figlet_fallback = self.frontmatter.figlet_fallback
self.hyphenator: Optional[Hyphenator] self.hyphenator: Optional[Hyphenator]
if self.hyphenate: if self.hyphenate:
if Hyphenator is None: if Hyphenator is None:
@ -322,9 +324,11 @@ class MarkdownToTxtConverter:
return [] return []
width = max(2, len(str(len(code_lines)))) width = max(2, len(str(len(code_lines))))
formatted: List[str] = [] formatted: List[str] = []
style = self._current_style()
margin_indent = " " * max(0, style.margin_left)
for idx, line in enumerate(code_lines, start=1): for idx, line in enumerate(code_lines, start=1):
formatted.append(f" {idx:0{width}d} | {line}") formatted.append(f"{margin_indent}{idx:0{width}d} | {line}")
formatted.append("") formatted.append(margin_indent if margin_indent else "")
return formatted return formatted
def _handle_blockquote(self, line: str, output: List[str]) -> None: def _handle_blockquote(self, line: str, output: List[str]) -> None:
@ -681,14 +685,60 @@ class MarkdownToTxtConverter:
if style_key in {"caps", "title"}: if style_key in {"caps", "title"}:
return self._render_h4_plus(text, style, transform=style_key) return self._render_h4_plus(text, style, transform=style_key)
if level <= 3: if level <= 3:
figlet_lines = self._figlet_render(level, text) figlet_lines = self._render_figlet_heading(level, text, style)
if figlet_lines: if figlet_lines is not None:
max_figlet_width = max((len(line.rstrip()) for line in figlet_lines), default=0) return figlet_lines + [""]
if max_figlet_width <= self._effective_width(style):
styled = self._apply_style_to_lines(figlet_lines, style)
return styled + [""]
return self._render_h4_plus(text, style) 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: def _render_horizontal_rule(self, style: BlockStyle) -> str:
margin_left = min(max(style.margin_left, 0), self.width - 1) margin_left = min(max(style.margin_left, 0), self.width - 1)
available_width = max(1, self._effective_width(style)) available_width = max(1, self._effective_width(style))
@ -920,7 +970,7 @@ class MarkdownToTxtConverter:
self.link_indices[url] = index self.link_indices[url] = index
return 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: if Figlet is None:
return None return None
font_name = getattr(self.frontmatter, f"h{level}_font", "standard") font_name = getattr(self.frontmatter, f"h{level}_font", "standard")
@ -934,10 +984,44 @@ class MarkdownToTxtConverter:
rendered = figlet.renderText(text).rstrip("\n") rendered = figlet.renderText(text).rstrip("\n")
lines = rendered.splitlines() lines = rendered.splitlines()
trimmed_lines = [line.rstrip("\n") for line in lines] 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 None
return [line.rstrip() for line in trimmed_lines] 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]: def _render_h4_plus(self, text: str, style: BlockStyle, transform: str = "caps") -> List[str]:
if transform == "title": if transform == "title":
processed = self._to_title_case(text) 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)), paragraph_spacing=max(0, _parse_int(paragraph_spacing_value, 0)),
hyphenate=_parse_bool(frontmatter.get("hyphenate"), False), hyphenate=_parse_bool(frontmatter.get("hyphenate"), False),
hyphen_lang=(frontmatter.get("hyphen_lang") or "en_US").strip() or "en_US", 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 return fm, remaining

View file

@ -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