"""Spine atlas 文本的解析与页名规范化。 .atlas 是文本格式:**第一行是贴图页文件名**,接着 `size:` / `filter:` / `format:` / `scale:` 等页面头,之后才是各区域的 `bounds` / `offsets` / `rotate`。换页就是再来一行页文件名。 两种书写风格都要吃(同一批页面里都存在): size:498,330 ← 紧凑式(nico-tea / zhidong-wonder / kv45) size: 256, 256 ← 带空格式(get-memory) 抓取期我们只关心两件事:**这具骨架需要哪些贴图页**,以及**页名能不能原样落盘** (运行时按第一行去请求贴图页,所以文件名必须与第一行逐字一致)。 """ from __future__ import annotations import re from dataclasses import dataclass, field __all__ = ["AtlasInfo", "parse", "page_names", "retarget_pages"] _PAGE_EXT = re.compile(r"\.(png|webp|jpg|jpeg)$", re.IGNORECASE) _PAGE_HEAD = re.compile(r"^(size|format|filter|repeat|pma|scale)\s*:", re.IGNORECASE) _REGION_ATTR = re.compile( r"^(bounds|offsets|rotate|xy|orig|index|split|pad|width|height)\s*:", re.IGNORECASE ) @dataclass class AtlasInfo: """一具骨架的 atlas 摘要。""" pages: list[str] = field(default_factory=list) regions: list[str] = field(default_factory=list) region_pages: list[str | None] = field(default_factory=list) @property def multi_page(self) -> bool: return len(self.pages) > 1 def parse(text: str) -> AtlasInfo: """解析 atlas 文本。无法识别的行按区域名处理(atlas 里非属性行就是区域名)。""" info = AtlasInfo() current: str | None = None for raw in text.splitlines(): line = raw.strip() if not line: current = None continue if ":" not in line and _PAGE_EXT.search(line): current = line info.pages.append(line) continue if _PAGE_HEAD.match(line) or _REGION_ATTR.match(line): continue info.regions.append(line) info.region_pages.append(current) return info def page_names(text: str) -> list[str]: """只要贴图页名(顺序即 atlas 里声明的顺序)。""" return parse(text).pages def retarget_pages(text: str, mapping: dict[str, str]) -> str: """把 atlas 里的页名按 mapping 改写(只改整行的页名行,不碰区域名)。""" out: list[str] = [] for raw in text.splitlines(): line = raw.strip() if line and ":" not in line and _PAGE_EXT.search(line) and line in mapping: out.append(mapping[line]) else: out.append(raw) tail = "\n" if text.endswith("\n") else "" return "\n".join(out) + tail