Spaces:
Paused
Paused
Update app/font_engine.py
Browse files- app/font_engine.py +59 -30
app/font_engine.py
CHANGED
|
@@ -199,6 +199,36 @@ def contours_to_svg_path(contours: list[ContourOps]) -> str:
|
|
| 199 |
# Aksan kaynağı bulma (standalone glyph / donor / synthetic)
|
| 200 |
# --------------------------------------------------------------------------
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
def _find_standalone_accent(font: TTFont, cmap: dict[int, str], accent: str) -> Optional[str]:
|
| 203 |
for cp in R.ACCENT_UNICODE_CANDIDATES.get(accent, []):
|
| 204 |
name = cmap.get(cp)
|
|
@@ -224,17 +254,8 @@ def _extract_donor_accent(font: TTFont, cmap: dict[int, str], accent: str) -> Op
|
|
| 224 |
contours = decompose_glyph(font, name)
|
| 225 |
if len(contours) < 2:
|
| 226 |
continue # tek konturlu glyph'ten aksan ayıklanamaz
|
| 227 |
-
picked =
|
| 228 |
-
|
| 229 |
-
b = contours_bounds([c])
|
| 230 |
-
if not b:
|
| 231 |
-
continue
|
| 232 |
-
_, c_ymin, _, c_ymax = b
|
| 233 |
-
if placement == "above" and c_ymin > xh * 0.72:
|
| 234 |
-
picked.append(c)
|
| 235 |
-
elif placement == "below" and c_ymax < upm * 0.08:
|
| 236 |
-
picked.append(c)
|
| 237 |
-
if picked:
|
| 238 |
return donor_char, picked
|
| 239 |
return None
|
| 240 |
|
|
@@ -246,25 +267,39 @@ def resolve_accent(font: TTFont, cmap: dict[int, str], accent: str, base_bounds:
|
|
| 246 |
"contours": [...], "bounds": (xmin,ymin,xmax,ymax) | None,
|
| 247 |
"warning": str | None}
|
| 248 |
"""
|
|
|
|
|
|
|
| 249 |
standalone = _find_standalone_accent(font, cmap, accent)
|
| 250 |
if standalone:
|
| 251 |
contours = decompose_glyph(font, standalone)
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
donor_result = _extract_donor_accent(font, cmap, accent)
|
| 258 |
if donor_result:
|
| 259 |
donor_char, contours = donor_result
|
|
|
|
|
|
|
|
|
|
| 260 |
return {
|
| 261 |
"kind": "donor", "ref": donor_char, "contours": contours,
|
| 262 |
-
"bounds": contours_bounds(contours),
|
| 263 |
-
"warning": f"Standalone '{accent}' glyph'i bulunamadı; şekil '{donor_char}' karakterinden otomatik ayıklandı.",
|
| 264 |
}
|
| 265 |
|
| 266 |
base_width = (base_bounds[2] - base_bounds[0]) if base_bounds else units_per_em(font) * 0.5
|
| 267 |
-
upm = units_per_em(font)
|
| 268 |
raw_contours = synthetic_accent_contours(accent, base_width, upm)
|
| 269 |
contours: list[ContourOps] = []
|
| 270 |
for poly in raw_contours:
|
|
@@ -273,10 +308,13 @@ def resolve_accent(font: TTFont, cmap: dict[int, str], accent: str, base_bounds:
|
|
| 273 |
ops.append(("lineTo", (pt,)))
|
| 274 |
ops.append(("closePath", ()))
|
| 275 |
contours.append(ops)
|
|
|
|
|
|
|
|
|
|
| 276 |
return {
|
| 277 |
"kind": "synthetic", "ref": accent, "contours": contours,
|
| 278 |
"bounds": contours_bounds(contours),
|
| 279 |
-
"warning":
|
| 280 |
}
|
| 281 |
|
| 282 |
|
|
@@ -292,17 +330,8 @@ def resolve_accent_by_source(font: TTFont, cmap: dict[int, str], accent_source:
|
|
| 292 |
xh = x_height(font)
|
| 293 |
placement = "below" if accent_type == "cedilla" else "above"
|
| 294 |
contours = decompose_glyph(font, name)
|
| 295 |
-
picked =
|
| 296 |
-
|
| 297 |
-
b = contours_bounds([c])
|
| 298 |
-
if not b:
|
| 299 |
-
continue
|
| 300 |
-
_, c_ymin, _, c_ymax = b
|
| 301 |
-
if placement == "above" and c_ymin > xh * 0.72:
|
| 302 |
-
picked.append(c)
|
| 303 |
-
elif placement == "below" and c_ymax < upm * 0.08:
|
| 304 |
-
picked.append(c)
|
| 305 |
-
if picked:
|
| 306 |
return {"kind": "donor", "ref": donor_char, "contours": picked, "bounds": contours_bounds(picked), "warning": None}
|
| 307 |
return resolve_accent(font, cmap, accent_type, base_bounds)
|
| 308 |
if accent_source.startswith(R.SYNTHETIC_PREFIX):
|
|
|
|
| 199 |
# Aksan kaynağı bulma (standalone glyph / donor / synthetic)
|
| 200 |
# --------------------------------------------------------------------------
|
| 201 |
|
| 202 |
+
def _is_plausible_accent_bounds(bounds: Optional[Bounds], upm: float) -> bool:
|
| 203 |
+
"""Bir aksan işaretinin akla yatkın boyutta olup olmadığını kontrol eder.
|
| 204 |
+
|
| 205 |
+
Bazı fontlarda 'cedilla'/'dieresis' gibi standart aksan glyph adları
|
| 206 |
+
yanlışlıkla alakasız, tam boyutlu bir şekle (ör. hatalı bir precomposed
|
| 207 |
+
harfe) işaret edebiliyor. Gerçek aksan işaretleri her zaman harfe göre
|
| 208 |
+
küçüktür (en büyük gerçek örnekler bile ~%30 UPM civarında); bu yüzden
|
| 209 |
+
%45 UPM üzerindeki bir "aksan" güvenilmez kabul edilip yoksayılır."""
|
| 210 |
+
if not bounds:
|
| 211 |
+
return False
|
| 212 |
+
width = bounds[2] - bounds[0]
|
| 213 |
+
height = bounds[3] - bounds[1]
|
| 214 |
+
limit = upm * 0.45
|
| 215 |
+
return width <= limit and height <= limit
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def _pick_accent_contours(contours: list[ContourOps], placement: str, upm: float, xh: float) -> list[ContourOps]:
|
| 219 |
+
picked = []
|
| 220 |
+
for c in contours:
|
| 221 |
+
b = contours_bounds([c])
|
| 222 |
+
if not b:
|
| 223 |
+
continue
|
| 224 |
+
_, c_ymin, _, c_ymax = b
|
| 225 |
+
if placement == "above" and c_ymin > xh * 0.72:
|
| 226 |
+
picked.append(c)
|
| 227 |
+
elif placement == "below" and c_ymax < upm * 0.08:
|
| 228 |
+
picked.append(c)
|
| 229 |
+
return picked
|
| 230 |
+
|
| 231 |
+
|
| 232 |
def _find_standalone_accent(font: TTFont, cmap: dict[int, str], accent: str) -> Optional[str]:
|
| 233 |
for cp in R.ACCENT_UNICODE_CANDIDATES.get(accent, []):
|
| 234 |
name = cmap.get(cp)
|
|
|
|
| 254 |
contours = decompose_glyph(font, name)
|
| 255 |
if len(contours) < 2:
|
| 256 |
continue # tek konturlu glyph'ten aksan ayıklanamaz
|
| 257 |
+
picked = _pick_accent_contours(contours, placement, upm, xh)
|
| 258 |
+
if picked and _is_plausible_accent_bounds(contours_bounds(picked), upm):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
return donor_char, picked
|
| 260 |
return None
|
| 261 |
|
|
|
|
| 267 |
"contours": [...], "bounds": (xmin,ymin,xmax,ymax) | None,
|
| 268 |
"warning": str | None}
|
| 269 |
"""
|
| 270 |
+
upm = units_per_em(font)
|
| 271 |
+
rejected_standalone: Optional[str] = None
|
| 272 |
standalone = _find_standalone_accent(font, cmap, accent)
|
| 273 |
if standalone:
|
| 274 |
contours = decompose_glyph(font, standalone)
|
| 275 |
+
bounds = contours_bounds(contours)
|
| 276 |
+
if _is_plausible_accent_bounds(bounds, upm):
|
| 277 |
+
return {
|
| 278 |
+
"kind": "glyph", "ref": standalone, "contours": contours,
|
| 279 |
+
"bounds": bounds, "warning": None,
|
| 280 |
+
}
|
| 281 |
+
# Glyph var ama bir aksan işareti için mantıksız büyük (muhtemelen
|
| 282 |
+
# yanlış adlandırılmış/bozuk) - yoksay, donor/sentetik'e devam et.
|
| 283 |
+
rejected_standalone = standalone
|
| 284 |
+
|
| 285 |
+
size_note = (
|
| 286 |
+
f"Fontta '{accent}' adıyla bir glyph var ama boyutu bir aksan işareti için "
|
| 287 |
+
f"mantıksız derecede büyük (muhtemelen yanlış adlandırılmış/bozuk bir glyph), bu yüzden yoksayıldı. "
|
| 288 |
+
if rejected_standalone else ""
|
| 289 |
+
)
|
| 290 |
|
| 291 |
donor_result = _extract_donor_accent(font, cmap, accent)
|
| 292 |
if donor_result:
|
| 293 |
donor_char, contours = donor_result
|
| 294 |
+
warning = size_note + f"'{donor_char}' karakterinden otomatik ayıklanan şekil kullanıldı."
|
| 295 |
+
if not size_note:
|
| 296 |
+
warning = f"Standalone '{accent}' glyph'i bulunamadı; şekil '{donor_char}' karakterinden otomatik ayıklandı."
|
| 297 |
return {
|
| 298 |
"kind": "donor", "ref": donor_char, "contours": contours,
|
| 299 |
+
"bounds": contours_bounds(contours), "warning": warning,
|
|
|
|
| 300 |
}
|
| 301 |
|
| 302 |
base_width = (base_bounds[2] - base_bounds[0]) if base_bounds else units_per_em(font) * 0.5
|
|
|
|
| 303 |
raw_contours = synthetic_accent_contours(accent, base_width, upm)
|
| 304 |
contours: list[ContourOps] = []
|
| 305 |
for poly in raw_contours:
|
|
|
|
| 308 |
ops.append(("lineTo", (pt,)))
|
| 309 |
ops.append(("closePath", ()))
|
| 310 |
contours.append(ops)
|
| 311 |
+
warning = size_note + "Fontta uygun bir bileşen bulunamadığından yerleşik basit bir vektör şekli kullanıldı. Görsel uyum için elle bir aksan glyph'i seçmeniz önerilir."
|
| 312 |
+
if not size_note:
|
| 313 |
+
warning = f"Fontta '{accent}' için hiçbir bileşen bulunamadı; yerleşik basit bir vektör şekli kullanıldı. Görsel uyum için elle bir aksan glyph'i seçmeniz önerilir."
|
| 314 |
return {
|
| 315 |
"kind": "synthetic", "ref": accent, "contours": contours,
|
| 316 |
"bounds": contours_bounds(contours),
|
| 317 |
+
"warning": warning,
|
| 318 |
}
|
| 319 |
|
| 320 |
|
|
|
|
| 330 |
xh = x_height(font)
|
| 331 |
placement = "below" if accent_type == "cedilla" else "above"
|
| 332 |
contours = decompose_glyph(font, name)
|
| 333 |
+
picked = _pick_accent_contours(contours, placement, upm, xh)
|
| 334 |
+
if picked and _is_plausible_accent_bounds(contours_bounds(picked), upm):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
return {"kind": "donor", "ref": donor_char, "contours": picked, "bounds": contours_bounds(picked), "warning": None}
|
| 336 |
return resolve_accent(font, cmap, accent_type, base_bounds)
|
| 337 |
if accent_source.startswith(R.SYNTHETIC_PREFIX):
|