97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
TARGETS = [
|
|
ROOT / "backend/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01B_fractions",
|
|
ROOT / "Programme/contenus_pedagogiques/cycle_3/mathematiques/cm1/01_nombres_calcul_resolution/01B_fractions",
|
|
]
|
|
|
|
STYLE_RE = re.compile(r"\.([A-Za-z0-9_-]+)\s*\{([^}]*)\}")
|
|
FONT_RE = re.compile(r"font-size\s*:\s*([0-9.]+)px?")
|
|
FRACTION_GROUP_RE = re.compile(
|
|
r'(?P<prefix><g\b(?=[^>]*class="[^"]*\bfraction-g\b[^"]*")[^>]*>)(?P<body>.*?)(?P<suffix></g>)',
|
|
re.DOTALL,
|
|
)
|
|
TEXT_RE = re.compile(r"(<text\b[^>]*\by=\")[-0-9.]+(\"[^>]*>)")
|
|
LINE_Y_RE = re.compile(r"<line\b[^>]*\by1=\"(?P<y1>[-0-9.]+)\"[^>]*\by2=\"(?P<y2>[-0-9.]+)\"")
|
|
DATA_BOX_RE = re.compile(r'data-box="(?P<x>[-0-9.]+) (?P<y>[-0-9.]+) (?P<w>[-0-9.]+) (?P<h>[-0-9.]+)"')
|
|
|
|
|
|
def parse_class_font_sizes(svg: str) -> dict[str, float]:
|
|
sizes: dict[str, float] = {}
|
|
for class_name, style in STYLE_RE.findall(svg):
|
|
match = FONT_RE.search(style)
|
|
if match:
|
|
sizes[class_name] = float(match.group(1))
|
|
return sizes
|
|
|
|
|
|
def group_font_size(body: str, class_sizes: dict[str, float]) -> float:
|
|
class_match = re.search(r'<text\b[^>]*class="([^"]+)"', body)
|
|
if class_match:
|
|
for class_name in class_match.group(1).split():
|
|
if class_name in class_sizes:
|
|
return class_sizes[class_name]
|
|
direct = re.search(r'<text\b[^>]*font-size="([0-9.]+)"', body)
|
|
if direct:
|
|
return float(direct.group(1))
|
|
return 36.0
|
|
|
|
|
|
def compact_group(match: re.Match[str], class_sizes: dict[str, float]) -> str:
|
|
body = match.group("body")
|
|
line_match = LINE_Y_RE.search(body)
|
|
if not line_match:
|
|
return match.group(0)
|
|
|
|
bar_y = (float(line_match.group("y1")) + float(line_match.group("y2"))) / 2
|
|
size = group_font_size(body, class_sizes)
|
|
numerator_y = bar_y - min(max(8.0, size * 0.38), size * 0.50)
|
|
# SVG text y is a baseline, not the visible top of the digit. The denominator
|
|
# baseline must therefore sit lower so the glyph itself clears the bar.
|
|
denominator_y = bar_y + max(18.0, size * 1.05)
|
|
|
|
replacements = [f"{numerator_y:g}", f"{denominator_y:g}"]
|
|
|
|
def replace_text_y(text_match: re.Match[str]) -> str:
|
|
if not replacements:
|
|
return text_match.group(0)
|
|
return f"{text_match.group(1)}{replacements.pop(0)}{text_match.group(2)}"
|
|
|
|
body = TEXT_RE.sub(replace_text_y, body, count=2)
|
|
|
|
data_box_y = numerator_y - size * 0.85 - 2
|
|
data_box_height = denominator_y + size * 0.25 + 2 - data_box_y
|
|
prefix = DATA_BOX_RE.sub(
|
|
lambda data_match: (
|
|
f'data-box="{data_match.group("x")} {data_box_y:g} '
|
|
f'{data_match.group("w")} {data_box_height:g}"'
|
|
),
|
|
match.group("prefix"),
|
|
count=1,
|
|
)
|
|
return f"{prefix}{body}{match.group('suffix')}"
|
|
|
|
|
|
def compact_svg(path: Path) -> None:
|
|
svg = path.read_text(encoding="utf-8")
|
|
class_sizes = parse_class_font_sizes(svg)
|
|
compacted = FRACTION_GROUP_RE.sub(lambda match: compact_group(match, class_sizes), svg)
|
|
if compacted != svg:
|
|
path.write_text(compacted, encoding="utf-8")
|
|
|
|
|
|
def main() -> None:
|
|
for target in TARGETS:
|
|
for path in sorted(target.rglob("*.svg")):
|
|
compact_svg(path)
|
|
print(f"compacted {path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|