initial commit

This commit is contained in:
jan 2024-02-20 22:23:20 -08:00
commit db6129d356

334
k2t.py Normal file
View File

@ -0,0 +1,334 @@
from typing import Self, Type, Any
import tomllib
from dataclasses import dataclass, fields
from xml.etree import ElementTree
@dataclass(slots=True)
class Entry:
name: str
source: str | None
frame_color: str | None
fill_color: str | None
members: list | None = None
expanded: bool = False
dither_pattern: str = 'C1'
line_style: str = 'C0'
frame_brightness: int = 0
fill_brightness: int = 0
width: int = 1
animation: int = 0
valid: bool = True
visible: bool = True
transparent: bool = False
marked: bool = False
xfill: bool = False
@classmethod
def from_xml(cls: Type[Self], el: ElementTree.Element) -> Self:
assert el.tag in ('properties', 'group-members')
members = []
args: dict[str, Any] = {
'frame_color': None,
'fill_color': None,
'source': None,
}
for child in el:
if not child.text:
continue
tag = child.tag
dtag = tag.replace('-', '_')
if tag in ('frame-color', 'fill-color'):
args[dtag] = child.text[1:]
elif tag in ('dither-pattern', 'line-style', 'source', 'name'):
args[dtag] = child.text
elif tag in ('valid', 'visible', 'transparent', 'marked', 'xfill', 'expanded'):
val = {
'false': False,
'true': True,
}[child.text.lower()]
args[tag] = val
elif tag in ('frame-brightness', 'fill-brightness', 'width', 'animation'):
args[dtag] = int(child.text)
elif tag == 'group-members':
members.append(Entry.from_xml(child))
else:
raise Exception(f'Unexpected tag: {child.tag}')
if not members:
members = None
return cls(members=members, **args)
def to_xml(self, is_member: bool = False) -> ElementTree.Element:
main_tag = 'group-members' if is_member else 'properties'
el = ElementTree.Element(main_tag)
for field in fields(self):
val = getattr(self, field.name)
if val is None or field.name == 'members':
continue
if (val == field.default
and (field not in ('dither-pattern', 'line-style')
or self.source is None
)):
continue
tag = field.name.replace('_', '-')
if tag in ('frame-color', 'fill-color'):
val = '#' + val
if not isinstance(val, str):
val = str(val).lower()
el2 = ElementTree.Element(tag)
el2.text = val
el.append(el2)
if self.members:
for member in self.members:
el.append(member.to_xml(is_member=True))
return el
def to_toml(self, depth: int = 0) -> str:
parts = ('layers',) + ('members',) * depth
s = '[[' + '.'.join(parts) + ']]\n'
for field in fields(self):
val = getattr(self, field.name)
if val == field.default or val is None:
continue
if isinstance(val, str):
vstr = '"' + toml_escape(val) + '"'
elif isinstance(val, bool):
vstr = str(val).lower()
elif isinstance(val, int):
vstr = str(val)
key = field.name.replace('_', '-')
s += f'{key} = {vstr}\n'
s += '\n'
if self.members:
for member in self.members:
s += member.to_toml(depth + 1)
return s
@classmethod
def from_toml(cls: Type[Self], tree: dict) -> Self:
members = []
args: dict[str, Any] = {
'frame_color': None,
'fill_color': None,
'source': None,
}
for key, val in tree.items():
if key != 'members':
args[key.replace('-', '_')] = val
if 'members' in tree:
for child in tree['members']:
members.append(Entry.from_toml(child))
if not members:
members = None
return cls(members=members, **args)
@dataclass(slots=True)
class Pattern:
pattern: str # 2D, Nx8 or Nx16 or Nx32
name: str | None = None
order: int | None = None
@classmethod
def from_xml(cls: Type[Self], el: ElementTree.Element) -> Self:
assert el.tag == 'custom-dither-pattern'
lines = []
args: dict[str, Any] = {}
for child in el:
if not child.text:
continue
tag = child.tag
if tag == 'name':
args[tag] = child.text
elif tag == 'order':
args[tag] = int(child.text)
elif tag == 'pattern':
for line in child:
assert line.tag == 'line'
assert line.text is not None
lines.append(line.text)
else:
raise Exception(f'Unexpected tag: {tag}')
if not lines:
raise Exception('No pattern found in custom-dither-pattern!')
pattern = '\n'.join(lines)
return cls(pattern=pattern, **args)
def to_xml(self) -> ElementTree.Element:
el = ElementTree.Element('custom-dither-pattern')
if self.name is not None:
el2 = ElementTree.Element('name')
el2.text = self.name
el.append(el2)
if self.order is not None:
el2 = ElementTree.Element('order')
el2.text = str(self.order)
el.append(el2)
pat = ElementTree.Element('pattern')
for line in self.pattern.strip().split('\n'):
el2 = ElementTree.Element('line')
el2.text = line
pat.append(el2)
el.append(pat)
return el
def to_toml(self) -> str:
s = '[[patterns]]\n'
if self.name is not None:
s += f'name = "{toml_escape(self.name)}"\n'
if self.order is not None:
s += f'order = {self.order}\n'
s += 'pattern = """\n'
s += self.pattern + '"""\n\n'
return s
@classmethod
def from_toml(cls: Type[Self], tree: dict) -> Self:
lines = tree['pattern'].split('\n')
n_px = len(lines[0])
assert all(len(line) == n_px for line in lines[1:])
return cls(**tree)
@dataclass(slots=True)
class LineStyle:
pattern: str # 1D, up to 32 values
name: str | None = None
order: int | None = None
@classmethod
def from_xml(cls: Type[Self], el: ElementTree.Element) -> Self:
assert el.tag == 'custom-line-style'
args: dict[str, Any] = {}
for child in el:
if not child.text:
continue
tag = child.tag
if tag in ('name', 'pattern'):
args[tag] = child.text
elif tag == 'order':
args[tag] = int(child.text)
else:
raise Exception(f'Unexpected tag: {tag}')
return cls(**args)
def to_xml(self) -> ElementTree.Element:
el = ElementTree.Element('custom-line-style')
if self.name is not None:
el2 = ElementTree.Element('name')
el2.text = self.name
el.append(el2)
if self.order is not None:
el2 = ElementTree.Element('order')
el2.text = str(self.order)
el.append(el2)
el2 = ElementTree.Element('pattern')
el2.text = self.pattern
el.append(el2)
return el
def to_toml(self) -> str:
s = '[[linestyles]]\n'
if self.name is not None:
s += f'name = "{toml_escape(self.name)}"\n'
if self.order is not None:
s += f'order = {self.order}\n'
s += f'pattern = "{self.pattern}"\n\n'
return s
@classmethod
def from_toml(cls: Type[Self], tree: dict) -> Self:
assert len(tree['pattern']) <= 32
return cls(**tree)
def k2t(kfile: str, tfile: str) -> None:
tree = ElementTree.parse(kfile)
root = tree.getroot()
assert root.tag == 'layer-properties'
name = None
entries = []
patterns = []
linestyles = []
for child in root:
if child.tag == 'custom-dither-pattern':
patterns.append(Pattern.from_xml(child))
elif child.tag == 'custom-line-style':
linestyles.append(LineStyle.from_xml(child))
elif child.tag == 'properties':
entries.append(Entry.from_xml(child))
elif child.tag == 'name':
name = child.text
else:
raise Exception(f'Unexpected tag: {child.tag}')
if name is not None:
print(f'Discarding tab name: {name}')
with open(tfile, 'wt') as ff:
for entry in entries:
ff.write(entry.to_toml())
for pattern in patterns:
ff.write(pattern.to_toml())
for linestyle in linestyles:
ff.write(linestyle.to_toml())
def t2k(tfile: str, kfile: str) -> None:
root = ElementTree.Element('layer-properties')
with open(tfile, 'rb') as ff:
ttree = tomllib.load(ff)
entries = [Entry.from_toml(entry)
for entry in ttree['layers']]
patterns = [Pattern.from_toml(pat)
for pat in ttree['patterns']]
linestyles = [LineStyle.from_toml(ls)
for ls in ttree['linestyles']]
for entry in entries:
root.append(entry.to_xml())
for pattern in patterns:
root.append(pattern.to_xml())
for linestyle in linestyles:
root.append(linestyle.to_xml())
ktree = ElementTree.ElementTree(root)
ElementTree.indent(ktree)
ktree.write(kfile)
def toml_escape(string: str) -> str:
return string.replace('\\', '\\\\').replace('"', '\\"')