"""Native optical routes, MATH/spacing and final metric boundaries.""" from fractions import Fraction import large_variant_policy as L import mapping as U import positioning from constructions import compose_glyph from windows_metrics import assign_windows def install_initial_math(sources, accents): ACC_CENTER = accents.center SCRIPTSCRIPT_RATIO = sources.rules.scriptscript_ratio SCRIPT_RATIO = sources.rules.script_ratio SOURCE_SKEW = dict(sources.rules.skewchar_by_tag) accent_glyphs = accents.glyphs fonts = sources.faces imported = sources.imported out = sources.font SKEW = SOURCE_SKEW topaccent = {} for tag in tuple(SOURCE_SKEW): if tag not in fonts: continue _, tfm, _ = fonts[tag] sk = SKEW[tag] for (tg, slot), gname in list(imported.items()): if tg != tag or slot not in tfm.chars: continue w = tfm.chars[slot]["wd"] ic = tfm.chars[slot].get("ic") or 0.0 kern = tfm.kerns.get((slot, sk), 0.0) if sk is not None else 0.0 # TeX centers over the accentee width plus # CHARIC, then adds skew kern. topaccent[gname] = round(((w + ic) / 2 + kern) * 1000) # Combining accents attach at the ink center. for gname in accent_glyphs: topaccent[gname] = ACC_CENTER # Other glyphs attach at the advance center. for gname in list(imported.values()): if gname not in topaccent: g = out[gname] topaccent[gname] = g.width // 2 # MATH constants. sy_fd = fonts["syt"][1].fontdimen ex_fd = fonts["exa"][1].fontdimen # Only mapped operator pairs define this threshold. The catalog/plan # already preflight both slots; unrelated NEXTLARGER chains are # excluded. operator_sizes = [ tuple( fonts["exa"][1].chars[slot]["ht"] + fonts["exa"][1].chars[slot]["dp"] for slot in (text, display) ) for text, (_, _, display) in U.EXA_OPS.items() ] C = compute_constants( sy_fd, ex_fd, script_sy=fonts["syt_s"][1].fontdimen, script_ratio=SCRIPT_RATIO, scriptscript_ratio=SCRIPTSCRIPT_RATIO, operator_sizes=operator_sizes, cap_height=fonts["mit"][1].chars[0x41]["ht"], quad=out.em, ) MINOVL = C.pop("MinConnectorOverlap") FF_NAMES = { "FractionNumDisplayStyleGapMin": "FractionNumeratorDisplayStyleGapMin", "FractionDenomDisplayStyleGapMin": ( "FractionDenominatorDisplayStyleGapMin" ), } for k, v in C.items(): try: setattr(out.math, FF_NAMES.get(k, k), int(v)) except AttributeError as exc: # Only these scalars are restored from # the snapshot by font_geometry. if k not in ( "ScriptPercentScaleDown", "ScriptScriptPercentScaleDown", "SpaceAfterScript", ): raise ValueError(f"MATH constant {k}: {exc}") from exc except Exception as exc: raise ValueError(f"MATH constant {k}: {exc}") from exc out.math.MinConnectorOverlap = MINOVL # Per-glyph MATH data. for gname, ta in topaccent.items(): try: out[gname].topaccent = ta except Exception as exc: raise ValueError(f"TopAccent {gname}: {exc}") from exc return C, MINOVL def assign_private_codepoints(sources): alias_unicode = sources.alias_unicode def compose(*args): return compose_glyph(sources, *args) imported = sources.imported out = sources.font uni_owner = sources.uni_owner alias_unicode(uni_owner[0x003A], 0x2236) # Compose proportion from two colons. if 0x2237 not in uni_owner and 0x003A in uni_owner: c = uni_owner[0x003A] cw = out[c].width compose(0x2237, None, c, [(0, 0), (cw + 167, 0)], 2 * cw + 167) # Expose private alternatives for Word, where cv/salt # controls are unavailable. PUA = 0xE000 def pua(gname): nonlocal PUA if gname not in out: return g = out[gname] if g.unicode == -1: g.unicode = PUA else: g.altuni = (g.altuni or ()) + ((PUA, -1, 0),) PUA += 1 if ("mit", 180) in imported: pua(imported[("mit", 180)]) # U+E000 z swash if ("ams", 191) in imported: pua(imported[("ams", 191)]) # U+E001 varnothing PUA = 0xE010 for slot, (u, nm) in sorted(U.alpha_map("curly").items()): if ("curly", slot) in imported: pua(imported[("curly", slot)]) # U+E010.. curly for base, tag in ( (0xE100, "bbd"), (0xE140, "bbi"), (0xE180, "hrb"), (0xE1C0, "hrbd"), (0xE200, "hbi"), ): PUA = base for slot, (u, nm) in sorted(U.alpha_map("bb").items()): if (tag, slot) in imported: pua(imported[(tag, slot)]) # Keep private assignments fixed across editions; # missing glyphs leave holes. PUA_FIXED = { 0xE23E: "lhook", 0xE23F: "rhook", 0xE240: "varbeta.it", 0xE241: "vardelta.it", 0xE242: "upvardelta", 0xE243: "dbar.it", 0xE244: "updbar", 0xE245: "negationslash", 0xE246: "mapstochar", 0xE247: "tie.sy", 0xE248: "compose", 0xE249: "Relbar", 0xE24A: "wwbar", 0xE24B: "dotup.accent", 0xE24C: "ddotup.accent", 0xE24D: "smallint", 0xE24E: "wbar", 0xE24F: "what", 0xE250: "wtilde", 0xE251: "wcheck", 0xE252: "clubshaded", 0xE253: "spadeshaded", 0xE254: "dddotup.accent", 0xE255: "ddddotup.accent", 0xE256: "hslash", 0xE257: "simarrow", 0xE258: "varland", 0xE259: "contraction", 0xE25A: "circdashbullet", 0xE25B: "bulletdashcirc", 0xE25C: "braceleft.straight", 0xE25D: "braceright.straight", 0xE25E: "midshaft", 0xE25F: "rarrowhead", 0xE260: "larrowhead", 0xE261: "varpropto", 0xE262: "smallsmile", 0xE263: "smallfrown", 0xE264: "lvertneqq", 0xE265: "gvertneqq", 0xE266: "nleqslant", 0xE267: "ngeqslant", 0xE268: "npreceq", 0xE269: "nsucceq", 0xE26A: "nleqq", 0xE26B: "ngeqq", 0xE26C: "nsim.ams", 0xE26D: "varsubsetneq", 0xE26E: "varsupsetneq", 0xE26F: "nsubseteqq", 0xE270: "nsupseteqq", 0xE271: "varsubsetneqq", 0xE272: "varsupsetneqq", 0xE273: "nsubseteq.ams", 0xE274: "nsupseteq.ams", 0xE275: "nshortmid", 0xE276: "nshortparallel", 0xE277: "shortmid", 0xE278: "shortparallel", 0xE279: "thicksim", 0xE27A: "thickapprox", 0xE27B: "nsqsubset", 0xE27C: "nsqsupset", 0xE27D: "leadsto", 0xE27E: "undercurvearrowleft", 0xE27F: "undercurvearrowright", 0xE280: "capprod.big", 0xE281: "slsum", 0xE282: "slprod", 0xE283: "slcoprod", 0xE284: "varland.big", 0xE285: "ast.big", 0xE286: "wideoverbar.compat", 0xE287: "widevector.compat", } # Construction materials are independent of public entrances. excluded = { "openbullet", "mapstochar", "Relbar", "what", "wtilde", "wcheck", "midshaft", "rarrowhead", "larrowhead", "lhook", "rhook", "dbar.it", "dotup.accent", "ddotup.accent", "dddotup.accent", "ddddotup.accent", "smallint", "nsqsubset", "nsqsupset", } _pua_done = set(U.EXCLUDED_GLYPHS) _maxcp = 0xE23D for _cp in sorted(PUA_FIXED): _maxcp = max(_maxcp, _cp) _nm = PUA_FIXED[_cp] if ( _nm in excluded or _nm in U.EXCLUDED_GLYPHS or _nm in {"wideoverbar.compat", "widevector.compat"} ): continue if _nm in out and _nm not in _pua_done and out[_nm].unicode == _cp: _pua_done.add(_nm) elif _nm in out and _nm not in _pua_done and out[_nm].unicode == -1: out[_nm].unicode = _cp _pua_done.add(_nm) elif _nm in out and _nm not in _pua_done: out[_nm].altuni = (out[_nm].altuni or ()) + ((_cp, -1, 0),) _pua_done.add(_nm) PUA = _maxcp + 1 # Unencoded mapped glyphs are assigned deterministically after the # fixed range, in source-table slot order. for _tag, _table in (("mit", U.MIT), ("syt", U.SYT), ("ams", U.AMSA)): for _slot, _ent in sorted(_table.items()): _u, _nm = _ent[0], _ent[1] if _u is not None or not _nm or _nm in excluded: continue _g = imported.get((_tag, _slot)) if ( _g and _g not in _pua_done and _g in out and out[_g].unicode == -1 ): pua(_g) _pua_done.add(_g) for _slot in sorted(U.EXA_OPS): _u, _nm = U.EXA_OPS[_slot][0], U.EXA_OPS[_slot][1] if _u is not None or not _nm: continue _g = imported.get(("exa", _slot)) if _g and _g not in _pua_done and _g in out and out[_g].unicode == -1: pua(_g) _pua_done.add(_g) for _nm in ("braceleft.straight", "braceright.straight"): if _nm in out and out[_nm].unicode == -1 and _nm not in _pua_done: pua(_nm) _pua_done.add(_nm) def assign_metrics(sources, *, cap_height): out = sources.font sy_fd = sources.faces["syt"][1].fontdimen out.ascent, out.descent = 800, 200 # Use absolute FontForge metrics rather than offsets # from the bounding box. out.os2_typoascent_add = out.os2_typodescent_add = 0 out.hhea_ascent_add = out.hhea_descent_add = 0 out.os2_typoascent, out.os2_typodescent, out.os2_typolinegap = ( 806, -194, 200, ) out.hhea_ascent, out.hhea_descent, out.hhea_linegap = 806, -194, 200 out.os2_xheight = round(sy_fd[5] * 1000) # Reuse the source cap height already converted for MATH. out.os2_capheight = cap_height # Cover all outlines with Windows clipping bounds; USE_TYPO_METRICS # controls spacing. Scan glyph bounds for compatibility # across FontForge versions. _bounds_y = assign_windows(out) return _bounds_y def finish_source_features( sources, limits, accents, variants, *, prime_accents ): ACC_CENTER = accents.center FAMILY = sources.font.familyname FSTYPE = 8 LARGE_POLICY = limits.condition MAXWIDTH = limits.max_width SOURCE_SKEW = dict(sources.rules.skewchar_by_tag) STYLE = "Regular" accent_glyphs = accents.glyphs fonts = sources.faces horiz_variants = variants.horizontal imported = sources.imported out = sources.font uni_owner = sources.uni_owner out.os2_use_typo_metrics = 1 out.os2_vendor = "MTP2" # Preserve the source trademark notice. try: out.appendSFNTName( "English (US)", "Trademark", ("MathTime is a trademark of Publish or Perish, Inc."), ) except Exception as exc: raise ValueError(f"name Trademark: {exc}") from exc try: out.appendSFNTName("English (US)", "Preferred Family", FAMILY) out.appendSFNTName("English (US)", "Preferred Styles", STYLE) except Exception as exc: raise ValueError( f"name Preferred Family/Preferred Styles: {exc}" ) from exc out.os2_stylemap = 0x40 # Apply the selected embedding flag; license terms remain separate. out.os2_fstype = FSTYPE # font_geometry.py sets the Windows-compatible technical font # version. # Word math-italic nabla aliases the source nabla glyph. if 0x2207 in uni_owner: _ng = out[uni_owner[0x2207]] _nabla_aliases = [0x1D6FB] for _u in _nabla_aliases: if _u not in uni_owner: _ng.altuni = (_ng.altuni or ()) + ((_u, -1, 0),) uni_owner[_u] = _ng.glyphname # Alias the fixed bar to U+0305 for Word. if 0x0304 in uni_owner and 0x0305 not in uni_owner: _g = out[uni_owner[0x0304]] _g.altuni = (_g.altuni or ()) + ((0x0305, -1, 0),) uni_owner[0x0305] = uni_owner[0x0304] # Finalize TopAccent and original pair kerning after all source # and alternate imports. MATH italic corrections remain independent. if "wideoverbar.compat" in out: accent_glyphs.add("wideoverbar.compat") if "widevector.compat" in out: accent_glyphs.add("widevector.compat") # Finite source accents retain positive advance # and attach at its center. _source_fixed_accent_names = frozenset( ( # Normalized dot accents instead retain zero advance and # ink-center attachment. "wbar", "wwbar", "what", "wtilde", "wcheck", ) ) _source_fixed_accent_glyphs = set() for _agn in _source_fixed_accent_names: if _agn in out: accent_glyphs.add(_agn) _source_fixed_accent_glyphs.add(_agn) # Apply family skewchar policy after every source # alphabet has been imported. for (_tag, _slot), _gname in sorted(imported.items()): if _tag not in SOURCE_SKEW or _tag not in fonts or _gname not in out: continue _tfm = fonts[_tag][1] if _slot not in _tfm.chars: continue if _gname in _source_fixed_accent_glyphs: _ta = out[_gname].width // 2 elif _gname in accent_glyphs: _ta = ACC_CENTER else: _wd = _tfm.chars[_slot].get("wd") or 0.0 _ic = _tfm.chars[_slot].get("ic") or 0.0 _sk = SOURCE_SKEW[_tag] _skern = ( (_tfm.kerns.get((_slot, _sk), 0.0) or 0.0) if _sk is not None else 0.0 ) _ta = round(((_wd + _ic) / 2 + _skern) * 1000) try: out[_gname].topaccent = int(_ta) except Exception as exc: raise ValueError(f"TopAccent {_gname}: {exc}") from exc # U+E286 uses positive-width bar coordinates; U+E287 uses # zero-advance vector coordinates. if "wideoverbar.compat" in out: out["wideoverbar.compat"].topaccent = ( out["wideoverbar.compat"].width // 2 ) if "widevector.compat" in out: out["widevector.compat"].topaccent = ACC_CENTER positioning.install_source_kerns(sources) if LARGE_POLICY == "source": for _cp in L.HORIZONTAL: _forms = horiz_variants.get(uni_owner.get(_cp), ()) for _gn in _forms if _cp in (0x23DE, 0x23DF) else _forms[1:]: _new = ( _cp in (0x23DE, 0x23DF) or out[_gn].width > MAXWIDTH or ( _cp == 0x23DC and any("." + t in _gn for t in ("exe", "exg")) ) ) if _new and _gn not in accent_glyphs: out[_gn].topaccent = out[_gn].width // 2 # Normal Unicode primes use the scaled source-S attachment. Their # old T-size source attachment must not overwrite this # transformation. for name, value in prime_accents.items(): out[name].topaccent = value # GDEF marks combining accents as marks and other glyphs as bases. for _g in out.glyphs(): _u = _g.unicode if _u == -1 and _g.altuni: _u = _g.altuni[0][0] if ( _g.glyphname in accent_glyphs or (0x0300 <= _u <= 0x036F) or (0x20D0 <= _u <= 0x20F0) ): _g.glyphclass = "mark" else: _g.glyphclass = "baseglyph" def check_native_ranges(sources): out = sources.font for _g in out.glyphs(): for _p in [p for c in _g.foreground for p in c]: L.field_range(_g.glyphname + ".x", _p.x, -32768, 32767) L.field_range(_g.glyphname + ".y", _p.y, -32768, 32767) for _i, _v in enumerate(_g.boundingBox()): L.field_range(_g.glyphname + ".bbox[%d]" % _i, _v, -32768, 32767) L.field_range(_g.glyphname + ".advance", _g.width, 0, 65535) def _pct(frac: Fraction) -> int: return round(frac.numerator * 100 / frac.denominator) def compute_constants( sy, ex, *, script_sy, script_ratio: Fraction, scriptscript_ratio: Fraction, operator_sizes, cap_height, quad=1000.0, ): """Return MATH values in output units from local fontdimens and conversion policy. """ upm = round(quad) def s(i): return round(sy[i] * upm) def e(i): return round(ex[i] * upm) theta = e(8) xh = s(5) c = {} c["AxisHeight"] = s(22) c["FractionNumeratorShiftUp"] = s(9) c["FractionNumeratorDisplayStyleShiftUp"] = s(8) c["FractionDenominatorShiftDown"] = s(12) c["FractionDenominatorDisplayStyleShiftDown"] = s(11) c["FractionNumeratorGapMin"] = theta c["FractionNumDisplayStyleGapMin"] = 3 * theta c["FractionDenominatorGapMin"] = theta c["FractionDenomDisplayStyleGapMin"] = 3 * theta c["FractionRuleThickness"] = theta c["StackTopShiftUp"] = s(10) c["StackTopDisplayStyleShiftUp"] = s(8) c["StackBottomShiftDown"] = s(12) c["StackBottomDisplayStyleShiftDown"] = s(11) c["StackGapMin"] = 3 * theta c["StackDisplayStyleGapMin"] = 7 * theta c["SuperscriptShiftUp"] = s(13) c["SuperscriptShiftUpCramped"] = s(15) c["SubscriptShiftDown"] = s(17) c["SubscriptTopMax"] = round(Fraction(4, 5) * s(5)) c["SuperscriptBottomMin"] = round(Fraction(1, 4) * s(5)) c["SuperscriptBottomMaxWithSubscript"] = round(Fraction(4, 5) * s(5)) c["SubSuperscriptGapMin"] = 4 * theta # Map the original upper-limit minimum gap and baseline rise by # their OpenType meanings. Do not include big_op_spacing5 here. c["UpperLimitBaselineRiseMin"] = e(11) c["UpperLimitGapMin"] = e(9) c["LowerLimitGapMin"] = e(10) c["LowerLimitBaselineDropMin"] = e(12) c["StretchStackTopShiftUp"] = e(11) c["StretchStackGapAboveMin"] = e(9) c["StretchStackGapBelowMin"] = e(10) c["StretchStackBottomShiftDown"] = e(12) c["OverbarRuleThickness"] = theta c["OverbarVerticalGap"] = 3 * theta c["OverbarExtraAscender"] = theta c["UnderbarRuleThickness"] = theta c["UnderbarVerticalGap"] = 3 * theta c["UnderbarExtraDescender"] = theta c["RadicalRuleThickness"] = theta c["RadicalExtraAscender"] = theta c["RadicalVerticalGap"] = theta + theta // 4 c["RadicalDisplayStyleVerticalGap"] = theta + xh // 4 c["RadicalKernBeforeDegree"] = round(Fraction(5, 18) * upm) c["RadicalKernAfterDegree"] = -round(Fraction(10, 18) * upm) c["RadicalDegreeBottomRaisePercent"] = 60 c["ScriptPercentScaleDown"] = _pct(script_ratio) c["ScriptScriptPercentScaleDown"] = _pct(scriptscript_ratio) # Round each mapped text/display TFM extent before taking extrema. # The shared threshold is a migration choice, not a TFM fontdimen. text_sizes, display_sizes = zip(*operator_sizes) display_operator_min_height = ( max(round(size * upm) for size in text_sizes) + min(round(size * upm) for size in display_sizes) ) // 2 # Source dimensions and explicit OpenType migration values. c["MinConnectorOverlap"] = 100 c["AccentBaseHeight"] = xh c["FlattenedAccentBaseHeight"] = round(cap_height * upm) c["DelimitedSubFormulaMinHeight"] = int(display_operator_min_height) c["DisplayOperatorMinHeight"] = int(display_operator_min_height) # Fixed reference median: LM 154, STIX Two 150, Cambria 300/2048 em. # These reference fonts are not build inputs. Skewed-fraction values # likewise remain explicit migration values, not TFM-derived values. c["MathLeading"] = 150 c["SkewedFractionHorizontalGap"] = 350 c["SkewedFractionVerticalGap"] = 102 c["SpaceAfterScript"] = round(Fraction(1, 20) * upm) # Convert the next-style TFM drops to the first-level parent em. c["SubscriptBaselineDropMin"] = round( script_sy[19] * float(script_ratio) * upm ) c["SuperscriptBaselineDropMax"] = round( script_sy[18] * float(script_ratio) * upm ) return c