|  | # narrow.py | 
        
          |  | # Usage: | 
        
          |  | #   fontforge -script narrow.py In.ttf Out.ttf sx blend "Family" "Style" [tracking] | 
        
          |  | # Examples: | 
        
          |  | #   fontforge -script narrow.py "Courier Prime.ttf" "CourierPrime-Condensed.ttf" 0.85 0.5 "Courier Prime" "Condensed" 15 | 
        
          |  |  | 
        
          |  | import sys, re, fontforge, psMat | 
        
          |  |  | 
        
          |  | if len(sys.argv) < 7: | 
        
          |  | raise SystemExit("Usage: fontforge -script narrow.py In.ttf Out.ttf sx blend \"Family\" \"Style\" [tracking]") | 
        
          |  |  | 
        
          |  | in_path  = sys.argv[-6] | 
        
          |  | out_path = sys.argv[-5] | 
        
          |  | sx       = float(sys.argv[-4])      # horizontal scale, e.g. 0.85 | 
        
          |  | t        = float(sys.argv[-3])      # blend: 0 keep widths, 1 fully scale widths | 
        
          |  | family   = sys.argv[-2]             # new Family (SFNT Family, Preferred Family) | 
        
          |  | style    = sys.argv[-1] if len(sys.argv) == 7 else sys.argv[-1]  # for clarity in usage | 
        
          |  | # Optional tracking if provided as 7th arg after style | 
        
          |  | track    = 0 | 
        
          |  | if len(sys.argv) >= 8: | 
        
          |  | try: | 
        
          |  | track = int(sys.argv[-1]) | 
        
          |  | style = sys.argv[-2] | 
        
          |  | family= sys.argv[-3] | 
        
          |  | except ValueError: | 
        
          |  | pass | 
        
          |  |  | 
        
          |  | # Open font | 
        
          |  | f = fontforge.open(in_path) | 
        
          |  |  | 
        
          |  | # Cache original metrics for spacing logic | 
        
          |  | origW = {} | 
        
          |  | origLSB = {} | 
        
          |  | for g in f.glyphs(): | 
        
          |  | origW[g.glyphname] = g.width | 
        
          |  | origLSB[g.glyphname] = g.left_side_bearing | 
        
          |  |  | 
        
          |  | # Scale outlines horizontally (no vertical change) | 
        
          |  | f.selection.all() | 
        
          |  | f.transform(psMat.scale(sx, 1.0)) | 
        
          |  |  | 
        
          |  | # Blend spacing toward scaled width, then re-center outlines to keep bearings balanced | 
        
          |  | for g in f.glyphs(): | 
        
          |  | W0 = origW.get(g.glyphname, g.width) | 
        
          |  | if W0 <= 0: | 
        
          |  | continue | 
        
          |  | # blended target advance width | 
        
          |  | Wt = int(round(((1.0 - t) + t * sx) * W0)) + track | 
        
          |  | # keep original LSB proportion if possible (fallback to centering) | 
        
          |  | r = origLSB.get(g.glyphname, 0) / float(W0) if W0 else 0.5 | 
        
          |  | r = min(max(r, 0.0), 1.0) | 
        
          |  | target_lsb = int(round(r * Wt)) | 
        
          |  |  | 
        
          |  | # translate outline to achieve target LSB (balances bearings visually) | 
        
          |  | dx = target_lsb - g.left_side_bearing | 
        
          |  | if dx: | 
        
          |  | g.transform(psMat.translate(dx, 0)) | 
        
          |  | g.width = Wt | 
        
          |  |  | 
        
          |  | # Build names | 
        
          |  | full     = f"{family} {style}" | 
        
          |  | ps_name  = re.sub(r'[^A-Za-z0-9-]', '', full.replace(' ', '-'))  # ASCII, no spaces | 
        
          |  |  | 
        
          |  | # Set PostScript-style fields | 
        
          |  | f.familyname = family | 
        
          |  | f.fullname   = full | 
        
          |  | f.fontname   = ps_name | 
        
          |  |  | 
        
          |  | # Set key SFNT name records for better app compatibility | 
        
          |  | def setname(lang, key, val): | 
        
          |  | # appendSFNTName adds/overwrites name records by key and language | 
        
          |  | try: | 
        
          |  | f.appendSFNTName(lang, key, val) | 
        
          |  | except Exception: | 
        
          |  | pass | 
        
          |  |  | 
        
          |  | lang = "English (US)" | 
        
          |  | setname(lang, "Family", family)            # nameID 1 | 
        
          |  | setname(lang, "SubFamily", style)          # nameID 2 | 
        
          |  | setname(lang, "Fullname", full)            # nameID 4 | 
        
          |  | setname(lang, "Preferred Family", family)  # nameID 16 | 
        
          |  | setname(lang, "Preferred Styles", style)   # nameID 17 | 
        
          |  | # Optional extra name records that some apps read: | 
        
          |  | setname(lang, "WWS Family", family)        # nameID 21 | 
        
          |  | setname(lang, "WWS Subfamily", style)      # nameID 22 | 
        
          |  | setname(lang, "Compatible Full", full)     # nameID 18 | 
        
          |  |  | 
        
          |  | # Generate the output TTF | 
        
          |  | f.generate(out_path) |