import re def _get_visible_length(text: str) -> int: """ Calculate the visible length of text, ignoring RPG Maker color codes. Args: text (str): The text to measure Returns: int: The length of the text excluding color codes """ # Remove all color codes like \c[5] or \C[35], and also ignore \\! and \\. cleaned_text = re.sub(r'[\\]+[cC]\[\d+\]', '', text) # Remove \\! and \\. cleaned_text = re.sub(r'[\\]+.', '', cleaned_text) return len(cleaned_text) def wrapText(text: str, width: int) -> str: """ Wrap text to the specified width, preserving RPG Maker color codes. Args: text (str): The text to wrap width (int): The maximum number of characters per line Returns: str: The wrapped text with lines separated by \n """ if not text: return "" # Split on double newlines (\n\n or \\n\\n) import re segments = re.split(r'(?:\n\n|\\n\\n)', text) wrapped_segments = [] for segment in segments: words = segment.split() lines = [] current_line = [] current_length = 0 for word in words: word_length = _get_visible_length(word) if current_length + word_length + len(current_line) <= width: current_line.append(word) current_length += word_length else: if current_line: lines.append(" ".join(current_line)) current_line = [word] current_length = word_length if current_line: lines.append(" ".join(current_line)) wrapped_segments.append("\n".join(lines)) # Rejoin with double newlines to preserve hard breaks return "\n\n".join(wrapped_segments) def wrapSGDesc(text: str, width: int) -> str: """Structured word-wrap for SG plugin description blocks. Unlike wrapText, this function understands the layout of SG info-box content: ◆ / ・ / • / ● lines are always kept on their own line as section headers; the body text immediately following is word-wrapped to *width*. Paragraph breaks (\\n\\n) are preserved between sections. """ HEADER_CHARS = ("◆", "・", "•", "●") paragraphs = text.split("\n\n") wrapped_paragraphs = [] for para in paragraphs: lines = para.split("\n") out_parts = [] body_buf: list[str] = [] def flush_body(): if not body_buf: return words = " ".join(body_buf).split() cur_line: list[str] = [] cur_len = 0 for word in words: wl = _get_visible_length(word) if cur_len + wl + len(cur_line) <= width: cur_line.append(word) cur_len += wl else: if cur_line: out_parts.append(" ".join(cur_line)) cur_line = [word] cur_len = wl if cur_line: out_parts.append(" ".join(cur_line)) body_buf.clear() for line in lines: stripped = line.strip() if not stripped: continue if stripped.startswith(HEADER_CHARS): flush_body() # Word-wrap the header line itself — the bullet stays on the # first wrapped line; continuation lines are plain body lines. words = stripped.split() cur_line: list[str] = [] cur_len = 0 for word in words: wl = _get_visible_length(word) if cur_len + wl + len(cur_line) <= width: cur_line.append(word) cur_len += wl else: if cur_line: out_parts.append(" ".join(cur_line)) cur_line = [word] cur_len = wl if cur_line: out_parts.append(" ".join(cur_line)) else: body_buf.append(stripped) flush_body() wrapped_paragraphs.append("\n".join(out_parts)) return "\n\n".join(wrapped_paragraphs)