Files
micronomicon/backend/uframe/emit_micron.py
2026-04-01 10:13:14 +02:00

114 lines
3.3 KiB
Python

"""Micron emitter — read CharGrid and output Micron markup with style tags.
Scans each line left-to-right, tracks style state, and opens/closes
Micron format codes at style transitions. Box-drawing characters pass
through as literal text.
Micron format reference:
`!bold`! `*italic`* `_underline`_
`Fhex text`f `Bhex text`b
`c center`a `r right`a
[label`dest] [label`dest.mu]
>H1 >>H2 >>>H3
"""
from __future__ import annotations
from uframe.grid import CharGrid, CellStyle
from uframe.ir import Page, Heading, HeadingLevel
def _emit_style_open(style: CellStyle) -> str:
"""Emit Micron opening tags for a style."""
tags: list[str] = []
if style.bold:
tags.append("`!")
if style.italic:
tags.append("`*")
if style.underline:
tags.append("`_")
if style.fg:
tags.append(f"`F{style.fg}")
if style.bg:
tags.append(f"`B{style.bg}")
return "".join(tags)
def _emit_style_close(style: CellStyle) -> str:
"""Emit Micron closing tags for a style (in reverse order)."""
tags: list[str] = []
if style.bg:
tags.append("`b")
if style.fg:
tags.append("`f")
if style.underline:
tags.append("`_")
if style.italic:
tags.append("`*")
if style.bold:
tags.append("`!")
return "".join(tags)
_EMPTY_STYLE = CellStyle()
def emit_micron(grid: CharGrid, page_title: str = "") -> str:
"""Emit the CharGrid as Micron markup."""
lines: list[str] = []
for row in range(grid.height):
line_parts: list[str] = []
cur_style = _EMPTY_STYLE
in_link: str | None = None
for col in range(grid.width):
cell = grid.cells[row][col]
ch = cell.char
style = cell.style
link = cell.link
# Handle link transitions
if link != in_link:
if in_link is not None:
# Close previous link: [label`dest]
line_parts.append(f"`{in_link}]")
if link is not None:
# Close any open style before link
if cur_style != _EMPTY_STYLE:
line_parts.append(_emit_style_close(cur_style))
cur_style = _EMPTY_STYLE
# Open new link
line_parts.append("[")
in_link = link
# Handle style transitions (not inside links — links handle their own style)
if link is None and style != cur_style:
# Close previous style
if cur_style != _EMPTY_STYLE:
line_parts.append(_emit_style_close(cur_style))
# Open new style
if style != _EMPTY_STYLE:
line_parts.append(_emit_style_open(style))
cur_style = style
line_parts.append(ch)
# Close any trailing link: [label`dest]
if in_link is not None:
line_parts.append(f"`{in_link}]")
in_link = None
# Close any trailing style
if cur_style != _EMPTY_STYLE:
line_parts.append(_emit_style_close(cur_style))
line = "".join(line_parts).rstrip()
lines.append(line)
# Strip trailing blank lines
while lines and not lines[-1]:
lines.pop()
return "\n".join(lines)