@@ -212,25 +212,50 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
212212# Glyph aliases and re-uses
213213# ---------------------------------------------------------------------------
214214
215- # U+20DE COMBINING ENCLOSING SQUARE — zero-width mark sized and positioned to
216- # enclose '?' with a small margin. GPOS would be needed for full generality.
215+ # U+20DE COMBINING ENCLOSING SQUARE / U+20E4 COMBINING ENCLOSING UPWARD
216+ # POINTING TRIANGLE — zero-width marks spanning the full font EM.
217+ # Centred over 'e' width ('e' renders correctly; narrower letters like 'f'
218+ # will be slightly off — unavoidable without GPOS anchors).
217219_sq = font [0x25A1 ]
218220_sq_bb = _sq .boundingBox ()
219221_sq_cx = (_sq_bb [0 ] + _sq_bb [2 ]) / 2
220- _sq_h = _sq_bb [3 ] - _sq_bb [1 ] # sq_bb[1] == 0 after baseline snap
221- _q_bb = font [ord ('?' )].boundingBox ()
222- _q_adv = font [ord ('?' )].width
223- _margin = 20
224- _scale_y = (_q_bb [3 ] - _q_bb [1 ] + 2 * _margin ) / _sq_h
225- _x_offset = - _q_adv / 2 - _sq_cx
226- _y_offset = _q_bb [1 ] - _margin
222+ _sq_h = _sq_bb [3 ] - _sq_bb [1 ]
223+ _e_adv = font ['e' ].width
224+ # Both combining marks share _comb_top so their tops are aligned.
225+ # 1.3× factor ensures they visually enclose tall capitals; the bottom
226+ # hangs 0.3 × _comb_top below the baseline on both marks.
227+ _comb_top = font .ascent + font .descent // 2
228+
229+ _sq_s = 1.3 * _comb_top / _sq_h
230+ _sq_dy = _comb_top - _sq_h * _sq_s
227231c = font .createMappedChar (0x20DE )
228232c .addReference (_sq .glyphname , psMat .compose (
229- psMat .scale (1 , _scale_y ),
230- psMat .translate (_x_offset , _y_offset ),
233+ psMat .scale (_sq_s ),
234+ psMat .translate (- _e_adv / 2 - _sq_cx * _sq_s , _sq_dy ),
231235))
232236c .width = 0
233237
238+ # Triangle: outlines copied (not a reference) so stroke weight can be
239+ # controlled independently of the regular △. Top at _comb_top;
240+ # 1.3× _comb_top tall so bottom hangs below baseline.
241+ _tri_g = font [0x25B3 ]
242+ _tri_bb = _tri_g .boundingBox ()
243+ _tri_cx = (_tri_bb [0 ] + _tri_bb [2 ]) / 2
244+ _tri_h = _tri_bb [3 ] - _tri_bb [1 ]
245+ _tri_comb_s = 1.3 * _comb_top / _tri_h
246+ _tri_dy = _comb_top - _tri_h * _tri_comb_s
247+ c = font .createMappedChar (0x20E4 )
248+ c .clear ()
249+ _layer = fontforge .layer ()
250+ for _cont in _tri_g .foreground :
251+ _layer += _cont
252+ c .foreground = _layer
253+ c .transform (psMat .scale (_tri_comb_s ))
254+ c .transform (psMat .translate (- _e_adv / 2 - _tri_cx * _tri_comb_s , _tri_dy ))
255+ c .changeWeight (- 40 )
256+ c .correctDirection ()
257+ c .width = 0
258+
234259# Vertical pipe: re-use the I glyph (same stroke, same weight).
235260c = font .createChar (- 1 , 'I.sansserif' )
236261c .addReference ('I' )
@@ -996,6 +1021,132 @@ def _greek_lc_to_uc(font, lc_cp, uc_cp, snap=True, weight_delta=0):
9961021_make_accented (font , 0x038F , font [0x03A9 ].glyphname , '_acute_mark' ) # Ώ
9971022
9981023
1024+ # ---------------------------------------------------------------------------
1025+ # Arrow mirrors and rotations
1026+ # ---------------------------------------------------------------------------
1027+
1028+ # Left-pointing arrows: horizontal flip of right-pointing.
1029+ for _src_cp , _dst_cp in [
1030+ (0x2192 , 0x2190 ), # → ← LEFTWARDS ARROW
1031+ (0x21D2 , 0x21D0 ), # ⇒ ⇐ LEFTWARDS DOUBLE ARROW
1032+ (0x21C0 , 0x21BC ), # ⇀ ↼ LEFTWARDS HARPOON WITH BARB UPWARDS
1033+ ]:
1034+ _src = font [_src_cp ]
1035+ _g = font .createMappedChar (_dst_cp )
1036+ _g .clear ()
1037+ _layer = fontforge .layer ()
1038+ for _c in _src .foreground :
1039+ _layer += _c
1040+ _g .foreground = _layer
1041+ _g .transform (psMat .scale (- 1 , 1 ))
1042+ _g .correctDirection ()
1043+ _bb = _g .boundingBox ()
1044+ _g .transform (psMat .translate (- _bb [0 ] + 20 , 0 ))
1045+ _g .width = _src .width
1046+
1047+ # Up/down arrows: 90° CCW rotation of each right-pointing arrow family,
1048+ # scaled to the ascent height; down is a vertical flip of up.
1049+ # → ↑ ↓ U+2192 U+2191 U+2193 RIGHTWARDS / UPWARDS / DOWNWARDS ARROW
1050+ # ⇒ ⇑ ⇓ U+21D2 U+21D1 U+21D3 RIGHTWARDS / UPWARDS / DOWNWARDS DOUBLE ARROW
1051+ # ⇀ ↿ ⇃ U+21C0 U+21BF U+21C3 RIGHTWARDS / UPWARDS / DOWNWARDS HARPOON
1052+ for _src_cp , _up_cp , _dn_cp in [
1053+ (0x2192 , 0x2191 , 0x2193 ),
1054+ (0x21D2 , 0x21D1 , 0x21D3 ),
1055+ (0x21C0 , 0x21BF , 0x21C3 ),
1056+ ]:
1057+ _g = font .createMappedChar (_up_cp )
1058+ _g .clear ()
1059+ _layer = fontforge .layer ()
1060+ for _c in font [_src_cp ].foreground :
1061+ _layer += _c
1062+ _g .foreground = _layer
1063+ _g .transform (psMat .rotate (math .radians (90 )))
1064+ _bb = _g .boundingBox ()
1065+ _s = font .ascent / (_bb [3 ] - _bb [1 ])
1066+ _g .transform (psMat .scale (_s ))
1067+ _bb = _g .boundingBox ()
1068+ _g .transform (psMat .translate (- _bb [0 ] + 20 , - _bb [1 ]))
1069+ _g .width = int (round (_g .boundingBox ()[2 ] + 20 ))
1070+
1071+ _g = font .createMappedChar (_dn_cp )
1072+ _g .clear ()
1073+ _layer = fontforge .layer ()
1074+ for _c in font [_up_cp ].foreground :
1075+ _layer += _c
1076+ _g .foreground = _layer
1077+ _up_bb = font [_up_cp ].boundingBox ()
1078+ _g .transform (psMat .compose (psMat .scale (1 , - 1 ), psMat .translate (0 , _up_bb [1 ] + _up_bb [3 ])))
1079+ _g .correctDirection ()
1080+ _g .width = font [_up_cp ].width
1081+
1082+ # ⇋ U+21CB LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON
1083+ # ⇌ U+21CC RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON
1084+ # Packed tightly: top harpoon shifted up, bottom harpoon flipped vertically and
1085+ # shifted down so its barb faces outward (away from centre).
1086+ _harp_bb = font [0x21C0 ].boundingBox ()
1087+ _harp_cy = (_harp_bb [1 ] + _harp_bb [3 ]) / 2
1088+ _harp_sep = (_harp_bb [3 ] - _harp_bb [1 ]) // 2 + 20
1089+ for _dst_cp , _top_cp , _bot_cp in [
1090+ (0x21CB , 0x21BC , 0x21C0 ), # ⇋: ↼ over ⇀
1091+ (0x21CC , 0x21C0 , 0x21BC ), # ⇌: ⇀ over ↼
1092+ ]:
1093+ c = font .createMappedChar (_dst_cp )
1094+ c .clear ()
1095+ c .addReference (font [_top_cp ].glyphname , psMat .translate (0 , _harp_sep ))
1096+ c .addReference (font [_bot_cp ].glyphname , psMat .compose (
1097+ psMat .scale (1 , - 1 ),
1098+ psMat .translate (0 , 2 * _harp_cy - _harp_sep ),
1099+ ))
1100+ c .width = font [_top_cp ].width
1101+
1102+ # 45° diagonal arrows: each right-pointing source rotated to NE/NW/SW/SE and
1103+ # scaled to the same ascent height as the 90° arrows.
1104+ # → ↗ ↖ ↙ ↘ U+2192 U+2197 U+2196 U+2199 U+2198
1105+ # ⇒ ⇗ ⇖ ⇙ ⇘ U+21D2 U+21D7 U+21D6 U+21D9 U+21D8
1106+ for _src_cp , _diag_cps in [
1107+ (0x2192 , [(0x2197 , 45 ), (0x2196 , 135 ), (0x2199 , 225 ), (0x2198 , - 45 )]),
1108+ (0x21D2 , [(0x21D7 , 45 ), (0x21D6 , 135 ), (0x21D9 , 225 ), (0x21D8 , - 45 )]),
1109+ ]:
1110+ _src = font [_src_cp ]
1111+ for _diag_cp , _angle in _diag_cps :
1112+ _g = font .createMappedChar (_diag_cp )
1113+ _g .clear ()
1114+ _layer = fontforge .layer ()
1115+ for _c in _src .foreground :
1116+ _layer += _c
1117+ _g .foreground = _layer
1118+ _g .transform (psMat .rotate (math .radians (_angle )))
1119+ _bb = _g .boundingBox ()
1120+ _s = font .ascent / (_bb [3 ] - _bb [1 ])
1121+ _g .transform (psMat .scale (_s ))
1122+ _bb = _g .boundingBox ()
1123+ _g .transform (psMat .translate (- _bb [0 ] + 20 , - _bb [1 ]))
1124+ _g .width = int (round (_g .boundingBox ()[2 ] + 20 ))
1125+
1126+
1127+ # ---------------------------------------------------------------------------
1128+ # Triangles
1129+ # ---------------------------------------------------------------------------
1130+
1131+ # ▽ U+25BD WHITE DOWN-POINTING TRIANGLE — △ flipped vertically, point at baseline.
1132+ _tri_up_bb = font [0x25B3 ].boundingBox ()
1133+ _g = font .createMappedChar (0x25BD )
1134+ _g .clear ()
1135+ _layer = fontforge .layer ()
1136+ for _c in font [0x25B3 ].foreground :
1137+ _layer += _c
1138+ _g .foreground = _layer
1139+ _g .transform (psMat .compose (psMat .scale (1 , - 1 ), psMat .translate (0 , _tri_up_bb [1 ] + _tri_up_bb [3 ])))
1140+ _g .correctDirection ()
1141+ _g .width = font [0x25B3 ].width
1142+
1143+ # ∇ U+2207 NABLA (del / gradient / curl operator) — same letterform as ▽.
1144+ _g = font .createMappedChar (0x2207 )
1145+ _g .clear ()
1146+ _g .addReference (font [0x25BD ].glyphname )
1147+ _g .width = font [0x25BD ].width
1148+
1149+
9991150# ---------------------------------------------------------------------------
10001151# Save
10011152# ---------------------------------------------------------------------------
0 commit comments