115 lines
3.4 KiB
Python
115 lines
3.4 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 — backtick enters formatting mode
|
|
# where the parser recognizes `[` as link start
|
|
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)
|