diff --git a/manimpango/cmanimpango.pyx b/manimpango/cmanimpango.pyx
index 6369f4dd..7b4b5e9a 100644
--- a/manimpango/cmanimpango.pyx
+++ b/manimpango/cmanimpango.pyx
@@ -19,6 +19,8 @@ class TextSetting:
weight: str,
line_num = -1,
color: str = None,
+ font_features: str = None,
+ font_variant: str = None,
):
self.start = start
self.end = end
@@ -27,6 +29,8 @@ class TextSetting:
self.weight = weight
self.line_num = line_num
self.color = color
+ self.font_features = font_features
+ self.font_variant = font_variant
def text2svg(
@@ -117,15 +121,27 @@ def text2svg(
pango_cairo_update_layout(cr,layout)
markup = escape(text_str)
+
+ # Build span attributes
+ span_attrs = []
if color:
- markup = (f"{markup}")
+ span_attrs.append(f"color='{color}'")
+ if setting.font_features:
+ span_attrs.append(f"font_features='{setting.font_features}'")
+ if setting.font_variant:
+ span_attrs.append(f"font_variant='{setting.font_variant}'")
+ if disable_liga:
+ span_attrs.append("font_features='liga=0,dlig=0,clig=0,hlig=0'")
+
+ if span_attrs:
+ attrs_str = ' '.join(span_attrs)
+ markup = f"{markup}"
if MarkupUtils.validate(markup):
cairo_destroy(cr)
cairo_surface_destroy(surface)
g_object_unref(layout)
- raise ValueError(f"Pango cannot recognize your color '{color}' for text '{text_str}'.")
- if disable_liga:
- markup = f"{markup}"
+ raise ValueError(f"Pango markup validation failed for text '{text_str}' with attributes: {attrs_str}")
+
pango_layout_set_markup(layout, markup.encode('utf-8'), -1)
pango_cairo_show_layout(cr, layout)
pango_layout_get_size(layout,&temp_width,NULL)
@@ -150,6 +166,105 @@ def text2svg(
return file_name
class MarkupUtils:
+ @staticmethod
+ def build_font_features(
+ liga: bool = None,
+ dlig: bool = None,
+ clig: bool = None,
+ hlig: bool = None,
+ kern: bool = None,
+ swsh: int = None,
+ calt: bool = None,
+ onum: bool = None,
+ tnum: bool = None,
+ frac: bool = None,
+ afrc: bool = None,
+ custom_features: str = None
+ ) -> str:
+ """Build a font_features string from individual feature parameters.
+
+ Parameters
+ ==========
+ liga : bool, optional
+ Common ligatures (default True in most fonts)
+ dlig : bool, optional
+ Discretionary ligatures
+ clig : bool, optional
+ Contextual ligatures
+ hlig : bool, optional
+ Historical ligatures
+ kern : bool, optional
+ Kerning (default True in most fonts)
+ swsh : int, optional
+ Swash (stylistic set number, 1-20)
+ calt : bool, optional
+ Contextual alternates
+ onum : bool, optional
+ Oldstyle figures
+ tnum : bool, optional
+ Tabular figures
+ frac : bool, optional
+ Fractions
+ afrc : bool, optional
+ Alternative fractions
+ custom_features : str, optional
+ Additional custom OpenType features
+
+ Returns
+ =======
+ str
+ Formatted font_features string for Pango markup
+ """
+ features = []
+
+ if liga is not None:
+ features.append(f"liga={'1' if liga else '0'}")
+ if dlig is not None:
+ features.append(f"dlig={'1' if dlig else '0'}")
+ if clig is not None:
+ features.append(f"clig={'1' if clig else '0'}")
+ if hlig is not None:
+ features.append(f"hlig={'1' if hlig else '0'}")
+ if kern is not None:
+ features.append(f"kern={'1' if kern else '0'}")
+ if swsh is not None:
+ features.append(f"swsh={swsh}")
+ if calt is not None:
+ features.append(f"calt={'1' if calt else '0'}")
+ if onum is not None:
+ features.append(f"onum={'1' if onum else '0'}")
+ if tnum is not None:
+ features.append(f"tnum={'1' if tnum else '0'}")
+ if frac is not None:
+ features.append(f"frac={'1' if frac else '0'}")
+ if afrc is not None:
+ features.append(f"afrc={'1' if afrc else '0'}")
+
+ if custom_features:
+ features.append(custom_features)
+
+ return ', '.join(features) if features else ''
+
+ @staticmethod
+ def validate_font_variant(variant: str) -> bool:
+ """Validate a font_variant value.
+
+ Parameters
+ ==========
+ variant : str
+ The font variant to validate
+
+ Returns
+ =======
+ bool
+ True if valid, False otherwise
+ """
+ valid_variants = {
+ 'normal', 'small-caps', 'all-small-caps',
+ 'petite-caps', 'all-petite-caps', 'unicase', 'title-caps'
+ }
+ return variant in valid_variants
+
@staticmethod
def validate(markup: str) -> str:
"""Validates whether markup is a valid Markup
@@ -205,6 +320,8 @@ class MarkupUtils:
line_spacing: float | None = None,
alignment: Alignment | None = None,
pango_width: int | None = None,
+ font_features: str | None = None,
+ font_variant: str | None = None,
) -> str:
"""Render an SVG file from a :class:`manim.mobject.svg.text_mobject.MarkupText` object."""
cdef cairo_surface_t* surface
@@ -218,8 +335,18 @@ class MarkupUtils:
file_name_bytes = file_name.encode("utf-8")
+ # Build span attributes for global text formatting
+ span_attrs = []
+ if font_features:
+ span_attrs.append(f"font_features='{font_features}'")
+ if font_variant:
+ span_attrs.append(f"font_variant='{font_variant}'")
if disable_liga:
- text_bytes = f"{text}".encode("utf-8")
+ span_attrs.append("font_features='liga=0,dlig=0,clig=0,hlig=0'")
+
+ if span_attrs:
+ attrs_str = ' '.join(span_attrs)
+ text_bytes = f"{text}".encode("utf-8")
else:
text_bytes = text.encode("utf-8")