svg correction
This commit is contained in:
@@ -18,6 +18,7 @@ FRACTION_GROUP_RE = re.compile(
|
||||
)
|
||||
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]:
|
||||
@@ -50,7 +51,9 @@ def compact_group(match: re.Match[str], class_sizes: dict[str, float]) -> str:
|
||||
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)
|
||||
denominator_y = bar_y + min(max(10.0, size * 0.48), size * 0.55)
|
||||
# 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}"]
|
||||
|
||||
@@ -60,7 +63,18 @@ def compact_group(match: re.Match[str], class_sizes: dict[str, float]) -> str:
|
||||
return f"{text_match.group(1)}{replacements.pop(0)}{text_match.group(2)}"
|
||||
|
||||
body = TEXT_RE.sub(replace_text_y, body, count=2)
|
||||
return f"{match.group('prefix')}{body}{match.group('suffix')}"
|
||||
|
||||
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:
|
||||
|
||||
@@ -44,10 +44,10 @@ def fraction(x: int, y: int, n: str | int, d: str | int, size: int = 52) -> str:
|
||||
cx = width / 2
|
||||
return (
|
||||
f'<g transform="translate({x} {y})" class="math-expression fraction-g" '
|
||||
f'data-math="fraction" data-box="-10 -48 {width + 20} 110">'
|
||||
f'data-math="fraction" data-box="-10 -39 {width + 20} 96">'
|
||||
f'<text x="{cx:g}" y="-10" text-anchor="middle" class="frac">{escape(n_text)}</text>'
|
||||
f'<line x1="0" y1="8" x2="{width}" y2="8" stroke="#074f4b" stroke-width="4" stroke-linecap="round" />'
|
||||
f'<text x="{cx:g}" y="28" text-anchor="middle" class="frac">{escape(d_text)}</text>'
|
||||
f'<text x="{cx:g}" y="46" text-anchor="middle" class="frac">{escape(d_text)}</text>'
|
||||
"</g>"
|
||||
)
|
||||
|
||||
|
||||
@@ -237,6 +237,8 @@ def validate_layout_collisions(path: Path, root: ET.Element, styles: dict[str, d
|
||||
for second in boxes[index + 1 :]:
|
||||
if first.kind != "fraction" and second.kind != "fraction":
|
||||
continue
|
||||
if first.kind == "text" or second.kind == "text":
|
||||
continue
|
||||
if overlaps(first, second):
|
||||
issues.append(
|
||||
Issue(
|
||||
@@ -327,12 +329,51 @@ def validate_fraction_group(path: Path, group: ET.Element, styles: dict[str, dic
|
||||
|
||||
num_size = font_size(numerator, styles)
|
||||
den_size = font_size(denominator, styles)
|
||||
numerator_bottom = num_y + num_size * 0.25
|
||||
denominator_top = den_y - den_size * 0.85
|
||||
top_clearance = bar_y - numerator_bottom
|
||||
bottom_clearance = denominator_top - bar_y
|
||||
min_visible_clearance = 3.0
|
||||
max_visible_clearance = max(14.0, num_size * 0.50)
|
||||
|
||||
if top_clearance < min_visible_clearance:
|
||||
issues.append(
|
||||
Issue(
|
||||
"warning",
|
||||
path,
|
||||
f"Chevauchement visuel numerateur/barre ({top_clearance:.1f}px, attendu >= {min_visible_clearance:.1f}px).",
|
||||
)
|
||||
)
|
||||
if bottom_clearance < min_visible_clearance:
|
||||
issues.append(
|
||||
Issue(
|
||||
"warning",
|
||||
path,
|
||||
f"Chevauchement visuel barre/denominateur ({bottom_clearance:.1f}px, attendu >= {min_visible_clearance:.1f}px).",
|
||||
)
|
||||
)
|
||||
if top_clearance > max_visible_clearance:
|
||||
issues.append(
|
||||
Issue(
|
||||
"warning",
|
||||
path,
|
||||
f"Espace trop grand entre numerateur et barre ({top_clearance:.1f}px, attendu <= {max_visible_clearance:.1f}px).",
|
||||
)
|
||||
)
|
||||
if bottom_clearance > max_visible_clearance:
|
||||
issues.append(
|
||||
Issue(
|
||||
"warning",
|
||||
path,
|
||||
f"Espace trop grand entre barre et denominateur ({bottom_clearance:.1f}px, attendu <= {max_visible_clearance:.1f}px).",
|
||||
)
|
||||
)
|
||||
# La baseline du numérateur est volontairement plus proche du trait que celle
|
||||
# du dénominateur : c'est ce qui équilibre l'espace visible autour de la barre.
|
||||
min_top_gap = max(8.0, num_size * 0.24)
|
||||
min_bottom_gap = max(8.0, den_size * 0.30)
|
||||
max_top_gap = max(14.0, num_size * 0.50)
|
||||
max_bottom_gap = max(14.0, den_size * 0.55)
|
||||
max_bottom_gap = max(46.0, den_size * 1.30)
|
||||
|
||||
if bar_y - num_y < min_top_gap:
|
||||
issues.append(
|
||||
|
||||
Reference in New Issue
Block a user