Upload folder using huggingface_hub (part 4)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +26 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/sfnt.py +664 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/standardGlyphOrder.py +271 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/B_A_S_E_.py +14 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_D_T_.py +113 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_L_C_.py +19 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F_.py +61 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F__2.py +26 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_O_L_R_.py +165 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_P_A_L_.py +305 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D_S_I_G_.py +170 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D__e_b_g.py +35 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/DefaultTable.py +63 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_D_T_.py +835 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_L_C_.py +718 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F_F_T_M_.py +52 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F__e_a_t.py +159 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_D_E_F_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_P_O_S_.py +14 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_S_U_B_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_a_t.py +235 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_o_c.py +85 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/H_V_A_R_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_.py +12 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_X_.py +12 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/J_S_T_F_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/L_T_S_H_.py +58 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_A_T_H_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_V_A_R_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/O_S_2f_2.py +752 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_T_A_T_.py +15 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_V_G_.py +223 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_f.py +1040 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_l.py +92 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_B_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_C_.py +14 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_D_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_J_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_P_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_S_.py +13 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_V_.py +26 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__0.py +70 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__1.py +163 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__2.py +17 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__3.py +22 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__5.py +60 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_T_F_A_.py +14 -0
- .venv/lib/python3.11/site-packages/fontTools/ttLib/tables/TupleVariation.py +886 -0
.gitattributes
CHANGED
|
@@ -47,3 +47,29 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 47 |
.venv/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 48 |
.venv/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 49 |
.venv/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
.venv/lib/python3.11/site-packages/fontTools/misc/bezierTools.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 48 |
.venv/lib/python3.11/site-packages/fontTools/pens/momentsPen.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 49 |
.venv/lib/python3.11/site-packages/fontTools/qu2cu/qu2cu.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
.venv/lib/python3.11/site-packages/fontTools/varLib/iup.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
.venv/lib/python3.11/site-packages/fsspec/__pycache__/spec.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
.venv/lib/python3.11/site-packages/h5py/_conv.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
.venv/lib/python3.11/site-packages/h5py/_errors.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
.venv/lib/python3.11/site-packages/h5py/_npystrings.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
.venv/lib/python3.11/site-packages/h5py/_objects.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
.venv/lib/python3.11/site-packages/h5py/_proxy.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
.venv/lib/python3.11/site-packages/h5py/_selector.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
.venv/lib/python3.11/site-packages/h5py/defs.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
.venv/lib/python3.11/site-packages/h5py/h5.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
.venv/lib/python3.11/site-packages/h5py/h5a.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
.venv/lib/python3.11/site-packages/h5py/h5ac.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 62 |
+
.venv/lib/python3.11/site-packages/h5py/h5d.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 63 |
+
.venv/lib/python3.11/site-packages/h5py/h5ds.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 64 |
+
.venv/lib/python3.11/site-packages/h5py/h5f.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
.venv/lib/python3.11/site-packages/h5py/h5fd.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 66 |
+
.venv/lib/python3.11/site-packages/h5py/h5g.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
.venv/lib/python3.11/site-packages/h5py/h5i.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
.venv/lib/python3.11/site-packages/h5py/h5l.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
.venv/lib/python3.11/site-packages/h5py/h5o.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 70 |
+
.venv/lib/python3.11/site-packages/h5py/h5p.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 71 |
+
.venv/lib/python3.11/site-packages/h5py/h5pl.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 72 |
+
.venv/lib/python3.11/site-packages/h5py/h5r.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 73 |
+
.venv/lib/python3.11/site-packages/h5py/h5s.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 74 |
+
.venv/lib/python3.11/site-packages/h5py/h5t.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 75 |
+
.venv/lib/python3.11/site-packages/h5py/h5z.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/sfnt.py
ADDED
|
@@ -0,0 +1,664 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ttLib/sfnt.py -- low-level module to deal with the sfnt file format.
|
| 2 |
+
|
| 3 |
+
Defines two public classes:
|
| 4 |
+
|
| 5 |
+
- SFNTReader
|
| 6 |
+
- SFNTWriter
|
| 7 |
+
|
| 8 |
+
(Normally you don't have to use these classes explicitly; they are
|
| 9 |
+
used automatically by ttLib.TTFont.)
|
| 10 |
+
|
| 11 |
+
The reading and writing of sfnt files is separated in two distinct
|
| 12 |
+
classes, since whenever the number of tables changes or whenever
|
| 13 |
+
a table's length changes you need to rewrite the whole file anyway.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
from collections.abc import KeysView
|
| 19 |
+
from io import BytesIO
|
| 20 |
+
from types import SimpleNamespace
|
| 21 |
+
from fontTools.misc.textTools import Tag
|
| 22 |
+
from fontTools.misc import sstruct
|
| 23 |
+
from fontTools.ttLib import TTLibError, TTLibFileIsCollectionError
|
| 24 |
+
import struct
|
| 25 |
+
from collections import OrderedDict
|
| 26 |
+
import logging
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
log = logging.getLogger(__name__)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class SFNTReader(object):
|
| 33 |
+
def __new__(cls, *args, **kwargs):
|
| 34 |
+
"""Return an instance of the SFNTReader sub-class which is compatible
|
| 35 |
+
with the input file type.
|
| 36 |
+
"""
|
| 37 |
+
if args and cls is SFNTReader:
|
| 38 |
+
infile = args[0]
|
| 39 |
+
infile.seek(0)
|
| 40 |
+
sfntVersion = Tag(infile.read(4))
|
| 41 |
+
infile.seek(0)
|
| 42 |
+
if sfntVersion == "wOF2":
|
| 43 |
+
# return new WOFF2Reader object
|
| 44 |
+
from fontTools.ttLib.woff2 import WOFF2Reader
|
| 45 |
+
|
| 46 |
+
return object.__new__(WOFF2Reader)
|
| 47 |
+
# return default object
|
| 48 |
+
return object.__new__(cls)
|
| 49 |
+
|
| 50 |
+
def __init__(self, file, checkChecksums=0, fontNumber=-1):
|
| 51 |
+
self.file = file
|
| 52 |
+
self.checkChecksums = checkChecksums
|
| 53 |
+
|
| 54 |
+
self.flavor = None
|
| 55 |
+
self.flavorData = None
|
| 56 |
+
self.DirectoryEntry = SFNTDirectoryEntry
|
| 57 |
+
self.file.seek(0)
|
| 58 |
+
self.sfntVersion = self.file.read(4)
|
| 59 |
+
self.file.seek(0)
|
| 60 |
+
if self.sfntVersion == b"ttcf":
|
| 61 |
+
header = readTTCHeader(self.file)
|
| 62 |
+
numFonts = header.numFonts
|
| 63 |
+
if not 0 <= fontNumber < numFonts:
|
| 64 |
+
raise TTLibFileIsCollectionError(
|
| 65 |
+
"specify a font number between 0 and %d (inclusive)"
|
| 66 |
+
% (numFonts - 1)
|
| 67 |
+
)
|
| 68 |
+
self.numFonts = numFonts
|
| 69 |
+
self.file.seek(header.offsetTable[fontNumber])
|
| 70 |
+
data = self.file.read(sfntDirectorySize)
|
| 71 |
+
if len(data) != sfntDirectorySize:
|
| 72 |
+
raise TTLibError("Not a Font Collection (not enough data)")
|
| 73 |
+
sstruct.unpack(sfntDirectoryFormat, data, self)
|
| 74 |
+
elif self.sfntVersion == b"wOFF":
|
| 75 |
+
self.flavor = "woff"
|
| 76 |
+
self.DirectoryEntry = WOFFDirectoryEntry
|
| 77 |
+
data = self.file.read(woffDirectorySize)
|
| 78 |
+
if len(data) != woffDirectorySize:
|
| 79 |
+
raise TTLibError("Not a WOFF font (not enough data)")
|
| 80 |
+
sstruct.unpack(woffDirectoryFormat, data, self)
|
| 81 |
+
else:
|
| 82 |
+
data = self.file.read(sfntDirectorySize)
|
| 83 |
+
if len(data) != sfntDirectorySize:
|
| 84 |
+
raise TTLibError("Not a TrueType or OpenType font (not enough data)")
|
| 85 |
+
sstruct.unpack(sfntDirectoryFormat, data, self)
|
| 86 |
+
self.sfntVersion = Tag(self.sfntVersion)
|
| 87 |
+
|
| 88 |
+
if self.sfntVersion not in ("\x00\x01\x00\x00", "OTTO", "true"):
|
| 89 |
+
raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
|
| 90 |
+
tables: dict[Tag, DirectoryEntry] = {}
|
| 91 |
+
for i in range(self.numTables):
|
| 92 |
+
entry = self.DirectoryEntry()
|
| 93 |
+
entry.fromFile(self.file)
|
| 94 |
+
tag = Tag(entry.tag)
|
| 95 |
+
tables[tag] = entry
|
| 96 |
+
self.tables = OrderedDict(sorted(tables.items(), key=lambda i: i[1].offset))
|
| 97 |
+
|
| 98 |
+
# Load flavor data if any
|
| 99 |
+
if self.flavor == "woff":
|
| 100 |
+
self.flavorData = WOFFFlavorData(self)
|
| 101 |
+
|
| 102 |
+
def has_key(self, tag: str | bytes) -> bool:
|
| 103 |
+
return tag in self.tables
|
| 104 |
+
|
| 105 |
+
__contains__ = has_key
|
| 106 |
+
|
| 107 |
+
def keys(self) -> KeysView[Tag]:
|
| 108 |
+
return self.tables.keys()
|
| 109 |
+
|
| 110 |
+
def __getitem__(self, tag: str | bytes) -> bytes:
|
| 111 |
+
"""Fetch the raw table data."""
|
| 112 |
+
entry = self.tables[Tag(tag)]
|
| 113 |
+
data = entry.loadData(self.file)
|
| 114 |
+
if self.checkChecksums:
|
| 115 |
+
if tag == "head":
|
| 116 |
+
# Beh: we have to special-case the 'head' table.
|
| 117 |
+
checksum = calcChecksum(data[:8] + b"\0\0\0\0" + data[12:])
|
| 118 |
+
else:
|
| 119 |
+
checksum = calcChecksum(data)
|
| 120 |
+
if self.checkChecksums > 1:
|
| 121 |
+
# Be obnoxious, and barf when it's wrong
|
| 122 |
+
assert checksum == entry.checkSum, "bad checksum for '%s' table" % tag
|
| 123 |
+
elif checksum != entry.checkSum:
|
| 124 |
+
# Be friendly, and just log a warning.
|
| 125 |
+
log.warning("bad checksum for '%s' table", tag)
|
| 126 |
+
return data
|
| 127 |
+
|
| 128 |
+
def __delitem__(self, tag: str | bytes) -> None:
|
| 129 |
+
del self.tables[Tag(tag)]
|
| 130 |
+
|
| 131 |
+
def close(self) -> None:
|
| 132 |
+
self.file.close()
|
| 133 |
+
|
| 134 |
+
# We define custom __getstate__ and __setstate__ to make SFNTReader pickle-able
|
| 135 |
+
# and deepcopy-able. When a TTFont is loaded as lazy=True, SFNTReader holds a
|
| 136 |
+
# reference to an external file object which is not pickleable. So in __getstate__
|
| 137 |
+
# we store the file name and current position, and in __setstate__ we reopen the
|
| 138 |
+
# same named file after unpickling.
|
| 139 |
+
|
| 140 |
+
def __getstate__(self):
|
| 141 |
+
if isinstance(self.file, BytesIO):
|
| 142 |
+
# BytesIO is already pickleable, return the state unmodified
|
| 143 |
+
return self.__dict__
|
| 144 |
+
|
| 145 |
+
# remove unpickleable file attribute, and only store its name and pos
|
| 146 |
+
state = self.__dict__.copy()
|
| 147 |
+
del state["file"]
|
| 148 |
+
state["_filename"] = self.file.name
|
| 149 |
+
state["_filepos"] = self.file.tell()
|
| 150 |
+
return state
|
| 151 |
+
|
| 152 |
+
def __setstate__(self, state):
|
| 153 |
+
if "file" not in state:
|
| 154 |
+
self.file = open(state.pop("_filename"), "rb")
|
| 155 |
+
self.file.seek(state.pop("_filepos"))
|
| 156 |
+
self.__dict__.update(state)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
# default compression level for WOFF 1.0 tables and metadata
|
| 160 |
+
ZLIB_COMPRESSION_LEVEL = 6
|
| 161 |
+
|
| 162 |
+
# if set to True, use zopfli instead of zlib for compressing WOFF 1.0.
|
| 163 |
+
# The Python bindings are available at https://pypi.python.org/pypi/zopfli
|
| 164 |
+
USE_ZOPFLI = False
|
| 165 |
+
|
| 166 |
+
# mapping between zlib's compression levels and zopfli's 'numiterations'.
|
| 167 |
+
# Use lower values for files over several MB in size or it will be too slow
|
| 168 |
+
ZOPFLI_LEVELS = {
|
| 169 |
+
# 0: 0, # can't do 0 iterations...
|
| 170 |
+
1: 1,
|
| 171 |
+
2: 3,
|
| 172 |
+
3: 5,
|
| 173 |
+
4: 8,
|
| 174 |
+
5: 10,
|
| 175 |
+
6: 15,
|
| 176 |
+
7: 25,
|
| 177 |
+
8: 50,
|
| 178 |
+
9: 100,
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def compress(data, level=ZLIB_COMPRESSION_LEVEL):
|
| 183 |
+
"""Compress 'data' to Zlib format. If 'USE_ZOPFLI' variable is True,
|
| 184 |
+
zopfli is used instead of the zlib module.
|
| 185 |
+
The compression 'level' must be between 0 and 9. 1 gives best speed,
|
| 186 |
+
9 gives best compression (0 gives no compression at all).
|
| 187 |
+
The default value is a compromise between speed and compression (6).
|
| 188 |
+
"""
|
| 189 |
+
if not (0 <= level <= 9):
|
| 190 |
+
raise ValueError("Bad compression level: %s" % level)
|
| 191 |
+
if not USE_ZOPFLI or level == 0:
|
| 192 |
+
from zlib import compress
|
| 193 |
+
|
| 194 |
+
return compress(data, level)
|
| 195 |
+
else:
|
| 196 |
+
from zopfli.zlib import compress
|
| 197 |
+
|
| 198 |
+
return compress(data, numiterations=ZOPFLI_LEVELS[level])
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class SFNTWriter(object):
|
| 202 |
+
def __new__(cls, *args, **kwargs):
|
| 203 |
+
"""Return an instance of the SFNTWriter sub-class which is compatible
|
| 204 |
+
with the specified 'flavor'.
|
| 205 |
+
"""
|
| 206 |
+
flavor = None
|
| 207 |
+
if kwargs and "flavor" in kwargs:
|
| 208 |
+
flavor = kwargs["flavor"]
|
| 209 |
+
elif args and len(args) > 3:
|
| 210 |
+
flavor = args[3]
|
| 211 |
+
if cls is SFNTWriter:
|
| 212 |
+
if flavor == "woff2":
|
| 213 |
+
# return new WOFF2Writer object
|
| 214 |
+
from fontTools.ttLib.woff2 import WOFF2Writer
|
| 215 |
+
|
| 216 |
+
return object.__new__(WOFF2Writer)
|
| 217 |
+
# return default object
|
| 218 |
+
return object.__new__(cls)
|
| 219 |
+
|
| 220 |
+
def __init__(
|
| 221 |
+
self,
|
| 222 |
+
file,
|
| 223 |
+
numTables,
|
| 224 |
+
sfntVersion="\000\001\000\000",
|
| 225 |
+
flavor=None,
|
| 226 |
+
flavorData=None,
|
| 227 |
+
):
|
| 228 |
+
self.file = file
|
| 229 |
+
self.numTables = numTables
|
| 230 |
+
self.sfntVersion = Tag(sfntVersion)
|
| 231 |
+
self.flavor = flavor
|
| 232 |
+
self.flavorData = flavorData
|
| 233 |
+
|
| 234 |
+
if self.flavor == "woff":
|
| 235 |
+
self.directoryFormat = woffDirectoryFormat
|
| 236 |
+
self.directorySize = woffDirectorySize
|
| 237 |
+
self.DirectoryEntry = WOFFDirectoryEntry
|
| 238 |
+
|
| 239 |
+
self.signature = "wOFF"
|
| 240 |
+
|
| 241 |
+
# to calculate WOFF checksum adjustment, we also need the original SFNT offsets
|
| 242 |
+
self.origNextTableOffset = (
|
| 243 |
+
sfntDirectorySize + numTables * sfntDirectoryEntrySize
|
| 244 |
+
)
|
| 245 |
+
else:
|
| 246 |
+
assert not self.flavor, "Unknown flavor '%s'" % self.flavor
|
| 247 |
+
self.directoryFormat = sfntDirectoryFormat
|
| 248 |
+
self.directorySize = sfntDirectorySize
|
| 249 |
+
self.DirectoryEntry = SFNTDirectoryEntry
|
| 250 |
+
|
| 251 |
+
from fontTools.ttLib import getSearchRange
|
| 252 |
+
|
| 253 |
+
self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(
|
| 254 |
+
numTables, 16
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
self.directoryOffset = self.file.tell()
|
| 258 |
+
self.nextTableOffset = (
|
| 259 |
+
self.directoryOffset
|
| 260 |
+
+ self.directorySize
|
| 261 |
+
+ numTables * self.DirectoryEntry.formatSize
|
| 262 |
+
)
|
| 263 |
+
# clear out directory area
|
| 264 |
+
self.file.seek(self.nextTableOffset)
|
| 265 |
+
# make sure we're actually where we want to be. (old cStringIO bug)
|
| 266 |
+
self.file.write(b"\0" * (self.nextTableOffset - self.file.tell()))
|
| 267 |
+
self.tables = OrderedDict()
|
| 268 |
+
|
| 269 |
+
def setEntry(self, tag, entry):
|
| 270 |
+
if tag in self.tables:
|
| 271 |
+
raise TTLibError("cannot rewrite '%s' table" % tag)
|
| 272 |
+
|
| 273 |
+
self.tables[tag] = entry
|
| 274 |
+
|
| 275 |
+
def __setitem__(self, tag, data):
|
| 276 |
+
"""Write raw table data to disk."""
|
| 277 |
+
if tag in self.tables:
|
| 278 |
+
raise TTLibError("cannot rewrite '%s' table" % tag)
|
| 279 |
+
|
| 280 |
+
entry = self.DirectoryEntry()
|
| 281 |
+
entry.tag = tag
|
| 282 |
+
entry.offset = self.nextTableOffset
|
| 283 |
+
if tag == "head":
|
| 284 |
+
entry.checkSum = calcChecksum(data[:8] + b"\0\0\0\0" + data[12:])
|
| 285 |
+
self.headTable = data
|
| 286 |
+
entry.uncompressed = True
|
| 287 |
+
else:
|
| 288 |
+
entry.checkSum = calcChecksum(data)
|
| 289 |
+
entry.saveData(self.file, data)
|
| 290 |
+
|
| 291 |
+
if self.flavor == "woff":
|
| 292 |
+
entry.origOffset = self.origNextTableOffset
|
| 293 |
+
self.origNextTableOffset += (entry.origLength + 3) & ~3
|
| 294 |
+
|
| 295 |
+
self.nextTableOffset = self.nextTableOffset + ((entry.length + 3) & ~3)
|
| 296 |
+
# Add NUL bytes to pad the table data to a 4-byte boundary.
|
| 297 |
+
# Don't depend on f.seek() as we need to add the padding even if no
|
| 298 |
+
# subsequent write follows (seek is lazy), ie. after the final table
|
| 299 |
+
# in the font.
|
| 300 |
+
self.file.write(b"\0" * (self.nextTableOffset - self.file.tell()))
|
| 301 |
+
assert self.nextTableOffset == self.file.tell()
|
| 302 |
+
|
| 303 |
+
self.setEntry(tag, entry)
|
| 304 |
+
|
| 305 |
+
def __getitem__(self, tag):
|
| 306 |
+
return self.tables[tag]
|
| 307 |
+
|
| 308 |
+
def close(self):
|
| 309 |
+
"""All tables must have been written to disk. Now write the
|
| 310 |
+
directory.
|
| 311 |
+
"""
|
| 312 |
+
tables = sorted(self.tables.items())
|
| 313 |
+
if len(tables) != self.numTables:
|
| 314 |
+
raise TTLibError(
|
| 315 |
+
"wrong number of tables; expected %d, found %d"
|
| 316 |
+
% (self.numTables, len(tables))
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
if self.flavor == "woff":
|
| 320 |
+
self.signature = b"wOFF"
|
| 321 |
+
self.reserved = 0
|
| 322 |
+
|
| 323 |
+
self.totalSfntSize = 12
|
| 324 |
+
self.totalSfntSize += 16 * len(tables)
|
| 325 |
+
for tag, entry in tables:
|
| 326 |
+
self.totalSfntSize += (entry.origLength + 3) & ~3
|
| 327 |
+
|
| 328 |
+
data = self.flavorData if self.flavorData else WOFFFlavorData()
|
| 329 |
+
if data.majorVersion is not None and data.minorVersion is not None:
|
| 330 |
+
self.majorVersion = data.majorVersion
|
| 331 |
+
self.minorVersion = data.minorVersion
|
| 332 |
+
else:
|
| 333 |
+
if hasattr(self, "headTable"):
|
| 334 |
+
self.majorVersion, self.minorVersion = struct.unpack(
|
| 335 |
+
">HH", self.headTable[4:8]
|
| 336 |
+
)
|
| 337 |
+
else:
|
| 338 |
+
self.majorVersion = self.minorVersion = 0
|
| 339 |
+
if data.metaData:
|
| 340 |
+
self.metaOrigLength = len(data.metaData)
|
| 341 |
+
self.file.seek(0, 2)
|
| 342 |
+
self.metaOffset = self.file.tell()
|
| 343 |
+
compressedMetaData = compress(data.metaData)
|
| 344 |
+
self.metaLength = len(compressedMetaData)
|
| 345 |
+
self.file.write(compressedMetaData)
|
| 346 |
+
else:
|
| 347 |
+
self.metaOffset = self.metaLength = self.metaOrigLength = 0
|
| 348 |
+
if data.privData:
|
| 349 |
+
self.file.seek(0, 2)
|
| 350 |
+
off = self.file.tell()
|
| 351 |
+
paddedOff = (off + 3) & ~3
|
| 352 |
+
self.file.write(b"\0" * (paddedOff - off))
|
| 353 |
+
self.privOffset = self.file.tell()
|
| 354 |
+
self.privLength = len(data.privData)
|
| 355 |
+
self.file.write(data.privData)
|
| 356 |
+
else:
|
| 357 |
+
self.privOffset = self.privLength = 0
|
| 358 |
+
|
| 359 |
+
self.file.seek(0, 2)
|
| 360 |
+
self.length = self.file.tell()
|
| 361 |
+
|
| 362 |
+
else:
|
| 363 |
+
assert not self.flavor, "Unknown flavor '%s'" % self.flavor
|
| 364 |
+
pass
|
| 365 |
+
|
| 366 |
+
directory = sstruct.pack(self.directoryFormat, self)
|
| 367 |
+
|
| 368 |
+
self.file.seek(self.directoryOffset + self.directorySize)
|
| 369 |
+
seenHead = 0
|
| 370 |
+
for tag, entry in tables:
|
| 371 |
+
if tag == "head":
|
| 372 |
+
seenHead = 1
|
| 373 |
+
directory = directory + entry.toString()
|
| 374 |
+
if seenHead:
|
| 375 |
+
self.writeMasterChecksum(directory)
|
| 376 |
+
self.file.seek(self.directoryOffset)
|
| 377 |
+
self.file.write(directory)
|
| 378 |
+
|
| 379 |
+
def _calcMasterChecksum(self, directory):
|
| 380 |
+
# calculate checkSumAdjustment
|
| 381 |
+
checksums = []
|
| 382 |
+
for tag in self.tables.keys():
|
| 383 |
+
checksums.append(self.tables[tag].checkSum)
|
| 384 |
+
|
| 385 |
+
if self.DirectoryEntry != SFNTDirectoryEntry:
|
| 386 |
+
# Create a SFNT directory for checksum calculation purposes
|
| 387 |
+
from fontTools.ttLib import getSearchRange
|
| 388 |
+
|
| 389 |
+
self.searchRange, self.entrySelector, self.rangeShift = getSearchRange(
|
| 390 |
+
self.numTables, 16
|
| 391 |
+
)
|
| 392 |
+
directory = sstruct.pack(sfntDirectoryFormat, self)
|
| 393 |
+
tables = sorted(self.tables.items())
|
| 394 |
+
for tag, entry in tables:
|
| 395 |
+
sfntEntry = SFNTDirectoryEntry()
|
| 396 |
+
sfntEntry.tag = entry.tag
|
| 397 |
+
sfntEntry.checkSum = entry.checkSum
|
| 398 |
+
sfntEntry.offset = entry.origOffset
|
| 399 |
+
sfntEntry.length = entry.origLength
|
| 400 |
+
directory = directory + sfntEntry.toString()
|
| 401 |
+
|
| 402 |
+
directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
|
| 403 |
+
assert directory_end == len(directory)
|
| 404 |
+
|
| 405 |
+
checksums.append(calcChecksum(directory))
|
| 406 |
+
checksum = sum(checksums) & 0xFFFFFFFF
|
| 407 |
+
# BiboAfba!
|
| 408 |
+
checksumadjustment = (0xB1B0AFBA - checksum) & 0xFFFFFFFF
|
| 409 |
+
return checksumadjustment
|
| 410 |
+
|
| 411 |
+
def writeMasterChecksum(self, directory):
|
| 412 |
+
checksumadjustment = self._calcMasterChecksum(directory)
|
| 413 |
+
# write the checksum to the file
|
| 414 |
+
self.file.seek(self.tables["head"].offset + 8)
|
| 415 |
+
self.file.write(struct.pack(">L", checksumadjustment))
|
| 416 |
+
|
| 417 |
+
def reordersTables(self):
|
| 418 |
+
return False
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
# -- sfnt directory helpers and cruft
|
| 422 |
+
|
| 423 |
+
ttcHeaderFormat = """
|
| 424 |
+
> # big endian
|
| 425 |
+
TTCTag: 4s # "ttcf"
|
| 426 |
+
Version: L # 0x00010000 or 0x00020000
|
| 427 |
+
numFonts: L # number of fonts
|
| 428 |
+
# OffsetTable[numFonts]: L # array with offsets from beginning of file
|
| 429 |
+
# ulDsigTag: L # version 2.0 only
|
| 430 |
+
# ulDsigLength: L # version 2.0 only
|
| 431 |
+
# ulDsigOffset: L # version 2.0 only
|
| 432 |
+
"""
|
| 433 |
+
|
| 434 |
+
ttcHeaderSize = sstruct.calcsize(ttcHeaderFormat)
|
| 435 |
+
|
| 436 |
+
sfntDirectoryFormat = """
|
| 437 |
+
> # big endian
|
| 438 |
+
sfntVersion: 4s
|
| 439 |
+
numTables: H # number of tables
|
| 440 |
+
searchRange: H # (max2 <= numTables)*16
|
| 441 |
+
entrySelector: H # log2(max2 <= numTables)
|
| 442 |
+
rangeShift: H # numTables*16-searchRange
|
| 443 |
+
"""
|
| 444 |
+
|
| 445 |
+
sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
|
| 446 |
+
|
| 447 |
+
sfntDirectoryEntryFormat = """
|
| 448 |
+
> # big endian
|
| 449 |
+
tag: 4s
|
| 450 |
+
checkSum: L
|
| 451 |
+
offset: L
|
| 452 |
+
length: L
|
| 453 |
+
"""
|
| 454 |
+
|
| 455 |
+
sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
|
| 456 |
+
|
| 457 |
+
woffDirectoryFormat = """
|
| 458 |
+
> # big endian
|
| 459 |
+
signature: 4s # "wOFF"
|
| 460 |
+
sfntVersion: 4s
|
| 461 |
+
length: L # total woff file size
|
| 462 |
+
numTables: H # number of tables
|
| 463 |
+
reserved: H # set to 0
|
| 464 |
+
totalSfntSize: L # uncompressed size
|
| 465 |
+
majorVersion: H # major version of WOFF file
|
| 466 |
+
minorVersion: H # minor version of WOFF file
|
| 467 |
+
metaOffset: L # offset to metadata block
|
| 468 |
+
metaLength: L # length of compressed metadata
|
| 469 |
+
metaOrigLength: L # length of uncompressed metadata
|
| 470 |
+
privOffset: L # offset to private data block
|
| 471 |
+
privLength: L # length of private data block
|
| 472 |
+
"""
|
| 473 |
+
|
| 474 |
+
woffDirectorySize = sstruct.calcsize(woffDirectoryFormat)
|
| 475 |
+
|
| 476 |
+
woffDirectoryEntryFormat = """
|
| 477 |
+
> # big endian
|
| 478 |
+
tag: 4s
|
| 479 |
+
offset: L
|
| 480 |
+
length: L # compressed length
|
| 481 |
+
origLength: L # original length
|
| 482 |
+
checkSum: L # original checksum
|
| 483 |
+
"""
|
| 484 |
+
|
| 485 |
+
woffDirectoryEntrySize = sstruct.calcsize(woffDirectoryEntryFormat)
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
class DirectoryEntry(object):
|
| 489 |
+
def __init__(self):
|
| 490 |
+
self.uncompressed = False # if True, always embed entry raw
|
| 491 |
+
|
| 492 |
+
def fromFile(self, file):
|
| 493 |
+
sstruct.unpack(self.format, file.read(self.formatSize), self)
|
| 494 |
+
|
| 495 |
+
def fromString(self, str):
|
| 496 |
+
sstruct.unpack(self.format, str, self)
|
| 497 |
+
|
| 498 |
+
def toString(self):
|
| 499 |
+
return sstruct.pack(self.format, self)
|
| 500 |
+
|
| 501 |
+
def __repr__(self):
|
| 502 |
+
if hasattr(self, "tag"):
|
| 503 |
+
return "<%s '%s' at %x>" % (self.__class__.__name__, self.tag, id(self))
|
| 504 |
+
else:
|
| 505 |
+
return "<%s at %x>" % (self.__class__.__name__, id(self))
|
| 506 |
+
|
| 507 |
+
def loadData(self, file):
|
| 508 |
+
file.seek(self.offset)
|
| 509 |
+
data = file.read(self.length)
|
| 510 |
+
assert len(data) == self.length
|
| 511 |
+
if hasattr(self.__class__, "decodeData"):
|
| 512 |
+
data = self.decodeData(data)
|
| 513 |
+
return data
|
| 514 |
+
|
| 515 |
+
def saveData(self, file, data):
|
| 516 |
+
if hasattr(self.__class__, "encodeData"):
|
| 517 |
+
data = self.encodeData(data)
|
| 518 |
+
self.length = len(data)
|
| 519 |
+
file.seek(self.offset)
|
| 520 |
+
file.write(data)
|
| 521 |
+
|
| 522 |
+
def decodeData(self, rawData):
|
| 523 |
+
return rawData
|
| 524 |
+
|
| 525 |
+
def encodeData(self, data):
|
| 526 |
+
return data
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
class SFNTDirectoryEntry(DirectoryEntry):
|
| 530 |
+
format = sfntDirectoryEntryFormat
|
| 531 |
+
formatSize = sfntDirectoryEntrySize
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
class WOFFDirectoryEntry(DirectoryEntry):
|
| 535 |
+
format = woffDirectoryEntryFormat
|
| 536 |
+
formatSize = woffDirectoryEntrySize
|
| 537 |
+
|
| 538 |
+
def __init__(self):
|
| 539 |
+
super(WOFFDirectoryEntry, self).__init__()
|
| 540 |
+
# With fonttools<=3.1.2, the only way to set a different zlib
|
| 541 |
+
# compression level for WOFF directory entries was to set the class
|
| 542 |
+
# attribute 'zlibCompressionLevel'. This is now replaced by a globally
|
| 543 |
+
# defined `ZLIB_COMPRESSION_LEVEL`, which is also applied when
|
| 544 |
+
# compressing the metadata. For backward compatibility, we still
|
| 545 |
+
# use the class attribute if it was already set.
|
| 546 |
+
if not hasattr(WOFFDirectoryEntry, "zlibCompressionLevel"):
|
| 547 |
+
self.zlibCompressionLevel = ZLIB_COMPRESSION_LEVEL
|
| 548 |
+
|
| 549 |
+
def decodeData(self, rawData):
|
| 550 |
+
import zlib
|
| 551 |
+
|
| 552 |
+
if self.length == self.origLength:
|
| 553 |
+
data = rawData
|
| 554 |
+
else:
|
| 555 |
+
assert self.length < self.origLength
|
| 556 |
+
data = zlib.decompress(rawData)
|
| 557 |
+
assert len(data) == self.origLength
|
| 558 |
+
return data
|
| 559 |
+
|
| 560 |
+
def encodeData(self, data):
|
| 561 |
+
self.origLength = len(data)
|
| 562 |
+
if not self.uncompressed:
|
| 563 |
+
compressedData = compress(data, self.zlibCompressionLevel)
|
| 564 |
+
if self.uncompressed or len(compressedData) >= self.origLength:
|
| 565 |
+
# Encode uncompressed
|
| 566 |
+
rawData = data
|
| 567 |
+
self.length = self.origLength
|
| 568 |
+
else:
|
| 569 |
+
rawData = compressedData
|
| 570 |
+
self.length = len(rawData)
|
| 571 |
+
return rawData
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
class WOFFFlavorData:
|
| 575 |
+
Flavor = "woff"
|
| 576 |
+
|
| 577 |
+
def __init__(self, reader=None):
|
| 578 |
+
self.majorVersion = None
|
| 579 |
+
self.minorVersion = None
|
| 580 |
+
self.metaData = None
|
| 581 |
+
self.privData = None
|
| 582 |
+
if reader:
|
| 583 |
+
self.majorVersion = reader.majorVersion
|
| 584 |
+
self.minorVersion = reader.minorVersion
|
| 585 |
+
if reader.metaLength:
|
| 586 |
+
reader.file.seek(reader.metaOffset)
|
| 587 |
+
rawData = reader.file.read(reader.metaLength)
|
| 588 |
+
assert len(rawData) == reader.metaLength
|
| 589 |
+
data = self._decompress(rawData)
|
| 590 |
+
assert len(data) == reader.metaOrigLength
|
| 591 |
+
self.metaData = data
|
| 592 |
+
if reader.privLength:
|
| 593 |
+
reader.file.seek(reader.privOffset)
|
| 594 |
+
data = reader.file.read(reader.privLength)
|
| 595 |
+
assert len(data) == reader.privLength
|
| 596 |
+
self.privData = data
|
| 597 |
+
|
| 598 |
+
def _decompress(self, rawData):
|
| 599 |
+
import zlib
|
| 600 |
+
|
| 601 |
+
return zlib.decompress(rawData)
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
def calcChecksum(data):
|
| 605 |
+
"""Calculate the checksum for an arbitrary block of data.
|
| 606 |
+
|
| 607 |
+
If the data length is not a multiple of four, it assumes
|
| 608 |
+
it is to be padded with null byte.
|
| 609 |
+
|
| 610 |
+
>>> print(calcChecksum(b"abcd"))
|
| 611 |
+
1633837924
|
| 612 |
+
>>> print(calcChecksum(b"abcdxyz"))
|
| 613 |
+
3655064932
|
| 614 |
+
"""
|
| 615 |
+
remainder = len(data) % 4
|
| 616 |
+
if remainder:
|
| 617 |
+
data += b"\0" * (4 - remainder)
|
| 618 |
+
value = 0
|
| 619 |
+
blockSize = 4096
|
| 620 |
+
assert blockSize % 4 == 0
|
| 621 |
+
for i in range(0, len(data), blockSize):
|
| 622 |
+
block = data[i : i + blockSize]
|
| 623 |
+
longs = struct.unpack(">%dL" % (len(block) // 4), block)
|
| 624 |
+
value = (value + sum(longs)) & 0xFFFFFFFF
|
| 625 |
+
return value
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
def readTTCHeader(file):
|
| 629 |
+
file.seek(0)
|
| 630 |
+
data = file.read(ttcHeaderSize)
|
| 631 |
+
if len(data) != ttcHeaderSize:
|
| 632 |
+
raise TTLibError("Not a Font Collection (not enough data)")
|
| 633 |
+
self = SimpleNamespace()
|
| 634 |
+
sstruct.unpack(ttcHeaderFormat, data, self)
|
| 635 |
+
if self.TTCTag != "ttcf":
|
| 636 |
+
raise TTLibError("Not a Font Collection")
|
| 637 |
+
assert self.Version == 0x00010000 or self.Version == 0x00020000, (
|
| 638 |
+
"unrecognized TTC version 0x%08x" % self.Version
|
| 639 |
+
)
|
| 640 |
+
self.offsetTable = struct.unpack(
|
| 641 |
+
">%dL" % self.numFonts, file.read(self.numFonts * 4)
|
| 642 |
+
)
|
| 643 |
+
if self.Version == 0x00020000:
|
| 644 |
+
pass # ignoring version 2.0 signatures
|
| 645 |
+
return self
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
def writeTTCHeader(file, numFonts):
|
| 649 |
+
self = SimpleNamespace()
|
| 650 |
+
self.TTCTag = "ttcf"
|
| 651 |
+
self.Version = 0x00010000
|
| 652 |
+
self.numFonts = numFonts
|
| 653 |
+
file.seek(0)
|
| 654 |
+
file.write(sstruct.pack(ttcHeaderFormat, self))
|
| 655 |
+
offset = file.tell()
|
| 656 |
+
file.write(struct.pack(">%dL" % self.numFonts, *([0] * self.numFonts)))
|
| 657 |
+
return offset
|
| 658 |
+
|
| 659 |
+
|
| 660 |
+
if __name__ == "__main__":
|
| 661 |
+
import sys
|
| 662 |
+
import doctest
|
| 663 |
+
|
| 664 |
+
sys.exit(doctest.testmod().failed)
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/standardGlyphOrder.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# 'post' table formats 1.0 and 2.0 rely on this list of "standard"
|
| 3 |
+
# glyphs.
|
| 4 |
+
#
|
| 5 |
+
# My list is correct according to the Apple documentation for the 'post' table:
|
| 6 |
+
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
|
| 7 |
+
# (However, it seems that TTFdump (from MS) and FontLab disagree, at
|
| 8 |
+
# least with respect to the last glyph, which they list as 'dslash'
|
| 9 |
+
# instead of 'dcroat'.)
|
| 10 |
+
#
|
| 11 |
+
|
| 12 |
+
standardGlyphOrder = [
|
| 13 |
+
".notdef", # 0
|
| 14 |
+
".null", # 1
|
| 15 |
+
"nonmarkingreturn", # 2
|
| 16 |
+
"space", # 3
|
| 17 |
+
"exclam", # 4
|
| 18 |
+
"quotedbl", # 5
|
| 19 |
+
"numbersign", # 6
|
| 20 |
+
"dollar", # 7
|
| 21 |
+
"percent", # 8
|
| 22 |
+
"ampersand", # 9
|
| 23 |
+
"quotesingle", # 10
|
| 24 |
+
"parenleft", # 11
|
| 25 |
+
"parenright", # 12
|
| 26 |
+
"asterisk", # 13
|
| 27 |
+
"plus", # 14
|
| 28 |
+
"comma", # 15
|
| 29 |
+
"hyphen", # 16
|
| 30 |
+
"period", # 17
|
| 31 |
+
"slash", # 18
|
| 32 |
+
"zero", # 19
|
| 33 |
+
"one", # 20
|
| 34 |
+
"two", # 21
|
| 35 |
+
"three", # 22
|
| 36 |
+
"four", # 23
|
| 37 |
+
"five", # 24
|
| 38 |
+
"six", # 25
|
| 39 |
+
"seven", # 26
|
| 40 |
+
"eight", # 27
|
| 41 |
+
"nine", # 28
|
| 42 |
+
"colon", # 29
|
| 43 |
+
"semicolon", # 30
|
| 44 |
+
"less", # 31
|
| 45 |
+
"equal", # 32
|
| 46 |
+
"greater", # 33
|
| 47 |
+
"question", # 34
|
| 48 |
+
"at", # 35
|
| 49 |
+
"A", # 36
|
| 50 |
+
"B", # 37
|
| 51 |
+
"C", # 38
|
| 52 |
+
"D", # 39
|
| 53 |
+
"E", # 40
|
| 54 |
+
"F", # 41
|
| 55 |
+
"G", # 42
|
| 56 |
+
"H", # 43
|
| 57 |
+
"I", # 44
|
| 58 |
+
"J", # 45
|
| 59 |
+
"K", # 46
|
| 60 |
+
"L", # 47
|
| 61 |
+
"M", # 48
|
| 62 |
+
"N", # 49
|
| 63 |
+
"O", # 50
|
| 64 |
+
"P", # 51
|
| 65 |
+
"Q", # 52
|
| 66 |
+
"R", # 53
|
| 67 |
+
"S", # 54
|
| 68 |
+
"T", # 55
|
| 69 |
+
"U", # 56
|
| 70 |
+
"V", # 57
|
| 71 |
+
"W", # 58
|
| 72 |
+
"X", # 59
|
| 73 |
+
"Y", # 60
|
| 74 |
+
"Z", # 61
|
| 75 |
+
"bracketleft", # 62
|
| 76 |
+
"backslash", # 63
|
| 77 |
+
"bracketright", # 64
|
| 78 |
+
"asciicircum", # 65
|
| 79 |
+
"underscore", # 66
|
| 80 |
+
"grave", # 67
|
| 81 |
+
"a", # 68
|
| 82 |
+
"b", # 69
|
| 83 |
+
"c", # 70
|
| 84 |
+
"d", # 71
|
| 85 |
+
"e", # 72
|
| 86 |
+
"f", # 73
|
| 87 |
+
"g", # 74
|
| 88 |
+
"h", # 75
|
| 89 |
+
"i", # 76
|
| 90 |
+
"j", # 77
|
| 91 |
+
"k", # 78
|
| 92 |
+
"l", # 79
|
| 93 |
+
"m", # 80
|
| 94 |
+
"n", # 81
|
| 95 |
+
"o", # 82
|
| 96 |
+
"p", # 83
|
| 97 |
+
"q", # 84
|
| 98 |
+
"r", # 85
|
| 99 |
+
"s", # 86
|
| 100 |
+
"t", # 87
|
| 101 |
+
"u", # 88
|
| 102 |
+
"v", # 89
|
| 103 |
+
"w", # 90
|
| 104 |
+
"x", # 91
|
| 105 |
+
"y", # 92
|
| 106 |
+
"z", # 93
|
| 107 |
+
"braceleft", # 94
|
| 108 |
+
"bar", # 95
|
| 109 |
+
"braceright", # 96
|
| 110 |
+
"asciitilde", # 97
|
| 111 |
+
"Adieresis", # 98
|
| 112 |
+
"Aring", # 99
|
| 113 |
+
"Ccedilla", # 100
|
| 114 |
+
"Eacute", # 101
|
| 115 |
+
"Ntilde", # 102
|
| 116 |
+
"Odieresis", # 103
|
| 117 |
+
"Udieresis", # 104
|
| 118 |
+
"aacute", # 105
|
| 119 |
+
"agrave", # 106
|
| 120 |
+
"acircumflex", # 107
|
| 121 |
+
"adieresis", # 108
|
| 122 |
+
"atilde", # 109
|
| 123 |
+
"aring", # 110
|
| 124 |
+
"ccedilla", # 111
|
| 125 |
+
"eacute", # 112
|
| 126 |
+
"egrave", # 113
|
| 127 |
+
"ecircumflex", # 114
|
| 128 |
+
"edieresis", # 115
|
| 129 |
+
"iacute", # 116
|
| 130 |
+
"igrave", # 117
|
| 131 |
+
"icircumflex", # 118
|
| 132 |
+
"idieresis", # 119
|
| 133 |
+
"ntilde", # 120
|
| 134 |
+
"oacute", # 121
|
| 135 |
+
"ograve", # 122
|
| 136 |
+
"ocircumflex", # 123
|
| 137 |
+
"odieresis", # 124
|
| 138 |
+
"otilde", # 125
|
| 139 |
+
"uacute", # 126
|
| 140 |
+
"ugrave", # 127
|
| 141 |
+
"ucircumflex", # 128
|
| 142 |
+
"udieresis", # 129
|
| 143 |
+
"dagger", # 130
|
| 144 |
+
"degree", # 131
|
| 145 |
+
"cent", # 132
|
| 146 |
+
"sterling", # 133
|
| 147 |
+
"section", # 134
|
| 148 |
+
"bullet", # 135
|
| 149 |
+
"paragraph", # 136
|
| 150 |
+
"germandbls", # 137
|
| 151 |
+
"registered", # 138
|
| 152 |
+
"copyright", # 139
|
| 153 |
+
"trademark", # 140
|
| 154 |
+
"acute", # 141
|
| 155 |
+
"dieresis", # 142
|
| 156 |
+
"notequal", # 143
|
| 157 |
+
"AE", # 144
|
| 158 |
+
"Oslash", # 145
|
| 159 |
+
"infinity", # 146
|
| 160 |
+
"plusminus", # 147
|
| 161 |
+
"lessequal", # 148
|
| 162 |
+
"greaterequal", # 149
|
| 163 |
+
"yen", # 150
|
| 164 |
+
"mu", # 151
|
| 165 |
+
"partialdiff", # 152
|
| 166 |
+
"summation", # 153
|
| 167 |
+
"product", # 154
|
| 168 |
+
"pi", # 155
|
| 169 |
+
"integral", # 156
|
| 170 |
+
"ordfeminine", # 157
|
| 171 |
+
"ordmasculine", # 158
|
| 172 |
+
"Omega", # 159
|
| 173 |
+
"ae", # 160
|
| 174 |
+
"oslash", # 161
|
| 175 |
+
"questiondown", # 162
|
| 176 |
+
"exclamdown", # 163
|
| 177 |
+
"logicalnot", # 164
|
| 178 |
+
"radical", # 165
|
| 179 |
+
"florin", # 166
|
| 180 |
+
"approxequal", # 167
|
| 181 |
+
"Delta", # 168
|
| 182 |
+
"guillemotleft", # 169
|
| 183 |
+
"guillemotright", # 170
|
| 184 |
+
"ellipsis", # 171
|
| 185 |
+
"nonbreakingspace", # 172
|
| 186 |
+
"Agrave", # 173
|
| 187 |
+
"Atilde", # 174
|
| 188 |
+
"Otilde", # 175
|
| 189 |
+
"OE", # 176
|
| 190 |
+
"oe", # 177
|
| 191 |
+
"endash", # 178
|
| 192 |
+
"emdash", # 179
|
| 193 |
+
"quotedblleft", # 180
|
| 194 |
+
"quotedblright", # 181
|
| 195 |
+
"quoteleft", # 182
|
| 196 |
+
"quoteright", # 183
|
| 197 |
+
"divide", # 184
|
| 198 |
+
"lozenge", # 185
|
| 199 |
+
"ydieresis", # 186
|
| 200 |
+
"Ydieresis", # 187
|
| 201 |
+
"fraction", # 188
|
| 202 |
+
"currency", # 189
|
| 203 |
+
"guilsinglleft", # 190
|
| 204 |
+
"guilsinglright", # 191
|
| 205 |
+
"fi", # 192
|
| 206 |
+
"fl", # 193
|
| 207 |
+
"daggerdbl", # 194
|
| 208 |
+
"periodcentered", # 195
|
| 209 |
+
"quotesinglbase", # 196
|
| 210 |
+
"quotedblbase", # 197
|
| 211 |
+
"perthousand", # 198
|
| 212 |
+
"Acircumflex", # 199
|
| 213 |
+
"Ecircumflex", # 200
|
| 214 |
+
"Aacute", # 201
|
| 215 |
+
"Edieresis", # 202
|
| 216 |
+
"Egrave", # 203
|
| 217 |
+
"Iacute", # 204
|
| 218 |
+
"Icircumflex", # 205
|
| 219 |
+
"Idieresis", # 206
|
| 220 |
+
"Igrave", # 207
|
| 221 |
+
"Oacute", # 208
|
| 222 |
+
"Ocircumflex", # 209
|
| 223 |
+
"apple", # 210
|
| 224 |
+
"Ograve", # 211
|
| 225 |
+
"Uacute", # 212
|
| 226 |
+
"Ucircumflex", # 213
|
| 227 |
+
"Ugrave", # 214
|
| 228 |
+
"dotlessi", # 215
|
| 229 |
+
"circumflex", # 216
|
| 230 |
+
"tilde", # 217
|
| 231 |
+
"macron", # 218
|
| 232 |
+
"breve", # 219
|
| 233 |
+
"dotaccent", # 220
|
| 234 |
+
"ring", # 221
|
| 235 |
+
"cedilla", # 222
|
| 236 |
+
"hungarumlaut", # 223
|
| 237 |
+
"ogonek", # 224
|
| 238 |
+
"caron", # 225
|
| 239 |
+
"Lslash", # 226
|
| 240 |
+
"lslash", # 227
|
| 241 |
+
"Scaron", # 228
|
| 242 |
+
"scaron", # 229
|
| 243 |
+
"Zcaron", # 230
|
| 244 |
+
"zcaron", # 231
|
| 245 |
+
"brokenbar", # 232
|
| 246 |
+
"Eth", # 233
|
| 247 |
+
"eth", # 234
|
| 248 |
+
"Yacute", # 235
|
| 249 |
+
"yacute", # 236
|
| 250 |
+
"Thorn", # 237
|
| 251 |
+
"thorn", # 238
|
| 252 |
+
"minus", # 239
|
| 253 |
+
"multiply", # 240
|
| 254 |
+
"onesuperior", # 241
|
| 255 |
+
"twosuperior", # 242
|
| 256 |
+
"threesuperior", # 243
|
| 257 |
+
"onehalf", # 244
|
| 258 |
+
"onequarter", # 245
|
| 259 |
+
"threequarters", # 246
|
| 260 |
+
"franc", # 247
|
| 261 |
+
"Gbreve", # 248
|
| 262 |
+
"gbreve", # 249
|
| 263 |
+
"Idotaccent", # 250
|
| 264 |
+
"Scedilla", # 251
|
| 265 |
+
"scedilla", # 252
|
| 266 |
+
"Cacute", # 253
|
| 267 |
+
"cacute", # 254
|
| 268 |
+
"Ccaron", # 255
|
| 269 |
+
"ccaron", # 256
|
| 270 |
+
"dcroat", # 257
|
| 271 |
+
]
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/B_A_S_E_.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_B_A_S_E_(BaseTTXConverter):
|
| 5 |
+
"""Baseline table
|
| 6 |
+
|
| 7 |
+
The ``BASE`` table contains information needed to align glyphs in
|
| 8 |
+
different scripts, from different fonts, or at different sizes
|
| 9 |
+
within the same line of text.
|
| 10 |
+
|
| 11 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/base
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/BitmapGlyphMetrics.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Since bitmap glyph metrics are shared between EBLC and EBDT
|
| 2 |
+
# this class gets its own python file.
|
| 3 |
+
from fontTools.misc import sstruct
|
| 4 |
+
from fontTools.misc.textTools import safeEval
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
log = logging.getLogger(__name__)
|
| 9 |
+
|
| 10 |
+
bigGlyphMetricsFormat = """
|
| 11 |
+
> # big endian
|
| 12 |
+
height: B
|
| 13 |
+
width: B
|
| 14 |
+
horiBearingX: b
|
| 15 |
+
horiBearingY: b
|
| 16 |
+
horiAdvance: B
|
| 17 |
+
vertBearingX: b
|
| 18 |
+
vertBearingY: b
|
| 19 |
+
vertAdvance: B
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
smallGlyphMetricsFormat = """
|
| 23 |
+
> # big endian
|
| 24 |
+
height: B
|
| 25 |
+
width: B
|
| 26 |
+
BearingX: b
|
| 27 |
+
BearingY: b
|
| 28 |
+
Advance: B
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class BitmapGlyphMetrics(object):
|
| 33 |
+
def toXML(self, writer, ttFont):
|
| 34 |
+
writer.begintag(self.__class__.__name__)
|
| 35 |
+
writer.newline()
|
| 36 |
+
for metricName in sstruct.getformat(self.__class__.binaryFormat)[1]:
|
| 37 |
+
writer.simpletag(metricName, value=getattr(self, metricName))
|
| 38 |
+
writer.newline()
|
| 39 |
+
writer.endtag(self.__class__.__name__)
|
| 40 |
+
writer.newline()
|
| 41 |
+
|
| 42 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 43 |
+
metricNames = set(sstruct.getformat(self.__class__.binaryFormat)[1])
|
| 44 |
+
for element in content:
|
| 45 |
+
if not isinstance(element, tuple):
|
| 46 |
+
continue
|
| 47 |
+
name, attrs, content = element
|
| 48 |
+
# Make sure this is a metric that is needed by GlyphMetrics.
|
| 49 |
+
if name in metricNames:
|
| 50 |
+
vars(self)[name] = safeEval(attrs["value"])
|
| 51 |
+
else:
|
| 52 |
+
log.warning(
|
| 53 |
+
"unknown name '%s' being ignored in %s.",
|
| 54 |
+
name,
|
| 55 |
+
self.__class__.__name__,
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class BigGlyphMetrics(BitmapGlyphMetrics):
|
| 60 |
+
binaryFormat = bigGlyphMetricsFormat
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class SmallGlyphMetrics(BitmapGlyphMetrics):
|
| 64 |
+
binaryFormat = smallGlyphMetricsFormat
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_D_T_.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2013 Google, Inc. All Rights Reserved.
|
| 2 |
+
#
|
| 3 |
+
# Google Author(s): Matt Fontaine
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
from fontTools.misc.textTools import bytesjoin
|
| 7 |
+
from fontTools.misc import sstruct
|
| 8 |
+
from . import E_B_D_T_
|
| 9 |
+
from .BitmapGlyphMetrics import (
|
| 10 |
+
BigGlyphMetrics,
|
| 11 |
+
bigGlyphMetricsFormat,
|
| 12 |
+
SmallGlyphMetrics,
|
| 13 |
+
smallGlyphMetricsFormat,
|
| 14 |
+
)
|
| 15 |
+
from .E_B_D_T_ import (
|
| 16 |
+
BitmapGlyph,
|
| 17 |
+
BitmapPlusSmallMetricsMixin,
|
| 18 |
+
BitmapPlusBigMetricsMixin,
|
| 19 |
+
)
|
| 20 |
+
import struct
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class table_C_B_D_T_(E_B_D_T_.table_E_B_D_T_):
|
| 24 |
+
"""Color Bitmap Data table
|
| 25 |
+
|
| 26 |
+
The ``CBDT`` table contains color bitmap data for glyphs. It must
|
| 27 |
+
be used in concert with the ``CBLC`` table.
|
| 28 |
+
|
| 29 |
+
It is backwards-compatible with the monochrome/grayscale ``EBDT`` table.
|
| 30 |
+
|
| 31 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
# Change the data locator table being referenced.
|
| 35 |
+
locatorName = "CBLC"
|
| 36 |
+
|
| 37 |
+
# Modify the format class accessor for color bitmap use.
|
| 38 |
+
def getImageFormatClass(self, imageFormat):
|
| 39 |
+
try:
|
| 40 |
+
return E_B_D_T_.table_E_B_D_T_.getImageFormatClass(self, imageFormat)
|
| 41 |
+
except KeyError:
|
| 42 |
+
return cbdt_bitmap_classes[imageFormat]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# Helper method for removing export features not supported by color bitmaps.
|
| 46 |
+
# Write data in the parent class will default to raw if an option is unsupported.
|
| 47 |
+
def _removeUnsupportedForColor(dataFunctions):
|
| 48 |
+
dataFunctions = dict(dataFunctions)
|
| 49 |
+
del dataFunctions["row"]
|
| 50 |
+
return dataFunctions
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class ColorBitmapGlyph(BitmapGlyph):
|
| 54 |
+
fileExtension = ".png"
|
| 55 |
+
xmlDataFunctions = _removeUnsupportedForColor(BitmapGlyph.xmlDataFunctions)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class cbdt_bitmap_format_17(BitmapPlusSmallMetricsMixin, ColorBitmapGlyph):
|
| 59 |
+
def decompile(self):
|
| 60 |
+
self.metrics = SmallGlyphMetrics()
|
| 61 |
+
dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
|
| 62 |
+
(dataLen,) = struct.unpack(">L", data[:4])
|
| 63 |
+
data = data[4:]
|
| 64 |
+
|
| 65 |
+
# For the image data cut it to the size specified by dataLen.
|
| 66 |
+
assert dataLen <= len(data), "Data overun in format 17"
|
| 67 |
+
self.imageData = data[:dataLen]
|
| 68 |
+
|
| 69 |
+
def compile(self, ttFont):
|
| 70 |
+
dataList = []
|
| 71 |
+
dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
|
| 72 |
+
dataList.append(struct.pack(">L", len(self.imageData)))
|
| 73 |
+
dataList.append(self.imageData)
|
| 74 |
+
return bytesjoin(dataList)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class cbdt_bitmap_format_18(BitmapPlusBigMetricsMixin, ColorBitmapGlyph):
|
| 78 |
+
def decompile(self):
|
| 79 |
+
self.metrics = BigGlyphMetrics()
|
| 80 |
+
dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
|
| 81 |
+
(dataLen,) = struct.unpack(">L", data[:4])
|
| 82 |
+
data = data[4:]
|
| 83 |
+
|
| 84 |
+
# For the image data cut it to the size specified by dataLen.
|
| 85 |
+
assert dataLen <= len(data), "Data overun in format 18"
|
| 86 |
+
self.imageData = data[:dataLen]
|
| 87 |
+
|
| 88 |
+
def compile(self, ttFont):
|
| 89 |
+
dataList = []
|
| 90 |
+
dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
|
| 91 |
+
dataList.append(struct.pack(">L", len(self.imageData)))
|
| 92 |
+
dataList.append(self.imageData)
|
| 93 |
+
return bytesjoin(dataList)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class cbdt_bitmap_format_19(ColorBitmapGlyph):
|
| 97 |
+
def decompile(self):
|
| 98 |
+
(dataLen,) = struct.unpack(">L", self.data[:4])
|
| 99 |
+
data = self.data[4:]
|
| 100 |
+
|
| 101 |
+
assert dataLen <= len(data), "Data overun in format 19"
|
| 102 |
+
self.imageData = data[:dataLen]
|
| 103 |
+
|
| 104 |
+
def compile(self, ttFont):
|
| 105 |
+
return struct.pack(">L", len(self.imageData)) + self.imageData
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# Dict for CBDT extended formats.
|
| 109 |
+
cbdt_bitmap_classes = {
|
| 110 |
+
17: cbdt_bitmap_format_17,
|
| 111 |
+
18: cbdt_bitmap_format_18,
|
| 112 |
+
19: cbdt_bitmap_format_19,
|
| 113 |
+
}
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_B_L_C_.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2013 Google, Inc. All Rights Reserved.
|
| 2 |
+
#
|
| 3 |
+
# Google Author(s): Matt Fontaine
|
| 4 |
+
|
| 5 |
+
from . import E_B_L_C_
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class table_C_B_L_C_(E_B_L_C_.table_E_B_L_C_):
|
| 9 |
+
"""Color Bitmap Location table
|
| 10 |
+
|
| 11 |
+
The ``CBLC`` table contains the locations of color bitmaps for glyphs. It must
|
| 12 |
+
be used in concert with the ``CBDT`` table.
|
| 13 |
+
|
| 14 |
+
It is backwards-compatible with the monochrome/grayscale ``EBLC`` table.
|
| 15 |
+
|
| 16 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/cblc
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
dependencies = ["CBDT"]
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F_.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from io import BytesIO
|
| 2 |
+
from fontTools import cffLib
|
| 3 |
+
from . import DefaultTable
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class table_C_F_F_(DefaultTable.DefaultTable):
|
| 7 |
+
"""Compact Font Format table (version 1)
|
| 8 |
+
|
| 9 |
+
The ``CFF`` table embeds a CFF-formatted font. The CFF font format
|
| 10 |
+
predates OpenType and could be used as a standalone font file, but the
|
| 11 |
+
``CFF`` table is also used to package CFF fonts into an OpenType
|
| 12 |
+
container.
|
| 13 |
+
|
| 14 |
+
.. note::
|
| 15 |
+
``CFF`` has been succeeded by ``CFF2``, which eliminates much of
|
| 16 |
+
the redundancy incurred by embedding CFF version 1 in an OpenType
|
| 17 |
+
font.
|
| 18 |
+
|
| 19 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/cff
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self, tag=None):
|
| 23 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 24 |
+
self.cff = cffLib.CFFFontSet()
|
| 25 |
+
self._gaveGlyphOrder = False
|
| 26 |
+
|
| 27 |
+
def decompile(self, data, otFont):
|
| 28 |
+
self.cff.decompile(BytesIO(data), otFont, isCFF2=False)
|
| 29 |
+
assert len(self.cff) == 1, "can't deal with multi-font CFF tables."
|
| 30 |
+
|
| 31 |
+
def compile(self, otFont):
|
| 32 |
+
f = BytesIO()
|
| 33 |
+
self.cff.compile(f, otFont, isCFF2=False)
|
| 34 |
+
return f.getvalue()
|
| 35 |
+
|
| 36 |
+
def haveGlyphNames(self):
|
| 37 |
+
if hasattr(self.cff[self.cff.fontNames[0]], "ROS"):
|
| 38 |
+
return False # CID-keyed font
|
| 39 |
+
else:
|
| 40 |
+
return True
|
| 41 |
+
|
| 42 |
+
def getGlyphOrder(self):
|
| 43 |
+
if self._gaveGlyphOrder:
|
| 44 |
+
from fontTools import ttLib
|
| 45 |
+
|
| 46 |
+
raise ttLib.TTLibError("illegal use of getGlyphOrder()")
|
| 47 |
+
self._gaveGlyphOrder = True
|
| 48 |
+
return self.cff[self.cff.fontNames[0]].getGlyphOrder()
|
| 49 |
+
|
| 50 |
+
def setGlyphOrder(self, glyphOrder):
|
| 51 |
+
pass
|
| 52 |
+
# XXX
|
| 53 |
+
# self.cff[self.cff.fontNames[0]].setGlyphOrder(glyphOrder)
|
| 54 |
+
|
| 55 |
+
def toXML(self, writer, otFont):
|
| 56 |
+
self.cff.toXML(writer)
|
| 57 |
+
|
| 58 |
+
def fromXML(self, name, attrs, content, otFont):
|
| 59 |
+
if not hasattr(self, "cff"):
|
| 60 |
+
self.cff = cffLib.CFFFontSet()
|
| 61 |
+
self.cff.fromXML(name, attrs, content, otFont)
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_F_F__2.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from io import BytesIO
|
| 2 |
+
from fontTools.ttLib.tables.C_F_F_ import table_C_F_F_
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class table_C_F_F__2(table_C_F_F_):
|
| 6 |
+
"""Compact Font Format version 2 table
|
| 7 |
+
|
| 8 |
+
The ``CFF2`` table contains glyph data for a CFF2-flavored OpenType
|
| 9 |
+
font.
|
| 10 |
+
|
| 11 |
+
.. note::
|
| 12 |
+
``CFF2`` is the successor to ``CFF``, and eliminates much of
|
| 13 |
+
the redundancy incurred by embedding CFF version 1 in an OpenType
|
| 14 |
+
font.
|
| 15 |
+
|
| 16 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/cff2
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def decompile(self, data, otFont):
|
| 20 |
+
self.cff.decompile(BytesIO(data), otFont, isCFF2=True)
|
| 21 |
+
assert len(self.cff) == 1, "can't deal with multi-font CFF tables."
|
| 22 |
+
|
| 23 |
+
def compile(self, otFont):
|
| 24 |
+
f = BytesIO()
|
| 25 |
+
self.cff.compile(f, otFont, isCFF2=True)
|
| 26 |
+
return f.getvalue()
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_O_L_R_.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2013 Google, Inc. All Rights Reserved.
|
| 2 |
+
#
|
| 3 |
+
# Google Author(s): Behdad Esfahbod
|
| 4 |
+
|
| 5 |
+
from fontTools.misc.textTools import safeEval
|
| 6 |
+
from . import DefaultTable
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class table_C_O_L_R_(DefaultTable.DefaultTable):
|
| 10 |
+
"""Color table
|
| 11 |
+
|
| 12 |
+
The ``COLR`` table defines color presentation of outline glyphs. It must
|
| 13 |
+
be used in concert with the ``CPAL`` table, which contains the color
|
| 14 |
+
descriptors used.
|
| 15 |
+
|
| 16 |
+
This table is structured so that you can treat it like a dictionary keyed by glyph name.
|
| 17 |
+
|
| 18 |
+
``ttFont['COLR'][<glyphName>]`` will return the color layers for any glyph.
|
| 19 |
+
|
| 20 |
+
``ttFont['COLR'][<glyphName>] = <value>`` will set the color layers for any glyph.
|
| 21 |
+
|
| 22 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/colr
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
@staticmethod
|
| 26 |
+
def _decompileColorLayersV0(table):
|
| 27 |
+
if not table.LayerRecordArray:
|
| 28 |
+
return {}
|
| 29 |
+
colorLayerLists = {}
|
| 30 |
+
layerRecords = table.LayerRecordArray.LayerRecord
|
| 31 |
+
numLayerRecords = len(layerRecords)
|
| 32 |
+
for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
|
| 33 |
+
baseGlyph = baseRec.BaseGlyph
|
| 34 |
+
firstLayerIndex = baseRec.FirstLayerIndex
|
| 35 |
+
numLayers = baseRec.NumLayers
|
| 36 |
+
assert firstLayerIndex + numLayers <= numLayerRecords
|
| 37 |
+
layers = []
|
| 38 |
+
for i in range(firstLayerIndex, firstLayerIndex + numLayers):
|
| 39 |
+
layerRec = layerRecords[i]
|
| 40 |
+
layers.append(LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex))
|
| 41 |
+
colorLayerLists[baseGlyph] = layers
|
| 42 |
+
return colorLayerLists
|
| 43 |
+
|
| 44 |
+
def _toOTTable(self, ttFont):
|
| 45 |
+
from . import otTables
|
| 46 |
+
from fontTools.colorLib.builder import populateCOLRv0
|
| 47 |
+
|
| 48 |
+
tableClass = getattr(otTables, self.tableTag)
|
| 49 |
+
table = tableClass()
|
| 50 |
+
table.Version = self.version
|
| 51 |
+
|
| 52 |
+
populateCOLRv0(
|
| 53 |
+
table,
|
| 54 |
+
{
|
| 55 |
+
baseGlyph: [(layer.name, layer.colorID) for layer in layers]
|
| 56 |
+
for baseGlyph, layers in self.ColorLayers.items()
|
| 57 |
+
},
|
| 58 |
+
glyphMap=ttFont.getReverseGlyphMap(rebuild=True),
|
| 59 |
+
)
|
| 60 |
+
return table
|
| 61 |
+
|
| 62 |
+
def decompile(self, data, ttFont):
|
| 63 |
+
from .otBase import OTTableReader
|
| 64 |
+
from . import otTables
|
| 65 |
+
|
| 66 |
+
# We use otData to decompile, but we adapt the decompiled otTables to the
|
| 67 |
+
# existing COLR v0 API for backward compatibility.
|
| 68 |
+
reader = OTTableReader(data, tableTag=self.tableTag)
|
| 69 |
+
tableClass = getattr(otTables, self.tableTag)
|
| 70 |
+
table = tableClass()
|
| 71 |
+
table.decompile(reader, ttFont)
|
| 72 |
+
|
| 73 |
+
self.version = table.Version
|
| 74 |
+
if self.version == 0:
|
| 75 |
+
self.ColorLayers = self._decompileColorLayersV0(table)
|
| 76 |
+
else:
|
| 77 |
+
# for new versions, keep the raw otTables around
|
| 78 |
+
self.table = table
|
| 79 |
+
|
| 80 |
+
def compile(self, ttFont):
|
| 81 |
+
from .otBase import OTTableWriter
|
| 82 |
+
|
| 83 |
+
if hasattr(self, "table"):
|
| 84 |
+
table = self.table
|
| 85 |
+
else:
|
| 86 |
+
table = self._toOTTable(ttFont)
|
| 87 |
+
|
| 88 |
+
writer = OTTableWriter(tableTag=self.tableTag)
|
| 89 |
+
table.compile(writer, ttFont)
|
| 90 |
+
return writer.getAllData()
|
| 91 |
+
|
| 92 |
+
def toXML(self, writer, ttFont):
|
| 93 |
+
if hasattr(self, "table"):
|
| 94 |
+
self.table.toXML2(writer, ttFont)
|
| 95 |
+
else:
|
| 96 |
+
writer.simpletag("version", value=self.version)
|
| 97 |
+
writer.newline()
|
| 98 |
+
for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID):
|
| 99 |
+
writer.begintag("ColorGlyph", name=baseGlyph)
|
| 100 |
+
writer.newline()
|
| 101 |
+
for layer in self.ColorLayers[baseGlyph]:
|
| 102 |
+
layer.toXML(writer, ttFont)
|
| 103 |
+
writer.endtag("ColorGlyph")
|
| 104 |
+
writer.newline()
|
| 105 |
+
|
| 106 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 107 |
+
if name == "version": # old COLR v0 API
|
| 108 |
+
setattr(self, name, safeEval(attrs["value"]))
|
| 109 |
+
elif name == "ColorGlyph":
|
| 110 |
+
if not hasattr(self, "ColorLayers"):
|
| 111 |
+
self.ColorLayers = {}
|
| 112 |
+
glyphName = attrs["name"]
|
| 113 |
+
for element in content:
|
| 114 |
+
if isinstance(element, str):
|
| 115 |
+
continue
|
| 116 |
+
layers = []
|
| 117 |
+
for element in content:
|
| 118 |
+
if isinstance(element, str):
|
| 119 |
+
continue
|
| 120 |
+
layer = LayerRecord()
|
| 121 |
+
layer.fromXML(element[0], element[1], element[2], ttFont)
|
| 122 |
+
layers.append(layer)
|
| 123 |
+
self.ColorLayers[glyphName] = layers
|
| 124 |
+
else: # new COLR v1 API
|
| 125 |
+
from . import otTables
|
| 126 |
+
|
| 127 |
+
if not hasattr(self, "table"):
|
| 128 |
+
tableClass = getattr(otTables, self.tableTag)
|
| 129 |
+
self.table = tableClass()
|
| 130 |
+
self.table.fromXML(name, attrs, content, ttFont)
|
| 131 |
+
self.table.populateDefaults()
|
| 132 |
+
self.version = self.table.Version
|
| 133 |
+
|
| 134 |
+
def __getitem__(self, glyphName):
|
| 135 |
+
if not isinstance(glyphName, str):
|
| 136 |
+
raise TypeError(f"expected str, found {type(glyphName).__name__}")
|
| 137 |
+
return self.ColorLayers[glyphName]
|
| 138 |
+
|
| 139 |
+
def __setitem__(self, glyphName, value):
|
| 140 |
+
if not isinstance(glyphName, str):
|
| 141 |
+
raise TypeError(f"expected str, found {type(glyphName).__name__}")
|
| 142 |
+
if value is not None:
|
| 143 |
+
self.ColorLayers[glyphName] = value
|
| 144 |
+
elif glyphName in self.ColorLayers:
|
| 145 |
+
del self.ColorLayers[glyphName]
|
| 146 |
+
|
| 147 |
+
def __delitem__(self, glyphName):
|
| 148 |
+
del self.ColorLayers[glyphName]
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
class LayerRecord(object):
|
| 152 |
+
def __init__(self, name=None, colorID=None):
|
| 153 |
+
self.name = name
|
| 154 |
+
self.colorID = colorID
|
| 155 |
+
|
| 156 |
+
def toXML(self, writer, ttFont):
|
| 157 |
+
writer.simpletag("layer", name=self.name, colorID=self.colorID)
|
| 158 |
+
writer.newline()
|
| 159 |
+
|
| 160 |
+
def fromXML(self, eltname, attrs, content, ttFont):
|
| 161 |
+
for name, value in attrs.items():
|
| 162 |
+
if name == "name":
|
| 163 |
+
setattr(self, name, value)
|
| 164 |
+
else:
|
| 165 |
+
setattr(self, name, safeEval(value))
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/C_P_A_L_.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2013 Google, Inc. All Rights Reserved.
|
| 2 |
+
#
|
| 3 |
+
# Google Author(s): Behdad Esfahbod
|
| 4 |
+
|
| 5 |
+
from fontTools.misc.textTools import bytesjoin, safeEval
|
| 6 |
+
from . import DefaultTable
|
| 7 |
+
import array
|
| 8 |
+
from collections import namedtuple
|
| 9 |
+
import struct
|
| 10 |
+
import sys
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class table_C_P_A_L_(DefaultTable.DefaultTable):
|
| 14 |
+
"""Color Palette table
|
| 15 |
+
|
| 16 |
+
The ``CPAL`` table contains a set of one or more color palettes. The color
|
| 17 |
+
records in each palette can be referenced by the ``COLR`` table to specify
|
| 18 |
+
the colors used in a color glyph.
|
| 19 |
+
|
| 20 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/cpal
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
NO_NAME_ID = 0xFFFF
|
| 24 |
+
DEFAULT_PALETTE_TYPE = 0
|
| 25 |
+
|
| 26 |
+
def __init__(self, tag=None):
|
| 27 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 28 |
+
self.palettes = []
|
| 29 |
+
self.paletteTypes = []
|
| 30 |
+
self.paletteLabels = []
|
| 31 |
+
self.paletteEntryLabels = []
|
| 32 |
+
|
| 33 |
+
def decompile(self, data, ttFont):
|
| 34 |
+
(
|
| 35 |
+
self.version,
|
| 36 |
+
self.numPaletteEntries,
|
| 37 |
+
numPalettes,
|
| 38 |
+
numColorRecords,
|
| 39 |
+
goffsetFirstColorRecord,
|
| 40 |
+
) = struct.unpack(">HHHHL", data[:12])
|
| 41 |
+
assert (
|
| 42 |
+
self.version <= 1
|
| 43 |
+
), "Version of CPAL table is higher than I know how to handle"
|
| 44 |
+
self.palettes = []
|
| 45 |
+
pos = 12
|
| 46 |
+
for i in range(numPalettes):
|
| 47 |
+
startIndex = struct.unpack(">H", data[pos : pos + 2])[0]
|
| 48 |
+
assert startIndex + self.numPaletteEntries <= numColorRecords
|
| 49 |
+
pos += 2
|
| 50 |
+
palette = []
|
| 51 |
+
ppos = goffsetFirstColorRecord + startIndex * 4
|
| 52 |
+
for j in range(self.numPaletteEntries):
|
| 53 |
+
palette.append(Color(*struct.unpack(">BBBB", data[ppos : ppos + 4])))
|
| 54 |
+
ppos += 4
|
| 55 |
+
self.palettes.append(palette)
|
| 56 |
+
if self.version == 0:
|
| 57 |
+
offsetToPaletteTypeArray = 0
|
| 58 |
+
offsetToPaletteLabelArray = 0
|
| 59 |
+
offsetToPaletteEntryLabelArray = 0
|
| 60 |
+
else:
|
| 61 |
+
pos = 12 + numPalettes * 2
|
| 62 |
+
(
|
| 63 |
+
offsetToPaletteTypeArray,
|
| 64 |
+
offsetToPaletteLabelArray,
|
| 65 |
+
offsetToPaletteEntryLabelArray,
|
| 66 |
+
) = struct.unpack(">LLL", data[pos : pos + 12])
|
| 67 |
+
self.paletteTypes = self._decompileUInt32Array(
|
| 68 |
+
data,
|
| 69 |
+
offsetToPaletteTypeArray,
|
| 70 |
+
numPalettes,
|
| 71 |
+
default=self.DEFAULT_PALETTE_TYPE,
|
| 72 |
+
)
|
| 73 |
+
self.paletteLabels = self._decompileUInt16Array(
|
| 74 |
+
data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID
|
| 75 |
+
)
|
| 76 |
+
self.paletteEntryLabels = self._decompileUInt16Array(
|
| 77 |
+
data,
|
| 78 |
+
offsetToPaletteEntryLabelArray,
|
| 79 |
+
self.numPaletteEntries,
|
| 80 |
+
default=self.NO_NAME_ID,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def _decompileUInt16Array(self, data, offset, numElements, default=0):
|
| 84 |
+
if offset == 0:
|
| 85 |
+
return [default] * numElements
|
| 86 |
+
result = array.array("H", data[offset : offset + 2 * numElements])
|
| 87 |
+
if sys.byteorder != "big":
|
| 88 |
+
result.byteswap()
|
| 89 |
+
assert len(result) == numElements, result
|
| 90 |
+
return result.tolist()
|
| 91 |
+
|
| 92 |
+
def _decompileUInt32Array(self, data, offset, numElements, default=0):
|
| 93 |
+
if offset == 0:
|
| 94 |
+
return [default] * numElements
|
| 95 |
+
result = array.array("I", data[offset : offset + 4 * numElements])
|
| 96 |
+
if sys.byteorder != "big":
|
| 97 |
+
result.byteswap()
|
| 98 |
+
assert len(result) == numElements, result
|
| 99 |
+
return result.tolist()
|
| 100 |
+
|
| 101 |
+
def compile(self, ttFont):
|
| 102 |
+
colorRecordIndices, colorRecords = self._compileColorRecords()
|
| 103 |
+
paletteTypes = self._compilePaletteTypes()
|
| 104 |
+
paletteLabels = self._compilePaletteLabels()
|
| 105 |
+
paletteEntryLabels = self._compilePaletteEntryLabels()
|
| 106 |
+
numColorRecords = len(colorRecords) // 4
|
| 107 |
+
offsetToFirstColorRecord = 12 + len(colorRecordIndices)
|
| 108 |
+
if self.version >= 1:
|
| 109 |
+
offsetToFirstColorRecord += 12
|
| 110 |
+
header = struct.pack(
|
| 111 |
+
">HHHHL",
|
| 112 |
+
self.version,
|
| 113 |
+
self.numPaletteEntries,
|
| 114 |
+
len(self.palettes),
|
| 115 |
+
numColorRecords,
|
| 116 |
+
offsetToFirstColorRecord,
|
| 117 |
+
)
|
| 118 |
+
if self.version == 0:
|
| 119 |
+
dataList = [header, colorRecordIndices, colorRecords]
|
| 120 |
+
else:
|
| 121 |
+
pos = offsetToFirstColorRecord + len(colorRecords)
|
| 122 |
+
if len(paletteTypes) == 0:
|
| 123 |
+
offsetToPaletteTypeArray = 0
|
| 124 |
+
else:
|
| 125 |
+
offsetToPaletteTypeArray = pos
|
| 126 |
+
pos += len(paletteTypes)
|
| 127 |
+
if len(paletteLabels) == 0:
|
| 128 |
+
offsetToPaletteLabelArray = 0
|
| 129 |
+
else:
|
| 130 |
+
offsetToPaletteLabelArray = pos
|
| 131 |
+
pos += len(paletteLabels)
|
| 132 |
+
if len(paletteEntryLabels) == 0:
|
| 133 |
+
offsetToPaletteEntryLabelArray = 0
|
| 134 |
+
else:
|
| 135 |
+
offsetToPaletteEntryLabelArray = pos
|
| 136 |
+
pos += len(paletteLabels)
|
| 137 |
+
header1 = struct.pack(
|
| 138 |
+
">LLL",
|
| 139 |
+
offsetToPaletteTypeArray,
|
| 140 |
+
offsetToPaletteLabelArray,
|
| 141 |
+
offsetToPaletteEntryLabelArray,
|
| 142 |
+
)
|
| 143 |
+
dataList = [
|
| 144 |
+
header,
|
| 145 |
+
colorRecordIndices,
|
| 146 |
+
header1,
|
| 147 |
+
colorRecords,
|
| 148 |
+
paletteTypes,
|
| 149 |
+
paletteLabels,
|
| 150 |
+
paletteEntryLabels,
|
| 151 |
+
]
|
| 152 |
+
return bytesjoin(dataList)
|
| 153 |
+
|
| 154 |
+
def _compilePalette(self, palette):
|
| 155 |
+
assert len(palette) == self.numPaletteEntries
|
| 156 |
+
pack = lambda c: struct.pack(">BBBB", c.blue, c.green, c.red, c.alpha)
|
| 157 |
+
return bytesjoin([pack(color) for color in palette])
|
| 158 |
+
|
| 159 |
+
def _compileColorRecords(self):
|
| 160 |
+
colorRecords, colorRecordIndices, pool = [], [], {}
|
| 161 |
+
for palette in self.palettes:
|
| 162 |
+
packedPalette = self._compilePalette(palette)
|
| 163 |
+
if packedPalette in pool:
|
| 164 |
+
index = pool[packedPalette]
|
| 165 |
+
else:
|
| 166 |
+
index = len(colorRecords)
|
| 167 |
+
colorRecords.append(packedPalette)
|
| 168 |
+
pool[packedPalette] = index
|
| 169 |
+
colorRecordIndices.append(struct.pack(">H", index * self.numPaletteEntries))
|
| 170 |
+
return bytesjoin(colorRecordIndices), bytesjoin(colorRecords)
|
| 171 |
+
|
| 172 |
+
def _compilePaletteTypes(self):
|
| 173 |
+
if self.version == 0 or not any(self.paletteTypes):
|
| 174 |
+
return b""
|
| 175 |
+
assert len(self.paletteTypes) == len(self.palettes)
|
| 176 |
+
result = bytesjoin([struct.pack(">I", ptype) for ptype in self.paletteTypes])
|
| 177 |
+
assert len(result) == 4 * len(self.palettes)
|
| 178 |
+
return result
|
| 179 |
+
|
| 180 |
+
def _compilePaletteLabels(self):
|
| 181 |
+
if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels):
|
| 182 |
+
return b""
|
| 183 |
+
assert len(self.paletteLabels) == len(self.palettes)
|
| 184 |
+
result = bytesjoin([struct.pack(">H", label) for label in self.paletteLabels])
|
| 185 |
+
assert len(result) == 2 * len(self.palettes)
|
| 186 |
+
return result
|
| 187 |
+
|
| 188 |
+
def _compilePaletteEntryLabels(self):
|
| 189 |
+
if self.version == 0 or all(
|
| 190 |
+
l == self.NO_NAME_ID for l in self.paletteEntryLabels
|
| 191 |
+
):
|
| 192 |
+
return b""
|
| 193 |
+
assert len(self.paletteEntryLabels) == self.numPaletteEntries
|
| 194 |
+
result = bytesjoin(
|
| 195 |
+
[struct.pack(">H", label) for label in self.paletteEntryLabels]
|
| 196 |
+
)
|
| 197 |
+
assert len(result) == 2 * self.numPaletteEntries
|
| 198 |
+
return result
|
| 199 |
+
|
| 200 |
+
def toXML(self, writer, ttFont):
|
| 201 |
+
numPalettes = len(self.palettes)
|
| 202 |
+
paletteLabels = {i: nameID for (i, nameID) in enumerate(self.paletteLabels)}
|
| 203 |
+
paletteTypes = {i: typ for (i, typ) in enumerate(self.paletteTypes)}
|
| 204 |
+
writer.simpletag("version", value=self.version)
|
| 205 |
+
writer.newline()
|
| 206 |
+
writer.simpletag("numPaletteEntries", value=self.numPaletteEntries)
|
| 207 |
+
writer.newline()
|
| 208 |
+
for index, palette in enumerate(self.palettes):
|
| 209 |
+
attrs = {"index": index}
|
| 210 |
+
paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE)
|
| 211 |
+
paletteLabel = paletteLabels.get(index, self.NO_NAME_ID)
|
| 212 |
+
if self.version > 0 and paletteLabel != self.NO_NAME_ID:
|
| 213 |
+
attrs["label"] = paletteLabel
|
| 214 |
+
if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE:
|
| 215 |
+
attrs["type"] = paletteType
|
| 216 |
+
writer.begintag("palette", **attrs)
|
| 217 |
+
writer.newline()
|
| 218 |
+
if (
|
| 219 |
+
self.version > 0
|
| 220 |
+
and paletteLabel != self.NO_NAME_ID
|
| 221 |
+
and ttFont
|
| 222 |
+
and "name" in ttFont
|
| 223 |
+
):
|
| 224 |
+
name = ttFont["name"].getDebugName(paletteLabel)
|
| 225 |
+
if name is not None:
|
| 226 |
+
writer.comment(name)
|
| 227 |
+
writer.newline()
|
| 228 |
+
assert len(palette) == self.numPaletteEntries
|
| 229 |
+
for cindex, color in enumerate(palette):
|
| 230 |
+
color.toXML(writer, ttFont, cindex)
|
| 231 |
+
writer.endtag("palette")
|
| 232 |
+
writer.newline()
|
| 233 |
+
if self.version > 0 and not all(
|
| 234 |
+
l == self.NO_NAME_ID for l in self.paletteEntryLabels
|
| 235 |
+
):
|
| 236 |
+
writer.begintag("paletteEntryLabels")
|
| 237 |
+
writer.newline()
|
| 238 |
+
for index, label in enumerate(self.paletteEntryLabels):
|
| 239 |
+
if label != self.NO_NAME_ID:
|
| 240 |
+
writer.simpletag("label", index=index, value=label)
|
| 241 |
+
if self.version > 0 and label and ttFont and "name" in ttFont:
|
| 242 |
+
name = ttFont["name"].getDebugName(label)
|
| 243 |
+
if name is not None:
|
| 244 |
+
writer.comment(name)
|
| 245 |
+
writer.newline()
|
| 246 |
+
writer.endtag("paletteEntryLabels")
|
| 247 |
+
writer.newline()
|
| 248 |
+
|
| 249 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 250 |
+
if name == "palette":
|
| 251 |
+
self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID)))
|
| 252 |
+
self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
|
| 253 |
+
palette = []
|
| 254 |
+
for element in content:
|
| 255 |
+
if isinstance(element, str):
|
| 256 |
+
continue
|
| 257 |
+
attrs = element[1]
|
| 258 |
+
color = Color.fromHex(attrs["value"])
|
| 259 |
+
palette.append(color)
|
| 260 |
+
self.palettes.append(palette)
|
| 261 |
+
elif name == "paletteEntryLabels":
|
| 262 |
+
colorLabels = {}
|
| 263 |
+
for element in content:
|
| 264 |
+
if isinstance(element, str):
|
| 265 |
+
continue
|
| 266 |
+
elementName, elementAttr, _ = element
|
| 267 |
+
if elementName == "label":
|
| 268 |
+
labelIndex = safeEval(elementAttr["index"])
|
| 269 |
+
nameID = safeEval(elementAttr["value"])
|
| 270 |
+
colorLabels[labelIndex] = nameID
|
| 271 |
+
self.paletteEntryLabels = [
|
| 272 |
+
colorLabels.get(i, self.NO_NAME_ID)
|
| 273 |
+
for i in range(self.numPaletteEntries)
|
| 274 |
+
]
|
| 275 |
+
elif "value" in attrs:
|
| 276 |
+
value = safeEval(attrs["value"])
|
| 277 |
+
setattr(self, name, value)
|
| 278 |
+
if name == "numPaletteEntries":
|
| 279 |
+
self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
class Color(namedtuple("Color", "blue green red alpha")):
|
| 283 |
+
def hex(self):
|
| 284 |
+
return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha)
|
| 285 |
+
|
| 286 |
+
def __repr__(self):
|
| 287 |
+
return self.hex()
|
| 288 |
+
|
| 289 |
+
def toXML(self, writer, ttFont, index=None):
|
| 290 |
+
writer.simpletag("color", value=self.hex(), index=index)
|
| 291 |
+
writer.newline()
|
| 292 |
+
|
| 293 |
+
@classmethod
|
| 294 |
+
def fromHex(cls, value):
|
| 295 |
+
if value[0] == "#":
|
| 296 |
+
value = value[1:]
|
| 297 |
+
red = int(value[0:2], 16)
|
| 298 |
+
green = int(value[2:4], 16)
|
| 299 |
+
blue = int(value[4:6], 16)
|
| 300 |
+
alpha = int(value[6:8], 16) if len(value) >= 8 else 0xFF
|
| 301 |
+
return cls(red=red, green=green, blue=blue, alpha=alpha)
|
| 302 |
+
|
| 303 |
+
@classmethod
|
| 304 |
+
def fromRGBA(cls, red, green, blue, alpha):
|
| 305 |
+
return cls(red=red, green=green, blue=blue, alpha=alpha)
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D_S_I_G_.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import base64
|
| 4 |
+
from typing import TYPE_CHECKING
|
| 5 |
+
|
| 6 |
+
from fontTools.misc import sstruct
|
| 7 |
+
from fontTools.misc.textTools import bytesjoin, safeEval, strjoin, tobytes, tostr
|
| 8 |
+
|
| 9 |
+
from . import DefaultTable
|
| 10 |
+
|
| 11 |
+
if TYPE_CHECKING:
|
| 12 |
+
from fontTools.misc.xmlWriter import XMLWriter
|
| 13 |
+
from fontTools.ttLib import TTFont
|
| 14 |
+
|
| 15 |
+
DSIG_HeaderFormat = """
|
| 16 |
+
> # big endian
|
| 17 |
+
ulVersion: L
|
| 18 |
+
usNumSigs: H
|
| 19 |
+
usFlag: H
|
| 20 |
+
"""
|
| 21 |
+
# followed by an array of usNumSigs DSIG_Signature records
|
| 22 |
+
DSIG_SignatureFormat = """
|
| 23 |
+
> # big endian
|
| 24 |
+
ulFormat: L
|
| 25 |
+
ulLength: L # length includes DSIG_SignatureBlock header
|
| 26 |
+
ulOffset: L
|
| 27 |
+
"""
|
| 28 |
+
# followed by an array of usNumSigs DSIG_SignatureBlock records,
|
| 29 |
+
# each followed immediately by the pkcs7 bytes
|
| 30 |
+
DSIG_SignatureBlockFormat = """
|
| 31 |
+
> # big endian
|
| 32 |
+
usReserved1: H
|
| 33 |
+
usReserved2: H
|
| 34 |
+
cbSignature: l # length of following raw pkcs7 data
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
#
|
| 38 |
+
# NOTE
|
| 39 |
+
# the DSIG table format allows for SignatureBlocks residing
|
| 40 |
+
# anywhere in the table and possibly in a different order as
|
| 41 |
+
# listed in the array after the first table header
|
| 42 |
+
#
|
| 43 |
+
# this implementation does not keep track of any gaps and/or data
|
| 44 |
+
# before or after the actual signature blocks while decompiling,
|
| 45 |
+
# and puts them in the same physical order as listed in the header
|
| 46 |
+
# on compilation with no padding whatsoever.
|
| 47 |
+
#
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class table_D_S_I_G_(DefaultTable.DefaultTable):
|
| 51 |
+
"""Digital Signature table
|
| 52 |
+
|
| 53 |
+
The ``DSIG`` table contains cryptographic signatures for the font.
|
| 54 |
+
|
| 55 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/dsig
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def decompile(self, data: bytes, ttFont: TTFont) -> None:
|
| 59 |
+
dummy, newData = sstruct.unpack2(DSIG_HeaderFormat, data, self)
|
| 60 |
+
assert self.ulVersion == 1, "DSIG ulVersion must be 1"
|
| 61 |
+
assert self.usFlag & ~1 == 0, "DSIG usFlag must be 0x1 or 0x0"
|
| 62 |
+
self.signatureRecords: list[SignatureRecord] = []
|
| 63 |
+
sigrecs = self.signatureRecords
|
| 64 |
+
for n in range(self.usNumSigs):
|
| 65 |
+
sigrec, newData = sstruct.unpack2(
|
| 66 |
+
DSIG_SignatureFormat, newData, SignatureRecord()
|
| 67 |
+
)
|
| 68 |
+
assert sigrec.ulFormat == 1, (
|
| 69 |
+
"DSIG signature record #%d ulFormat must be 1" % n
|
| 70 |
+
)
|
| 71 |
+
sigrecs.append(sigrec)
|
| 72 |
+
for sigrec in sigrecs:
|
| 73 |
+
dummy, newData = sstruct.unpack2(
|
| 74 |
+
DSIG_SignatureBlockFormat, data[sigrec.ulOffset :], sigrec
|
| 75 |
+
)
|
| 76 |
+
assert sigrec.usReserved1 == 0, (
|
| 77 |
+
"DSIG signature record #%d usReserverd1 must be 0" % n
|
| 78 |
+
)
|
| 79 |
+
assert sigrec.usReserved2 == 0, (
|
| 80 |
+
"DSIG signature record #%d usReserverd2 must be 0" % n
|
| 81 |
+
)
|
| 82 |
+
sigrec.pkcs7 = newData[: sigrec.cbSignature]
|
| 83 |
+
|
| 84 |
+
def compile(self, ttFont: TTFont) -> bytes:
|
| 85 |
+
packed = sstruct.pack(DSIG_HeaderFormat, self)
|
| 86 |
+
headers = [packed]
|
| 87 |
+
offset = len(packed) + self.usNumSigs * sstruct.calcsize(DSIG_SignatureFormat)
|
| 88 |
+
data = []
|
| 89 |
+
for sigrec in self.signatureRecords:
|
| 90 |
+
# first pack signature block
|
| 91 |
+
sigrec.cbSignature = len(sigrec.pkcs7)
|
| 92 |
+
packed = sstruct.pack(DSIG_SignatureBlockFormat, sigrec) + sigrec.pkcs7
|
| 93 |
+
data.append(packed)
|
| 94 |
+
# update redundant length field
|
| 95 |
+
sigrec.ulLength = len(packed)
|
| 96 |
+
# update running table offset
|
| 97 |
+
sigrec.ulOffset = offset
|
| 98 |
+
headers.append(sstruct.pack(DSIG_SignatureFormat, sigrec))
|
| 99 |
+
offset += sigrec.ulLength
|
| 100 |
+
if offset % 2:
|
| 101 |
+
# Pad to even bytes
|
| 102 |
+
data.append(b"\0")
|
| 103 |
+
return bytesjoin(headers + data)
|
| 104 |
+
|
| 105 |
+
def toXML(self, xmlWriter: XMLWriter, ttFont: TTFont) -> None:
|
| 106 |
+
xmlWriter.comment(
|
| 107 |
+
"note that the Digital Signature will be invalid after recompilation!"
|
| 108 |
+
)
|
| 109 |
+
xmlWriter.newline()
|
| 110 |
+
xmlWriter.simpletag(
|
| 111 |
+
"tableHeader",
|
| 112 |
+
version=self.ulVersion,
|
| 113 |
+
numSigs=self.usNumSigs,
|
| 114 |
+
flag="0x%X" % self.usFlag,
|
| 115 |
+
)
|
| 116 |
+
for sigrec in self.signatureRecords:
|
| 117 |
+
xmlWriter.newline()
|
| 118 |
+
sigrec.toXML(xmlWriter, ttFont)
|
| 119 |
+
xmlWriter.newline()
|
| 120 |
+
|
| 121 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 122 |
+
if name == "tableHeader":
|
| 123 |
+
self.signatureRecords = []
|
| 124 |
+
self.ulVersion = safeEval(attrs["version"])
|
| 125 |
+
self.usNumSigs = safeEval(attrs["numSigs"])
|
| 126 |
+
self.usFlag = safeEval(attrs["flag"])
|
| 127 |
+
return
|
| 128 |
+
if name == "SignatureRecord":
|
| 129 |
+
sigrec = SignatureRecord()
|
| 130 |
+
sigrec.fromXML(name, attrs, content, ttFont)
|
| 131 |
+
self.signatureRecords.append(sigrec)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
pem_spam = lambda l, spam={
|
| 135 |
+
"-----BEGIN PKCS7-----": True,
|
| 136 |
+
"-----END PKCS7-----": True,
|
| 137 |
+
"": True,
|
| 138 |
+
}: not spam.get(l.strip())
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def b64encode(b):
|
| 142 |
+
s = base64.b64encode(b)
|
| 143 |
+
# Line-break at 76 chars.
|
| 144 |
+
items = []
|
| 145 |
+
while s:
|
| 146 |
+
items.append(tostr(s[:76]))
|
| 147 |
+
items.append("\n")
|
| 148 |
+
s = s[76:]
|
| 149 |
+
return strjoin(items)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class SignatureRecord:
|
| 153 |
+
def __repr__(self) -> str:
|
| 154 |
+
return "<%s: %s>" % (self.__class__.__name__, self.__dict__)
|
| 155 |
+
|
| 156 |
+
def toXML(self, writer, ttFont: TTFont) -> None:
|
| 157 |
+
writer.begintag(self.__class__.__name__, format=self.ulFormat)
|
| 158 |
+
writer.newline()
|
| 159 |
+
writer.write_noindent("-----BEGIN PKCS7-----\n")
|
| 160 |
+
writer.write_noindent(b64encode(self.pkcs7))
|
| 161 |
+
writer.write_noindent("-----END PKCS7-----\n")
|
| 162 |
+
writer.endtag(self.__class__.__name__)
|
| 163 |
+
|
| 164 |
+
def fromXML(
|
| 165 |
+
self, name: str, attrs: dict[str, str], content: str, ttFont: TTFont
|
| 166 |
+
) -> None:
|
| 167 |
+
self.ulFormat: int = safeEval(attrs["format"])
|
| 168 |
+
self.usReserved1: int = safeEval(attrs.get("reserved1", "0"))
|
| 169 |
+
self.usReserved2: int = safeEval(attrs.get("reserved2", "0"))
|
| 170 |
+
self.pkcs7 = base64.b64decode(tobytes(strjoin(filter(pem_spam, content))))
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/D__e_b_g.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from textwrap import indent
|
| 3 |
+
|
| 4 |
+
from . import DefaultTable
|
| 5 |
+
from fontTools.misc.textTools import tostr
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class table_D__e_b_g(DefaultTable.DefaultTable):
|
| 9 |
+
def __init__(self, tag=None):
|
| 10 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 11 |
+
self.data = {}
|
| 12 |
+
|
| 13 |
+
def decompile(self, data, ttFont):
|
| 14 |
+
self.data = json.loads(data)
|
| 15 |
+
|
| 16 |
+
def compile(self, ttFont):
|
| 17 |
+
return json.dumps(self.data).encode("utf-8")
|
| 18 |
+
|
| 19 |
+
def toXML(self, writer, ttFont):
|
| 20 |
+
# make sure json indentation inside CDATA block matches XMLWriter's
|
| 21 |
+
data = json.dumps(self.data, indent=len(writer.indentwhite))
|
| 22 |
+
prefix = tostr(writer.indentwhite) * (writer.indentlevel + 1)
|
| 23 |
+
# but don't indent the first json line so it's adjacent to `<![CDATA[{`
|
| 24 |
+
cdata = indent(data, prefix, lambda ln: ln != "{\n")
|
| 25 |
+
|
| 26 |
+
writer.begintag("json")
|
| 27 |
+
writer.newline()
|
| 28 |
+
writer.writecdata(cdata)
|
| 29 |
+
writer.newline()
|
| 30 |
+
writer.endtag("json")
|
| 31 |
+
writer.newline()
|
| 32 |
+
|
| 33 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 34 |
+
if name == "json":
|
| 35 |
+
self.data = json.loads("".join(content))
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/DefaultTable.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import TYPE_CHECKING
|
| 4 |
+
|
| 5 |
+
from fontTools.misc.textTools import Tag
|
| 6 |
+
from fontTools.ttLib import getClassTag
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
from fontTools.misc.xmlWriter import XMLWriter
|
| 12 |
+
from fontTools.ttLib import TTFont
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class DefaultTable:
|
| 16 |
+
dependencies: list[str] = []
|
| 17 |
+
|
| 18 |
+
def __init__(self, tag: str | bytes | None = None) -> None:
|
| 19 |
+
if tag is None:
|
| 20 |
+
tag = getClassTag(self.__class__)
|
| 21 |
+
self.tableTag = Tag(tag)
|
| 22 |
+
|
| 23 |
+
def decompile(self, data: bytes, ttFont: TTFont) -> None:
|
| 24 |
+
self.data = data
|
| 25 |
+
|
| 26 |
+
def compile(self, ttFont: TTFont) -> bytes:
|
| 27 |
+
return self.data
|
| 28 |
+
|
| 29 |
+
def toXML(
|
| 30 |
+
self, writer: XMLWriter, ttFont: TTFont, **kwargs: dict[str, Any]
|
| 31 |
+
) -> None:
|
| 32 |
+
if hasattr(self, "ERROR"):
|
| 33 |
+
writer.comment("An error occurred during the decompilation of this table")
|
| 34 |
+
writer.newline()
|
| 35 |
+
writer.comment(self.ERROR)
|
| 36 |
+
writer.newline()
|
| 37 |
+
writer.begintag("hexdata")
|
| 38 |
+
writer.newline()
|
| 39 |
+
writer.dumphex(self.compile(ttFont))
|
| 40 |
+
writer.endtag("hexdata")
|
| 41 |
+
writer.newline()
|
| 42 |
+
|
| 43 |
+
def fromXML(
|
| 44 |
+
self, name: str, attrs: dict[str, str], content: str, ttFont: TTFont
|
| 45 |
+
) -> None:
|
| 46 |
+
from fontTools import ttLib
|
| 47 |
+
from fontTools.misc.textTools import readHex
|
| 48 |
+
|
| 49 |
+
if name != "hexdata":
|
| 50 |
+
raise ttLib.TTLibError("can't handle '%s' element" % name)
|
| 51 |
+
self.decompile(readHex(content), ttFont)
|
| 52 |
+
|
| 53 |
+
def __repr__(self) -> str:
|
| 54 |
+
return "<'%s' table at %x>" % (self.tableTag, id(self))
|
| 55 |
+
|
| 56 |
+
def __eq__(self, other: Any) -> bool:
|
| 57 |
+
if type(self) != type(other):
|
| 58 |
+
return NotImplemented
|
| 59 |
+
return self.__dict__ == other.__dict__
|
| 60 |
+
|
| 61 |
+
def __ne__(self, other: Any) -> bool:
|
| 62 |
+
result = self.__eq__(other)
|
| 63 |
+
return result if result is NotImplemented else not result
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_D_T_.py
ADDED
|
@@ -0,0 +1,835 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.textTools import (
|
| 3 |
+
bytechr,
|
| 4 |
+
byteord,
|
| 5 |
+
bytesjoin,
|
| 6 |
+
strjoin,
|
| 7 |
+
safeEval,
|
| 8 |
+
readHex,
|
| 9 |
+
hexStr,
|
| 10 |
+
deHexStr,
|
| 11 |
+
)
|
| 12 |
+
from .BitmapGlyphMetrics import (
|
| 13 |
+
BigGlyphMetrics,
|
| 14 |
+
bigGlyphMetricsFormat,
|
| 15 |
+
SmallGlyphMetrics,
|
| 16 |
+
smallGlyphMetricsFormat,
|
| 17 |
+
)
|
| 18 |
+
from . import DefaultTable
|
| 19 |
+
import itertools
|
| 20 |
+
import os
|
| 21 |
+
import struct
|
| 22 |
+
import logging
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
log = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
ebdtTableVersionFormat = """
|
| 28 |
+
> # big endian
|
| 29 |
+
version: 16.16F
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
ebdtComponentFormat = """
|
| 33 |
+
> # big endian
|
| 34 |
+
glyphCode: H
|
| 35 |
+
xOffset: b
|
| 36 |
+
yOffset: b
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class table_E_B_D_T_(DefaultTable.DefaultTable):
|
| 41 |
+
"""Embedded Bitmap Data table
|
| 42 |
+
|
| 43 |
+
The ``EBDT`` table contains monochrome or grayscale bitmap data for
|
| 44 |
+
glyphs. It must be used in concert with the ``EBLC`` table.
|
| 45 |
+
|
| 46 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
# Keep a reference to the name of the data locator table.
|
| 50 |
+
locatorName = "EBLC"
|
| 51 |
+
|
| 52 |
+
# This method can be overridden in subclasses to support new formats
|
| 53 |
+
# without changing the other implementation. Also can be used as a
|
| 54 |
+
# convenience method for coverting a font file to an alternative format.
|
| 55 |
+
def getImageFormatClass(self, imageFormat):
|
| 56 |
+
return ebdt_bitmap_classes[imageFormat]
|
| 57 |
+
|
| 58 |
+
def decompile(self, data, ttFont):
|
| 59 |
+
# Get the version but don't advance the slice.
|
| 60 |
+
# Most of the lookup for this table is done relative
|
| 61 |
+
# to the begining so slice by the offsets provided
|
| 62 |
+
# in the EBLC table.
|
| 63 |
+
sstruct.unpack2(ebdtTableVersionFormat, data, self)
|
| 64 |
+
|
| 65 |
+
# Keep a dict of glyphs that have been seen so they aren't remade.
|
| 66 |
+
# This dict maps intervals of data to the BitmapGlyph.
|
| 67 |
+
glyphDict = {}
|
| 68 |
+
|
| 69 |
+
# Pull out the EBLC table and loop through glyphs.
|
| 70 |
+
# A strike is a concept that spans both tables.
|
| 71 |
+
# The actual bitmap data is stored in the EBDT.
|
| 72 |
+
locator = ttFont[self.__class__.locatorName]
|
| 73 |
+
self.strikeData = []
|
| 74 |
+
for curStrike in locator.strikes:
|
| 75 |
+
bitmapGlyphDict = {}
|
| 76 |
+
self.strikeData.append(bitmapGlyphDict)
|
| 77 |
+
for indexSubTable in curStrike.indexSubTables:
|
| 78 |
+
dataIter = zip(indexSubTable.names, indexSubTable.locations)
|
| 79 |
+
for curName, curLoc in dataIter:
|
| 80 |
+
# Don't create duplicate data entries for the same glyphs.
|
| 81 |
+
# Instead just use the structures that already exist if they exist.
|
| 82 |
+
if curLoc in glyphDict:
|
| 83 |
+
curGlyph = glyphDict[curLoc]
|
| 84 |
+
else:
|
| 85 |
+
curGlyphData = data[slice(*curLoc)]
|
| 86 |
+
imageFormatClass = self.getImageFormatClass(
|
| 87 |
+
indexSubTable.imageFormat
|
| 88 |
+
)
|
| 89 |
+
curGlyph = imageFormatClass(curGlyphData, ttFont)
|
| 90 |
+
glyphDict[curLoc] = curGlyph
|
| 91 |
+
bitmapGlyphDict[curName] = curGlyph
|
| 92 |
+
|
| 93 |
+
def compile(self, ttFont):
|
| 94 |
+
dataList = []
|
| 95 |
+
dataList.append(sstruct.pack(ebdtTableVersionFormat, self))
|
| 96 |
+
dataSize = len(dataList[0])
|
| 97 |
+
|
| 98 |
+
# Keep a dict of glyphs that have been seen so they aren't remade.
|
| 99 |
+
# This dict maps the id of the BitmapGlyph to the interval
|
| 100 |
+
# in the data.
|
| 101 |
+
glyphDict = {}
|
| 102 |
+
|
| 103 |
+
# Go through the bitmap glyph data. Just in case the data for a glyph
|
| 104 |
+
# changed the size metrics should be recalculated. There are a variety
|
| 105 |
+
# of formats and they get stored in the EBLC table. That is why
|
| 106 |
+
# recalculation is defered to the EblcIndexSubTable class and just
|
| 107 |
+
# pass what is known about bitmap glyphs from this particular table.
|
| 108 |
+
locator = ttFont[self.__class__.locatorName]
|
| 109 |
+
for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
|
| 110 |
+
for curIndexSubTable in curStrike.indexSubTables:
|
| 111 |
+
dataLocations = []
|
| 112 |
+
for curName in curIndexSubTable.names:
|
| 113 |
+
# Handle the data placement based on seeing the glyph or not.
|
| 114 |
+
# Just save a reference to the location if the glyph has already
|
| 115 |
+
# been saved in compile. This code assumes that glyphs will only
|
| 116 |
+
# be referenced multiple times from indexFormat5. By luck the
|
| 117 |
+
# code may still work when referencing poorly ordered fonts with
|
| 118 |
+
# duplicate references. If there is a font that is unlucky the
|
| 119 |
+
# respective compile methods for the indexSubTables will fail
|
| 120 |
+
# their assertions. All fonts seem to follow this assumption.
|
| 121 |
+
# More complicated packing may be needed if a counter-font exists.
|
| 122 |
+
glyph = curGlyphDict[curName]
|
| 123 |
+
objectId = id(glyph)
|
| 124 |
+
if objectId not in glyphDict:
|
| 125 |
+
data = glyph.compile(ttFont)
|
| 126 |
+
data = curIndexSubTable.padBitmapData(data)
|
| 127 |
+
startByte = dataSize
|
| 128 |
+
dataSize += len(data)
|
| 129 |
+
endByte = dataSize
|
| 130 |
+
dataList.append(data)
|
| 131 |
+
dataLoc = (startByte, endByte)
|
| 132 |
+
glyphDict[objectId] = dataLoc
|
| 133 |
+
else:
|
| 134 |
+
dataLoc = glyphDict[objectId]
|
| 135 |
+
dataLocations.append(dataLoc)
|
| 136 |
+
# Just use the new data locations in the indexSubTable.
|
| 137 |
+
# The respective compile implementations will take care
|
| 138 |
+
# of any of the problems in the convertion that may arise.
|
| 139 |
+
curIndexSubTable.locations = dataLocations
|
| 140 |
+
|
| 141 |
+
return bytesjoin(dataList)
|
| 142 |
+
|
| 143 |
+
def toXML(self, writer, ttFont):
|
| 144 |
+
# When exporting to XML if one of the data export formats
|
| 145 |
+
# requires metrics then those metrics may be in the locator.
|
| 146 |
+
# In this case populate the bitmaps with "export metrics".
|
| 147 |
+
if ttFont.bitmapGlyphDataFormat in ("row", "bitwise"):
|
| 148 |
+
locator = ttFont[self.__class__.locatorName]
|
| 149 |
+
for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData):
|
| 150 |
+
for curIndexSubTable in curStrike.indexSubTables:
|
| 151 |
+
for curName in curIndexSubTable.names:
|
| 152 |
+
glyph = curGlyphDict[curName]
|
| 153 |
+
# I'm not sure which metrics have priority here.
|
| 154 |
+
# For now if both metrics exist go with glyph metrics.
|
| 155 |
+
if hasattr(glyph, "metrics"):
|
| 156 |
+
glyph.exportMetrics = glyph.metrics
|
| 157 |
+
else:
|
| 158 |
+
glyph.exportMetrics = curIndexSubTable.metrics
|
| 159 |
+
glyph.exportBitDepth = curStrike.bitmapSizeTable.bitDepth
|
| 160 |
+
|
| 161 |
+
writer.simpletag("header", [("version", self.version)])
|
| 162 |
+
writer.newline()
|
| 163 |
+
locator = ttFont[self.__class__.locatorName]
|
| 164 |
+
for strikeIndex, bitmapGlyphDict in enumerate(self.strikeData):
|
| 165 |
+
writer.begintag("strikedata", [("index", strikeIndex)])
|
| 166 |
+
writer.newline()
|
| 167 |
+
for curName, curBitmap in bitmapGlyphDict.items():
|
| 168 |
+
curBitmap.toXML(strikeIndex, curName, writer, ttFont)
|
| 169 |
+
writer.endtag("strikedata")
|
| 170 |
+
writer.newline()
|
| 171 |
+
|
| 172 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 173 |
+
if name == "header":
|
| 174 |
+
self.version = safeEval(attrs["version"])
|
| 175 |
+
elif name == "strikedata":
|
| 176 |
+
if not hasattr(self, "strikeData"):
|
| 177 |
+
self.strikeData = []
|
| 178 |
+
strikeIndex = safeEval(attrs["index"])
|
| 179 |
+
|
| 180 |
+
bitmapGlyphDict = {}
|
| 181 |
+
for element in content:
|
| 182 |
+
if not isinstance(element, tuple):
|
| 183 |
+
continue
|
| 184 |
+
name, attrs, content = element
|
| 185 |
+
if name[4:].startswith(_bitmapGlyphSubclassPrefix[4:]):
|
| 186 |
+
imageFormat = safeEval(name[len(_bitmapGlyphSubclassPrefix) :])
|
| 187 |
+
glyphName = attrs["name"]
|
| 188 |
+
imageFormatClass = self.getImageFormatClass(imageFormat)
|
| 189 |
+
curGlyph = imageFormatClass(None, None)
|
| 190 |
+
curGlyph.fromXML(name, attrs, content, ttFont)
|
| 191 |
+
assert glyphName not in bitmapGlyphDict, (
|
| 192 |
+
"Duplicate glyphs with the same name '%s' in the same strike."
|
| 193 |
+
% glyphName
|
| 194 |
+
)
|
| 195 |
+
bitmapGlyphDict[glyphName] = curGlyph
|
| 196 |
+
else:
|
| 197 |
+
log.warning("%s being ignored by %s", name, self.__class__.__name__)
|
| 198 |
+
|
| 199 |
+
# Grow the strike data array to the appropriate size. The XML
|
| 200 |
+
# format allows the strike index value to be out of order.
|
| 201 |
+
if strikeIndex >= len(self.strikeData):
|
| 202 |
+
self.strikeData += [None] * (strikeIndex + 1 - len(self.strikeData))
|
| 203 |
+
assert (
|
| 204 |
+
self.strikeData[strikeIndex] is None
|
| 205 |
+
), "Duplicate strike EBDT indices."
|
| 206 |
+
self.strikeData[strikeIndex] = bitmapGlyphDict
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
class EbdtComponent(object):
|
| 210 |
+
def toXML(self, writer, ttFont):
|
| 211 |
+
writer.begintag("ebdtComponent", [("name", self.name)])
|
| 212 |
+
writer.newline()
|
| 213 |
+
for componentName in sstruct.getformat(ebdtComponentFormat)[1][1:]:
|
| 214 |
+
writer.simpletag(componentName, value=getattr(self, componentName))
|
| 215 |
+
writer.newline()
|
| 216 |
+
writer.endtag("ebdtComponent")
|
| 217 |
+
writer.newline()
|
| 218 |
+
|
| 219 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 220 |
+
self.name = attrs["name"]
|
| 221 |
+
componentNames = set(sstruct.getformat(ebdtComponentFormat)[1][1:])
|
| 222 |
+
for element in content:
|
| 223 |
+
if not isinstance(element, tuple):
|
| 224 |
+
continue
|
| 225 |
+
name, attrs, content = element
|
| 226 |
+
if name in componentNames:
|
| 227 |
+
vars(self)[name] = safeEval(attrs["value"])
|
| 228 |
+
else:
|
| 229 |
+
log.warning("unknown name '%s' being ignored by EbdtComponent.", name)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
# Helper functions for dealing with binary.
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def _data2binary(data, numBits):
|
| 236 |
+
binaryList = []
|
| 237 |
+
for curByte in data:
|
| 238 |
+
value = byteord(curByte)
|
| 239 |
+
numBitsCut = min(8, numBits)
|
| 240 |
+
for i in range(numBitsCut):
|
| 241 |
+
if value & 0x1:
|
| 242 |
+
binaryList.append("1")
|
| 243 |
+
else:
|
| 244 |
+
binaryList.append("0")
|
| 245 |
+
value = value >> 1
|
| 246 |
+
numBits -= numBitsCut
|
| 247 |
+
return strjoin(binaryList)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def _binary2data(binary):
|
| 251 |
+
byteList = []
|
| 252 |
+
for bitLoc in range(0, len(binary), 8):
|
| 253 |
+
byteString = binary[bitLoc : bitLoc + 8]
|
| 254 |
+
curByte = 0
|
| 255 |
+
for curBit in reversed(byteString):
|
| 256 |
+
curByte = curByte << 1
|
| 257 |
+
if curBit == "1":
|
| 258 |
+
curByte |= 1
|
| 259 |
+
byteList.append(bytechr(curByte))
|
| 260 |
+
return bytesjoin(byteList)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def _memoize(f):
|
| 264 |
+
class memodict(dict):
|
| 265 |
+
def __missing__(self, key):
|
| 266 |
+
ret = f(key)
|
| 267 |
+
if isinstance(key, int) or len(key) == 1:
|
| 268 |
+
self[key] = ret
|
| 269 |
+
return ret
|
| 270 |
+
|
| 271 |
+
return memodict().__getitem__
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
# 00100111 -> 11100100 per byte, not to be confused with little/big endian.
|
| 275 |
+
# Bitmap data per byte is in the order that binary is written on the page
|
| 276 |
+
# with the least significant bit as far right as possible. This is the
|
| 277 |
+
# opposite of what makes sense algorithmically and hence this function.
|
| 278 |
+
@_memoize
|
| 279 |
+
def _reverseBytes(data):
|
| 280 |
+
r"""
|
| 281 |
+
>>> bin(ord(_reverseBytes(0b00100111)))
|
| 282 |
+
'0b11100100'
|
| 283 |
+
>>> _reverseBytes(b'\x00\xf0')
|
| 284 |
+
b'\x00\x0f'
|
| 285 |
+
"""
|
| 286 |
+
if isinstance(data, bytes) and len(data) != 1:
|
| 287 |
+
return bytesjoin(map(_reverseBytes, data))
|
| 288 |
+
byte = byteord(data)
|
| 289 |
+
result = 0
|
| 290 |
+
for i in range(8):
|
| 291 |
+
result = result << 1
|
| 292 |
+
result |= byte & 1
|
| 293 |
+
byte = byte >> 1
|
| 294 |
+
return bytechr(result)
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# This section of code is for reading and writing image data to/from XML.
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def _writeRawImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
|
| 301 |
+
writer.begintag("rawimagedata")
|
| 302 |
+
writer.newline()
|
| 303 |
+
writer.dumphex(bitmapObject.imageData)
|
| 304 |
+
writer.endtag("rawimagedata")
|
| 305 |
+
writer.newline()
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def _readRawImageData(bitmapObject, name, attrs, content, ttFont):
|
| 309 |
+
bitmapObject.imageData = readHex(content)
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def _writeRowImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
|
| 313 |
+
metrics = bitmapObject.exportMetrics
|
| 314 |
+
del bitmapObject.exportMetrics
|
| 315 |
+
bitDepth = bitmapObject.exportBitDepth
|
| 316 |
+
del bitmapObject.exportBitDepth
|
| 317 |
+
|
| 318 |
+
writer.begintag(
|
| 319 |
+
"rowimagedata", bitDepth=bitDepth, width=metrics.width, height=metrics.height
|
| 320 |
+
)
|
| 321 |
+
writer.newline()
|
| 322 |
+
for curRow in range(metrics.height):
|
| 323 |
+
rowData = bitmapObject.getRow(curRow, bitDepth=bitDepth, metrics=metrics)
|
| 324 |
+
writer.simpletag("row", value=hexStr(rowData))
|
| 325 |
+
writer.newline()
|
| 326 |
+
writer.endtag("rowimagedata")
|
| 327 |
+
writer.newline()
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def _readRowImageData(bitmapObject, name, attrs, content, ttFont):
|
| 331 |
+
bitDepth = safeEval(attrs["bitDepth"])
|
| 332 |
+
metrics = SmallGlyphMetrics()
|
| 333 |
+
metrics.width = safeEval(attrs["width"])
|
| 334 |
+
metrics.height = safeEval(attrs["height"])
|
| 335 |
+
|
| 336 |
+
dataRows = []
|
| 337 |
+
for element in content:
|
| 338 |
+
if not isinstance(element, tuple):
|
| 339 |
+
continue
|
| 340 |
+
name, attr, content = element
|
| 341 |
+
# Chop off 'imagedata' from the tag to get just the option.
|
| 342 |
+
if name == "row":
|
| 343 |
+
dataRows.append(deHexStr(attr["value"]))
|
| 344 |
+
bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics)
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def _writeBitwiseImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
|
| 348 |
+
metrics = bitmapObject.exportMetrics
|
| 349 |
+
del bitmapObject.exportMetrics
|
| 350 |
+
bitDepth = bitmapObject.exportBitDepth
|
| 351 |
+
del bitmapObject.exportBitDepth
|
| 352 |
+
|
| 353 |
+
# A dict for mapping binary to more readable/artistic ASCII characters.
|
| 354 |
+
binaryConv = {"0": ".", "1": "@"}
|
| 355 |
+
|
| 356 |
+
writer.begintag(
|
| 357 |
+
"bitwiseimagedata",
|
| 358 |
+
bitDepth=bitDepth,
|
| 359 |
+
width=metrics.width,
|
| 360 |
+
height=metrics.height,
|
| 361 |
+
)
|
| 362 |
+
writer.newline()
|
| 363 |
+
for curRow in range(metrics.height):
|
| 364 |
+
rowData = bitmapObject.getRow(
|
| 365 |
+
curRow, bitDepth=1, metrics=metrics, reverseBytes=True
|
| 366 |
+
)
|
| 367 |
+
rowData = _data2binary(rowData, metrics.width)
|
| 368 |
+
# Make the output a readable ASCII art form.
|
| 369 |
+
rowData = strjoin(map(binaryConv.get, rowData))
|
| 370 |
+
writer.simpletag("row", value=rowData)
|
| 371 |
+
writer.newline()
|
| 372 |
+
writer.endtag("bitwiseimagedata")
|
| 373 |
+
writer.newline()
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def _readBitwiseImageData(bitmapObject, name, attrs, content, ttFont):
|
| 377 |
+
bitDepth = safeEval(attrs["bitDepth"])
|
| 378 |
+
metrics = SmallGlyphMetrics()
|
| 379 |
+
metrics.width = safeEval(attrs["width"])
|
| 380 |
+
metrics.height = safeEval(attrs["height"])
|
| 381 |
+
|
| 382 |
+
# A dict for mapping from ASCII to binary. All characters are considered
|
| 383 |
+
# a '1' except space, period and '0' which maps to '0'.
|
| 384 |
+
binaryConv = {" ": "0", ".": "0", "0": "0"}
|
| 385 |
+
|
| 386 |
+
dataRows = []
|
| 387 |
+
for element in content:
|
| 388 |
+
if not isinstance(element, tuple):
|
| 389 |
+
continue
|
| 390 |
+
name, attr, content = element
|
| 391 |
+
if name == "row":
|
| 392 |
+
mapParams = zip(attr["value"], itertools.repeat("1"))
|
| 393 |
+
rowData = strjoin(itertools.starmap(binaryConv.get, mapParams))
|
| 394 |
+
dataRows.append(_binary2data(rowData))
|
| 395 |
+
|
| 396 |
+
bitmapObject.setRows(
|
| 397 |
+
dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def _writeExtFileImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
|
| 402 |
+
try:
|
| 403 |
+
folder = os.path.dirname(writer.file.name)
|
| 404 |
+
except AttributeError:
|
| 405 |
+
# fall back to current directory if output file's directory isn't found
|
| 406 |
+
folder = "."
|
| 407 |
+
folder = os.path.join(folder, "bitmaps")
|
| 408 |
+
filename = glyphName + bitmapObject.fileExtension
|
| 409 |
+
if not os.path.isdir(folder):
|
| 410 |
+
os.makedirs(folder)
|
| 411 |
+
folder = os.path.join(folder, "strike%d" % strikeIndex)
|
| 412 |
+
if not os.path.isdir(folder):
|
| 413 |
+
os.makedirs(folder)
|
| 414 |
+
|
| 415 |
+
fullPath = os.path.join(folder, filename)
|
| 416 |
+
writer.simpletag("extfileimagedata", value=fullPath)
|
| 417 |
+
writer.newline()
|
| 418 |
+
|
| 419 |
+
with open(fullPath, "wb") as file:
|
| 420 |
+
file.write(bitmapObject.imageData)
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
def _readExtFileImageData(bitmapObject, name, attrs, content, ttFont):
|
| 424 |
+
fullPath = attrs["value"]
|
| 425 |
+
with open(fullPath, "rb") as file:
|
| 426 |
+
bitmapObject.imageData = file.read()
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
# End of XML writing code.
|
| 430 |
+
|
| 431 |
+
# Important information about the naming scheme. Used for identifying formats
|
| 432 |
+
# in XML.
|
| 433 |
+
_bitmapGlyphSubclassPrefix = "ebdt_bitmap_format_"
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
class BitmapGlyph(object):
|
| 437 |
+
# For the external file format. This can be changed in subclasses. This way
|
| 438 |
+
# when the extfile option is turned on files have the form: glyphName.ext
|
| 439 |
+
# The default is just a flat binary file with no meaning.
|
| 440 |
+
fileExtension = ".bin"
|
| 441 |
+
|
| 442 |
+
# Keep track of reading and writing of various forms.
|
| 443 |
+
xmlDataFunctions = {
|
| 444 |
+
"raw": (_writeRawImageData, _readRawImageData),
|
| 445 |
+
"row": (_writeRowImageData, _readRowImageData),
|
| 446 |
+
"bitwise": (_writeBitwiseImageData, _readBitwiseImageData),
|
| 447 |
+
"extfile": (_writeExtFileImageData, _readExtFileImageData),
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
def __init__(self, data, ttFont):
|
| 451 |
+
self.data = data
|
| 452 |
+
self.ttFont = ttFont
|
| 453 |
+
# TODO Currently non-lazy decompilation is untested here...
|
| 454 |
+
# if not ttFont.lazy:
|
| 455 |
+
# self.decompile()
|
| 456 |
+
# del self.data
|
| 457 |
+
|
| 458 |
+
def __getattr__(self, attr):
|
| 459 |
+
# Allow lazy decompile.
|
| 460 |
+
if attr[:2] == "__":
|
| 461 |
+
raise AttributeError(attr)
|
| 462 |
+
if attr == "data":
|
| 463 |
+
raise AttributeError(attr)
|
| 464 |
+
self.decompile()
|
| 465 |
+
del self.data
|
| 466 |
+
return getattr(self, attr)
|
| 467 |
+
|
| 468 |
+
def ensureDecompiled(self, recurse=False):
|
| 469 |
+
if hasattr(self, "data"):
|
| 470 |
+
self.decompile()
|
| 471 |
+
del self.data
|
| 472 |
+
|
| 473 |
+
# Not a fan of this but it is needed for safer safety checking.
|
| 474 |
+
def getFormat(self):
|
| 475 |
+
return safeEval(self.__class__.__name__[len(_bitmapGlyphSubclassPrefix) :])
|
| 476 |
+
|
| 477 |
+
def toXML(self, strikeIndex, glyphName, writer, ttFont):
|
| 478 |
+
writer.begintag(self.__class__.__name__, [("name", glyphName)])
|
| 479 |
+
writer.newline()
|
| 480 |
+
|
| 481 |
+
self.writeMetrics(writer, ttFont)
|
| 482 |
+
# Use the internal write method to write using the correct output format.
|
| 483 |
+
self.writeData(strikeIndex, glyphName, writer, ttFont)
|
| 484 |
+
|
| 485 |
+
writer.endtag(self.__class__.__name__)
|
| 486 |
+
writer.newline()
|
| 487 |
+
|
| 488 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 489 |
+
self.readMetrics(name, attrs, content, ttFont)
|
| 490 |
+
for element in content:
|
| 491 |
+
if not isinstance(element, tuple):
|
| 492 |
+
continue
|
| 493 |
+
name, attr, content = element
|
| 494 |
+
if not name.endswith("imagedata"):
|
| 495 |
+
continue
|
| 496 |
+
# Chop off 'imagedata' from the tag to get just the option.
|
| 497 |
+
option = name[: -len("imagedata")]
|
| 498 |
+
assert option in self.__class__.xmlDataFunctions
|
| 499 |
+
self.readData(name, attr, content, ttFont)
|
| 500 |
+
|
| 501 |
+
# Some of the glyphs have the metrics. This allows for metrics to be
|
| 502 |
+
# added if the glyph format has them. Default behavior is to do nothing.
|
| 503 |
+
def writeMetrics(self, writer, ttFont):
|
| 504 |
+
pass
|
| 505 |
+
|
| 506 |
+
# The opposite of write metrics.
|
| 507 |
+
def readMetrics(self, name, attrs, content, ttFont):
|
| 508 |
+
pass
|
| 509 |
+
|
| 510 |
+
def writeData(self, strikeIndex, glyphName, writer, ttFont):
|
| 511 |
+
try:
|
| 512 |
+
writeFunc, readFunc = self.__class__.xmlDataFunctions[
|
| 513 |
+
ttFont.bitmapGlyphDataFormat
|
| 514 |
+
]
|
| 515 |
+
except KeyError:
|
| 516 |
+
writeFunc = _writeRawImageData
|
| 517 |
+
writeFunc(strikeIndex, glyphName, self, writer, ttFont)
|
| 518 |
+
|
| 519 |
+
def readData(self, name, attrs, content, ttFont):
|
| 520 |
+
# Chop off 'imagedata' from the tag to get just the option.
|
| 521 |
+
option = name[: -len("imagedata")]
|
| 522 |
+
writeFunc, readFunc = self.__class__.xmlDataFunctions[option]
|
| 523 |
+
readFunc(self, name, attrs, content, ttFont)
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
# A closure for creating a mixin for the two types of metrics handling.
|
| 527 |
+
# Most of the code is very similar so its easier to deal with here.
|
| 528 |
+
# Everything works just by passing the class that the mixin is for.
|
| 529 |
+
def _createBitmapPlusMetricsMixin(metricsClass):
|
| 530 |
+
# Both metrics names are listed here to make meaningful error messages.
|
| 531 |
+
metricStrings = [BigGlyphMetrics.__name__, SmallGlyphMetrics.__name__]
|
| 532 |
+
curMetricsName = metricsClass.__name__
|
| 533 |
+
# Find which metrics this is for and determine the opposite name.
|
| 534 |
+
metricsId = metricStrings.index(curMetricsName)
|
| 535 |
+
oppositeMetricsName = metricStrings[1 - metricsId]
|
| 536 |
+
|
| 537 |
+
class BitmapPlusMetricsMixin(object):
|
| 538 |
+
def writeMetrics(self, writer, ttFont):
|
| 539 |
+
self.metrics.toXML(writer, ttFont)
|
| 540 |
+
|
| 541 |
+
def readMetrics(self, name, attrs, content, ttFont):
|
| 542 |
+
for element in content:
|
| 543 |
+
if not isinstance(element, tuple):
|
| 544 |
+
continue
|
| 545 |
+
name, attrs, content = element
|
| 546 |
+
if name == curMetricsName:
|
| 547 |
+
self.metrics = metricsClass()
|
| 548 |
+
self.metrics.fromXML(name, attrs, content, ttFont)
|
| 549 |
+
elif name == oppositeMetricsName:
|
| 550 |
+
log.warning(
|
| 551 |
+
"Warning: %s being ignored in format %d.",
|
| 552 |
+
oppositeMetricsName,
|
| 553 |
+
self.getFormat(),
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
return BitmapPlusMetricsMixin
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
# Since there are only two types of mixin's just create them here.
|
| 560 |
+
BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics)
|
| 561 |
+
BitmapPlusSmallMetricsMixin = _createBitmapPlusMetricsMixin(SmallGlyphMetrics)
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
# Data that is bit aligned can be tricky to deal with. These classes implement
|
| 565 |
+
# helper functionality for dealing with the data and getting a particular row
|
| 566 |
+
# of bitwise data. Also helps implement fancy data export/import in XML.
|
| 567 |
+
class BitAlignedBitmapMixin(object):
|
| 568 |
+
def _getBitRange(self, row, bitDepth, metrics):
|
| 569 |
+
rowBits = bitDepth * metrics.width
|
| 570 |
+
bitOffset = row * rowBits
|
| 571 |
+
return (bitOffset, bitOffset + rowBits)
|
| 572 |
+
|
| 573 |
+
def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False):
|
| 574 |
+
if metrics is None:
|
| 575 |
+
metrics = self.metrics
|
| 576 |
+
assert 0 <= row and row < metrics.height, "Illegal row access in bitmap"
|
| 577 |
+
|
| 578 |
+
# Loop through each byte. This can cover two bytes in the original data or
|
| 579 |
+
# a single byte if things happen to be aligned. The very last entry might
|
| 580 |
+
# not be aligned so take care to trim the binary data to size and pad with
|
| 581 |
+
# zeros in the row data. Bit aligned data is somewhat tricky.
|
| 582 |
+
#
|
| 583 |
+
# Example of data cut. Data cut represented in x's.
|
| 584 |
+
# '|' represents byte boundary.
|
| 585 |
+
# data = ...0XX|XXXXXX00|000... => XXXXXXXX
|
| 586 |
+
# or
|
| 587 |
+
# data = ...0XX|XXXX0000|000... => XXXXXX00
|
| 588 |
+
# or
|
| 589 |
+
# data = ...000|XXXXXXXX|000... => XXXXXXXX
|
| 590 |
+
# or
|
| 591 |
+
# data = ...000|00XXXX00|000... => XXXX0000
|
| 592 |
+
#
|
| 593 |
+
dataList = []
|
| 594 |
+
bitRange = self._getBitRange(row, bitDepth, metrics)
|
| 595 |
+
stepRange = bitRange + (8,)
|
| 596 |
+
for curBit in range(*stepRange):
|
| 597 |
+
endBit = min(curBit + 8, bitRange[1])
|
| 598 |
+
numBits = endBit - curBit
|
| 599 |
+
cutPoint = curBit % 8
|
| 600 |
+
firstByteLoc = curBit // 8
|
| 601 |
+
secondByteLoc = endBit // 8
|
| 602 |
+
if firstByteLoc < secondByteLoc:
|
| 603 |
+
numBitsCut = 8 - cutPoint
|
| 604 |
+
else:
|
| 605 |
+
numBitsCut = endBit - curBit
|
| 606 |
+
curByte = _reverseBytes(self.imageData[firstByteLoc])
|
| 607 |
+
firstHalf = byteord(curByte) >> cutPoint
|
| 608 |
+
firstHalf = ((1 << numBitsCut) - 1) & firstHalf
|
| 609 |
+
newByte = firstHalf
|
| 610 |
+
if firstByteLoc < secondByteLoc and secondByteLoc < len(self.imageData):
|
| 611 |
+
curByte = _reverseBytes(self.imageData[secondByteLoc])
|
| 612 |
+
secondHalf = byteord(curByte) << numBitsCut
|
| 613 |
+
newByte = (firstHalf | secondHalf) & ((1 << numBits) - 1)
|
| 614 |
+
dataList.append(bytechr(newByte))
|
| 615 |
+
|
| 616 |
+
# The way the data is kept is opposite the algorithm used.
|
| 617 |
+
data = bytesjoin(dataList)
|
| 618 |
+
if not reverseBytes:
|
| 619 |
+
data = _reverseBytes(data)
|
| 620 |
+
return data
|
| 621 |
+
|
| 622 |
+
def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
|
| 623 |
+
if metrics is None:
|
| 624 |
+
metrics = self.metrics
|
| 625 |
+
if not reverseBytes:
|
| 626 |
+
dataRows = list(map(_reverseBytes, dataRows))
|
| 627 |
+
|
| 628 |
+
# Keep track of a list of ordinal values as they are easier to modify
|
| 629 |
+
# than a list of strings. Map to actual strings later.
|
| 630 |
+
numBytes = (self._getBitRange(len(dataRows), bitDepth, metrics)[0] + 7) // 8
|
| 631 |
+
ordDataList = [0] * numBytes
|
| 632 |
+
for row, data in enumerate(dataRows):
|
| 633 |
+
bitRange = self._getBitRange(row, bitDepth, metrics)
|
| 634 |
+
stepRange = bitRange + (8,)
|
| 635 |
+
for curBit, curByte in zip(range(*stepRange), data):
|
| 636 |
+
endBit = min(curBit + 8, bitRange[1])
|
| 637 |
+
cutPoint = curBit % 8
|
| 638 |
+
firstByteLoc = curBit // 8
|
| 639 |
+
secondByteLoc = endBit // 8
|
| 640 |
+
if firstByteLoc < secondByteLoc:
|
| 641 |
+
numBitsCut = 8 - cutPoint
|
| 642 |
+
else:
|
| 643 |
+
numBitsCut = endBit - curBit
|
| 644 |
+
curByte = byteord(curByte)
|
| 645 |
+
firstByte = curByte & ((1 << numBitsCut) - 1)
|
| 646 |
+
ordDataList[firstByteLoc] |= firstByte << cutPoint
|
| 647 |
+
if firstByteLoc < secondByteLoc and secondByteLoc < numBytes:
|
| 648 |
+
secondByte = (curByte >> numBitsCut) & ((1 << 8 - numBitsCut) - 1)
|
| 649 |
+
ordDataList[secondByteLoc] |= secondByte
|
| 650 |
+
|
| 651 |
+
# Save the image data with the bits going the correct way.
|
| 652 |
+
self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList)))
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
class ByteAlignedBitmapMixin(object):
|
| 656 |
+
def _getByteRange(self, row, bitDepth, metrics):
|
| 657 |
+
rowBytes = (bitDepth * metrics.width + 7) // 8
|
| 658 |
+
byteOffset = row * rowBytes
|
| 659 |
+
return (byteOffset, byteOffset + rowBytes)
|
| 660 |
+
|
| 661 |
+
def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False):
|
| 662 |
+
if metrics is None:
|
| 663 |
+
metrics = self.metrics
|
| 664 |
+
assert 0 <= row and row < metrics.height, "Illegal row access in bitmap"
|
| 665 |
+
byteRange = self._getByteRange(row, bitDepth, metrics)
|
| 666 |
+
data = self.imageData[slice(*byteRange)]
|
| 667 |
+
if reverseBytes:
|
| 668 |
+
data = _reverseBytes(data)
|
| 669 |
+
return data
|
| 670 |
+
|
| 671 |
+
def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False):
|
| 672 |
+
if metrics is None:
|
| 673 |
+
metrics = self.metrics
|
| 674 |
+
if reverseBytes:
|
| 675 |
+
dataRows = map(_reverseBytes, dataRows)
|
| 676 |
+
self.imageData = bytesjoin(dataRows)
|
| 677 |
+
|
| 678 |
+
|
| 679 |
+
class ebdt_bitmap_format_1(
|
| 680 |
+
ByteAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph
|
| 681 |
+
):
|
| 682 |
+
def decompile(self):
|
| 683 |
+
self.metrics = SmallGlyphMetrics()
|
| 684 |
+
dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
|
| 685 |
+
self.imageData = data
|
| 686 |
+
|
| 687 |
+
def compile(self, ttFont):
|
| 688 |
+
data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
|
| 689 |
+
return data + self.imageData
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
class ebdt_bitmap_format_2(
|
| 693 |
+
BitAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph
|
| 694 |
+
):
|
| 695 |
+
def decompile(self):
|
| 696 |
+
self.metrics = SmallGlyphMetrics()
|
| 697 |
+
dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
|
| 698 |
+
self.imageData = data
|
| 699 |
+
|
| 700 |
+
def compile(self, ttFont):
|
| 701 |
+
data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
|
| 702 |
+
return data + self.imageData
|
| 703 |
+
|
| 704 |
+
|
| 705 |
+
class ebdt_bitmap_format_5(BitAlignedBitmapMixin, BitmapGlyph):
|
| 706 |
+
def decompile(self):
|
| 707 |
+
self.imageData = self.data
|
| 708 |
+
|
| 709 |
+
def compile(self, ttFont):
|
| 710 |
+
return self.imageData
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
class ebdt_bitmap_format_6(
|
| 714 |
+
ByteAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph
|
| 715 |
+
):
|
| 716 |
+
def decompile(self):
|
| 717 |
+
self.metrics = BigGlyphMetrics()
|
| 718 |
+
dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
|
| 719 |
+
self.imageData = data
|
| 720 |
+
|
| 721 |
+
def compile(self, ttFont):
|
| 722 |
+
data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
|
| 723 |
+
return data + self.imageData
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
class ebdt_bitmap_format_7(
|
| 727 |
+
BitAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph
|
| 728 |
+
):
|
| 729 |
+
def decompile(self):
|
| 730 |
+
self.metrics = BigGlyphMetrics()
|
| 731 |
+
dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
|
| 732 |
+
self.imageData = data
|
| 733 |
+
|
| 734 |
+
def compile(self, ttFont):
|
| 735 |
+
data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
|
| 736 |
+
return data + self.imageData
|
| 737 |
+
|
| 738 |
+
|
| 739 |
+
class ComponentBitmapGlyph(BitmapGlyph):
|
| 740 |
+
def toXML(self, strikeIndex, glyphName, writer, ttFont):
|
| 741 |
+
writer.begintag(self.__class__.__name__, [("name", glyphName)])
|
| 742 |
+
writer.newline()
|
| 743 |
+
|
| 744 |
+
self.writeMetrics(writer, ttFont)
|
| 745 |
+
|
| 746 |
+
writer.begintag("components")
|
| 747 |
+
writer.newline()
|
| 748 |
+
for curComponent in self.componentArray:
|
| 749 |
+
curComponent.toXML(writer, ttFont)
|
| 750 |
+
writer.endtag("components")
|
| 751 |
+
writer.newline()
|
| 752 |
+
|
| 753 |
+
writer.endtag(self.__class__.__name__)
|
| 754 |
+
writer.newline()
|
| 755 |
+
|
| 756 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 757 |
+
self.readMetrics(name, attrs, content, ttFont)
|
| 758 |
+
for element in content:
|
| 759 |
+
if not isinstance(element, tuple):
|
| 760 |
+
continue
|
| 761 |
+
name, attr, content = element
|
| 762 |
+
if name == "components":
|
| 763 |
+
self.componentArray = []
|
| 764 |
+
for compElement in content:
|
| 765 |
+
if not isinstance(compElement, tuple):
|
| 766 |
+
continue
|
| 767 |
+
name, attrs, content = compElement
|
| 768 |
+
if name == "ebdtComponent":
|
| 769 |
+
curComponent = EbdtComponent()
|
| 770 |
+
curComponent.fromXML(name, attrs, content, ttFont)
|
| 771 |
+
self.componentArray.append(curComponent)
|
| 772 |
+
else:
|
| 773 |
+
log.warning("'%s' being ignored in component array.", name)
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph):
|
| 777 |
+
def decompile(self):
|
| 778 |
+
self.metrics = SmallGlyphMetrics()
|
| 779 |
+
dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics)
|
| 780 |
+
data = data[1:]
|
| 781 |
+
|
| 782 |
+
(numComponents,) = struct.unpack(">H", data[:2])
|
| 783 |
+
data = data[2:]
|
| 784 |
+
self.componentArray = []
|
| 785 |
+
for i in range(numComponents):
|
| 786 |
+
curComponent = EbdtComponent()
|
| 787 |
+
dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent)
|
| 788 |
+
curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode)
|
| 789 |
+
self.componentArray.append(curComponent)
|
| 790 |
+
|
| 791 |
+
def compile(self, ttFont):
|
| 792 |
+
dataList = []
|
| 793 |
+
dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics))
|
| 794 |
+
dataList.append(b"\0")
|
| 795 |
+
dataList.append(struct.pack(">H", len(self.componentArray)))
|
| 796 |
+
for curComponent in self.componentArray:
|
| 797 |
+
curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
|
| 798 |
+
dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
|
| 799 |
+
return bytesjoin(dataList)
|
| 800 |
+
|
| 801 |
+
|
| 802 |
+
class ebdt_bitmap_format_9(BitmapPlusBigMetricsMixin, ComponentBitmapGlyph):
|
| 803 |
+
def decompile(self):
|
| 804 |
+
self.metrics = BigGlyphMetrics()
|
| 805 |
+
dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics)
|
| 806 |
+
(numComponents,) = struct.unpack(">H", data[:2])
|
| 807 |
+
data = data[2:]
|
| 808 |
+
self.componentArray = []
|
| 809 |
+
for i in range(numComponents):
|
| 810 |
+
curComponent = EbdtComponent()
|
| 811 |
+
dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent)
|
| 812 |
+
curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode)
|
| 813 |
+
self.componentArray.append(curComponent)
|
| 814 |
+
|
| 815 |
+
def compile(self, ttFont):
|
| 816 |
+
dataList = []
|
| 817 |
+
dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
|
| 818 |
+
dataList.append(struct.pack(">H", len(self.componentArray)))
|
| 819 |
+
for curComponent in self.componentArray:
|
| 820 |
+
curComponent.glyphCode = ttFont.getGlyphID(curComponent.name)
|
| 821 |
+
dataList.append(sstruct.pack(ebdtComponentFormat, curComponent))
|
| 822 |
+
return bytesjoin(dataList)
|
| 823 |
+
|
| 824 |
+
|
| 825 |
+
# Dictionary of bitmap formats to the class representing that format
|
| 826 |
+
# currently only the ones listed in this map are the ones supported.
|
| 827 |
+
ebdt_bitmap_classes = {
|
| 828 |
+
1: ebdt_bitmap_format_1,
|
| 829 |
+
2: ebdt_bitmap_format_2,
|
| 830 |
+
5: ebdt_bitmap_format_5,
|
| 831 |
+
6: ebdt_bitmap_format_6,
|
| 832 |
+
7: ebdt_bitmap_format_7,
|
| 833 |
+
8: ebdt_bitmap_format_8,
|
| 834 |
+
9: ebdt_bitmap_format_9,
|
| 835 |
+
}
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/E_B_L_C_.py
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from . import DefaultTable
|
| 3 |
+
from fontTools.misc.textTools import bytesjoin, safeEval
|
| 4 |
+
from .BitmapGlyphMetrics import (
|
| 5 |
+
BigGlyphMetrics,
|
| 6 |
+
bigGlyphMetricsFormat,
|
| 7 |
+
SmallGlyphMetrics,
|
| 8 |
+
smallGlyphMetricsFormat,
|
| 9 |
+
)
|
| 10 |
+
import struct
|
| 11 |
+
import itertools
|
| 12 |
+
from collections import deque
|
| 13 |
+
import logging
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
log = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
eblcHeaderFormat = """
|
| 19 |
+
> # big endian
|
| 20 |
+
version: 16.16F
|
| 21 |
+
numSizes: I
|
| 22 |
+
"""
|
| 23 |
+
# The table format string is split to handle sbitLineMetrics simply.
|
| 24 |
+
bitmapSizeTableFormatPart1 = """
|
| 25 |
+
> # big endian
|
| 26 |
+
indexSubTableArrayOffset: I
|
| 27 |
+
indexTablesSize: I
|
| 28 |
+
numberOfIndexSubTables: I
|
| 29 |
+
colorRef: I
|
| 30 |
+
"""
|
| 31 |
+
# The compound type for hori and vert.
|
| 32 |
+
sbitLineMetricsFormat = """
|
| 33 |
+
> # big endian
|
| 34 |
+
ascender: b
|
| 35 |
+
descender: b
|
| 36 |
+
widthMax: B
|
| 37 |
+
caretSlopeNumerator: b
|
| 38 |
+
caretSlopeDenominator: b
|
| 39 |
+
caretOffset: b
|
| 40 |
+
minOriginSB: b
|
| 41 |
+
minAdvanceSB: b
|
| 42 |
+
maxBeforeBL: b
|
| 43 |
+
minAfterBL: b
|
| 44 |
+
pad1: b
|
| 45 |
+
pad2: b
|
| 46 |
+
"""
|
| 47 |
+
# hori and vert go between the two parts.
|
| 48 |
+
bitmapSizeTableFormatPart2 = """
|
| 49 |
+
> # big endian
|
| 50 |
+
startGlyphIndex: H
|
| 51 |
+
endGlyphIndex: H
|
| 52 |
+
ppemX: B
|
| 53 |
+
ppemY: B
|
| 54 |
+
bitDepth: B
|
| 55 |
+
flags: b
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
indexSubTableArrayFormat = ">HHL"
|
| 59 |
+
indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat)
|
| 60 |
+
|
| 61 |
+
indexSubHeaderFormat = ">HHL"
|
| 62 |
+
indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat)
|
| 63 |
+
|
| 64 |
+
codeOffsetPairFormat = ">HH"
|
| 65 |
+
codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class table_E_B_L_C_(DefaultTable.DefaultTable):
|
| 69 |
+
"""Embedded Bitmap Location table
|
| 70 |
+
|
| 71 |
+
The ``EBLC`` table contains the locations of monochrome or grayscale
|
| 72 |
+
bitmaps for glyphs. It must be used in concert with the ``EBDT`` table.
|
| 73 |
+
|
| 74 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/eblc
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
dependencies = ["EBDT"]
|
| 78 |
+
|
| 79 |
+
# This method can be overridden in subclasses to support new formats
|
| 80 |
+
# without changing the other implementation. Also can be used as a
|
| 81 |
+
# convenience method for coverting a font file to an alternative format.
|
| 82 |
+
def getIndexFormatClass(self, indexFormat):
|
| 83 |
+
return eblc_sub_table_classes[indexFormat]
|
| 84 |
+
|
| 85 |
+
def decompile(self, data, ttFont):
|
| 86 |
+
# Save the original data because offsets are from the start of the table.
|
| 87 |
+
origData = data
|
| 88 |
+
i = 0
|
| 89 |
+
|
| 90 |
+
dummy = sstruct.unpack(eblcHeaderFormat, data[:8], self)
|
| 91 |
+
i += 8
|
| 92 |
+
|
| 93 |
+
self.strikes = []
|
| 94 |
+
for curStrikeIndex in range(self.numSizes):
|
| 95 |
+
curStrike = Strike()
|
| 96 |
+
self.strikes.append(curStrike)
|
| 97 |
+
curTable = curStrike.bitmapSizeTable
|
| 98 |
+
dummy = sstruct.unpack2(
|
| 99 |
+
bitmapSizeTableFormatPart1, data[i : i + 16], curTable
|
| 100 |
+
)
|
| 101 |
+
i += 16
|
| 102 |
+
for metric in ("hori", "vert"):
|
| 103 |
+
metricObj = SbitLineMetrics()
|
| 104 |
+
vars(curTable)[metric] = metricObj
|
| 105 |
+
dummy = sstruct.unpack2(
|
| 106 |
+
sbitLineMetricsFormat, data[i : i + 12], metricObj
|
| 107 |
+
)
|
| 108 |
+
i += 12
|
| 109 |
+
dummy = sstruct.unpack(
|
| 110 |
+
bitmapSizeTableFormatPart2, data[i : i + 8], curTable
|
| 111 |
+
)
|
| 112 |
+
i += 8
|
| 113 |
+
|
| 114 |
+
for curStrike in self.strikes:
|
| 115 |
+
curTable = curStrike.bitmapSizeTable
|
| 116 |
+
for subtableIndex in range(curTable.numberOfIndexSubTables):
|
| 117 |
+
i = (
|
| 118 |
+
curTable.indexSubTableArrayOffset
|
| 119 |
+
+ subtableIndex * indexSubTableArraySize
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
tup = struct.unpack(
|
| 123 |
+
indexSubTableArrayFormat, data[i : i + indexSubTableArraySize]
|
| 124 |
+
)
|
| 125 |
+
(firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup
|
| 126 |
+
i = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable
|
| 127 |
+
|
| 128 |
+
tup = struct.unpack(
|
| 129 |
+
indexSubHeaderFormat, data[i : i + indexSubHeaderSize]
|
| 130 |
+
)
|
| 131 |
+
(indexFormat, imageFormat, imageDataOffset) = tup
|
| 132 |
+
|
| 133 |
+
indexFormatClass = self.getIndexFormatClass(indexFormat)
|
| 134 |
+
indexSubTable = indexFormatClass(data[i + indexSubHeaderSize :], ttFont)
|
| 135 |
+
indexSubTable.firstGlyphIndex = firstGlyphIndex
|
| 136 |
+
indexSubTable.lastGlyphIndex = lastGlyphIndex
|
| 137 |
+
indexSubTable.additionalOffsetToIndexSubtable = (
|
| 138 |
+
additionalOffsetToIndexSubtable
|
| 139 |
+
)
|
| 140 |
+
indexSubTable.indexFormat = indexFormat
|
| 141 |
+
indexSubTable.imageFormat = imageFormat
|
| 142 |
+
indexSubTable.imageDataOffset = imageDataOffset
|
| 143 |
+
indexSubTable.decompile() # https://github.com/fonttools/fonttools/issues/317
|
| 144 |
+
curStrike.indexSubTables.append(indexSubTable)
|
| 145 |
+
|
| 146 |
+
def compile(self, ttFont):
|
| 147 |
+
dataList = []
|
| 148 |
+
self.numSizes = len(self.strikes)
|
| 149 |
+
dataList.append(sstruct.pack(eblcHeaderFormat, self))
|
| 150 |
+
|
| 151 |
+
# Data size of the header + bitmapSizeTable needs to be calculated
|
| 152 |
+
# in order to form offsets. This value will hold the size of the data
|
| 153 |
+
# in dataList after all the data is consolidated in dataList.
|
| 154 |
+
dataSize = len(dataList[0])
|
| 155 |
+
|
| 156 |
+
# The table will be structured in the following order:
|
| 157 |
+
# (0) header
|
| 158 |
+
# (1) Each bitmapSizeTable [1 ... self.numSizes]
|
| 159 |
+
# (2) Alternate between indexSubTableArray and indexSubTable
|
| 160 |
+
# for each bitmapSizeTable present.
|
| 161 |
+
#
|
| 162 |
+
# The issue is maintaining the proper offsets when table information
|
| 163 |
+
# gets moved around. All offsets and size information must be recalculated
|
| 164 |
+
# when building the table to allow editing within ttLib and also allow easy
|
| 165 |
+
# import/export to and from XML. All of this offset information is lost
|
| 166 |
+
# when exporting to XML so everything must be calculated fresh so importing
|
| 167 |
+
# from XML will work cleanly. Only byte offset and size information is
|
| 168 |
+
# calculated fresh. Count information like numberOfIndexSubTables is
|
| 169 |
+
# checked through assertions. If the information in this table was not
|
| 170 |
+
# touched or was changed properly then these types of values should match.
|
| 171 |
+
#
|
| 172 |
+
# The table will be rebuilt the following way:
|
| 173 |
+
# (0) Precompute the size of all the bitmapSizeTables. This is needed to
|
| 174 |
+
# compute the offsets properly.
|
| 175 |
+
# (1) For each bitmapSizeTable compute the indexSubTable and
|
| 176 |
+
# indexSubTableArray pair. The indexSubTable must be computed first
|
| 177 |
+
# so that the offset information in indexSubTableArray can be
|
| 178 |
+
# calculated. Update the data size after each pairing.
|
| 179 |
+
# (2) Build each bitmapSizeTable.
|
| 180 |
+
# (3) Consolidate all the data into the main dataList in the correct order.
|
| 181 |
+
|
| 182 |
+
for _ in self.strikes:
|
| 183 |
+
dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1)
|
| 184 |
+
dataSize += len(("hori", "vert")) * sstruct.calcsize(sbitLineMetricsFormat)
|
| 185 |
+
dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2)
|
| 186 |
+
|
| 187 |
+
indexSubTablePairDataList = []
|
| 188 |
+
for curStrike in self.strikes:
|
| 189 |
+
curTable = curStrike.bitmapSizeTable
|
| 190 |
+
curTable.numberOfIndexSubTables = len(curStrike.indexSubTables)
|
| 191 |
+
curTable.indexSubTableArrayOffset = dataSize
|
| 192 |
+
|
| 193 |
+
# Precompute the size of the indexSubTableArray. This information
|
| 194 |
+
# is important for correctly calculating the new value for
|
| 195 |
+
# additionalOffsetToIndexSubtable.
|
| 196 |
+
sizeOfSubTableArray = (
|
| 197 |
+
curTable.numberOfIndexSubTables * indexSubTableArraySize
|
| 198 |
+
)
|
| 199 |
+
lowerBound = dataSize
|
| 200 |
+
dataSize += sizeOfSubTableArray
|
| 201 |
+
upperBound = dataSize
|
| 202 |
+
|
| 203 |
+
indexSubTableDataList = []
|
| 204 |
+
for indexSubTable in curStrike.indexSubTables:
|
| 205 |
+
indexSubTable.additionalOffsetToIndexSubtable = (
|
| 206 |
+
dataSize - curTable.indexSubTableArrayOffset
|
| 207 |
+
)
|
| 208 |
+
glyphIds = list(map(ttFont.getGlyphID, indexSubTable.names))
|
| 209 |
+
indexSubTable.firstGlyphIndex = min(glyphIds)
|
| 210 |
+
indexSubTable.lastGlyphIndex = max(glyphIds)
|
| 211 |
+
data = indexSubTable.compile(ttFont)
|
| 212 |
+
indexSubTableDataList.append(data)
|
| 213 |
+
dataSize += len(data)
|
| 214 |
+
curTable.startGlyphIndex = min(
|
| 215 |
+
ist.firstGlyphIndex for ist in curStrike.indexSubTables
|
| 216 |
+
)
|
| 217 |
+
curTable.endGlyphIndex = max(
|
| 218 |
+
ist.lastGlyphIndex for ist in curStrike.indexSubTables
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
for i in curStrike.indexSubTables:
|
| 222 |
+
data = struct.pack(
|
| 223 |
+
indexSubHeaderFormat,
|
| 224 |
+
i.firstGlyphIndex,
|
| 225 |
+
i.lastGlyphIndex,
|
| 226 |
+
i.additionalOffsetToIndexSubtable,
|
| 227 |
+
)
|
| 228 |
+
indexSubTablePairDataList.append(data)
|
| 229 |
+
indexSubTablePairDataList.extend(indexSubTableDataList)
|
| 230 |
+
curTable.indexTablesSize = dataSize - curTable.indexSubTableArrayOffset
|
| 231 |
+
|
| 232 |
+
for curStrike in self.strikes:
|
| 233 |
+
curTable = curStrike.bitmapSizeTable
|
| 234 |
+
data = sstruct.pack(bitmapSizeTableFormatPart1, curTable)
|
| 235 |
+
dataList.append(data)
|
| 236 |
+
for metric in ("hori", "vert"):
|
| 237 |
+
metricObj = vars(curTable)[metric]
|
| 238 |
+
data = sstruct.pack(sbitLineMetricsFormat, metricObj)
|
| 239 |
+
dataList.append(data)
|
| 240 |
+
data = sstruct.pack(bitmapSizeTableFormatPart2, curTable)
|
| 241 |
+
dataList.append(data)
|
| 242 |
+
dataList.extend(indexSubTablePairDataList)
|
| 243 |
+
|
| 244 |
+
return bytesjoin(dataList)
|
| 245 |
+
|
| 246 |
+
def toXML(self, writer, ttFont):
|
| 247 |
+
writer.simpletag("header", [("version", self.version)])
|
| 248 |
+
writer.newline()
|
| 249 |
+
for curIndex, curStrike in enumerate(self.strikes):
|
| 250 |
+
curStrike.toXML(curIndex, writer, ttFont)
|
| 251 |
+
|
| 252 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 253 |
+
if name == "header":
|
| 254 |
+
self.version = safeEval(attrs["version"])
|
| 255 |
+
elif name == "strike":
|
| 256 |
+
if not hasattr(self, "strikes"):
|
| 257 |
+
self.strikes = []
|
| 258 |
+
strikeIndex = safeEval(attrs["index"])
|
| 259 |
+
curStrike = Strike()
|
| 260 |
+
curStrike.fromXML(name, attrs, content, ttFont, self)
|
| 261 |
+
|
| 262 |
+
# Grow the strike array to the appropriate size. The XML format
|
| 263 |
+
# allows for the strike index value to be out of order.
|
| 264 |
+
if strikeIndex >= len(self.strikes):
|
| 265 |
+
self.strikes += [None] * (strikeIndex + 1 - len(self.strikes))
|
| 266 |
+
assert self.strikes[strikeIndex] is None, "Duplicate strike EBLC indices."
|
| 267 |
+
self.strikes[strikeIndex] = curStrike
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
class Strike(object):
|
| 271 |
+
def __init__(self):
|
| 272 |
+
self.bitmapSizeTable = BitmapSizeTable()
|
| 273 |
+
self.indexSubTables = []
|
| 274 |
+
|
| 275 |
+
def toXML(self, strikeIndex, writer, ttFont):
|
| 276 |
+
writer.begintag("strike", [("index", strikeIndex)])
|
| 277 |
+
writer.newline()
|
| 278 |
+
self.bitmapSizeTable.toXML(writer, ttFont)
|
| 279 |
+
writer.comment(
|
| 280 |
+
"GlyphIds are written but not read. The firstGlyphIndex and\nlastGlyphIndex values will be recalculated by the compiler."
|
| 281 |
+
)
|
| 282 |
+
writer.newline()
|
| 283 |
+
for indexSubTable in self.indexSubTables:
|
| 284 |
+
indexSubTable.toXML(writer, ttFont)
|
| 285 |
+
writer.endtag("strike")
|
| 286 |
+
writer.newline()
|
| 287 |
+
|
| 288 |
+
def fromXML(self, name, attrs, content, ttFont, locator):
|
| 289 |
+
for element in content:
|
| 290 |
+
if not isinstance(element, tuple):
|
| 291 |
+
continue
|
| 292 |
+
name, attrs, content = element
|
| 293 |
+
if name == "bitmapSizeTable":
|
| 294 |
+
self.bitmapSizeTable.fromXML(name, attrs, content, ttFont)
|
| 295 |
+
elif name.startswith(_indexSubTableSubclassPrefix):
|
| 296 |
+
indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix) :])
|
| 297 |
+
indexFormatClass = locator.getIndexFormatClass(indexFormat)
|
| 298 |
+
indexSubTable = indexFormatClass(None, None)
|
| 299 |
+
indexSubTable.indexFormat = indexFormat
|
| 300 |
+
indexSubTable.fromXML(name, attrs, content, ttFont)
|
| 301 |
+
self.indexSubTables.append(indexSubTable)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
class BitmapSizeTable(object):
|
| 305 |
+
# Returns all the simple metric names that bitmap size table
|
| 306 |
+
# cares about in terms of XML creation.
|
| 307 |
+
def _getXMLMetricNames(self):
|
| 308 |
+
dataNames = sstruct.getformat(bitmapSizeTableFormatPart1)[1]
|
| 309 |
+
dataNames = {**dataNames, **sstruct.getformat(bitmapSizeTableFormatPart2)[1]}
|
| 310 |
+
# Skip the first 3 data names because they are byte offsets and counts.
|
| 311 |
+
return list(dataNames.keys())[3:]
|
| 312 |
+
|
| 313 |
+
def toXML(self, writer, ttFont):
|
| 314 |
+
writer.begintag("bitmapSizeTable")
|
| 315 |
+
writer.newline()
|
| 316 |
+
for metric in ("hori", "vert"):
|
| 317 |
+
getattr(self, metric).toXML(metric, writer, ttFont)
|
| 318 |
+
for metricName in self._getXMLMetricNames():
|
| 319 |
+
writer.simpletag(metricName, value=getattr(self, metricName))
|
| 320 |
+
writer.newline()
|
| 321 |
+
writer.endtag("bitmapSizeTable")
|
| 322 |
+
writer.newline()
|
| 323 |
+
|
| 324 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 325 |
+
# Create a lookup for all the simple names that make sense to
|
| 326 |
+
# bitmap size table. Only read the information from these names.
|
| 327 |
+
dataNames = set(self._getXMLMetricNames())
|
| 328 |
+
for element in content:
|
| 329 |
+
if not isinstance(element, tuple):
|
| 330 |
+
continue
|
| 331 |
+
name, attrs, content = element
|
| 332 |
+
if name == "sbitLineMetrics":
|
| 333 |
+
direction = attrs["direction"]
|
| 334 |
+
assert direction in (
|
| 335 |
+
"hori",
|
| 336 |
+
"vert",
|
| 337 |
+
), "SbitLineMetrics direction specified invalid."
|
| 338 |
+
metricObj = SbitLineMetrics()
|
| 339 |
+
metricObj.fromXML(name, attrs, content, ttFont)
|
| 340 |
+
vars(self)[direction] = metricObj
|
| 341 |
+
elif name in dataNames:
|
| 342 |
+
vars(self)[name] = safeEval(attrs["value"])
|
| 343 |
+
else:
|
| 344 |
+
log.warning("unknown name '%s' being ignored in BitmapSizeTable.", name)
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
class SbitLineMetrics(object):
|
| 348 |
+
def toXML(self, name, writer, ttFont):
|
| 349 |
+
writer.begintag("sbitLineMetrics", [("direction", name)])
|
| 350 |
+
writer.newline()
|
| 351 |
+
for metricName in sstruct.getformat(sbitLineMetricsFormat)[1]:
|
| 352 |
+
writer.simpletag(metricName, value=getattr(self, metricName))
|
| 353 |
+
writer.newline()
|
| 354 |
+
writer.endtag("sbitLineMetrics")
|
| 355 |
+
writer.newline()
|
| 356 |
+
|
| 357 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 358 |
+
metricNames = set(sstruct.getformat(sbitLineMetricsFormat)[1])
|
| 359 |
+
for element in content:
|
| 360 |
+
if not isinstance(element, tuple):
|
| 361 |
+
continue
|
| 362 |
+
name, attrs, content = element
|
| 363 |
+
if name in metricNames:
|
| 364 |
+
vars(self)[name] = safeEval(attrs["value"])
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
# Important information about the naming scheme. Used for identifying subtables.
|
| 368 |
+
_indexSubTableSubclassPrefix = "eblc_index_sub_table_"
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
class EblcIndexSubTable(object):
|
| 372 |
+
def __init__(self, data, ttFont):
|
| 373 |
+
self.data = data
|
| 374 |
+
self.ttFont = ttFont
|
| 375 |
+
# TODO Currently non-lazy decompiling doesn't work for this class...
|
| 376 |
+
# if not ttFont.lazy:
|
| 377 |
+
# self.decompile()
|
| 378 |
+
# del self.data, self.ttFont
|
| 379 |
+
|
| 380 |
+
def __getattr__(self, attr):
|
| 381 |
+
# Allow lazy decompile.
|
| 382 |
+
if attr[:2] == "__":
|
| 383 |
+
raise AttributeError(attr)
|
| 384 |
+
if attr == "data":
|
| 385 |
+
raise AttributeError(attr)
|
| 386 |
+
self.decompile()
|
| 387 |
+
return getattr(self, attr)
|
| 388 |
+
|
| 389 |
+
def ensureDecompiled(self, recurse=False):
|
| 390 |
+
if hasattr(self, "data"):
|
| 391 |
+
self.decompile()
|
| 392 |
+
|
| 393 |
+
# This method just takes care of the indexSubHeader. Implementing subclasses
|
| 394 |
+
# should call it to compile the indexSubHeader and then continue compiling
|
| 395 |
+
# the remainder of their unique format.
|
| 396 |
+
def compile(self, ttFont):
|
| 397 |
+
return struct.pack(
|
| 398 |
+
indexSubHeaderFormat,
|
| 399 |
+
self.indexFormat,
|
| 400 |
+
self.imageFormat,
|
| 401 |
+
self.imageDataOffset,
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
# Creates the XML for bitmap glyphs. Each index sub table basically makes
|
| 405 |
+
# the same XML except for specific metric information that is written
|
| 406 |
+
# out via a method call that a subclass implements optionally.
|
| 407 |
+
def toXML(self, writer, ttFont):
|
| 408 |
+
writer.begintag(
|
| 409 |
+
self.__class__.__name__,
|
| 410 |
+
[
|
| 411 |
+
("imageFormat", self.imageFormat),
|
| 412 |
+
("firstGlyphIndex", self.firstGlyphIndex),
|
| 413 |
+
("lastGlyphIndex", self.lastGlyphIndex),
|
| 414 |
+
],
|
| 415 |
+
)
|
| 416 |
+
writer.newline()
|
| 417 |
+
self.writeMetrics(writer, ttFont)
|
| 418 |
+
# Write out the names as thats all thats needed to rebuild etc.
|
| 419 |
+
# For font debugging of consecutive formats the ids are also written.
|
| 420 |
+
# The ids are not read when moving from the XML format.
|
| 421 |
+
glyphIds = map(ttFont.getGlyphID, self.names)
|
| 422 |
+
for glyphName, glyphId in zip(self.names, glyphIds):
|
| 423 |
+
writer.simpletag("glyphLoc", name=glyphName, id=glyphId)
|
| 424 |
+
writer.newline()
|
| 425 |
+
writer.endtag(self.__class__.__name__)
|
| 426 |
+
writer.newline()
|
| 427 |
+
|
| 428 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 429 |
+
# Read all the attributes. Even though the glyph indices are
|
| 430 |
+
# recalculated, they are still read in case there needs to
|
| 431 |
+
# be an immediate export of the data.
|
| 432 |
+
self.imageFormat = safeEval(attrs["imageFormat"])
|
| 433 |
+
self.firstGlyphIndex = safeEval(attrs["firstGlyphIndex"])
|
| 434 |
+
self.lastGlyphIndex = safeEval(attrs["lastGlyphIndex"])
|
| 435 |
+
|
| 436 |
+
self.readMetrics(name, attrs, content, ttFont)
|
| 437 |
+
|
| 438 |
+
self.names = []
|
| 439 |
+
for element in content:
|
| 440 |
+
if not isinstance(element, tuple):
|
| 441 |
+
continue
|
| 442 |
+
name, attrs, content = element
|
| 443 |
+
if name == "glyphLoc":
|
| 444 |
+
self.names.append(attrs["name"])
|
| 445 |
+
|
| 446 |
+
# A helper method that writes the metrics for the index sub table. It also
|
| 447 |
+
# is responsible for writing the image size for fixed size data since fixed
|
| 448 |
+
# size is not recalculated on compile. Default behavior is to do nothing.
|
| 449 |
+
def writeMetrics(self, writer, ttFont):
|
| 450 |
+
pass
|
| 451 |
+
|
| 452 |
+
# A helper method that is the inverse of writeMetrics.
|
| 453 |
+
def readMetrics(self, name, attrs, content, ttFont):
|
| 454 |
+
pass
|
| 455 |
+
|
| 456 |
+
# This method is for fixed glyph data sizes. There are formats where
|
| 457 |
+
# the glyph data is fixed but are actually composite glyphs. To handle
|
| 458 |
+
# this the font spec in indexSubTable makes the data the size of the
|
| 459 |
+
# fixed size by padding the component arrays. This function abstracts
|
| 460 |
+
# out this padding process. Input is data unpadded. Output is data
|
| 461 |
+
# padded only in fixed formats. Default behavior is to return the data.
|
| 462 |
+
def padBitmapData(self, data):
|
| 463 |
+
return data
|
| 464 |
+
|
| 465 |
+
# Remove any of the glyph locations and names that are flagged as skipped.
|
| 466 |
+
# This only occurs in formats {1,3}.
|
| 467 |
+
def removeSkipGlyphs(self):
|
| 468 |
+
# Determines if a name, location pair is a valid data location.
|
| 469 |
+
# Skip glyphs are marked when the size is equal to zero.
|
| 470 |
+
def isValidLocation(args):
|
| 471 |
+
(name, (startByte, endByte)) = args
|
| 472 |
+
return startByte < endByte
|
| 473 |
+
|
| 474 |
+
# Remove all skip glyphs.
|
| 475 |
+
dataPairs = list(filter(isValidLocation, zip(self.names, self.locations)))
|
| 476 |
+
self.names, self.locations = list(map(list, zip(*dataPairs)))
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
# A closure for creating a custom mixin. This is done because formats 1 and 3
|
| 480 |
+
# are very similar. The only difference between them is the size per offset
|
| 481 |
+
# value. Code put in here should handle both cases generally.
|
| 482 |
+
def _createOffsetArrayIndexSubTableMixin(formatStringForDataType):
|
| 483 |
+
# Prep the data size for the offset array data format.
|
| 484 |
+
dataFormat = ">" + formatStringForDataType
|
| 485 |
+
offsetDataSize = struct.calcsize(dataFormat)
|
| 486 |
+
|
| 487 |
+
class OffsetArrayIndexSubTableMixin(object):
|
| 488 |
+
def decompile(self):
|
| 489 |
+
numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1
|
| 490 |
+
indexingOffsets = [
|
| 491 |
+
glyphIndex * offsetDataSize for glyphIndex in range(numGlyphs + 2)
|
| 492 |
+
]
|
| 493 |
+
indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
|
| 494 |
+
offsetArray = [
|
| 495 |
+
struct.unpack(dataFormat, self.data[slice(*loc)])[0]
|
| 496 |
+
for loc in indexingLocations
|
| 497 |
+
]
|
| 498 |
+
|
| 499 |
+
glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
|
| 500 |
+
modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray]
|
| 501 |
+
self.locations = list(zip(modifiedOffsets, modifiedOffsets[1:]))
|
| 502 |
+
|
| 503 |
+
self.names = list(map(self.ttFont.getGlyphName, glyphIds))
|
| 504 |
+
self.removeSkipGlyphs()
|
| 505 |
+
del self.data, self.ttFont
|
| 506 |
+
|
| 507 |
+
def compile(self, ttFont):
|
| 508 |
+
# First make sure that all the data lines up properly. Formats 1 and 3
|
| 509 |
+
# must have all its data lined up consecutively. If not this will fail.
|
| 510 |
+
for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
|
| 511 |
+
assert (
|
| 512 |
+
curLoc[1] == nxtLoc[0]
|
| 513 |
+
), "Data must be consecutive in indexSubTable offset formats"
|
| 514 |
+
|
| 515 |
+
glyphIds = list(map(ttFont.getGlyphID, self.names))
|
| 516 |
+
# Make sure that all ids are sorted strictly increasing.
|
| 517 |
+
assert all(glyphIds[i] < glyphIds[i + 1] for i in range(len(glyphIds) - 1))
|
| 518 |
+
|
| 519 |
+
# Run a simple algorithm to add skip glyphs to the data locations at
|
| 520 |
+
# the places where an id is not present.
|
| 521 |
+
idQueue = deque(glyphIds)
|
| 522 |
+
locQueue = deque(self.locations)
|
| 523 |
+
allGlyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
|
| 524 |
+
allLocations = []
|
| 525 |
+
for curId in allGlyphIds:
|
| 526 |
+
if curId != idQueue[0]:
|
| 527 |
+
allLocations.append((locQueue[0][0], locQueue[0][0]))
|
| 528 |
+
else:
|
| 529 |
+
idQueue.popleft()
|
| 530 |
+
allLocations.append(locQueue.popleft())
|
| 531 |
+
|
| 532 |
+
# Now that all the locations are collected, pack them appropriately into
|
| 533 |
+
# offsets. This is the form where offset[i] is the location and
|
| 534 |
+
# offset[i+1]-offset[i] is the size of the data location.
|
| 535 |
+
offsets = list(allLocations[0]) + [loc[1] for loc in allLocations[1:]]
|
| 536 |
+
# Image data offset must be less than or equal to the minimum of locations.
|
| 537 |
+
# This offset may change the value for round tripping but is safer and
|
| 538 |
+
# allows imageDataOffset to not be required to be in the XML version.
|
| 539 |
+
self.imageDataOffset = min(offsets)
|
| 540 |
+
offsetArray = [offset - self.imageDataOffset for offset in offsets]
|
| 541 |
+
|
| 542 |
+
dataList = [EblcIndexSubTable.compile(self, ttFont)]
|
| 543 |
+
dataList += [
|
| 544 |
+
struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray
|
| 545 |
+
]
|
| 546 |
+
# Take care of any padding issues. Only occurs in format 3.
|
| 547 |
+
if offsetDataSize * len(offsetArray) % 4 != 0:
|
| 548 |
+
dataList.append(struct.pack(dataFormat, 0))
|
| 549 |
+
return bytesjoin(dataList)
|
| 550 |
+
|
| 551 |
+
return OffsetArrayIndexSubTableMixin
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
# A Mixin for functionality shared between the different kinds
|
| 555 |
+
# of fixed sized data handling. Both kinds have big metrics so
|
| 556 |
+
# that kind of special processing is also handled in this mixin.
|
| 557 |
+
class FixedSizeIndexSubTableMixin(object):
|
| 558 |
+
def writeMetrics(self, writer, ttFont):
|
| 559 |
+
writer.simpletag("imageSize", value=self.imageSize)
|
| 560 |
+
writer.newline()
|
| 561 |
+
self.metrics.toXML(writer, ttFont)
|
| 562 |
+
|
| 563 |
+
def readMetrics(self, name, attrs, content, ttFont):
|
| 564 |
+
for element in content:
|
| 565 |
+
if not isinstance(element, tuple):
|
| 566 |
+
continue
|
| 567 |
+
name, attrs, content = element
|
| 568 |
+
if name == "imageSize":
|
| 569 |
+
self.imageSize = safeEval(attrs["value"])
|
| 570 |
+
elif name == BigGlyphMetrics.__name__:
|
| 571 |
+
self.metrics = BigGlyphMetrics()
|
| 572 |
+
self.metrics.fromXML(name, attrs, content, ttFont)
|
| 573 |
+
elif name == SmallGlyphMetrics.__name__:
|
| 574 |
+
log.warning(
|
| 575 |
+
"SmallGlyphMetrics being ignored in format %d.", self.indexFormat
|
| 576 |
+
)
|
| 577 |
+
|
| 578 |
+
def padBitmapData(self, data):
|
| 579 |
+
# Make sure that the data isn't bigger than the fixed size.
|
| 580 |
+
assert len(data) <= self.imageSize, (
|
| 581 |
+
"Data in indexSubTable format %d must be less than the fixed size."
|
| 582 |
+
% self.indexFormat
|
| 583 |
+
)
|
| 584 |
+
# Pad the data so that it matches the fixed size.
|
| 585 |
+
pad = (self.imageSize - len(data)) * b"\0"
|
| 586 |
+
return data + pad
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
class eblc_index_sub_table_1(
|
| 590 |
+
_createOffsetArrayIndexSubTableMixin("L"), EblcIndexSubTable
|
| 591 |
+
):
|
| 592 |
+
pass
|
| 593 |
+
|
| 594 |
+
|
| 595 |
+
class eblc_index_sub_table_2(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
|
| 596 |
+
def decompile(self):
|
| 597 |
+
(self.imageSize,) = struct.unpack(">L", self.data[:4])
|
| 598 |
+
self.metrics = BigGlyphMetrics()
|
| 599 |
+
sstruct.unpack2(bigGlyphMetricsFormat, self.data[4:], self.metrics)
|
| 600 |
+
glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
|
| 601 |
+
offsets = [
|
| 602 |
+
self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
|
| 603 |
+
]
|
| 604 |
+
self.locations = list(zip(offsets, offsets[1:]))
|
| 605 |
+
self.names = list(map(self.ttFont.getGlyphName, glyphIds))
|
| 606 |
+
del self.data, self.ttFont
|
| 607 |
+
|
| 608 |
+
def compile(self, ttFont):
|
| 609 |
+
glyphIds = list(map(ttFont.getGlyphID, self.names))
|
| 610 |
+
# Make sure all the ids are consecutive. This is required by Format 2.
|
| 611 |
+
assert glyphIds == list(
|
| 612 |
+
range(self.firstGlyphIndex, self.lastGlyphIndex + 1)
|
| 613 |
+
), "Format 2 ids must be consecutive."
|
| 614 |
+
self.imageDataOffset = min(next(iter(zip(*self.locations))))
|
| 615 |
+
|
| 616 |
+
dataList = [EblcIndexSubTable.compile(self, ttFont)]
|
| 617 |
+
dataList.append(struct.pack(">L", self.imageSize))
|
| 618 |
+
dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
|
| 619 |
+
return bytesjoin(dataList)
|
| 620 |
+
|
| 621 |
+
|
| 622 |
+
class eblc_index_sub_table_3(
|
| 623 |
+
_createOffsetArrayIndexSubTableMixin("H"), EblcIndexSubTable
|
| 624 |
+
):
|
| 625 |
+
pass
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
class eblc_index_sub_table_4(EblcIndexSubTable):
|
| 629 |
+
def decompile(self):
|
| 630 |
+
(numGlyphs,) = struct.unpack(">L", self.data[:4])
|
| 631 |
+
data = self.data[4:]
|
| 632 |
+
indexingOffsets = [
|
| 633 |
+
glyphIndex * codeOffsetPairSize for glyphIndex in range(numGlyphs + 2)
|
| 634 |
+
]
|
| 635 |
+
indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
|
| 636 |
+
glyphArray = [
|
| 637 |
+
struct.unpack(codeOffsetPairFormat, data[slice(*loc)])
|
| 638 |
+
for loc in indexingLocations
|
| 639 |
+
]
|
| 640 |
+
glyphIds, offsets = list(map(list, zip(*glyphArray)))
|
| 641 |
+
# There are one too many glyph ids. Get rid of the last one.
|
| 642 |
+
glyphIds.pop()
|
| 643 |
+
|
| 644 |
+
offsets = [offset + self.imageDataOffset for offset in offsets]
|
| 645 |
+
self.locations = list(zip(offsets, offsets[1:]))
|
| 646 |
+
self.names = list(map(self.ttFont.getGlyphName, glyphIds))
|
| 647 |
+
del self.data, self.ttFont
|
| 648 |
+
|
| 649 |
+
def compile(self, ttFont):
|
| 650 |
+
# First make sure that all the data lines up properly. Format 4
|
| 651 |
+
# must have all its data lined up consecutively. If not this will fail.
|
| 652 |
+
for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
|
| 653 |
+
assert (
|
| 654 |
+
curLoc[1] == nxtLoc[0]
|
| 655 |
+
), "Data must be consecutive in indexSubTable format 4"
|
| 656 |
+
|
| 657 |
+
offsets = list(self.locations[0]) + [loc[1] for loc in self.locations[1:]]
|
| 658 |
+
# Image data offset must be less than or equal to the minimum of locations.
|
| 659 |
+
# Resetting this offset may change the value for round tripping but is safer
|
| 660 |
+
# and allows imageDataOffset to not be required to be in the XML version.
|
| 661 |
+
self.imageDataOffset = min(offsets)
|
| 662 |
+
offsets = [offset - self.imageDataOffset for offset in offsets]
|
| 663 |
+
glyphIds = list(map(ttFont.getGlyphID, self.names))
|
| 664 |
+
# Create an iterator over the ids plus a padding value.
|
| 665 |
+
idsPlusPad = list(itertools.chain(glyphIds, [0]))
|
| 666 |
+
|
| 667 |
+
dataList = [EblcIndexSubTable.compile(self, ttFont)]
|
| 668 |
+
dataList.append(struct.pack(">L", len(glyphIds)))
|
| 669 |
+
tmp = [
|
| 670 |
+
struct.pack(codeOffsetPairFormat, *cop) for cop in zip(idsPlusPad, offsets)
|
| 671 |
+
]
|
| 672 |
+
dataList += tmp
|
| 673 |
+
data = bytesjoin(dataList)
|
| 674 |
+
return data
|
| 675 |
+
|
| 676 |
+
|
| 677 |
+
class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
|
| 678 |
+
def decompile(self):
|
| 679 |
+
self.origDataLen = 0
|
| 680 |
+
(self.imageSize,) = struct.unpack(">L", self.data[:4])
|
| 681 |
+
data = self.data[4:]
|
| 682 |
+
self.metrics, data = sstruct.unpack2(
|
| 683 |
+
bigGlyphMetricsFormat, data, BigGlyphMetrics()
|
| 684 |
+
)
|
| 685 |
+
(numGlyphs,) = struct.unpack(">L", data[:4])
|
| 686 |
+
data = data[4:]
|
| 687 |
+
glyphIds = [
|
| 688 |
+
struct.unpack(">H", data[2 * i : 2 * (i + 1)])[0] for i in range(numGlyphs)
|
| 689 |
+
]
|
| 690 |
+
|
| 691 |
+
offsets = [
|
| 692 |
+
self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
|
| 693 |
+
]
|
| 694 |
+
self.locations = list(zip(offsets, offsets[1:]))
|
| 695 |
+
self.names = list(map(self.ttFont.getGlyphName, glyphIds))
|
| 696 |
+
del self.data, self.ttFont
|
| 697 |
+
|
| 698 |
+
def compile(self, ttFont):
|
| 699 |
+
self.imageDataOffset = min(next(iter(zip(*self.locations))))
|
| 700 |
+
dataList = [EblcIndexSubTable.compile(self, ttFont)]
|
| 701 |
+
dataList.append(struct.pack(">L", self.imageSize))
|
| 702 |
+
dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
|
| 703 |
+
glyphIds = list(map(ttFont.getGlyphID, self.names))
|
| 704 |
+
dataList.append(struct.pack(">L", len(glyphIds)))
|
| 705 |
+
dataList += [struct.pack(">H", curId) for curId in glyphIds]
|
| 706 |
+
if len(glyphIds) % 2 == 1:
|
| 707 |
+
dataList.append(struct.pack(">H", 0))
|
| 708 |
+
return bytesjoin(dataList)
|
| 709 |
+
|
| 710 |
+
|
| 711 |
+
# Dictionary of indexFormat to the class representing that format.
|
| 712 |
+
eblc_sub_table_classes = {
|
| 713 |
+
1: eblc_index_sub_table_1,
|
| 714 |
+
2: eblc_index_sub_table_2,
|
| 715 |
+
3: eblc_index_sub_table_3,
|
| 716 |
+
4: eblc_index_sub_table_4,
|
| 717 |
+
5: eblc_index_sub_table_5,
|
| 718 |
+
}
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F_F_T_M_.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.textTools import safeEval
|
| 3 |
+
from fontTools.misc.timeTools import timestampFromString, timestampToString
|
| 4 |
+
from . import DefaultTable
|
| 5 |
+
|
| 6 |
+
FFTMFormat = """
|
| 7 |
+
> # big endian
|
| 8 |
+
version: I
|
| 9 |
+
FFTimeStamp: Q
|
| 10 |
+
sourceCreated: Q
|
| 11 |
+
sourceModified: Q
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class table_F_F_T_M_(DefaultTable.DefaultTable):
|
| 16 |
+
"""FontForge Time Stamp table
|
| 17 |
+
|
| 18 |
+
The ``FFTM`` table is used by the free-software font editor
|
| 19 |
+
FontForge to record timestamps for the creation and modification
|
| 20 |
+
of font source (.sfd) files and a timestamp for FontForge's
|
| 21 |
+
own source code.
|
| 22 |
+
|
| 23 |
+
See also https://fontforge.org/docs/techref/non-standard.html
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
def decompile(self, data, ttFont):
|
| 27 |
+
dummy, rest = sstruct.unpack2(FFTMFormat, data, self)
|
| 28 |
+
|
| 29 |
+
def compile(self, ttFont):
|
| 30 |
+
data = sstruct.pack(FFTMFormat, self)
|
| 31 |
+
return data
|
| 32 |
+
|
| 33 |
+
def toXML(self, writer, ttFont):
|
| 34 |
+
writer.comment(
|
| 35 |
+
"FontForge's timestamp, font source creation and modification dates"
|
| 36 |
+
)
|
| 37 |
+
writer.newline()
|
| 38 |
+
formatstring, names, fixes = sstruct.getformat(FFTMFormat)
|
| 39 |
+
for name in names:
|
| 40 |
+
value = getattr(self, name)
|
| 41 |
+
if name in ("FFTimeStamp", "sourceCreated", "sourceModified"):
|
| 42 |
+
value = timestampToString(value)
|
| 43 |
+
writer.simpletag(name, value=value)
|
| 44 |
+
writer.newline()
|
| 45 |
+
|
| 46 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 47 |
+
value = attrs["value"]
|
| 48 |
+
if name in ("FFTimeStamp", "sourceCreated", "sourceModified"):
|
| 49 |
+
value = timestampFromString(value)
|
| 50 |
+
else:
|
| 51 |
+
value = safeEval(value)
|
| 52 |
+
setattr(self, name, value)
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/F__e_a_t.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.fixedTools import floatToFixedToStr
|
| 3 |
+
from fontTools.misc.textTools import safeEval
|
| 4 |
+
from . import DefaultTable
|
| 5 |
+
from . import grUtils
|
| 6 |
+
import struct
|
| 7 |
+
|
| 8 |
+
Feat_hdr_format = """
|
| 9 |
+
>
|
| 10 |
+
version: 16.16F
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class table_F__e_a_t(DefaultTable.DefaultTable):
|
| 15 |
+
"""Feature table
|
| 16 |
+
|
| 17 |
+
The ``Feat`` table is used exclusively by the Graphite shaping engine
|
| 18 |
+
to store features and possible settings specified in GDL. Graphite features
|
| 19 |
+
determine what rules are applied to transform a glyph stream.
|
| 20 |
+
|
| 21 |
+
Not to be confused with ``feat``, or the OpenType Layout tables
|
| 22 |
+
``GSUB``/``GPOS``.
|
| 23 |
+
|
| 24 |
+
See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(self, tag=None):
|
| 28 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 29 |
+
self.features = {}
|
| 30 |
+
|
| 31 |
+
def decompile(self, data, ttFont):
|
| 32 |
+
(_, data) = sstruct.unpack2(Feat_hdr_format, data, self)
|
| 33 |
+
self.version = float(floatToFixedToStr(self.version, precisionBits=16))
|
| 34 |
+
(numFeats,) = struct.unpack(">H", data[:2])
|
| 35 |
+
data = data[8:]
|
| 36 |
+
allfeats = []
|
| 37 |
+
maxsetting = 0
|
| 38 |
+
for i in range(numFeats):
|
| 39 |
+
if self.version >= 2.0:
|
| 40 |
+
(fid, nums, _, offset, flags, lid) = struct.unpack(
|
| 41 |
+
">LHHLHH", data[16 * i : 16 * (i + 1)]
|
| 42 |
+
)
|
| 43 |
+
offset = int((offset - 12 - 16 * numFeats) / 4)
|
| 44 |
+
else:
|
| 45 |
+
(fid, nums, offset, flags, lid) = struct.unpack(
|
| 46 |
+
">HHLHH", data[12 * i : 12 * (i + 1)]
|
| 47 |
+
)
|
| 48 |
+
offset = int((offset - 12 - 12 * numFeats) / 4)
|
| 49 |
+
allfeats.append((fid, nums, offset, flags, lid))
|
| 50 |
+
maxsetting = max(maxsetting, offset + nums)
|
| 51 |
+
data = data[16 * numFeats :]
|
| 52 |
+
allsettings = []
|
| 53 |
+
for i in range(maxsetting):
|
| 54 |
+
if len(data) >= 4 * (i + 1):
|
| 55 |
+
(val, lid) = struct.unpack(">HH", data[4 * i : 4 * (i + 1)])
|
| 56 |
+
allsettings.append((val, lid))
|
| 57 |
+
for i, f in enumerate(allfeats):
|
| 58 |
+
(fid, nums, offset, flags, lid) = f
|
| 59 |
+
fobj = Feature()
|
| 60 |
+
fobj.flags = flags
|
| 61 |
+
fobj.label = lid
|
| 62 |
+
self.features[grUtils.num2tag(fid)] = fobj
|
| 63 |
+
fobj.settings = {}
|
| 64 |
+
fobj.default = None
|
| 65 |
+
fobj.index = i
|
| 66 |
+
for i in range(offset, offset + nums):
|
| 67 |
+
if i >= len(allsettings):
|
| 68 |
+
continue
|
| 69 |
+
(vid, vlid) = allsettings[i]
|
| 70 |
+
fobj.settings[vid] = vlid
|
| 71 |
+
if fobj.default is None:
|
| 72 |
+
fobj.default = vid
|
| 73 |
+
|
| 74 |
+
def compile(self, ttFont):
|
| 75 |
+
fdat = b""
|
| 76 |
+
vdat = b""
|
| 77 |
+
offset = 0
|
| 78 |
+
for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
|
| 79 |
+
fnum = grUtils.tag2num(f)
|
| 80 |
+
if self.version >= 2.0:
|
| 81 |
+
fdat += struct.pack(
|
| 82 |
+
">LHHLHH",
|
| 83 |
+
grUtils.tag2num(f),
|
| 84 |
+
len(v.settings),
|
| 85 |
+
0,
|
| 86 |
+
offset * 4 + 12 + 16 * len(self.features),
|
| 87 |
+
v.flags,
|
| 88 |
+
v.label,
|
| 89 |
+
)
|
| 90 |
+
elif fnum > 65535: # self healing for alphabetic ids
|
| 91 |
+
self.version = 2.0
|
| 92 |
+
return self.compile(ttFont)
|
| 93 |
+
else:
|
| 94 |
+
fdat += struct.pack(
|
| 95 |
+
">HHLHH",
|
| 96 |
+
grUtils.tag2num(f),
|
| 97 |
+
len(v.settings),
|
| 98 |
+
offset * 4 + 12 + 12 * len(self.features),
|
| 99 |
+
v.flags,
|
| 100 |
+
v.label,
|
| 101 |
+
)
|
| 102 |
+
for s, l in sorted(
|
| 103 |
+
v.settings.items(), key=lambda x: (-1, x[1]) if x[0] == v.default else x
|
| 104 |
+
):
|
| 105 |
+
vdat += struct.pack(">HH", s, l)
|
| 106 |
+
offset += len(v.settings)
|
| 107 |
+
hdr = sstruct.pack(Feat_hdr_format, self)
|
| 108 |
+
return hdr + struct.pack(">HHL", len(self.features), 0, 0) + fdat + vdat
|
| 109 |
+
|
| 110 |
+
def toXML(self, writer, ttFont):
|
| 111 |
+
writer.simpletag("version", version=self.version)
|
| 112 |
+
writer.newline()
|
| 113 |
+
nameTable = ttFont["name"] if "name" in ttFont else None
|
| 114 |
+
for f, v in sorted(self.features.items(), key=lambda x: x[1].index):
|
| 115 |
+
name = nameTable.getDebugName(v.label) if nameTable else None
|
| 116 |
+
if name is not None:
|
| 117 |
+
writer.newline()
|
| 118 |
+
writer.comment(name)
|
| 119 |
+
writer.newline()
|
| 120 |
+
writer.begintag(
|
| 121 |
+
"feature",
|
| 122 |
+
fid=f,
|
| 123 |
+
label=v.label,
|
| 124 |
+
flags=v.flags,
|
| 125 |
+
default=(v.default if v.default else 0),
|
| 126 |
+
)
|
| 127 |
+
writer.newline()
|
| 128 |
+
for s, l in sorted(v.settings.items()):
|
| 129 |
+
writer.simpletag("setting", value=s, label=l)
|
| 130 |
+
name = nameTable.getDebugName(l) if nameTable else None
|
| 131 |
+
if name is not None:
|
| 132 |
+
writer.write(" ")
|
| 133 |
+
writer.comment(name)
|
| 134 |
+
writer.newline()
|
| 135 |
+
writer.endtag("feature")
|
| 136 |
+
writer.newline()
|
| 137 |
+
|
| 138 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 139 |
+
if name == "version":
|
| 140 |
+
self.version = float(safeEval(attrs["version"]))
|
| 141 |
+
elif name == "feature":
|
| 142 |
+
fid = attrs["fid"]
|
| 143 |
+
fobj = Feature()
|
| 144 |
+
fobj.flags = int(safeEval(attrs["flags"]))
|
| 145 |
+
fobj.label = int(safeEval(attrs["label"]))
|
| 146 |
+
fobj.default = int(safeEval(attrs.get("default", "0")))
|
| 147 |
+
fobj.index = len(self.features)
|
| 148 |
+
self.features[fid] = fobj
|
| 149 |
+
fobj.settings = {}
|
| 150 |
+
for element in content:
|
| 151 |
+
if not isinstance(element, tuple):
|
| 152 |
+
continue
|
| 153 |
+
tag, a, c = element
|
| 154 |
+
if tag == "setting":
|
| 155 |
+
fobj.settings[int(safeEval(a["value"]))] = int(safeEval(a["label"]))
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class Feature(object):
|
| 159 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_D_E_F_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_G_D_E_F_(BaseTTXConverter):
|
| 5 |
+
"""Glyph Definition table
|
| 6 |
+
|
| 7 |
+
The ``GDEF`` table stores various glyph properties that are used
|
| 8 |
+
by OpenType Layout.
|
| 9 |
+
|
| 10 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/gdef
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_P_O_S_.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_G_P_O_S_(BaseTTXConverter):
|
| 5 |
+
"""Glyph Positioning table
|
| 6 |
+
|
| 7 |
+
The ``GPOS`` table stores advanced glyph-positioning data
|
| 8 |
+
used in OpenType Layout features, such as mark attachment,
|
| 9 |
+
cursive attachment, kerning, and other position adjustments.
|
| 10 |
+
|
| 11 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/gpos
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_S_U_B_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_G_S_U_B_(BaseTTXConverter):
|
| 5 |
+
"""Glyph Substitution table
|
| 6 |
+
|
| 7 |
+
The ``GSUB`` table contains glyph-substitution rules used in
|
| 8 |
+
OpenType Layout.
|
| 9 |
+
|
| 10 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/gsub
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G_V_A_R_.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ._g_v_a_r import table__g_v_a_r
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_G_V_A_R_(table__g_v_a_r):
|
| 5 |
+
gid_size = 3
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_a_t.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.fixedTools import floatToFixedToStr
|
| 3 |
+
from fontTools.misc.textTools import safeEval
|
| 4 |
+
|
| 5 |
+
# from itertools import *
|
| 6 |
+
from functools import partial
|
| 7 |
+
from . import DefaultTable
|
| 8 |
+
from . import grUtils
|
| 9 |
+
import struct
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
Glat_format_0 = """
|
| 13 |
+
> # big endian
|
| 14 |
+
version: 16.16F
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
Glat_format_3 = """
|
| 18 |
+
>
|
| 19 |
+
version: 16.16F
|
| 20 |
+
compression:L # compression scheme or reserved
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
Glat_format_1_entry = """
|
| 24 |
+
>
|
| 25 |
+
attNum: B # Attribute number of first attribute
|
| 26 |
+
num: B # Number of attributes in this run
|
| 27 |
+
"""
|
| 28 |
+
Glat_format_23_entry = """
|
| 29 |
+
>
|
| 30 |
+
attNum: H # Attribute number of first attribute
|
| 31 |
+
num: H # Number of attributes in this run
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
Glat_format_3_octabox_metrics = """
|
| 35 |
+
>
|
| 36 |
+
subboxBitmap: H # Which subboxes exist on 4x4 grid
|
| 37 |
+
diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
|
| 38 |
+
diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
|
| 39 |
+
diagPosMin: B # Defines minimum positively-sloped diagonal (di)
|
| 40 |
+
diagPosMax: B # Defines maximum positively-sloped diagonal (da)
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
Glat_format_3_subbox_entry = """
|
| 44 |
+
>
|
| 45 |
+
left: B # xi
|
| 46 |
+
right: B # xa
|
| 47 |
+
bottom: B # yi
|
| 48 |
+
top: B # ya
|
| 49 |
+
diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
|
| 50 |
+
diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
|
| 51 |
+
diagPosMin: B # Defines minimum positively-sloped diagonal (di)
|
| 52 |
+
diagPosMax: B # Defines maximum positively-sloped diagonal (da)
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class _Object:
|
| 57 |
+
pass
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class _Dict(dict):
|
| 61 |
+
pass
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class table_G__l_a_t(DefaultTable.DefaultTable):
|
| 65 |
+
"""Graphite Glyph Attributes table
|
| 66 |
+
|
| 67 |
+
See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
|
| 68 |
+
"""
|
| 69 |
+
|
| 70 |
+
def __init__(self, tag=None):
|
| 71 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 72 |
+
self.scheme = 0
|
| 73 |
+
|
| 74 |
+
def decompile(self, data, ttFont):
|
| 75 |
+
sstruct.unpack2(Glat_format_0, data, self)
|
| 76 |
+
self.version = float(floatToFixedToStr(self.version, precisionBits=16))
|
| 77 |
+
if self.version <= 1.9:
|
| 78 |
+
decoder = partial(self.decompileAttributes12, fmt=Glat_format_1_entry)
|
| 79 |
+
elif self.version <= 2.9:
|
| 80 |
+
decoder = partial(self.decompileAttributes12, fmt=Glat_format_23_entry)
|
| 81 |
+
elif self.version >= 3.0:
|
| 82 |
+
(data, self.scheme) = grUtils.decompress(data)
|
| 83 |
+
sstruct.unpack2(Glat_format_3, data, self)
|
| 84 |
+
self.hasOctaboxes = (self.compression & 1) == 1
|
| 85 |
+
decoder = self.decompileAttributes3
|
| 86 |
+
|
| 87 |
+
gloc = ttFont["Gloc"]
|
| 88 |
+
self.attributes = {}
|
| 89 |
+
count = 0
|
| 90 |
+
for s, e in zip(gloc, gloc[1:]):
|
| 91 |
+
self.attributes[ttFont.getGlyphName(count)] = decoder(data[s:e])
|
| 92 |
+
count += 1
|
| 93 |
+
|
| 94 |
+
def decompileAttributes12(self, data, fmt):
|
| 95 |
+
attributes = _Dict()
|
| 96 |
+
while len(data) > 3:
|
| 97 |
+
e, data = sstruct.unpack2(fmt, data, _Object())
|
| 98 |
+
keys = range(e.attNum, e.attNum + e.num)
|
| 99 |
+
if len(data) >= 2 * e.num:
|
| 100 |
+
vals = struct.unpack_from((">%dh" % e.num), data)
|
| 101 |
+
attributes.update(zip(keys, vals))
|
| 102 |
+
data = data[2 * e.num :]
|
| 103 |
+
return attributes
|
| 104 |
+
|
| 105 |
+
def decompileAttributes3(self, data):
|
| 106 |
+
if self.hasOctaboxes:
|
| 107 |
+
o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
|
| 108 |
+
numsub = bin(o.subboxBitmap).count("1")
|
| 109 |
+
o.subboxes = []
|
| 110 |
+
for b in range(numsub):
|
| 111 |
+
if len(data) >= 8:
|
| 112 |
+
subbox, data = sstruct.unpack2(
|
| 113 |
+
Glat_format_3_subbox_entry, data, _Object()
|
| 114 |
+
)
|
| 115 |
+
o.subboxes.append(subbox)
|
| 116 |
+
attrs = self.decompileAttributes12(data, Glat_format_23_entry)
|
| 117 |
+
if self.hasOctaboxes:
|
| 118 |
+
attrs.octabox = o
|
| 119 |
+
return attrs
|
| 120 |
+
|
| 121 |
+
def compile(self, ttFont):
|
| 122 |
+
data = sstruct.pack(Glat_format_0, self)
|
| 123 |
+
if self.version <= 1.9:
|
| 124 |
+
encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
|
| 125 |
+
elif self.version <= 2.9:
|
| 126 |
+
encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
|
| 127 |
+
elif self.version >= 3.0:
|
| 128 |
+
self.compression = (self.scheme << 27) + (1 if self.hasOctaboxes else 0)
|
| 129 |
+
data = sstruct.pack(Glat_format_3, self)
|
| 130 |
+
encoder = self.compileAttributes3
|
| 131 |
+
|
| 132 |
+
glocs = []
|
| 133 |
+
for n in range(len(self.attributes)):
|
| 134 |
+
glocs.append(len(data))
|
| 135 |
+
data += encoder(self.attributes[ttFont.getGlyphName(n)])
|
| 136 |
+
glocs.append(len(data))
|
| 137 |
+
ttFont["Gloc"].set(glocs)
|
| 138 |
+
|
| 139 |
+
if self.version >= 3.0:
|
| 140 |
+
data = grUtils.compress(self.scheme, data)
|
| 141 |
+
return data
|
| 142 |
+
|
| 143 |
+
def compileAttributes12(self, attrs, fmt):
|
| 144 |
+
data = b""
|
| 145 |
+
for e in grUtils.entries(attrs):
|
| 146 |
+
data += sstruct.pack(fmt, {"attNum": e[0], "num": e[1]}) + struct.pack(
|
| 147 |
+
(">%dh" % len(e[2])), *e[2]
|
| 148 |
+
)
|
| 149 |
+
return data
|
| 150 |
+
|
| 151 |
+
def compileAttributes3(self, attrs):
|
| 152 |
+
if self.hasOctaboxes:
|
| 153 |
+
o = attrs.octabox
|
| 154 |
+
data = sstruct.pack(Glat_format_3_octabox_metrics, o)
|
| 155 |
+
numsub = bin(o.subboxBitmap).count("1")
|
| 156 |
+
for b in range(numsub):
|
| 157 |
+
data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
|
| 158 |
+
else:
|
| 159 |
+
data = ""
|
| 160 |
+
return data + self.compileAttributes12(attrs, Glat_format_23_entry)
|
| 161 |
+
|
| 162 |
+
def toXML(self, writer, ttFont):
|
| 163 |
+
writer.simpletag("version", version=self.version, compressionScheme=self.scheme)
|
| 164 |
+
writer.newline()
|
| 165 |
+
for n, a in sorted(
|
| 166 |
+
self.attributes.items(), key=lambda x: ttFont.getGlyphID(x[0])
|
| 167 |
+
):
|
| 168 |
+
writer.begintag("glyph", name=n)
|
| 169 |
+
writer.newline()
|
| 170 |
+
if hasattr(a, "octabox"):
|
| 171 |
+
o = a.octabox
|
| 172 |
+
formatstring, names, fixes = sstruct.getformat(
|
| 173 |
+
Glat_format_3_octabox_metrics
|
| 174 |
+
)
|
| 175 |
+
vals = {}
|
| 176 |
+
for k in names:
|
| 177 |
+
if k == "subboxBitmap":
|
| 178 |
+
continue
|
| 179 |
+
vals[k] = "{:.3f}%".format(getattr(o, k) * 100.0 / 255)
|
| 180 |
+
vals["bitmap"] = "{:0X}".format(o.subboxBitmap)
|
| 181 |
+
writer.begintag("octaboxes", **vals)
|
| 182 |
+
writer.newline()
|
| 183 |
+
formatstring, names, fixes = sstruct.getformat(
|
| 184 |
+
Glat_format_3_subbox_entry
|
| 185 |
+
)
|
| 186 |
+
for s in o.subboxes:
|
| 187 |
+
vals = {}
|
| 188 |
+
for k in names:
|
| 189 |
+
vals[k] = "{:.3f}%".format(getattr(s, k) * 100.0 / 255)
|
| 190 |
+
writer.simpletag("octabox", **vals)
|
| 191 |
+
writer.newline()
|
| 192 |
+
writer.endtag("octaboxes")
|
| 193 |
+
writer.newline()
|
| 194 |
+
for k, v in sorted(a.items()):
|
| 195 |
+
writer.simpletag("attribute", index=k, value=v)
|
| 196 |
+
writer.newline()
|
| 197 |
+
writer.endtag("glyph")
|
| 198 |
+
writer.newline()
|
| 199 |
+
|
| 200 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 201 |
+
if name == "version":
|
| 202 |
+
self.version = float(safeEval(attrs["version"]))
|
| 203 |
+
self.scheme = int(safeEval(attrs["compressionScheme"]))
|
| 204 |
+
if name != "glyph":
|
| 205 |
+
return
|
| 206 |
+
if not hasattr(self, "attributes"):
|
| 207 |
+
self.attributes = {}
|
| 208 |
+
gname = attrs["name"]
|
| 209 |
+
attributes = _Dict()
|
| 210 |
+
for element in content:
|
| 211 |
+
if not isinstance(element, tuple):
|
| 212 |
+
continue
|
| 213 |
+
tag, attrs, subcontent = element
|
| 214 |
+
if tag == "attribute":
|
| 215 |
+
k = int(safeEval(attrs["index"]))
|
| 216 |
+
v = int(safeEval(attrs["value"]))
|
| 217 |
+
attributes[k] = v
|
| 218 |
+
elif tag == "octaboxes":
|
| 219 |
+
self.hasOctaboxes = True
|
| 220 |
+
o = _Object()
|
| 221 |
+
o.subboxBitmap = int(attrs["bitmap"], 16)
|
| 222 |
+
o.subboxes = []
|
| 223 |
+
del attrs["bitmap"]
|
| 224 |
+
for k, v in attrs.items():
|
| 225 |
+
setattr(o, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
|
| 226 |
+
for element in subcontent:
|
| 227 |
+
if not isinstance(element, tuple):
|
| 228 |
+
continue
|
| 229 |
+
(tag, attrs, subcontent) = element
|
| 230 |
+
so = _Object()
|
| 231 |
+
for k, v in attrs.items():
|
| 232 |
+
setattr(so, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
|
| 233 |
+
o.subboxes.append(so)
|
| 234 |
+
attributes.octabox = o
|
| 235 |
+
self.attributes[gname] = attributes
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/G__l_o_c.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.textTools import safeEval
|
| 3 |
+
from . import DefaultTable
|
| 4 |
+
import array
|
| 5 |
+
import sys
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
Gloc_header = """
|
| 9 |
+
> # big endian
|
| 10 |
+
version: 16.16F # Table version
|
| 11 |
+
flags: H # bit 0: 1=long format, 0=short format
|
| 12 |
+
# bit 1: 1=attribute names, 0=no names
|
| 13 |
+
numAttribs: H # NUmber of attributes
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class table_G__l_o_c(DefaultTable.DefaultTable):
|
| 18 |
+
"""Graphite Index to Glyph Atttributes table
|
| 19 |
+
|
| 20 |
+
See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
dependencies = ["Glat"]
|
| 24 |
+
|
| 25 |
+
def __init__(self, tag=None):
|
| 26 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 27 |
+
self.attribIds = None
|
| 28 |
+
self.numAttribs = 0
|
| 29 |
+
|
| 30 |
+
def decompile(self, data, ttFont):
|
| 31 |
+
_, data = sstruct.unpack2(Gloc_header, data, self)
|
| 32 |
+
flags = self.flags
|
| 33 |
+
del self.flags
|
| 34 |
+
self.locations = array.array("I" if flags & 1 else "H")
|
| 35 |
+
self.locations.frombytes(data[: len(data) - self.numAttribs * (flags & 2)])
|
| 36 |
+
if sys.byteorder != "big":
|
| 37 |
+
self.locations.byteswap()
|
| 38 |
+
self.attribIds = array.array("H")
|
| 39 |
+
if flags & 2:
|
| 40 |
+
self.attribIds.frombytes(data[-self.numAttribs * 2 :])
|
| 41 |
+
if sys.byteorder != "big":
|
| 42 |
+
self.attribIds.byteswap()
|
| 43 |
+
|
| 44 |
+
def compile(self, ttFont):
|
| 45 |
+
data = sstruct.pack(
|
| 46 |
+
Gloc_header,
|
| 47 |
+
dict(
|
| 48 |
+
version=1.0,
|
| 49 |
+
flags=(bool(self.attribIds) << 1) + (self.locations.typecode == "I"),
|
| 50 |
+
numAttribs=self.numAttribs,
|
| 51 |
+
),
|
| 52 |
+
)
|
| 53 |
+
if sys.byteorder != "big":
|
| 54 |
+
self.locations.byteswap()
|
| 55 |
+
data += self.locations.tobytes()
|
| 56 |
+
if sys.byteorder != "big":
|
| 57 |
+
self.locations.byteswap()
|
| 58 |
+
if self.attribIds:
|
| 59 |
+
if sys.byteorder != "big":
|
| 60 |
+
self.attribIds.byteswap()
|
| 61 |
+
data += self.attribIds.tobytes()
|
| 62 |
+
if sys.byteorder != "big":
|
| 63 |
+
self.attribIds.byteswap()
|
| 64 |
+
return data
|
| 65 |
+
|
| 66 |
+
def set(self, locations):
|
| 67 |
+
long_format = max(locations) >= 65536
|
| 68 |
+
self.locations = array.array("I" if long_format else "H", locations)
|
| 69 |
+
|
| 70 |
+
def toXML(self, writer, ttFont):
|
| 71 |
+
writer.simpletag("attributes", number=self.numAttribs)
|
| 72 |
+
writer.newline()
|
| 73 |
+
|
| 74 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 75 |
+
if name == "attributes":
|
| 76 |
+
self.numAttribs = int(safeEval(attrs["number"]))
|
| 77 |
+
|
| 78 |
+
def __getitem__(self, index):
|
| 79 |
+
return self.locations[index]
|
| 80 |
+
|
| 81 |
+
def __len__(self):
|
| 82 |
+
return len(self.locations)
|
| 83 |
+
|
| 84 |
+
def __iter__(self):
|
| 85 |
+
return iter(self.locations)
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/H_V_A_R_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_H_V_A_R_(BaseTTXConverter):
|
| 5 |
+
"""Horizontal Metrics Variations table
|
| 6 |
+
|
| 7 |
+
The ``HVAR`` table contains variations in horizontal glyph metrics
|
| 8 |
+
in variable fonts.
|
| 9 |
+
|
| 10 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/hvar
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""IFT - Incremental Font Transfer table."""
|
| 2 |
+
|
| 3 |
+
# Reference: https://github.com/googlefonts/fontations/blob/main/read-fonts/src/tables/ift.rs
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
from .otBase import BaseTTXConverter
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class table_I_F_T_(BaseTTXConverter):
|
| 10 |
+
"""Incremental Font Transfer table."""
|
| 11 |
+
|
| 12 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/I_F_T_X_.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""IFT - Incremental Font Transfer table."""
|
| 2 |
+
|
| 3 |
+
# Reference: https://github.com/googlefonts/fontations/blob/main/read-fonts/src/tables/ift.rs
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
from .otBase import BaseTTXConverter
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class table_I_F_T_X_(BaseTTXConverter):
|
| 10 |
+
"""Incremental Font Transfer table."""
|
| 11 |
+
|
| 12 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/J_S_T_F_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_J_S_T_F_(BaseTTXConverter):
|
| 5 |
+
"""Justification table
|
| 6 |
+
|
| 7 |
+
The ``JSTF`` table contains glyph substitution and positioning
|
| 8 |
+
data used to perform text justification.
|
| 9 |
+
|
| 10 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/jstf
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/L_T_S_H_.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc.textTools import safeEval
|
| 2 |
+
from . import DefaultTable
|
| 3 |
+
import struct
|
| 4 |
+
import array
|
| 5 |
+
|
| 6 |
+
# XXX I've lowered the strictness, to make sure Apple's own Chicago
|
| 7 |
+
# XXX gets through. They're looking into it, I hope to raise the standards
|
| 8 |
+
# XXX back to normal eventually.
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class table_L_T_S_H_(DefaultTable.DefaultTable):
|
| 12 |
+
"""Linear Threshold table
|
| 13 |
+
|
| 14 |
+
The ``LTSH`` table contains per-glyph settings indicating the ppem sizes
|
| 15 |
+
at which the advance width metric should be scaled linearly, despite the
|
| 16 |
+
effects of any TrueType instructions that might otherwise alter the
|
| 17 |
+
advance width.
|
| 18 |
+
|
| 19 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/ltsh
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def decompile(self, data, ttFont):
|
| 23 |
+
version, numGlyphs = struct.unpack(">HH", data[:4])
|
| 24 |
+
data = data[4:]
|
| 25 |
+
assert version == 0, "unknown version: %s" % version
|
| 26 |
+
assert (len(data) % numGlyphs) < 4, "numGlyphs doesn't match data length"
|
| 27 |
+
# ouch: the assertion is not true in Chicago!
|
| 28 |
+
# assert numGlyphs == ttFont['maxp'].numGlyphs
|
| 29 |
+
yPels = array.array("B")
|
| 30 |
+
yPels.frombytes(data)
|
| 31 |
+
self.yPels = {}
|
| 32 |
+
for i in range(numGlyphs):
|
| 33 |
+
self.yPels[ttFont.getGlyphName(i)] = yPels[i]
|
| 34 |
+
|
| 35 |
+
def compile(self, ttFont):
|
| 36 |
+
version = 0
|
| 37 |
+
names = list(self.yPels.keys())
|
| 38 |
+
numGlyphs = len(names)
|
| 39 |
+
yPels = [0] * numGlyphs
|
| 40 |
+
# ouch: the assertion is not true in Chicago!
|
| 41 |
+
# assert len(self.yPels) == ttFont['maxp'].numGlyphs == numGlyphs
|
| 42 |
+
for name in names:
|
| 43 |
+
yPels[ttFont.getGlyphID(name)] = self.yPels[name]
|
| 44 |
+
yPels = array.array("B", yPels)
|
| 45 |
+
return struct.pack(">HH", version, numGlyphs) + yPels.tobytes()
|
| 46 |
+
|
| 47 |
+
def toXML(self, writer, ttFont):
|
| 48 |
+
names = sorted(self.yPels.keys())
|
| 49 |
+
for name in names:
|
| 50 |
+
writer.simpletag("yPel", name=name, value=self.yPels[name])
|
| 51 |
+
writer.newline()
|
| 52 |
+
|
| 53 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 54 |
+
if not hasattr(self, "yPels"):
|
| 55 |
+
self.yPels = {}
|
| 56 |
+
if name != "yPel":
|
| 57 |
+
return # ignore unknown tags
|
| 58 |
+
self.yPels[attrs["name"]] = safeEval(attrs["value"])
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_A_T_H_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_M_A_T_H_(BaseTTXConverter):
|
| 5 |
+
"""Mathematical Typesetting table
|
| 6 |
+
|
| 7 |
+
The ``MATH`` table contains a variety of information needed to
|
| 8 |
+
typeset glyphs in mathematical formulas and expressions.
|
| 9 |
+
|
| 10 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/math
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/M_V_A_R_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_M_V_A_R_(BaseTTXConverter):
|
| 5 |
+
"""Metrics Variations table
|
| 6 |
+
|
| 7 |
+
The ``MVAR`` table contains variation information for font-wide
|
| 8 |
+
metrics in a variable font.
|
| 9 |
+
|
| 10 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/mvar
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/O_S_2f_2.py
ADDED
|
@@ -0,0 +1,752 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.roundTools import otRound
|
| 3 |
+
from fontTools.misc.textTools import safeEval, num2binary, binary2num
|
| 4 |
+
from fontTools.ttLib.tables import DefaultTable
|
| 5 |
+
import bisect
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
log = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
# panose classification
|
| 12 |
+
|
| 13 |
+
panoseFormat = """
|
| 14 |
+
bFamilyType: B
|
| 15 |
+
bSerifStyle: B
|
| 16 |
+
bWeight: B
|
| 17 |
+
bProportion: B
|
| 18 |
+
bContrast: B
|
| 19 |
+
bStrokeVariation: B
|
| 20 |
+
bArmStyle: B
|
| 21 |
+
bLetterForm: B
|
| 22 |
+
bMidline: B
|
| 23 |
+
bXHeight: B
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class Panose(object):
|
| 28 |
+
def __init__(self, **kwargs):
|
| 29 |
+
_, names, _ = sstruct.getformat(panoseFormat)
|
| 30 |
+
for name in names:
|
| 31 |
+
setattr(self, name, kwargs.pop(name, 0))
|
| 32 |
+
for k in kwargs:
|
| 33 |
+
raise TypeError(f"Panose() got an unexpected keyword argument {k!r}")
|
| 34 |
+
|
| 35 |
+
def toXML(self, writer, ttFont):
|
| 36 |
+
formatstring, names, fixes = sstruct.getformat(panoseFormat)
|
| 37 |
+
for name in names:
|
| 38 |
+
writer.simpletag(name, value=getattr(self, name))
|
| 39 |
+
writer.newline()
|
| 40 |
+
|
| 41 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 42 |
+
setattr(self, name, safeEval(attrs["value"]))
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# 'sfnt' OS/2 and Windows Metrics table - 'OS/2'
|
| 46 |
+
|
| 47 |
+
OS2_format_0 = """
|
| 48 |
+
> # big endian
|
| 49 |
+
version: H # version
|
| 50 |
+
xAvgCharWidth: h # average character width
|
| 51 |
+
usWeightClass: H # degree of thickness of strokes
|
| 52 |
+
usWidthClass: H # aspect ratio
|
| 53 |
+
fsType: H # type flags
|
| 54 |
+
ySubscriptXSize: h # subscript horizontal font size
|
| 55 |
+
ySubscriptYSize: h # subscript vertical font size
|
| 56 |
+
ySubscriptXOffset: h # subscript x offset
|
| 57 |
+
ySubscriptYOffset: h # subscript y offset
|
| 58 |
+
ySuperscriptXSize: h # superscript horizontal font size
|
| 59 |
+
ySuperscriptYSize: h # superscript vertical font size
|
| 60 |
+
ySuperscriptXOffset: h # superscript x offset
|
| 61 |
+
ySuperscriptYOffset: h # superscript y offset
|
| 62 |
+
yStrikeoutSize: h # strikeout size
|
| 63 |
+
yStrikeoutPosition: h # strikeout position
|
| 64 |
+
sFamilyClass: h # font family class and subclass
|
| 65 |
+
panose: 10s # panose classification number
|
| 66 |
+
ulUnicodeRange1: L # character range
|
| 67 |
+
ulUnicodeRange2: L # character range
|
| 68 |
+
ulUnicodeRange3: L # character range
|
| 69 |
+
ulUnicodeRange4: L # character range
|
| 70 |
+
achVendID: 4s # font vendor identification
|
| 71 |
+
fsSelection: H # font selection flags
|
| 72 |
+
usFirstCharIndex: H # first unicode character index
|
| 73 |
+
usLastCharIndex: H # last unicode character index
|
| 74 |
+
sTypoAscender: h # typographic ascender
|
| 75 |
+
sTypoDescender: h # typographic descender
|
| 76 |
+
sTypoLineGap: h # typographic line gap
|
| 77 |
+
usWinAscent: H # Windows ascender
|
| 78 |
+
usWinDescent: H # Windows descender
|
| 79 |
+
"""
|
| 80 |
+
|
| 81 |
+
OS2_format_1_addition = """
|
| 82 |
+
ulCodePageRange1: L
|
| 83 |
+
ulCodePageRange2: L
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
OS2_format_2_addition = (
|
| 87 |
+
OS2_format_1_addition
|
| 88 |
+
+ """
|
| 89 |
+
sxHeight: h
|
| 90 |
+
sCapHeight: h
|
| 91 |
+
usDefaultChar: H
|
| 92 |
+
usBreakChar: H
|
| 93 |
+
usMaxContext: H
|
| 94 |
+
"""
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
OS2_format_5_addition = (
|
| 98 |
+
OS2_format_2_addition
|
| 99 |
+
+ """
|
| 100 |
+
usLowerOpticalPointSize: H
|
| 101 |
+
usUpperOpticalPointSize: H
|
| 102 |
+
"""
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
bigendian = " > # big endian\n"
|
| 106 |
+
|
| 107 |
+
OS2_format_1 = OS2_format_0 + OS2_format_1_addition
|
| 108 |
+
OS2_format_2 = OS2_format_0 + OS2_format_2_addition
|
| 109 |
+
OS2_format_5 = OS2_format_0 + OS2_format_5_addition
|
| 110 |
+
OS2_format_1_addition = bigendian + OS2_format_1_addition
|
| 111 |
+
OS2_format_2_addition = bigendian + OS2_format_2_addition
|
| 112 |
+
OS2_format_5_addition = bigendian + OS2_format_5_addition
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class table_O_S_2f_2(DefaultTable.DefaultTable):
|
| 116 |
+
"""OS/2 and Windows Metrics table
|
| 117 |
+
|
| 118 |
+
The ``OS/2`` table contains a variety of font-wide metrics and
|
| 119 |
+
parameters that may be useful to an operating system or other
|
| 120 |
+
software for system-integration purposes.
|
| 121 |
+
|
| 122 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/os2
|
| 123 |
+
"""
|
| 124 |
+
|
| 125 |
+
dependencies = ["head"]
|
| 126 |
+
|
| 127 |
+
def decompile(self, data, ttFont):
|
| 128 |
+
dummy, data = sstruct.unpack2(OS2_format_0, data, self)
|
| 129 |
+
|
| 130 |
+
if self.version == 1:
|
| 131 |
+
dummy, data = sstruct.unpack2(OS2_format_1_addition, data, self)
|
| 132 |
+
elif self.version in (2, 3, 4):
|
| 133 |
+
dummy, data = sstruct.unpack2(OS2_format_2_addition, data, self)
|
| 134 |
+
elif self.version == 5:
|
| 135 |
+
dummy, data = sstruct.unpack2(OS2_format_5_addition, data, self)
|
| 136 |
+
self.usLowerOpticalPointSize /= 20
|
| 137 |
+
self.usUpperOpticalPointSize /= 20
|
| 138 |
+
elif self.version != 0:
|
| 139 |
+
from fontTools import ttLib
|
| 140 |
+
|
| 141 |
+
raise ttLib.TTLibError(
|
| 142 |
+
"unknown format for OS/2 table: version %s" % self.version
|
| 143 |
+
)
|
| 144 |
+
if len(data):
|
| 145 |
+
log.warning("too much 'OS/2' table data")
|
| 146 |
+
|
| 147 |
+
self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
|
| 148 |
+
|
| 149 |
+
def compile(self, ttFont):
|
| 150 |
+
self.updateFirstAndLastCharIndex(ttFont)
|
| 151 |
+
panose = self.panose
|
| 152 |
+
head = ttFont["head"]
|
| 153 |
+
if (self.fsSelection & 1) and not (head.macStyle & 1 << 1):
|
| 154 |
+
log.warning(
|
| 155 |
+
"fsSelection bit 0 (italic) and "
|
| 156 |
+
"head table macStyle bit 1 (italic) should match"
|
| 157 |
+
)
|
| 158 |
+
if (self.fsSelection & 1 << 5) and not (head.macStyle & 1):
|
| 159 |
+
log.warning(
|
| 160 |
+
"fsSelection bit 5 (bold) and "
|
| 161 |
+
"head table macStyle bit 0 (bold) should match"
|
| 162 |
+
)
|
| 163 |
+
if (self.fsSelection & 1 << 6) and (self.fsSelection & 1 + (1 << 5)):
|
| 164 |
+
log.warning(
|
| 165 |
+
"fsSelection bit 6 (regular) is set, "
|
| 166 |
+
"bits 0 (italic) and 5 (bold) must be clear"
|
| 167 |
+
)
|
| 168 |
+
if self.version < 4 and self.fsSelection & 0b1110000000:
|
| 169 |
+
log.warning(
|
| 170 |
+
"fsSelection bits 7, 8 and 9 are only defined in "
|
| 171 |
+
"OS/2 table version 4 and up: version %s",
|
| 172 |
+
self.version,
|
| 173 |
+
)
|
| 174 |
+
self.panose = sstruct.pack(panoseFormat, self.panose)
|
| 175 |
+
if self.version == 0:
|
| 176 |
+
data = sstruct.pack(OS2_format_0, self)
|
| 177 |
+
elif self.version == 1:
|
| 178 |
+
data = sstruct.pack(OS2_format_1, self)
|
| 179 |
+
elif self.version in (2, 3, 4):
|
| 180 |
+
data = sstruct.pack(OS2_format_2, self)
|
| 181 |
+
elif self.version == 5:
|
| 182 |
+
d = self.__dict__.copy()
|
| 183 |
+
d["usLowerOpticalPointSize"] = round(self.usLowerOpticalPointSize * 20)
|
| 184 |
+
d["usUpperOpticalPointSize"] = round(self.usUpperOpticalPointSize * 20)
|
| 185 |
+
data = sstruct.pack(OS2_format_5, d)
|
| 186 |
+
else:
|
| 187 |
+
from fontTools import ttLib
|
| 188 |
+
|
| 189 |
+
raise ttLib.TTLibError(
|
| 190 |
+
"unknown format for OS/2 table: version %s" % self.version
|
| 191 |
+
)
|
| 192 |
+
self.panose = panose
|
| 193 |
+
return data
|
| 194 |
+
|
| 195 |
+
def toXML(self, writer, ttFont):
|
| 196 |
+
writer.comment(
|
| 197 |
+
"The fields 'usFirstCharIndex' and 'usLastCharIndex'\n"
|
| 198 |
+
"will be recalculated by the compiler"
|
| 199 |
+
)
|
| 200 |
+
writer.newline()
|
| 201 |
+
if self.version == 1:
|
| 202 |
+
format = OS2_format_1
|
| 203 |
+
elif self.version in (2, 3, 4):
|
| 204 |
+
format = OS2_format_2
|
| 205 |
+
elif self.version == 5:
|
| 206 |
+
format = OS2_format_5
|
| 207 |
+
else:
|
| 208 |
+
format = OS2_format_0
|
| 209 |
+
formatstring, names, fixes = sstruct.getformat(format)
|
| 210 |
+
for name in names:
|
| 211 |
+
value = getattr(self, name)
|
| 212 |
+
if name == "panose":
|
| 213 |
+
writer.begintag("panose")
|
| 214 |
+
writer.newline()
|
| 215 |
+
value.toXML(writer, ttFont)
|
| 216 |
+
writer.endtag("panose")
|
| 217 |
+
elif name in (
|
| 218 |
+
"ulUnicodeRange1",
|
| 219 |
+
"ulUnicodeRange2",
|
| 220 |
+
"ulUnicodeRange3",
|
| 221 |
+
"ulUnicodeRange4",
|
| 222 |
+
"ulCodePageRange1",
|
| 223 |
+
"ulCodePageRange2",
|
| 224 |
+
):
|
| 225 |
+
writer.simpletag(name, value=num2binary(value))
|
| 226 |
+
elif name in ("fsType", "fsSelection"):
|
| 227 |
+
writer.simpletag(name, value=num2binary(value, 16))
|
| 228 |
+
elif name == "achVendID":
|
| 229 |
+
writer.simpletag(name, value=repr(value)[1:-1])
|
| 230 |
+
else:
|
| 231 |
+
writer.simpletag(name, value=value)
|
| 232 |
+
writer.newline()
|
| 233 |
+
|
| 234 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 235 |
+
if name == "panose":
|
| 236 |
+
self.panose = panose = Panose()
|
| 237 |
+
for element in content:
|
| 238 |
+
if isinstance(element, tuple):
|
| 239 |
+
name, attrs, content = element
|
| 240 |
+
panose.fromXML(name, attrs, content, ttFont)
|
| 241 |
+
elif name in (
|
| 242 |
+
"ulUnicodeRange1",
|
| 243 |
+
"ulUnicodeRange2",
|
| 244 |
+
"ulUnicodeRange3",
|
| 245 |
+
"ulUnicodeRange4",
|
| 246 |
+
"ulCodePageRange1",
|
| 247 |
+
"ulCodePageRange2",
|
| 248 |
+
"fsType",
|
| 249 |
+
"fsSelection",
|
| 250 |
+
):
|
| 251 |
+
setattr(self, name, binary2num(attrs["value"]))
|
| 252 |
+
elif name == "achVendID":
|
| 253 |
+
setattr(self, name, safeEval("'''" + attrs["value"] + "'''"))
|
| 254 |
+
else:
|
| 255 |
+
setattr(self, name, safeEval(attrs["value"]))
|
| 256 |
+
|
| 257 |
+
def updateFirstAndLastCharIndex(self, ttFont):
|
| 258 |
+
if "cmap" not in ttFont:
|
| 259 |
+
return
|
| 260 |
+
codes = set()
|
| 261 |
+
for table in getattr(ttFont["cmap"], "tables", []):
|
| 262 |
+
if table.isUnicode():
|
| 263 |
+
codes.update(table.cmap.keys())
|
| 264 |
+
if codes:
|
| 265 |
+
minCode = min(codes)
|
| 266 |
+
maxCode = max(codes)
|
| 267 |
+
# USHORT cannot hold codepoints greater than 0xFFFF
|
| 268 |
+
self.usFirstCharIndex = min(0xFFFF, minCode)
|
| 269 |
+
self.usLastCharIndex = min(0xFFFF, maxCode)
|
| 270 |
+
|
| 271 |
+
# misspelled attributes kept for legacy reasons
|
| 272 |
+
|
| 273 |
+
@property
|
| 274 |
+
def usMaxContex(self):
|
| 275 |
+
return self.usMaxContext
|
| 276 |
+
|
| 277 |
+
@usMaxContex.setter
|
| 278 |
+
def usMaxContex(self, value):
|
| 279 |
+
self.usMaxContext = value
|
| 280 |
+
|
| 281 |
+
@property
|
| 282 |
+
def fsFirstCharIndex(self):
|
| 283 |
+
return self.usFirstCharIndex
|
| 284 |
+
|
| 285 |
+
@fsFirstCharIndex.setter
|
| 286 |
+
def fsFirstCharIndex(self, value):
|
| 287 |
+
self.usFirstCharIndex = value
|
| 288 |
+
|
| 289 |
+
@property
|
| 290 |
+
def fsLastCharIndex(self):
|
| 291 |
+
return self.usLastCharIndex
|
| 292 |
+
|
| 293 |
+
@fsLastCharIndex.setter
|
| 294 |
+
def fsLastCharIndex(self, value):
|
| 295 |
+
self.usLastCharIndex = value
|
| 296 |
+
|
| 297 |
+
def getUnicodeRanges(self):
|
| 298 |
+
"""Return the set of 'ulUnicodeRange*' bits currently enabled."""
|
| 299 |
+
bits = set()
|
| 300 |
+
ul1, ul2 = self.ulUnicodeRange1, self.ulUnicodeRange2
|
| 301 |
+
ul3, ul4 = self.ulUnicodeRange3, self.ulUnicodeRange4
|
| 302 |
+
for i in range(32):
|
| 303 |
+
if ul1 & (1 << i):
|
| 304 |
+
bits.add(i)
|
| 305 |
+
if ul2 & (1 << i):
|
| 306 |
+
bits.add(i + 32)
|
| 307 |
+
if ul3 & (1 << i):
|
| 308 |
+
bits.add(i + 64)
|
| 309 |
+
if ul4 & (1 << i):
|
| 310 |
+
bits.add(i + 96)
|
| 311 |
+
return bits
|
| 312 |
+
|
| 313 |
+
def setUnicodeRanges(self, bits):
|
| 314 |
+
"""Set the 'ulUnicodeRange*' fields to the specified 'bits'."""
|
| 315 |
+
ul1, ul2, ul3, ul4 = 0, 0, 0, 0
|
| 316 |
+
for bit in bits:
|
| 317 |
+
if 0 <= bit < 32:
|
| 318 |
+
ul1 |= 1 << bit
|
| 319 |
+
elif 32 <= bit < 64:
|
| 320 |
+
ul2 |= 1 << (bit - 32)
|
| 321 |
+
elif 64 <= bit < 96:
|
| 322 |
+
ul3 |= 1 << (bit - 64)
|
| 323 |
+
elif 96 <= bit < 128:
|
| 324 |
+
ul4 |= 1 << (bit - 96)
|
| 325 |
+
else:
|
| 326 |
+
raise ValueError("expected 0 <= int <= 127, found: %r" % bit)
|
| 327 |
+
self.ulUnicodeRange1, self.ulUnicodeRange2 = ul1, ul2
|
| 328 |
+
self.ulUnicodeRange3, self.ulUnicodeRange4 = ul3, ul4
|
| 329 |
+
|
| 330 |
+
def recalcUnicodeRanges(self, ttFont, pruneOnly=False):
|
| 331 |
+
"""Intersect the codepoints in the font's Unicode cmap subtables with
|
| 332 |
+
the Unicode block ranges defined in the OpenType specification (v1.7),
|
| 333 |
+
and set the respective 'ulUnicodeRange*' bits if there is at least ONE
|
| 334 |
+
intersection.
|
| 335 |
+
If 'pruneOnly' is True, only clear unused bits with NO intersection.
|
| 336 |
+
"""
|
| 337 |
+
unicodes = set()
|
| 338 |
+
for table in ttFont["cmap"].tables:
|
| 339 |
+
if table.isUnicode():
|
| 340 |
+
unicodes.update(table.cmap.keys())
|
| 341 |
+
if pruneOnly:
|
| 342 |
+
empty = intersectUnicodeRanges(unicodes, inverse=True)
|
| 343 |
+
bits = self.getUnicodeRanges() - empty
|
| 344 |
+
else:
|
| 345 |
+
bits = intersectUnicodeRanges(unicodes)
|
| 346 |
+
self.setUnicodeRanges(bits)
|
| 347 |
+
return bits
|
| 348 |
+
|
| 349 |
+
def getCodePageRanges(self):
|
| 350 |
+
"""Return the set of 'ulCodePageRange*' bits currently enabled."""
|
| 351 |
+
bits = set()
|
| 352 |
+
if self.version < 1:
|
| 353 |
+
return bits
|
| 354 |
+
ul1, ul2 = self.ulCodePageRange1, self.ulCodePageRange2
|
| 355 |
+
for i in range(32):
|
| 356 |
+
if ul1 & (1 << i):
|
| 357 |
+
bits.add(i)
|
| 358 |
+
if ul2 & (1 << i):
|
| 359 |
+
bits.add(i + 32)
|
| 360 |
+
return bits
|
| 361 |
+
|
| 362 |
+
def setCodePageRanges(self, bits):
|
| 363 |
+
"""Set the 'ulCodePageRange*' fields to the specified 'bits'."""
|
| 364 |
+
ul1, ul2 = 0, 0
|
| 365 |
+
for bit in bits:
|
| 366 |
+
if 0 <= bit < 32:
|
| 367 |
+
ul1 |= 1 << bit
|
| 368 |
+
elif 32 <= bit < 64:
|
| 369 |
+
ul2 |= 1 << (bit - 32)
|
| 370 |
+
else:
|
| 371 |
+
raise ValueError(f"expected 0 <= int <= 63, found: {bit:r}")
|
| 372 |
+
if self.version < 1:
|
| 373 |
+
self.version = 1
|
| 374 |
+
self.ulCodePageRange1, self.ulCodePageRange2 = ul1, ul2
|
| 375 |
+
|
| 376 |
+
def recalcCodePageRanges(self, ttFont, pruneOnly=False):
|
| 377 |
+
unicodes = set()
|
| 378 |
+
for table in ttFont["cmap"].tables:
|
| 379 |
+
if table.isUnicode():
|
| 380 |
+
unicodes.update(table.cmap.keys())
|
| 381 |
+
bits = calcCodePageRanges(unicodes)
|
| 382 |
+
if pruneOnly:
|
| 383 |
+
bits &= self.getCodePageRanges()
|
| 384 |
+
# when no codepage ranges can be enabled, fall back to enabling bit 0
|
| 385 |
+
# (Latin 1) so that the font works in MS Word:
|
| 386 |
+
# https://github.com/googlei18n/fontmake/issues/468
|
| 387 |
+
if not bits:
|
| 388 |
+
bits = {0}
|
| 389 |
+
self.setCodePageRanges(bits)
|
| 390 |
+
return bits
|
| 391 |
+
|
| 392 |
+
def recalcAvgCharWidth(self, ttFont):
|
| 393 |
+
"""Recalculate xAvgCharWidth using metrics from ttFont's 'hmtx' table.
|
| 394 |
+
|
| 395 |
+
Set it to 0 if the unlikely event 'hmtx' table is not found.
|
| 396 |
+
"""
|
| 397 |
+
avg_width = 0
|
| 398 |
+
hmtx = ttFont.get("hmtx")
|
| 399 |
+
if hmtx is not None:
|
| 400 |
+
widths = [width for width, _ in hmtx.metrics.values() if width > 0]
|
| 401 |
+
if widths:
|
| 402 |
+
avg_width = otRound(sum(widths) / len(widths))
|
| 403 |
+
self.xAvgCharWidth = avg_width
|
| 404 |
+
return avg_width
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
# Unicode ranges data from the OpenType OS/2 table specification v1.7
|
| 408 |
+
|
| 409 |
+
OS2_UNICODE_RANGES = (
|
| 410 |
+
(("Basic Latin", (0x0000, 0x007F)),),
|
| 411 |
+
(("Latin-1 Supplement", (0x0080, 0x00FF)),),
|
| 412 |
+
(("Latin Extended-A", (0x0100, 0x017F)),),
|
| 413 |
+
(("Latin Extended-B", (0x0180, 0x024F)),),
|
| 414 |
+
(
|
| 415 |
+
("IPA Extensions", (0x0250, 0x02AF)),
|
| 416 |
+
("Phonetic Extensions", (0x1D00, 0x1D7F)),
|
| 417 |
+
("Phonetic Extensions Supplement", (0x1D80, 0x1DBF)),
|
| 418 |
+
),
|
| 419 |
+
(
|
| 420 |
+
("Spacing Modifier Letters", (0x02B0, 0x02FF)),
|
| 421 |
+
("Modifier Tone Letters", (0xA700, 0xA71F)),
|
| 422 |
+
),
|
| 423 |
+
(
|
| 424 |
+
("Combining Diacritical Marks", (0x0300, 0x036F)),
|
| 425 |
+
("Combining Diacritical Marks Supplement", (0x1DC0, 0x1DFF)),
|
| 426 |
+
),
|
| 427 |
+
(("Greek and Coptic", (0x0370, 0x03FF)),),
|
| 428 |
+
(("Coptic", (0x2C80, 0x2CFF)),),
|
| 429 |
+
(
|
| 430 |
+
("Cyrillic", (0x0400, 0x04FF)),
|
| 431 |
+
("Cyrillic Supplement", (0x0500, 0x052F)),
|
| 432 |
+
("Cyrillic Extended-A", (0x2DE0, 0x2DFF)),
|
| 433 |
+
("Cyrillic Extended-B", (0xA640, 0xA69F)),
|
| 434 |
+
),
|
| 435 |
+
(("Armenian", (0x0530, 0x058F)),),
|
| 436 |
+
(("Hebrew", (0x0590, 0x05FF)),),
|
| 437 |
+
(("Vai", (0xA500, 0xA63F)),),
|
| 438 |
+
(("Arabic", (0x0600, 0x06FF)), ("Arabic Supplement", (0x0750, 0x077F))),
|
| 439 |
+
(("NKo", (0x07C0, 0x07FF)),),
|
| 440 |
+
(("Devanagari", (0x0900, 0x097F)),),
|
| 441 |
+
(("Bengali", (0x0980, 0x09FF)),),
|
| 442 |
+
(("Gurmukhi", (0x0A00, 0x0A7F)),),
|
| 443 |
+
(("Gujarati", (0x0A80, 0x0AFF)),),
|
| 444 |
+
(("Oriya", (0x0B00, 0x0B7F)),),
|
| 445 |
+
(("Tamil", (0x0B80, 0x0BFF)),),
|
| 446 |
+
(("Telugu", (0x0C00, 0x0C7F)),),
|
| 447 |
+
(("Kannada", (0x0C80, 0x0CFF)),),
|
| 448 |
+
(("Malayalam", (0x0D00, 0x0D7F)),),
|
| 449 |
+
(("Thai", (0x0E00, 0x0E7F)),),
|
| 450 |
+
(("Lao", (0x0E80, 0x0EFF)),),
|
| 451 |
+
(("Georgian", (0x10A0, 0x10FF)), ("Georgian Supplement", (0x2D00, 0x2D2F))),
|
| 452 |
+
(("Balinese", (0x1B00, 0x1B7F)),),
|
| 453 |
+
(("Hangul Jamo", (0x1100, 0x11FF)),),
|
| 454 |
+
(
|
| 455 |
+
("Latin Extended Additional", (0x1E00, 0x1EFF)),
|
| 456 |
+
("Latin Extended-C", (0x2C60, 0x2C7F)),
|
| 457 |
+
("Latin Extended-D", (0xA720, 0xA7FF)),
|
| 458 |
+
),
|
| 459 |
+
(("Greek Extended", (0x1F00, 0x1FFF)),),
|
| 460 |
+
(
|
| 461 |
+
("General Punctuation", (0x2000, 0x206F)),
|
| 462 |
+
("Supplemental Punctuation", (0x2E00, 0x2E7F)),
|
| 463 |
+
),
|
| 464 |
+
(("Superscripts And Subscripts", (0x2070, 0x209F)),),
|
| 465 |
+
(("Currency Symbols", (0x20A0, 0x20CF)),),
|
| 466 |
+
(("Combining Diacritical Marks For Symbols", (0x20D0, 0x20FF)),),
|
| 467 |
+
(("Letterlike Symbols", (0x2100, 0x214F)),),
|
| 468 |
+
(("Number Forms", (0x2150, 0x218F)),),
|
| 469 |
+
(
|
| 470 |
+
("Arrows", (0x2190, 0x21FF)),
|
| 471 |
+
("Supplemental Arrows-A", (0x27F0, 0x27FF)),
|
| 472 |
+
("Supplemental Arrows-B", (0x2900, 0x297F)),
|
| 473 |
+
("Miscellaneous Symbols and Arrows", (0x2B00, 0x2BFF)),
|
| 474 |
+
),
|
| 475 |
+
(
|
| 476 |
+
("Mathematical Operators", (0x2200, 0x22FF)),
|
| 477 |
+
("Supplemental Mathematical Operators", (0x2A00, 0x2AFF)),
|
| 478 |
+
("Miscellaneous Mathematical Symbols-A", (0x27C0, 0x27EF)),
|
| 479 |
+
("Miscellaneous Mathematical Symbols-B", (0x2980, 0x29FF)),
|
| 480 |
+
),
|
| 481 |
+
(("Miscellaneous Technical", (0x2300, 0x23FF)),),
|
| 482 |
+
(("Control Pictures", (0x2400, 0x243F)),),
|
| 483 |
+
(("Optical Character Recognition", (0x2440, 0x245F)),),
|
| 484 |
+
(("Enclosed Alphanumerics", (0x2460, 0x24FF)),),
|
| 485 |
+
(("Box Drawing", (0x2500, 0x257F)),),
|
| 486 |
+
(("Block Elements", (0x2580, 0x259F)),),
|
| 487 |
+
(("Geometric Shapes", (0x25A0, 0x25FF)),),
|
| 488 |
+
(("Miscellaneous Symbols", (0x2600, 0x26FF)),),
|
| 489 |
+
(("Dingbats", (0x2700, 0x27BF)),),
|
| 490 |
+
(("CJK Symbols And Punctuation", (0x3000, 0x303F)),),
|
| 491 |
+
(("Hiragana", (0x3040, 0x309F)),),
|
| 492 |
+
(
|
| 493 |
+
("Katakana", (0x30A0, 0x30FF)),
|
| 494 |
+
("Katakana Phonetic Extensions", (0x31F0, 0x31FF)),
|
| 495 |
+
),
|
| 496 |
+
(("Bopomofo", (0x3100, 0x312F)), ("Bopomofo Extended", (0x31A0, 0x31BF))),
|
| 497 |
+
(("Hangul Compatibility Jamo", (0x3130, 0x318F)),),
|
| 498 |
+
(("Phags-pa", (0xA840, 0xA87F)),),
|
| 499 |
+
(("Enclosed CJK Letters And Months", (0x3200, 0x32FF)),),
|
| 500 |
+
(("CJK Compatibility", (0x3300, 0x33FF)),),
|
| 501 |
+
(("Hangul Syllables", (0xAC00, 0xD7AF)),),
|
| 502 |
+
(("Non-Plane 0 *", (0xD800, 0xDFFF)),),
|
| 503 |
+
(("Phoenician", (0x10900, 0x1091F)),),
|
| 504 |
+
(
|
| 505 |
+
("CJK Unified Ideographs", (0x4E00, 0x9FFF)),
|
| 506 |
+
("CJK Radicals Supplement", (0x2E80, 0x2EFF)),
|
| 507 |
+
("Kangxi Radicals", (0x2F00, 0x2FDF)),
|
| 508 |
+
("Ideographic Description Characters", (0x2FF0, 0x2FFF)),
|
| 509 |
+
("CJK Unified Ideographs Extension A", (0x3400, 0x4DBF)),
|
| 510 |
+
("CJK Unified Ideographs Extension B", (0x20000, 0x2A6DF)),
|
| 511 |
+
("Kanbun", (0x3190, 0x319F)),
|
| 512 |
+
),
|
| 513 |
+
(("Private Use Area (plane 0)", (0xE000, 0xF8FF)),),
|
| 514 |
+
(
|
| 515 |
+
("CJK Strokes", (0x31C0, 0x31EF)),
|
| 516 |
+
("CJK Compatibility Ideographs", (0xF900, 0xFAFF)),
|
| 517 |
+
("CJK Compatibility Ideographs Supplement", (0x2F800, 0x2FA1F)),
|
| 518 |
+
),
|
| 519 |
+
(("Alphabetic Presentation Forms", (0xFB00, 0xFB4F)),),
|
| 520 |
+
(("Arabic Presentation Forms-A", (0xFB50, 0xFDFF)),),
|
| 521 |
+
(("Combining Half Marks", (0xFE20, 0xFE2F)),),
|
| 522 |
+
(
|
| 523 |
+
("Vertical Forms", (0xFE10, 0xFE1F)),
|
| 524 |
+
("CJK Compatibility Forms", (0xFE30, 0xFE4F)),
|
| 525 |
+
),
|
| 526 |
+
(("Small Form Variants", (0xFE50, 0xFE6F)),),
|
| 527 |
+
(("Arabic Presentation Forms-B", (0xFE70, 0xFEFF)),),
|
| 528 |
+
(("Halfwidth And Fullwidth Forms", (0xFF00, 0xFFEF)),),
|
| 529 |
+
(("Specials", (0xFFF0, 0xFFFF)),),
|
| 530 |
+
(("Tibetan", (0x0F00, 0x0FFF)),),
|
| 531 |
+
(("Syriac", (0x0700, 0x074F)),),
|
| 532 |
+
(("Thaana", (0x0780, 0x07BF)),),
|
| 533 |
+
(("Sinhala", (0x0D80, 0x0DFF)),),
|
| 534 |
+
(("Myanmar", (0x1000, 0x109F)),),
|
| 535 |
+
(
|
| 536 |
+
("Ethiopic", (0x1200, 0x137F)),
|
| 537 |
+
("Ethiopic Supplement", (0x1380, 0x139F)),
|
| 538 |
+
("Ethiopic Extended", (0x2D80, 0x2DDF)),
|
| 539 |
+
),
|
| 540 |
+
(("Cherokee", (0x13A0, 0x13FF)),),
|
| 541 |
+
(("Unified Canadian Aboriginal Syllabics", (0x1400, 0x167F)),),
|
| 542 |
+
(("Ogham", (0x1680, 0x169F)),),
|
| 543 |
+
(("Runic", (0x16A0, 0x16FF)),),
|
| 544 |
+
(("Khmer", (0x1780, 0x17FF)), ("Khmer Symbols", (0x19E0, 0x19FF))),
|
| 545 |
+
(("Mongolian", (0x1800, 0x18AF)),),
|
| 546 |
+
(("Braille Patterns", (0x2800, 0x28FF)),),
|
| 547 |
+
(("Yi Syllables", (0xA000, 0xA48F)), ("Yi Radicals", (0xA490, 0xA4CF))),
|
| 548 |
+
(
|
| 549 |
+
("Tagalog", (0x1700, 0x171F)),
|
| 550 |
+
("Hanunoo", (0x1720, 0x173F)),
|
| 551 |
+
("Buhid", (0x1740, 0x175F)),
|
| 552 |
+
("Tagbanwa", (0x1760, 0x177F)),
|
| 553 |
+
),
|
| 554 |
+
(("Old Italic", (0x10300, 0x1032F)),),
|
| 555 |
+
(("Gothic", (0x10330, 0x1034F)),),
|
| 556 |
+
(("Deseret", (0x10400, 0x1044F)),),
|
| 557 |
+
(
|
| 558 |
+
("Byzantine Musical Symbols", (0x1D000, 0x1D0FF)),
|
| 559 |
+
("Musical Symbols", (0x1D100, 0x1D1FF)),
|
| 560 |
+
("Ancient Greek Musical Notation", (0x1D200, 0x1D24F)),
|
| 561 |
+
),
|
| 562 |
+
(("Mathematical Alphanumeric Symbols", (0x1D400, 0x1D7FF)),),
|
| 563 |
+
(
|
| 564 |
+
("Private Use (plane 15)", (0xF0000, 0xFFFFD)),
|
| 565 |
+
("Private Use (plane 16)", (0x100000, 0x10FFFD)),
|
| 566 |
+
),
|
| 567 |
+
(
|
| 568 |
+
("Variation Selectors", (0xFE00, 0xFE0F)),
|
| 569 |
+
("Variation Selectors Supplement", (0xE0100, 0xE01EF)),
|
| 570 |
+
),
|
| 571 |
+
(("Tags", (0xE0000, 0xE007F)),),
|
| 572 |
+
(("Limbu", (0x1900, 0x194F)),),
|
| 573 |
+
(("Tai Le", (0x1950, 0x197F)),),
|
| 574 |
+
(("New Tai Lue", (0x1980, 0x19DF)),),
|
| 575 |
+
(("Buginese", (0x1A00, 0x1A1F)),),
|
| 576 |
+
(("Glagolitic", (0x2C00, 0x2C5F)),),
|
| 577 |
+
(("Tifinagh", (0x2D30, 0x2D7F)),),
|
| 578 |
+
(("Yijing Hexagram Symbols", (0x4DC0, 0x4DFF)),),
|
| 579 |
+
(("Syloti Nagri", (0xA800, 0xA82F)),),
|
| 580 |
+
(
|
| 581 |
+
("Linear B Syllabary", (0x10000, 0x1007F)),
|
| 582 |
+
("Linear B Ideograms", (0x10080, 0x100FF)),
|
| 583 |
+
("Aegean Numbers", (0x10100, 0x1013F)),
|
| 584 |
+
),
|
| 585 |
+
(("Ancient Greek Numbers", (0x10140, 0x1018F)),),
|
| 586 |
+
(("Ugaritic", (0x10380, 0x1039F)),),
|
| 587 |
+
(("Old Persian", (0x103A0, 0x103DF)),),
|
| 588 |
+
(("Shavian", (0x10450, 0x1047F)),),
|
| 589 |
+
(("Osmanya", (0x10480, 0x104AF)),),
|
| 590 |
+
(("Cypriot Syllabary", (0x10800, 0x1083F)),),
|
| 591 |
+
(("Kharoshthi", (0x10A00, 0x10A5F)),),
|
| 592 |
+
(("Tai Xuan Jing Symbols", (0x1D300, 0x1D35F)),),
|
| 593 |
+
(
|
| 594 |
+
("Cuneiform", (0x12000, 0x123FF)),
|
| 595 |
+
("Cuneiform Numbers and Punctuation", (0x12400, 0x1247F)),
|
| 596 |
+
),
|
| 597 |
+
(("Counting Rod Numerals", (0x1D360, 0x1D37F)),),
|
| 598 |
+
(("Sundanese", (0x1B80, 0x1BBF)),),
|
| 599 |
+
(("Lepcha", (0x1C00, 0x1C4F)),),
|
| 600 |
+
(("Ol Chiki", (0x1C50, 0x1C7F)),),
|
| 601 |
+
(("Saurashtra", (0xA880, 0xA8DF)),),
|
| 602 |
+
(("Kayah Li", (0xA900, 0xA92F)),),
|
| 603 |
+
(("Rejang", (0xA930, 0xA95F)),),
|
| 604 |
+
(("Cham", (0xAA00, 0xAA5F)),),
|
| 605 |
+
(("Ancient Symbols", (0x10190, 0x101CF)),),
|
| 606 |
+
(("Phaistos Disc", (0x101D0, 0x101FF)),),
|
| 607 |
+
(
|
| 608 |
+
("Carian", (0x102A0, 0x102DF)),
|
| 609 |
+
("Lycian", (0x10280, 0x1029F)),
|
| 610 |
+
("Lydian", (0x10920, 0x1093F)),
|
| 611 |
+
),
|
| 612 |
+
(("Domino Tiles", (0x1F030, 0x1F09F)), ("Mahjong Tiles", (0x1F000, 0x1F02F))),
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
_unicodeStarts = []
|
| 617 |
+
_unicodeValues = [None]
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def _getUnicodeRanges():
|
| 621 |
+
# build the ranges of codepoints for each unicode range bit, and cache result
|
| 622 |
+
if not _unicodeStarts:
|
| 623 |
+
unicodeRanges = [
|
| 624 |
+
(start, (stop, bit))
|
| 625 |
+
for bit, blocks in enumerate(OS2_UNICODE_RANGES)
|
| 626 |
+
for _, (start, stop) in blocks
|
| 627 |
+
]
|
| 628 |
+
for start, (stop, bit) in sorted(unicodeRanges):
|
| 629 |
+
_unicodeStarts.append(start)
|
| 630 |
+
_unicodeValues.append((stop, bit))
|
| 631 |
+
return _unicodeStarts, _unicodeValues
|
| 632 |
+
|
| 633 |
+
|
| 634 |
+
def intersectUnicodeRanges(unicodes, inverse=False):
|
| 635 |
+
"""Intersect a sequence of (int) Unicode codepoints with the Unicode block
|
| 636 |
+
ranges defined in the OpenType specification v1.7, and return the set of
|
| 637 |
+
'ulUnicodeRanges' bits for which there is at least ONE intersection.
|
| 638 |
+
If 'inverse' is True, return the the bits for which there is NO intersection.
|
| 639 |
+
|
| 640 |
+
>>> intersectUnicodeRanges([0x0410]) == {9}
|
| 641 |
+
True
|
| 642 |
+
>>> intersectUnicodeRanges([0x0410, 0x1F000]) == {9, 57, 122}
|
| 643 |
+
True
|
| 644 |
+
>>> intersectUnicodeRanges([0x0410, 0x1F000], inverse=True) == (
|
| 645 |
+
... set(range(len(OS2_UNICODE_RANGES))) - {9, 57, 122})
|
| 646 |
+
True
|
| 647 |
+
"""
|
| 648 |
+
unicodes = set(unicodes)
|
| 649 |
+
unicodestarts, unicodevalues = _getUnicodeRanges()
|
| 650 |
+
bits = set()
|
| 651 |
+
for code in unicodes:
|
| 652 |
+
stop, bit = unicodevalues[bisect.bisect(unicodestarts, code)]
|
| 653 |
+
if code <= stop:
|
| 654 |
+
bits.add(bit)
|
| 655 |
+
# The spec says that bit 57 ("Non Plane 0") implies that there's
|
| 656 |
+
# at least one codepoint beyond the BMP; so I also include all
|
| 657 |
+
# the non-BMP codepoints here
|
| 658 |
+
if any(0x10000 <= code < 0x110000 for code in unicodes):
|
| 659 |
+
bits.add(57)
|
| 660 |
+
return set(range(len(OS2_UNICODE_RANGES))) - bits if inverse else bits
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
def calcCodePageRanges(unicodes):
|
| 664 |
+
"""Given a set of Unicode codepoints (integers), calculate the
|
| 665 |
+
corresponding OS/2 CodePage range bits.
|
| 666 |
+
This is a direct translation of FontForge implementation:
|
| 667 |
+
https://github.com/fontforge/fontforge/blob/7b2c074/fontforge/tottf.c#L3158
|
| 668 |
+
"""
|
| 669 |
+
bits = set()
|
| 670 |
+
hasAscii = set(range(0x20, 0x7E)).issubset(unicodes)
|
| 671 |
+
hasLineart = ord("┤") in unicodes
|
| 672 |
+
|
| 673 |
+
for uni in unicodes:
|
| 674 |
+
if uni == ord("Þ") and hasAscii:
|
| 675 |
+
bits.add(0) # Latin 1
|
| 676 |
+
elif uni == ord("Ľ") and hasAscii:
|
| 677 |
+
bits.add(1) # Latin 2: Eastern Europe
|
| 678 |
+
if hasLineart:
|
| 679 |
+
bits.add(58) # Latin 2
|
| 680 |
+
elif uni == ord("Б"):
|
| 681 |
+
bits.add(2) # Cyrillic
|
| 682 |
+
if ord("Ѕ") in unicodes and hasLineart:
|
| 683 |
+
bits.add(57) # IBM Cyrillic
|
| 684 |
+
if ord("╜") in unicodes and hasLineart:
|
| 685 |
+
bits.add(49) # MS-DOS Russian
|
| 686 |
+
elif uni == ord("Ά"):
|
| 687 |
+
bits.add(3) # Greek
|
| 688 |
+
if hasLineart and ord("½") in unicodes:
|
| 689 |
+
bits.add(48) # IBM Greek
|
| 690 |
+
if hasLineart and ord("√") in unicodes:
|
| 691 |
+
bits.add(60) # Greek, former 437 G
|
| 692 |
+
elif uni == ord("İ") and hasAscii:
|
| 693 |
+
bits.add(4) # Turkish
|
| 694 |
+
if hasLineart:
|
| 695 |
+
bits.add(56) # IBM turkish
|
| 696 |
+
elif uni == ord("א"):
|
| 697 |
+
bits.add(5) # Hebrew
|
| 698 |
+
if hasLineart and ord("√") in unicodes:
|
| 699 |
+
bits.add(53) # Hebrew
|
| 700 |
+
elif uni == ord("ر"):
|
| 701 |
+
bits.add(6) # Arabic
|
| 702 |
+
if ord("√") in unicodes:
|
| 703 |
+
bits.add(51) # Arabic
|
| 704 |
+
if hasLineart:
|
| 705 |
+
bits.add(61) # Arabic; ASMO 708
|
| 706 |
+
elif uni == ord("ŗ") and hasAscii:
|
| 707 |
+
bits.add(7) # Windows Baltic
|
| 708 |
+
if hasLineart:
|
| 709 |
+
bits.add(59) # MS-DOS Baltic
|
| 710 |
+
elif uni == ord("₫") and hasAscii:
|
| 711 |
+
bits.add(8) # Vietnamese
|
| 712 |
+
elif uni == ord("ๅ"):
|
| 713 |
+
bits.add(16) # Thai
|
| 714 |
+
elif uni == ord("エ"):
|
| 715 |
+
bits.add(17) # JIS/Japan
|
| 716 |
+
elif uni == ord("ㄅ"):
|
| 717 |
+
bits.add(18) # Chinese: Simplified
|
| 718 |
+
elif uni == ord("ㄱ"):
|
| 719 |
+
bits.add(19) # Korean wansung
|
| 720 |
+
elif uni == ord("央"):
|
| 721 |
+
bits.add(20) # Chinese: Traditional
|
| 722 |
+
elif uni == ord("곴"):
|
| 723 |
+
bits.add(21) # Korean Johab
|
| 724 |
+
elif uni == ord("♥") and hasAscii:
|
| 725 |
+
bits.add(30) # OEM Character Set
|
| 726 |
+
# TODO: Symbol bit has a special meaning (check the spec), we need
|
| 727 |
+
# to confirm if this is wanted by default.
|
| 728 |
+
# elif chr(0xF000) <= char <= chr(0xF0FF):
|
| 729 |
+
# codepageRanges.add(31) # Symbol Character Set
|
| 730 |
+
elif uni == ord("þ") and hasAscii and hasLineart:
|
| 731 |
+
bits.add(54) # MS-DOS Icelandic
|
| 732 |
+
elif uni == ord("╚") and hasAscii:
|
| 733 |
+
bits.add(62) # WE/Latin 1
|
| 734 |
+
bits.add(63) # US
|
| 735 |
+
elif hasAscii and hasLineart and ord("√") in unicodes:
|
| 736 |
+
if uni == ord("Å"):
|
| 737 |
+
bits.add(50) # MS-DOS Nordic
|
| 738 |
+
elif uni == ord("é"):
|
| 739 |
+
bits.add(52) # MS-DOS Canadian French
|
| 740 |
+
elif uni == ord("õ"):
|
| 741 |
+
bits.add(55) # MS-DOS Portuguese
|
| 742 |
+
|
| 743 |
+
if hasAscii and ord("‰") in unicodes and ord("∑") in unicodes:
|
| 744 |
+
bits.add(29) # Macintosh Character Set (US Roman)
|
| 745 |
+
|
| 746 |
+
return bits
|
| 747 |
+
|
| 748 |
+
|
| 749 |
+
if __name__ == "__main__":
|
| 750 |
+
import doctest, sys
|
| 751 |
+
|
| 752 |
+
sys.exit(doctest.testmod().failed)
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_T_A_T_.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .otBase import BaseTTXConverter
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_S_T_A_T_(BaseTTXConverter):
|
| 5 |
+
"""Style Attributes table
|
| 6 |
+
|
| 7 |
+
The ``STAT`` table records stylistic or typeface-design attributes that
|
| 8 |
+
differentiate the individual fonts within a font family from one another.
|
| 9 |
+
Those attributes can be used to assist users when navigating the style
|
| 10 |
+
variations of a variable font or a family of static fonts.
|
| 11 |
+
|
| 12 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/stat
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S_V_G_.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compiles/decompiles SVG table.
|
| 2 |
+
|
| 3 |
+
https://docs.microsoft.com/en-us/typography/opentype/spec/svg
|
| 4 |
+
|
| 5 |
+
The XML format is:
|
| 6 |
+
|
| 7 |
+
.. code-block:: xml
|
| 8 |
+
|
| 9 |
+
<SVG>
|
| 10 |
+
<svgDoc endGlyphID="1" startGlyphID="1">
|
| 11 |
+
<![CDATA[ <complete SVG doc> ]]
|
| 12 |
+
</svgDoc>
|
| 13 |
+
...
|
| 14 |
+
<svgDoc endGlyphID="n" startGlyphID="m">
|
| 15 |
+
<![CDATA[ <complete SVG doc> ]]
|
| 16 |
+
</svgDoc>
|
| 17 |
+
</SVG>
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from fontTools.misc.textTools import bytesjoin, safeEval, strjoin, tobytes, tostr
|
| 21 |
+
from fontTools.misc import sstruct
|
| 22 |
+
from . import DefaultTable
|
| 23 |
+
from collections.abc import Sequence
|
| 24 |
+
from dataclasses import dataclass, astuple
|
| 25 |
+
from io import BytesIO
|
| 26 |
+
import struct
|
| 27 |
+
import logging
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
log = logging.getLogger(__name__)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
SVG_format_0 = """
|
| 34 |
+
> # big endian
|
| 35 |
+
version: H
|
| 36 |
+
offsetToSVGDocIndex: L
|
| 37 |
+
reserved: L
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
SVG_format_0Size = sstruct.calcsize(SVG_format_0)
|
| 41 |
+
|
| 42 |
+
doc_index_entry_format_0 = """
|
| 43 |
+
> # big endian
|
| 44 |
+
startGlyphID: H
|
| 45 |
+
endGlyphID: H
|
| 46 |
+
svgDocOffset: L
|
| 47 |
+
svgDocLength: L
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
doc_index_entry_format_0Size = sstruct.calcsize(doc_index_entry_format_0)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class table_S_V_G_(DefaultTable.DefaultTable):
|
| 54 |
+
"""Scalable Vector Graphics table
|
| 55 |
+
|
| 56 |
+
The ``SVG`` table contains representations for glyphs in the SVG
|
| 57 |
+
image format.
|
| 58 |
+
|
| 59 |
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/stat
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def decompile(self, data, ttFont):
|
| 63 |
+
self.docList = []
|
| 64 |
+
# Version 0 is the standardized version of the table; and current.
|
| 65 |
+
# https://www.microsoft.com/typography/otspec/svg.htm
|
| 66 |
+
sstruct.unpack(SVG_format_0, data[:SVG_format_0Size], self)
|
| 67 |
+
if self.version != 0:
|
| 68 |
+
log.warning(
|
| 69 |
+
"Unknown SVG table version '%s'. Decompiling as version 0.",
|
| 70 |
+
self.version,
|
| 71 |
+
)
|
| 72 |
+
# read in SVG Documents Index
|
| 73 |
+
# data starts with the first entry of the entry list.
|
| 74 |
+
pos = subTableStart = self.offsetToSVGDocIndex
|
| 75 |
+
self.numEntries = struct.unpack(">H", data[pos : pos + 2])[0]
|
| 76 |
+
pos += 2
|
| 77 |
+
if self.numEntries > 0:
|
| 78 |
+
data2 = data[pos:]
|
| 79 |
+
entries = []
|
| 80 |
+
for i in range(self.numEntries):
|
| 81 |
+
record_data = data2[
|
| 82 |
+
i
|
| 83 |
+
* doc_index_entry_format_0Size : (i + 1)
|
| 84 |
+
* doc_index_entry_format_0Size
|
| 85 |
+
]
|
| 86 |
+
docIndexEntry = sstruct.unpack(
|
| 87 |
+
doc_index_entry_format_0, record_data, DocumentIndexEntry()
|
| 88 |
+
)
|
| 89 |
+
entries.append(docIndexEntry)
|
| 90 |
+
|
| 91 |
+
for entry in entries:
|
| 92 |
+
start = entry.svgDocOffset + subTableStart
|
| 93 |
+
end = start + entry.svgDocLength
|
| 94 |
+
doc = data[start:end]
|
| 95 |
+
compressed = False
|
| 96 |
+
if doc.startswith(b"\x1f\x8b"):
|
| 97 |
+
import gzip
|
| 98 |
+
|
| 99 |
+
bytesIO = BytesIO(doc)
|
| 100 |
+
with gzip.GzipFile(None, "r", fileobj=bytesIO) as gunzipper:
|
| 101 |
+
doc = gunzipper.read()
|
| 102 |
+
del bytesIO
|
| 103 |
+
compressed = True
|
| 104 |
+
doc = tostr(doc, "utf_8")
|
| 105 |
+
self.docList.append(
|
| 106 |
+
SVGDocument(doc, entry.startGlyphID, entry.endGlyphID, compressed)
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
def compile(self, ttFont):
|
| 110 |
+
version = 0
|
| 111 |
+
offsetToSVGDocIndex = (
|
| 112 |
+
SVG_format_0Size # I start the SVGDocIndex right after the header.
|
| 113 |
+
)
|
| 114 |
+
# get SGVDoc info.
|
| 115 |
+
docList = []
|
| 116 |
+
entryList = []
|
| 117 |
+
numEntries = len(self.docList)
|
| 118 |
+
datum = struct.pack(">H", numEntries)
|
| 119 |
+
entryList.append(datum)
|
| 120 |
+
curOffset = len(datum) + doc_index_entry_format_0Size * numEntries
|
| 121 |
+
seenDocs = {}
|
| 122 |
+
allCompressed = getattr(self, "compressed", False)
|
| 123 |
+
for i, doc in enumerate(self.docList):
|
| 124 |
+
if isinstance(doc, (list, tuple)):
|
| 125 |
+
doc = SVGDocument(*doc)
|
| 126 |
+
self.docList[i] = doc
|
| 127 |
+
docBytes = tobytes(doc.data, encoding="utf_8")
|
| 128 |
+
if (allCompressed or doc.compressed) and not docBytes.startswith(
|
| 129 |
+
b"\x1f\x8b"
|
| 130 |
+
):
|
| 131 |
+
import gzip
|
| 132 |
+
|
| 133 |
+
bytesIO = BytesIO()
|
| 134 |
+
# mtime=0 strips the useless timestamp and makes gzip output reproducible;
|
| 135 |
+
# equivalent to `gzip -n`
|
| 136 |
+
with gzip.GzipFile(None, "w", fileobj=bytesIO, mtime=0) as gzipper:
|
| 137 |
+
gzipper.write(docBytes)
|
| 138 |
+
gzipped = bytesIO.getvalue()
|
| 139 |
+
if len(gzipped) < len(docBytes):
|
| 140 |
+
docBytes = gzipped
|
| 141 |
+
del gzipped, bytesIO
|
| 142 |
+
docLength = len(docBytes)
|
| 143 |
+
if docBytes in seenDocs:
|
| 144 |
+
docOffset = seenDocs[docBytes]
|
| 145 |
+
else:
|
| 146 |
+
docOffset = curOffset
|
| 147 |
+
curOffset += docLength
|
| 148 |
+
seenDocs[docBytes] = docOffset
|
| 149 |
+
docList.append(docBytes)
|
| 150 |
+
entry = struct.pack(
|
| 151 |
+
">HHLL", doc.startGlyphID, doc.endGlyphID, docOffset, docLength
|
| 152 |
+
)
|
| 153 |
+
entryList.append(entry)
|
| 154 |
+
entryList.extend(docList)
|
| 155 |
+
svgDocData = bytesjoin(entryList)
|
| 156 |
+
|
| 157 |
+
reserved = 0
|
| 158 |
+
header = struct.pack(">HLL", version, offsetToSVGDocIndex, reserved)
|
| 159 |
+
data = [header, svgDocData]
|
| 160 |
+
data = bytesjoin(data)
|
| 161 |
+
return data
|
| 162 |
+
|
| 163 |
+
def toXML(self, writer, ttFont):
|
| 164 |
+
for i, doc in enumerate(self.docList):
|
| 165 |
+
if isinstance(doc, (list, tuple)):
|
| 166 |
+
doc = SVGDocument(*doc)
|
| 167 |
+
self.docList[i] = doc
|
| 168 |
+
attrs = {"startGlyphID": doc.startGlyphID, "endGlyphID": doc.endGlyphID}
|
| 169 |
+
if doc.compressed:
|
| 170 |
+
attrs["compressed"] = 1
|
| 171 |
+
writer.begintag("svgDoc", **attrs)
|
| 172 |
+
writer.newline()
|
| 173 |
+
writer.writecdata(doc.data)
|
| 174 |
+
writer.newline()
|
| 175 |
+
writer.endtag("svgDoc")
|
| 176 |
+
writer.newline()
|
| 177 |
+
|
| 178 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 179 |
+
if name == "svgDoc":
|
| 180 |
+
if not hasattr(self, "docList"):
|
| 181 |
+
self.docList = []
|
| 182 |
+
doc = strjoin(content)
|
| 183 |
+
doc = doc.strip()
|
| 184 |
+
startGID = int(attrs["startGlyphID"])
|
| 185 |
+
endGID = int(attrs["endGlyphID"])
|
| 186 |
+
compressed = bool(safeEval(attrs.get("compressed", "0")))
|
| 187 |
+
self.docList.append(SVGDocument(doc, startGID, endGID, compressed))
|
| 188 |
+
else:
|
| 189 |
+
log.warning("Unknown %s %s", name, content)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class DocumentIndexEntry(object):
|
| 193 |
+
def __init__(self):
|
| 194 |
+
self.startGlyphID = None # USHORT
|
| 195 |
+
self.endGlyphID = None # USHORT
|
| 196 |
+
self.svgDocOffset = None # ULONG
|
| 197 |
+
self.svgDocLength = None # ULONG
|
| 198 |
+
|
| 199 |
+
def __repr__(self):
|
| 200 |
+
return (
|
| 201 |
+
"startGlyphID: %s, endGlyphID: %s, svgDocOffset: %s, svgDocLength: %s"
|
| 202 |
+
% (self.startGlyphID, self.endGlyphID, self.svgDocOffset, self.svgDocLength)
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
@dataclass
|
| 207 |
+
class SVGDocument(Sequence):
|
| 208 |
+
data: str
|
| 209 |
+
startGlyphID: int
|
| 210 |
+
endGlyphID: int
|
| 211 |
+
compressed: bool = False
|
| 212 |
+
|
| 213 |
+
# Previously, the SVG table's docList attribute contained a lists of 3 items:
|
| 214 |
+
# [doc, startGlyphID, endGlyphID]; later, we added a `compressed` attribute.
|
| 215 |
+
# For backward compatibility with code that depends of them being sequences of
|
| 216 |
+
# fixed length=3, we subclass the Sequence abstract base class and pretend only
|
| 217 |
+
# the first three items are present. 'compressed' is only accessible via named
|
| 218 |
+
# attribute lookup like regular dataclasses: i.e. `doc.compressed`, not `doc[3]`
|
| 219 |
+
def __getitem__(self, index):
|
| 220 |
+
return astuple(self)[:3][index]
|
| 221 |
+
|
| 222 |
+
def __len__(self):
|
| 223 |
+
return 3
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_f.py
ADDED
|
@@ -0,0 +1,1040 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.fixedTools import floatToFixedToStr
|
| 3 |
+
from fontTools.misc.textTools import byteord, safeEval
|
| 4 |
+
|
| 5 |
+
# from itertools import *
|
| 6 |
+
from . import DefaultTable
|
| 7 |
+
from . import grUtils
|
| 8 |
+
from array import array
|
| 9 |
+
from functools import reduce
|
| 10 |
+
import struct, re, sys
|
| 11 |
+
|
| 12 |
+
Silf_hdr_format = """
|
| 13 |
+
>
|
| 14 |
+
version: 16.16F
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
Silf_hdr_format_3 = """
|
| 18 |
+
>
|
| 19 |
+
version: 16.16F
|
| 20 |
+
compilerVersion: L
|
| 21 |
+
numSilf: H
|
| 22 |
+
x
|
| 23 |
+
x
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
Silf_part1_format_v3 = """
|
| 27 |
+
>
|
| 28 |
+
ruleVersion: 16.16F
|
| 29 |
+
passOffset: H
|
| 30 |
+
pseudosOffset: H
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
Silf_part1_format = """
|
| 34 |
+
>
|
| 35 |
+
maxGlyphID: H
|
| 36 |
+
extraAscent: h
|
| 37 |
+
extraDescent: h
|
| 38 |
+
numPasses: B
|
| 39 |
+
iSubst: B
|
| 40 |
+
iPos: B
|
| 41 |
+
iJust: B
|
| 42 |
+
iBidi: B
|
| 43 |
+
flags: B
|
| 44 |
+
maxPreContext: B
|
| 45 |
+
maxPostContext: B
|
| 46 |
+
attrPseudo: B
|
| 47 |
+
attrBreakWeight: B
|
| 48 |
+
attrDirectionality: B
|
| 49 |
+
attrMirroring: B
|
| 50 |
+
attrSkipPasses: B
|
| 51 |
+
numJLevels: B
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
Silf_justify_format = """
|
| 55 |
+
>
|
| 56 |
+
attrStretch: B
|
| 57 |
+
attrShrink: B
|
| 58 |
+
attrStep: B
|
| 59 |
+
attrWeight: B
|
| 60 |
+
runto: B
|
| 61 |
+
x
|
| 62 |
+
x
|
| 63 |
+
x
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
Silf_part2_format = """
|
| 67 |
+
>
|
| 68 |
+
numLigComp: H
|
| 69 |
+
numUserDefn: B
|
| 70 |
+
maxCompPerLig: B
|
| 71 |
+
direction: B
|
| 72 |
+
attCollisions: B
|
| 73 |
+
x
|
| 74 |
+
x
|
| 75 |
+
x
|
| 76 |
+
numCritFeatures: B
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
Silf_pseudomap_format = """
|
| 80 |
+
>
|
| 81 |
+
unicode: L
|
| 82 |
+
nPseudo: H
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
Silf_pseudomap_format_h = """
|
| 86 |
+
>
|
| 87 |
+
unicode: H
|
| 88 |
+
nPseudo: H
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
Silf_classmap_format = """
|
| 92 |
+
>
|
| 93 |
+
numClass: H
|
| 94 |
+
numLinear: H
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
Silf_lookupclass_format = """
|
| 98 |
+
>
|
| 99 |
+
numIDs: H
|
| 100 |
+
searchRange: H
|
| 101 |
+
entrySelector: H
|
| 102 |
+
rangeShift: H
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
Silf_lookuppair_format = """
|
| 106 |
+
>
|
| 107 |
+
glyphId: H
|
| 108 |
+
index: H
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
Silf_pass_format = """
|
| 112 |
+
>
|
| 113 |
+
flags: B
|
| 114 |
+
maxRuleLoop: B
|
| 115 |
+
maxRuleContext: B
|
| 116 |
+
maxBackup: B
|
| 117 |
+
numRules: H
|
| 118 |
+
fsmOffset: H
|
| 119 |
+
pcCode: L
|
| 120 |
+
rcCode: L
|
| 121 |
+
aCode: L
|
| 122 |
+
oDebug: L
|
| 123 |
+
numRows: H
|
| 124 |
+
numTransitional: H
|
| 125 |
+
numSuccess: H
|
| 126 |
+
numColumns: H
|
| 127 |
+
"""
|
| 128 |
+
|
| 129 |
+
aCode_info = (
|
| 130 |
+
("NOP", 0),
|
| 131 |
+
("PUSH_BYTE", "b"),
|
| 132 |
+
("PUSH_BYTE_U", "B"),
|
| 133 |
+
("PUSH_SHORT", ">h"),
|
| 134 |
+
("PUSH_SHORT_U", ">H"),
|
| 135 |
+
("PUSH_LONG", ">L"),
|
| 136 |
+
("ADD", 0),
|
| 137 |
+
("SUB", 0),
|
| 138 |
+
("MUL", 0),
|
| 139 |
+
("DIV", 0),
|
| 140 |
+
("MIN", 0),
|
| 141 |
+
("MAX", 0),
|
| 142 |
+
("NEG", 0),
|
| 143 |
+
("TRUNC8", 0),
|
| 144 |
+
("TRUNC16", 0),
|
| 145 |
+
("COND", 0),
|
| 146 |
+
("AND", 0), # x10
|
| 147 |
+
("OR", 0),
|
| 148 |
+
("NOT", 0),
|
| 149 |
+
("EQUAL", 0),
|
| 150 |
+
("NOT_EQ", 0),
|
| 151 |
+
("LESS", 0),
|
| 152 |
+
("GTR", 0),
|
| 153 |
+
("LESS_EQ", 0),
|
| 154 |
+
("GTR_EQ", 0),
|
| 155 |
+
("NEXT", 0),
|
| 156 |
+
("NEXT_N", "b"),
|
| 157 |
+
("COPY_NEXT", 0),
|
| 158 |
+
("PUT_GLYPH_8BIT_OBS", "B"),
|
| 159 |
+
("PUT_SUBS_8BIT_OBS", "bBB"),
|
| 160 |
+
("PUT_COPY", "b"),
|
| 161 |
+
("INSERT", 0),
|
| 162 |
+
("DELETE", 0), # x20
|
| 163 |
+
("ASSOC", -1),
|
| 164 |
+
("CNTXT_ITEM", "bB"),
|
| 165 |
+
("ATTR_SET", "B"),
|
| 166 |
+
("ATTR_ADD", "B"),
|
| 167 |
+
("ATTR_SUB", "B"),
|
| 168 |
+
("ATTR_SET_SLOT", "B"),
|
| 169 |
+
("IATTR_SET_SLOT", "BB"),
|
| 170 |
+
("PUSH_SLOT_ATTR", "Bb"),
|
| 171 |
+
("PUSH_GLYPH_ATTR_OBS", "Bb"),
|
| 172 |
+
("PUSH_GLYPH_METRIC", "Bbb"),
|
| 173 |
+
("PUSH_FEAT", "Bb"),
|
| 174 |
+
("PUSH_ATT_TO_GATTR_OBS", "Bb"),
|
| 175 |
+
("PUSH_ATT_TO_GLYPH_METRIC", "Bbb"),
|
| 176 |
+
("PUSH_ISLOT_ATTR", "Bbb"),
|
| 177 |
+
("PUSH_IGLYPH_ATTR", "Bbb"),
|
| 178 |
+
("POP_RET", 0), # x30
|
| 179 |
+
("RET_ZERO", 0),
|
| 180 |
+
("RET_TRUE", 0),
|
| 181 |
+
("IATTR_SET", "BB"),
|
| 182 |
+
("IATTR_ADD", "BB"),
|
| 183 |
+
("IATTR_SUB", "BB"),
|
| 184 |
+
("PUSH_PROC_STATE", "B"),
|
| 185 |
+
("PUSH_VERSION", 0),
|
| 186 |
+
("PUT_SUBS", ">bHH"),
|
| 187 |
+
("PUT_SUBS2", 0),
|
| 188 |
+
("PUT_SUBS3", 0),
|
| 189 |
+
("PUT_GLYPH", ">H"),
|
| 190 |
+
("PUSH_GLYPH_ATTR", ">Hb"),
|
| 191 |
+
("PUSH_ATT_TO_GLYPH_ATTR", ">Hb"),
|
| 192 |
+
("BITOR", 0),
|
| 193 |
+
("BITAND", 0),
|
| 194 |
+
("BITNOT", 0), # x40
|
| 195 |
+
("BITSET", ">HH"),
|
| 196 |
+
("SET_FEAT", "Bb"),
|
| 197 |
+
)
|
| 198 |
+
aCode_map = dict([(x[0], (i, x[1])) for i, x in enumerate(aCode_info)])
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def disassemble(aCode):
|
| 202 |
+
codelen = len(aCode)
|
| 203 |
+
pc = 0
|
| 204 |
+
res = []
|
| 205 |
+
while pc < codelen:
|
| 206 |
+
opcode = byteord(aCode[pc : pc + 1])
|
| 207 |
+
if opcode > len(aCode_info):
|
| 208 |
+
instr = aCode_info[0]
|
| 209 |
+
else:
|
| 210 |
+
instr = aCode_info[opcode]
|
| 211 |
+
pc += 1
|
| 212 |
+
if instr[1] != 0 and pc >= codelen:
|
| 213 |
+
return res
|
| 214 |
+
if instr[1] == -1:
|
| 215 |
+
count = byteord(aCode[pc])
|
| 216 |
+
fmt = "%dB" % count
|
| 217 |
+
pc += 1
|
| 218 |
+
elif instr[1] == 0:
|
| 219 |
+
fmt = ""
|
| 220 |
+
else:
|
| 221 |
+
fmt = instr[1]
|
| 222 |
+
if fmt == "":
|
| 223 |
+
res.append(instr[0])
|
| 224 |
+
continue
|
| 225 |
+
parms = struct.unpack_from(fmt, aCode[pc:])
|
| 226 |
+
res.append(instr[0] + "(" + ", ".join(map(str, parms)) + ")")
|
| 227 |
+
pc += struct.calcsize(fmt)
|
| 228 |
+
return res
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
instre = re.compile(r"^\s*([^(]+)\s*(?:\(([^)]+)\))?")
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def assemble(instrs):
|
| 235 |
+
res = b""
|
| 236 |
+
for inst in instrs:
|
| 237 |
+
m = instre.match(inst)
|
| 238 |
+
if not m or not m.group(1) in aCode_map:
|
| 239 |
+
continue
|
| 240 |
+
opcode, parmfmt = aCode_map[m.group(1)]
|
| 241 |
+
res += struct.pack("B", opcode)
|
| 242 |
+
if m.group(2):
|
| 243 |
+
if parmfmt == 0:
|
| 244 |
+
continue
|
| 245 |
+
parms = [int(x) for x in re.split(r",\s*", m.group(2))]
|
| 246 |
+
if parmfmt == -1:
|
| 247 |
+
l = len(parms)
|
| 248 |
+
res += struct.pack(("%dB" % (l + 1)), l, *parms)
|
| 249 |
+
else:
|
| 250 |
+
res += struct.pack(parmfmt, *parms)
|
| 251 |
+
return res
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def writecode(tag, writer, instrs):
|
| 255 |
+
writer.begintag(tag)
|
| 256 |
+
writer.newline()
|
| 257 |
+
for l in disassemble(instrs):
|
| 258 |
+
writer.write(l)
|
| 259 |
+
writer.newline()
|
| 260 |
+
writer.endtag(tag)
|
| 261 |
+
writer.newline()
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def readcode(content):
|
| 265 |
+
res = []
|
| 266 |
+
for e in content_string(content).split("\n"):
|
| 267 |
+
e = e.strip()
|
| 268 |
+
if not len(e):
|
| 269 |
+
continue
|
| 270 |
+
res.append(e)
|
| 271 |
+
return assemble(res)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
attrs_info = (
|
| 275 |
+
"flags",
|
| 276 |
+
"extraAscent",
|
| 277 |
+
"extraDescent",
|
| 278 |
+
"maxGlyphID",
|
| 279 |
+
"numLigComp",
|
| 280 |
+
"numUserDefn",
|
| 281 |
+
"maxCompPerLig",
|
| 282 |
+
"direction",
|
| 283 |
+
"lbGID",
|
| 284 |
+
)
|
| 285 |
+
attrs_passindexes = ("iSubst", "iPos", "iJust", "iBidi")
|
| 286 |
+
attrs_contexts = ("maxPreContext", "maxPostContext")
|
| 287 |
+
attrs_attributes = (
|
| 288 |
+
"attrPseudo",
|
| 289 |
+
"attrBreakWeight",
|
| 290 |
+
"attrDirectionality",
|
| 291 |
+
"attrMirroring",
|
| 292 |
+
"attrSkipPasses",
|
| 293 |
+
"attCollisions",
|
| 294 |
+
)
|
| 295 |
+
pass_attrs_info = (
|
| 296 |
+
"flags",
|
| 297 |
+
"maxRuleLoop",
|
| 298 |
+
"maxRuleContext",
|
| 299 |
+
"maxBackup",
|
| 300 |
+
"minRulePreContext",
|
| 301 |
+
"maxRulePreContext",
|
| 302 |
+
"collisionThreshold",
|
| 303 |
+
)
|
| 304 |
+
pass_attrs_fsm = ("numRows", "numTransitional", "numSuccess", "numColumns")
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def writesimple(tag, self, writer, *attrkeys):
|
| 308 |
+
attrs = dict([(k, getattr(self, k)) for k in attrkeys])
|
| 309 |
+
writer.simpletag(tag, **attrs)
|
| 310 |
+
writer.newline()
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def getSimple(self, attrs, *attr_list):
|
| 314 |
+
for k in attr_list:
|
| 315 |
+
if k in attrs:
|
| 316 |
+
setattr(self, k, int(safeEval(attrs[k])))
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def content_string(contents):
|
| 320 |
+
res = ""
|
| 321 |
+
for element in contents:
|
| 322 |
+
if isinstance(element, tuple):
|
| 323 |
+
continue
|
| 324 |
+
res += element
|
| 325 |
+
return res.strip()
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def wrapline(writer, dat, length=80):
|
| 329 |
+
currline = ""
|
| 330 |
+
for d in dat:
|
| 331 |
+
if len(currline) > length:
|
| 332 |
+
writer.write(currline[:-1])
|
| 333 |
+
writer.newline()
|
| 334 |
+
currline = ""
|
| 335 |
+
currline += d + " "
|
| 336 |
+
if len(currline):
|
| 337 |
+
writer.write(currline[:-1])
|
| 338 |
+
writer.newline()
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
class _Object:
|
| 342 |
+
pass
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
class table_S__i_l_f(DefaultTable.DefaultTable):
|
| 346 |
+
"""Graphite Rules table
|
| 347 |
+
|
| 348 |
+
See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
|
| 349 |
+
"""
|
| 350 |
+
|
| 351 |
+
def __init__(self, tag=None):
|
| 352 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 353 |
+
self.silfs = []
|
| 354 |
+
|
| 355 |
+
def decompile(self, data, ttFont):
|
| 356 |
+
sstruct.unpack2(Silf_hdr_format, data, self)
|
| 357 |
+
self.version = float(floatToFixedToStr(self.version, precisionBits=16))
|
| 358 |
+
if self.version >= 5.0:
|
| 359 |
+
(data, self.scheme) = grUtils.decompress(data)
|
| 360 |
+
sstruct.unpack2(Silf_hdr_format_3, data, self)
|
| 361 |
+
base = sstruct.calcsize(Silf_hdr_format_3)
|
| 362 |
+
elif self.version < 3.0:
|
| 363 |
+
self.numSilf = struct.unpack(">H", data[4:6])
|
| 364 |
+
self.scheme = 0
|
| 365 |
+
self.compilerVersion = 0
|
| 366 |
+
base = 8
|
| 367 |
+
else:
|
| 368 |
+
self.scheme = 0
|
| 369 |
+
sstruct.unpack2(Silf_hdr_format_3, data, self)
|
| 370 |
+
base = sstruct.calcsize(Silf_hdr_format_3)
|
| 371 |
+
|
| 372 |
+
silfoffsets = struct.unpack_from((">%dL" % self.numSilf), data[base:])
|
| 373 |
+
for offset in silfoffsets:
|
| 374 |
+
s = Silf()
|
| 375 |
+
self.silfs.append(s)
|
| 376 |
+
s.decompile(data[offset:], ttFont, self.version)
|
| 377 |
+
|
| 378 |
+
def compile(self, ttFont):
|
| 379 |
+
self.numSilf = len(self.silfs)
|
| 380 |
+
if self.version < 3.0:
|
| 381 |
+
hdr = sstruct.pack(Silf_hdr_format, self)
|
| 382 |
+
hdr += struct.pack(">HH", self.numSilf, 0)
|
| 383 |
+
else:
|
| 384 |
+
hdr = sstruct.pack(Silf_hdr_format_3, self)
|
| 385 |
+
offset = len(hdr) + 4 * self.numSilf
|
| 386 |
+
data = b""
|
| 387 |
+
for s in self.silfs:
|
| 388 |
+
hdr += struct.pack(">L", offset)
|
| 389 |
+
subdata = s.compile(ttFont, self.version)
|
| 390 |
+
offset += len(subdata)
|
| 391 |
+
data += subdata
|
| 392 |
+
if self.version >= 5.0:
|
| 393 |
+
return grUtils.compress(self.scheme, hdr + data)
|
| 394 |
+
return hdr + data
|
| 395 |
+
|
| 396 |
+
def toXML(self, writer, ttFont):
|
| 397 |
+
writer.comment("Attributes starting with _ are informative only")
|
| 398 |
+
writer.newline()
|
| 399 |
+
writer.simpletag(
|
| 400 |
+
"version",
|
| 401 |
+
version=self.version,
|
| 402 |
+
compilerVersion=self.compilerVersion,
|
| 403 |
+
compressionScheme=self.scheme,
|
| 404 |
+
)
|
| 405 |
+
writer.newline()
|
| 406 |
+
for s in self.silfs:
|
| 407 |
+
writer.begintag("silf")
|
| 408 |
+
writer.newline()
|
| 409 |
+
s.toXML(writer, ttFont, self.version)
|
| 410 |
+
writer.endtag("silf")
|
| 411 |
+
writer.newline()
|
| 412 |
+
|
| 413 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 414 |
+
if name == "version":
|
| 415 |
+
self.scheme = int(safeEval(attrs["compressionScheme"]))
|
| 416 |
+
self.version = float(safeEval(attrs["version"]))
|
| 417 |
+
self.compilerVersion = int(safeEval(attrs["compilerVersion"]))
|
| 418 |
+
return
|
| 419 |
+
if name == "silf":
|
| 420 |
+
s = Silf()
|
| 421 |
+
self.silfs.append(s)
|
| 422 |
+
for element in content:
|
| 423 |
+
if not isinstance(element, tuple):
|
| 424 |
+
continue
|
| 425 |
+
tag, attrs, subcontent = element
|
| 426 |
+
s.fromXML(tag, attrs, subcontent, ttFont, self.version)
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
class Silf(object):
|
| 430 |
+
"""A particular Silf subtable"""
|
| 431 |
+
|
| 432 |
+
def __init__(self):
|
| 433 |
+
self.passes = []
|
| 434 |
+
self.scriptTags = []
|
| 435 |
+
self.critFeatures = []
|
| 436 |
+
self.jLevels = []
|
| 437 |
+
self.pMap = {}
|
| 438 |
+
|
| 439 |
+
def decompile(self, data, ttFont, version=2.0):
|
| 440 |
+
if version >= 3.0:
|
| 441 |
+
_, data = sstruct.unpack2(Silf_part1_format_v3, data, self)
|
| 442 |
+
self.ruleVersion = float(
|
| 443 |
+
floatToFixedToStr(self.ruleVersion, precisionBits=16)
|
| 444 |
+
)
|
| 445 |
+
_, data = sstruct.unpack2(Silf_part1_format, data, self)
|
| 446 |
+
for jlevel in range(self.numJLevels):
|
| 447 |
+
j, data = sstruct.unpack2(Silf_justify_format, data, _Object())
|
| 448 |
+
self.jLevels.append(j)
|
| 449 |
+
_, data = sstruct.unpack2(Silf_part2_format, data, self)
|
| 450 |
+
if self.numCritFeatures:
|
| 451 |
+
self.critFeatures = struct.unpack_from(
|
| 452 |
+
(">%dH" % self.numCritFeatures), data
|
| 453 |
+
)
|
| 454 |
+
data = data[self.numCritFeatures * 2 + 1 :]
|
| 455 |
+
(numScriptTag,) = struct.unpack_from("B", data)
|
| 456 |
+
if numScriptTag:
|
| 457 |
+
self.scriptTags = [
|
| 458 |
+
struct.unpack("4s", data[x : x + 4])[0].decode("ascii")
|
| 459 |
+
for x in range(1, 1 + 4 * numScriptTag, 4)
|
| 460 |
+
]
|
| 461 |
+
data = data[1 + 4 * numScriptTag :]
|
| 462 |
+
(self.lbGID,) = struct.unpack(">H", data[:2])
|
| 463 |
+
if self.numPasses:
|
| 464 |
+
self.oPasses = struct.unpack(
|
| 465 |
+
(">%dL" % (self.numPasses + 1)), data[2 : 6 + 4 * self.numPasses]
|
| 466 |
+
)
|
| 467 |
+
data = data[6 + 4 * self.numPasses :]
|
| 468 |
+
(numPseudo,) = struct.unpack(">H", data[:2])
|
| 469 |
+
for i in range(numPseudo):
|
| 470 |
+
if version >= 3.0:
|
| 471 |
+
pseudo = sstruct.unpack(
|
| 472 |
+
Silf_pseudomap_format, data[8 + 6 * i : 14 + 6 * i], _Object()
|
| 473 |
+
)
|
| 474 |
+
else:
|
| 475 |
+
pseudo = sstruct.unpack(
|
| 476 |
+
Silf_pseudomap_format_h, data[8 + 4 * i : 12 + 4 * i], _Object()
|
| 477 |
+
)
|
| 478 |
+
self.pMap[pseudo.unicode] = ttFont.getGlyphName(pseudo.nPseudo)
|
| 479 |
+
data = data[8 + 6 * numPseudo :]
|
| 480 |
+
currpos = (
|
| 481 |
+
sstruct.calcsize(Silf_part1_format)
|
| 482 |
+
+ sstruct.calcsize(Silf_justify_format) * self.numJLevels
|
| 483 |
+
+ sstruct.calcsize(Silf_part2_format)
|
| 484 |
+
+ 2 * self.numCritFeatures
|
| 485 |
+
+ 1
|
| 486 |
+
+ 1
|
| 487 |
+
+ 4 * numScriptTag
|
| 488 |
+
+ 6
|
| 489 |
+
+ 4 * self.numPasses
|
| 490 |
+
+ 8
|
| 491 |
+
+ 6 * numPseudo
|
| 492 |
+
)
|
| 493 |
+
if version >= 3.0:
|
| 494 |
+
currpos += sstruct.calcsize(Silf_part1_format_v3)
|
| 495 |
+
self.classes = Classes()
|
| 496 |
+
self.classes.decompile(data, ttFont, version)
|
| 497 |
+
for i in range(self.numPasses):
|
| 498 |
+
p = Pass()
|
| 499 |
+
self.passes.append(p)
|
| 500 |
+
p.decompile(
|
| 501 |
+
data[self.oPasses[i] - currpos : self.oPasses[i + 1] - currpos],
|
| 502 |
+
ttFont,
|
| 503 |
+
version,
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
def compile(self, ttFont, version=2.0):
|
| 507 |
+
self.numPasses = len(self.passes)
|
| 508 |
+
self.numJLevels = len(self.jLevels)
|
| 509 |
+
self.numCritFeatures = len(self.critFeatures)
|
| 510 |
+
numPseudo = len(self.pMap)
|
| 511 |
+
data = b""
|
| 512 |
+
if version >= 3.0:
|
| 513 |
+
hdroffset = sstruct.calcsize(Silf_part1_format_v3)
|
| 514 |
+
else:
|
| 515 |
+
hdroffset = 0
|
| 516 |
+
data += sstruct.pack(Silf_part1_format, self)
|
| 517 |
+
for j in self.jLevels:
|
| 518 |
+
data += sstruct.pack(Silf_justify_format, j)
|
| 519 |
+
data += sstruct.pack(Silf_part2_format, self)
|
| 520 |
+
if self.numCritFeatures:
|
| 521 |
+
data += struct.pack((">%dH" % self.numCritFeaturs), *self.critFeatures)
|
| 522 |
+
data += struct.pack("BB", 0, len(self.scriptTags))
|
| 523 |
+
if len(self.scriptTags):
|
| 524 |
+
tdata = [struct.pack("4s", x.encode("ascii")) for x in self.scriptTags]
|
| 525 |
+
data += b"".join(tdata)
|
| 526 |
+
data += struct.pack(">H", self.lbGID)
|
| 527 |
+
self.passOffset = len(data)
|
| 528 |
+
|
| 529 |
+
data1 = grUtils.bininfo(numPseudo, 6)
|
| 530 |
+
currpos = hdroffset + len(data) + 4 * (self.numPasses + 1)
|
| 531 |
+
self.pseudosOffset = currpos + len(data1)
|
| 532 |
+
for u, p in sorted(self.pMap.items()):
|
| 533 |
+
data1 += struct.pack(
|
| 534 |
+
(">LH" if version >= 3.0 else ">HH"), u, ttFont.getGlyphID(p)
|
| 535 |
+
)
|
| 536 |
+
data1 += self.classes.compile(ttFont, version)
|
| 537 |
+
currpos += len(data1)
|
| 538 |
+
data2 = b""
|
| 539 |
+
datao = b""
|
| 540 |
+
for i, p in enumerate(self.passes):
|
| 541 |
+
base = currpos + len(data2)
|
| 542 |
+
datao += struct.pack(">L", base)
|
| 543 |
+
data2 += p.compile(ttFont, base, version)
|
| 544 |
+
datao += struct.pack(">L", currpos + len(data2))
|
| 545 |
+
|
| 546 |
+
if version >= 3.0:
|
| 547 |
+
data3 = sstruct.pack(Silf_part1_format_v3, self)
|
| 548 |
+
else:
|
| 549 |
+
data3 = b""
|
| 550 |
+
return data3 + data + datao + data1 + data2
|
| 551 |
+
|
| 552 |
+
def toXML(self, writer, ttFont, version=2.0):
|
| 553 |
+
if version >= 3.0:
|
| 554 |
+
writer.simpletag("version", ruleVersion=self.ruleVersion)
|
| 555 |
+
writer.newline()
|
| 556 |
+
writesimple("info", self, writer, *attrs_info)
|
| 557 |
+
writesimple("passindexes", self, writer, *attrs_passindexes)
|
| 558 |
+
writesimple("contexts", self, writer, *attrs_contexts)
|
| 559 |
+
writesimple("attributes", self, writer, *attrs_attributes)
|
| 560 |
+
if len(self.jLevels):
|
| 561 |
+
writer.begintag("justifications")
|
| 562 |
+
writer.newline()
|
| 563 |
+
jformat, jnames, jfixes = sstruct.getformat(Silf_justify_format)
|
| 564 |
+
for i, j in enumerate(self.jLevels):
|
| 565 |
+
attrs = dict([(k, getattr(j, k)) for k in jnames])
|
| 566 |
+
writer.simpletag("justify", **attrs)
|
| 567 |
+
writer.newline()
|
| 568 |
+
writer.endtag("justifications")
|
| 569 |
+
writer.newline()
|
| 570 |
+
if len(self.critFeatures):
|
| 571 |
+
writer.begintag("critFeatures")
|
| 572 |
+
writer.newline()
|
| 573 |
+
writer.write(" ".join(map(str, self.critFeatures)))
|
| 574 |
+
writer.newline()
|
| 575 |
+
writer.endtag("critFeatures")
|
| 576 |
+
writer.newline()
|
| 577 |
+
if len(self.scriptTags):
|
| 578 |
+
writer.begintag("scriptTags")
|
| 579 |
+
writer.newline()
|
| 580 |
+
writer.write(" ".join(self.scriptTags))
|
| 581 |
+
writer.newline()
|
| 582 |
+
writer.endtag("scriptTags")
|
| 583 |
+
writer.newline()
|
| 584 |
+
if self.pMap:
|
| 585 |
+
writer.begintag("pseudoMap")
|
| 586 |
+
writer.newline()
|
| 587 |
+
for k, v in sorted(self.pMap.items()):
|
| 588 |
+
writer.simpletag("pseudo", unicode=hex(k), pseudo=v)
|
| 589 |
+
writer.newline()
|
| 590 |
+
writer.endtag("pseudoMap")
|
| 591 |
+
writer.newline()
|
| 592 |
+
self.classes.toXML(writer, ttFont, version)
|
| 593 |
+
if len(self.passes):
|
| 594 |
+
writer.begintag("passes")
|
| 595 |
+
writer.newline()
|
| 596 |
+
for i, p in enumerate(self.passes):
|
| 597 |
+
writer.begintag("pass", _index=i)
|
| 598 |
+
writer.newline()
|
| 599 |
+
p.toXML(writer, ttFont, version)
|
| 600 |
+
writer.endtag("pass")
|
| 601 |
+
writer.newline()
|
| 602 |
+
writer.endtag("passes")
|
| 603 |
+
writer.newline()
|
| 604 |
+
|
| 605 |
+
def fromXML(self, name, attrs, content, ttFont, version=2.0):
|
| 606 |
+
if name == "version":
|
| 607 |
+
self.ruleVersion = float(safeEval(attrs.get("ruleVersion", "0")))
|
| 608 |
+
if name == "info":
|
| 609 |
+
getSimple(self, attrs, *attrs_info)
|
| 610 |
+
elif name == "passindexes":
|
| 611 |
+
getSimple(self, attrs, *attrs_passindexes)
|
| 612 |
+
elif name == "contexts":
|
| 613 |
+
getSimple(self, attrs, *attrs_contexts)
|
| 614 |
+
elif name == "attributes":
|
| 615 |
+
getSimple(self, attrs, *attrs_attributes)
|
| 616 |
+
elif name == "justifications":
|
| 617 |
+
for element in content:
|
| 618 |
+
if not isinstance(element, tuple):
|
| 619 |
+
continue
|
| 620 |
+
(tag, attrs, subcontent) = element
|
| 621 |
+
if tag == "justify":
|
| 622 |
+
j = _Object()
|
| 623 |
+
for k, v in attrs.items():
|
| 624 |
+
setattr(j, k, int(v))
|
| 625 |
+
self.jLevels.append(j)
|
| 626 |
+
elif name == "critFeatures":
|
| 627 |
+
self.critFeatures = []
|
| 628 |
+
element = content_string(content)
|
| 629 |
+
self.critFeatures.extend(map(int, element.split()))
|
| 630 |
+
elif name == "scriptTags":
|
| 631 |
+
self.scriptTags = []
|
| 632 |
+
element = content_string(content)
|
| 633 |
+
for n in element.split():
|
| 634 |
+
self.scriptTags.append(n)
|
| 635 |
+
elif name == "pseudoMap":
|
| 636 |
+
self.pMap = {}
|
| 637 |
+
for element in content:
|
| 638 |
+
if not isinstance(element, tuple):
|
| 639 |
+
continue
|
| 640 |
+
(tag, attrs, subcontent) = element
|
| 641 |
+
if tag == "pseudo":
|
| 642 |
+
k = int(attrs["unicode"], 16)
|
| 643 |
+
v = attrs["pseudo"]
|
| 644 |
+
self.pMap[k] = v
|
| 645 |
+
elif name == "classes":
|
| 646 |
+
self.classes = Classes()
|
| 647 |
+
for element in content:
|
| 648 |
+
if not isinstance(element, tuple):
|
| 649 |
+
continue
|
| 650 |
+
tag, attrs, subcontent = element
|
| 651 |
+
self.classes.fromXML(tag, attrs, subcontent, ttFont, version)
|
| 652 |
+
elif name == "passes":
|
| 653 |
+
for element in content:
|
| 654 |
+
if not isinstance(element, tuple):
|
| 655 |
+
continue
|
| 656 |
+
tag, attrs, subcontent = element
|
| 657 |
+
if tag == "pass":
|
| 658 |
+
p = Pass()
|
| 659 |
+
for e in subcontent:
|
| 660 |
+
if not isinstance(e, tuple):
|
| 661 |
+
continue
|
| 662 |
+
p.fromXML(e[0], e[1], e[2], ttFont, version)
|
| 663 |
+
self.passes.append(p)
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
class Classes(object):
|
| 667 |
+
def __init__(self):
|
| 668 |
+
self.linear = []
|
| 669 |
+
self.nonLinear = []
|
| 670 |
+
|
| 671 |
+
def decompile(self, data, ttFont, version=2.0):
|
| 672 |
+
sstruct.unpack2(Silf_classmap_format, data, self)
|
| 673 |
+
if version >= 4.0:
|
| 674 |
+
oClasses = struct.unpack(
|
| 675 |
+
(">%dL" % (self.numClass + 1)), data[4 : 8 + 4 * self.numClass]
|
| 676 |
+
)
|
| 677 |
+
else:
|
| 678 |
+
oClasses = struct.unpack(
|
| 679 |
+
(">%dH" % (self.numClass + 1)), data[4 : 6 + 2 * self.numClass]
|
| 680 |
+
)
|
| 681 |
+
for s, e in zip(oClasses[: self.numLinear], oClasses[1 : self.numLinear + 1]):
|
| 682 |
+
self.linear.append(
|
| 683 |
+
ttFont.getGlyphName(x)
|
| 684 |
+
for x in struct.unpack((">%dH" % ((e - s) / 2)), data[s:e])
|
| 685 |
+
)
|
| 686 |
+
for s, e in zip(
|
| 687 |
+
oClasses[self.numLinear : self.numClass],
|
| 688 |
+
oClasses[self.numLinear + 1 : self.numClass + 1],
|
| 689 |
+
):
|
| 690 |
+
nonLinids = [
|
| 691 |
+
struct.unpack(">HH", data[x : x + 4]) for x in range(s + 8, e, 4)
|
| 692 |
+
]
|
| 693 |
+
nonLin = dict([(ttFont.getGlyphName(x[0]), x[1]) for x in nonLinids])
|
| 694 |
+
self.nonLinear.append(nonLin)
|
| 695 |
+
|
| 696 |
+
def compile(self, ttFont, version=2.0):
|
| 697 |
+
data = b""
|
| 698 |
+
oClasses = []
|
| 699 |
+
if version >= 4.0:
|
| 700 |
+
offset = 8 + 4 * (len(self.linear) + len(self.nonLinear))
|
| 701 |
+
else:
|
| 702 |
+
offset = 6 + 2 * (len(self.linear) + len(self.nonLinear))
|
| 703 |
+
for l in self.linear:
|
| 704 |
+
oClasses.append(len(data) + offset)
|
| 705 |
+
gs = [ttFont.getGlyphID(x) for x in l]
|
| 706 |
+
data += struct.pack((">%dH" % len(l)), *gs)
|
| 707 |
+
for l in self.nonLinear:
|
| 708 |
+
oClasses.append(len(data) + offset)
|
| 709 |
+
gs = [(ttFont.getGlyphID(x[0]), x[1]) for x in l.items()]
|
| 710 |
+
data += grUtils.bininfo(len(gs))
|
| 711 |
+
data += b"".join([struct.pack(">HH", *x) for x in sorted(gs)])
|
| 712 |
+
oClasses.append(len(data) + offset)
|
| 713 |
+
self.numClass = len(oClasses) - 1
|
| 714 |
+
self.numLinear = len(self.linear)
|
| 715 |
+
return (
|
| 716 |
+
sstruct.pack(Silf_classmap_format, self)
|
| 717 |
+
+ struct.pack(
|
| 718 |
+
((">%dL" if version >= 4.0 else ">%dH") % len(oClasses)), *oClasses
|
| 719 |
+
)
|
| 720 |
+
+ data
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
def toXML(self, writer, ttFont, version=2.0):
|
| 724 |
+
writer.begintag("classes")
|
| 725 |
+
writer.newline()
|
| 726 |
+
writer.begintag("linearClasses")
|
| 727 |
+
writer.newline()
|
| 728 |
+
for i, l in enumerate(self.linear):
|
| 729 |
+
writer.begintag("linear", _index=i)
|
| 730 |
+
writer.newline()
|
| 731 |
+
wrapline(writer, l)
|
| 732 |
+
writer.endtag("linear")
|
| 733 |
+
writer.newline()
|
| 734 |
+
writer.endtag("linearClasses")
|
| 735 |
+
writer.newline()
|
| 736 |
+
writer.begintag("nonLinearClasses")
|
| 737 |
+
writer.newline()
|
| 738 |
+
for i, l in enumerate(self.nonLinear):
|
| 739 |
+
writer.begintag("nonLinear", _index=i + self.numLinear)
|
| 740 |
+
writer.newline()
|
| 741 |
+
for inp, ind in l.items():
|
| 742 |
+
writer.simpletag("map", glyph=inp, index=ind)
|
| 743 |
+
writer.newline()
|
| 744 |
+
writer.endtag("nonLinear")
|
| 745 |
+
writer.newline()
|
| 746 |
+
writer.endtag("nonLinearClasses")
|
| 747 |
+
writer.newline()
|
| 748 |
+
writer.endtag("classes")
|
| 749 |
+
writer.newline()
|
| 750 |
+
|
| 751 |
+
def fromXML(self, name, attrs, content, ttFont, version=2.0):
|
| 752 |
+
if name == "linearClasses":
|
| 753 |
+
for element in content:
|
| 754 |
+
if not isinstance(element, tuple):
|
| 755 |
+
continue
|
| 756 |
+
tag, attrs, subcontent = element
|
| 757 |
+
if tag == "linear":
|
| 758 |
+
l = content_string(subcontent).split()
|
| 759 |
+
self.linear.append(l)
|
| 760 |
+
elif name == "nonLinearClasses":
|
| 761 |
+
for element in content:
|
| 762 |
+
if not isinstance(element, tuple):
|
| 763 |
+
continue
|
| 764 |
+
tag, attrs, subcontent = element
|
| 765 |
+
if tag == "nonLinear":
|
| 766 |
+
l = {}
|
| 767 |
+
for e in subcontent:
|
| 768 |
+
if not isinstance(e, tuple):
|
| 769 |
+
continue
|
| 770 |
+
tag, attrs, subsubcontent = e
|
| 771 |
+
if tag == "map":
|
| 772 |
+
l[attrs["glyph"]] = int(safeEval(attrs["index"]))
|
| 773 |
+
self.nonLinear.append(l)
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
class Pass(object):
|
| 777 |
+
def __init__(self):
|
| 778 |
+
self.colMap = {}
|
| 779 |
+
self.rules = []
|
| 780 |
+
self.rulePreContexts = []
|
| 781 |
+
self.ruleSortKeys = []
|
| 782 |
+
self.ruleConstraints = []
|
| 783 |
+
self.passConstraints = b""
|
| 784 |
+
self.actions = []
|
| 785 |
+
self.stateTrans = []
|
| 786 |
+
self.startStates = []
|
| 787 |
+
|
| 788 |
+
def decompile(self, data, ttFont, version=2.0):
|
| 789 |
+
_, data = sstruct.unpack2(Silf_pass_format, data, self)
|
| 790 |
+
(numRange, _, _, _) = struct.unpack(">4H", data[:8])
|
| 791 |
+
data = data[8:]
|
| 792 |
+
for i in range(numRange):
|
| 793 |
+
(first, last, col) = struct.unpack(">3H", data[6 * i : 6 * i + 6])
|
| 794 |
+
for g in range(first, last + 1):
|
| 795 |
+
self.colMap[ttFont.getGlyphName(g)] = col
|
| 796 |
+
data = data[6 * numRange :]
|
| 797 |
+
oRuleMap = struct.unpack_from((">%dH" % (self.numSuccess + 1)), data)
|
| 798 |
+
data = data[2 + 2 * self.numSuccess :]
|
| 799 |
+
rules = struct.unpack_from((">%dH" % oRuleMap[-1]), data)
|
| 800 |
+
self.rules = [rules[s:e] for (s, e) in zip(oRuleMap, oRuleMap[1:])]
|
| 801 |
+
data = data[2 * oRuleMap[-1] :]
|
| 802 |
+
(self.minRulePreContext, self.maxRulePreContext) = struct.unpack("BB", data[:2])
|
| 803 |
+
numStartStates = self.maxRulePreContext - self.minRulePreContext + 1
|
| 804 |
+
self.startStates = struct.unpack(
|
| 805 |
+
(">%dH" % numStartStates), data[2 : 2 + numStartStates * 2]
|
| 806 |
+
)
|
| 807 |
+
data = data[2 + numStartStates * 2 :]
|
| 808 |
+
self.ruleSortKeys = struct.unpack(
|
| 809 |
+
(">%dH" % self.numRules), data[: 2 * self.numRules]
|
| 810 |
+
)
|
| 811 |
+
data = data[2 * self.numRules :]
|
| 812 |
+
self.rulePreContexts = struct.unpack(
|
| 813 |
+
("%dB" % self.numRules), data[: self.numRules]
|
| 814 |
+
)
|
| 815 |
+
data = data[self.numRules :]
|
| 816 |
+
(self.collisionThreshold, pConstraint) = struct.unpack(">BH", data[:3])
|
| 817 |
+
oConstraints = list(
|
| 818 |
+
struct.unpack(
|
| 819 |
+
(">%dH" % (self.numRules + 1)), data[3 : 5 + self.numRules * 2]
|
| 820 |
+
)
|
| 821 |
+
)
|
| 822 |
+
data = data[5 + self.numRules * 2 :]
|
| 823 |
+
oActions = list(
|
| 824 |
+
struct.unpack((">%dH" % (self.numRules + 1)), data[: 2 + self.numRules * 2])
|
| 825 |
+
)
|
| 826 |
+
data = data[2 * self.numRules + 2 :]
|
| 827 |
+
for i in range(self.numTransitional):
|
| 828 |
+
a = array(
|
| 829 |
+
"H", data[i * self.numColumns * 2 : (i + 1) * self.numColumns * 2]
|
| 830 |
+
)
|
| 831 |
+
if sys.byteorder != "big":
|
| 832 |
+
a.byteswap()
|
| 833 |
+
self.stateTrans.append(a)
|
| 834 |
+
data = data[self.numTransitional * self.numColumns * 2 + 1 :]
|
| 835 |
+
self.passConstraints = data[:pConstraint]
|
| 836 |
+
data = data[pConstraint:]
|
| 837 |
+
for i in range(len(oConstraints) - 2, -1, -1):
|
| 838 |
+
if oConstraints[i] == 0:
|
| 839 |
+
oConstraints[i] = oConstraints[i + 1]
|
| 840 |
+
self.ruleConstraints = [
|
| 841 |
+
(data[s:e] if (e - s > 1) else b"")
|
| 842 |
+
for (s, e) in zip(oConstraints, oConstraints[1:])
|
| 843 |
+
]
|
| 844 |
+
data = data[oConstraints[-1] :]
|
| 845 |
+
self.actions = [
|
| 846 |
+
(data[s:e] if (e - s > 1) else "") for (s, e) in zip(oActions, oActions[1:])
|
| 847 |
+
]
|
| 848 |
+
data = data[oActions[-1] :]
|
| 849 |
+
# not using debug
|
| 850 |
+
|
| 851 |
+
def compile(self, ttFont, base, version=2.0):
|
| 852 |
+
# build it all up backwards
|
| 853 |
+
oActions = reduce(
|
| 854 |
+
lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.actions + [b""], (0, [])
|
| 855 |
+
)[1]
|
| 856 |
+
oConstraints = reduce(
|
| 857 |
+
lambda a, x: (a[0] + len(x), a[1] + [a[0]]),
|
| 858 |
+
self.ruleConstraints + [b""],
|
| 859 |
+
(1, []),
|
| 860 |
+
)[1]
|
| 861 |
+
constraintCode = b"\000" + b"".join(self.ruleConstraints)
|
| 862 |
+
transes = []
|
| 863 |
+
for t in self.stateTrans:
|
| 864 |
+
if sys.byteorder != "big":
|
| 865 |
+
t.byteswap()
|
| 866 |
+
transes.append(t.tobytes())
|
| 867 |
+
if sys.byteorder != "big":
|
| 868 |
+
t.byteswap()
|
| 869 |
+
if not len(transes):
|
| 870 |
+
self.startStates = [0]
|
| 871 |
+
oRuleMap = reduce(
|
| 872 |
+
lambda a, x: (a[0] + len(x), a[1] + [a[0]]), self.rules + [[]], (0, [])
|
| 873 |
+
)[1]
|
| 874 |
+
passRanges = []
|
| 875 |
+
gidcolmap = dict([(ttFont.getGlyphID(x[0]), x[1]) for x in self.colMap.items()])
|
| 876 |
+
for e in grUtils.entries(gidcolmap, sameval=True):
|
| 877 |
+
if e[1]:
|
| 878 |
+
passRanges.append((e[0], e[0] + e[1] - 1, e[2][0]))
|
| 879 |
+
self.numRules = len(self.actions)
|
| 880 |
+
self.fsmOffset = (
|
| 881 |
+
sstruct.calcsize(Silf_pass_format)
|
| 882 |
+
+ 8
|
| 883 |
+
+ len(passRanges) * 6
|
| 884 |
+
+ len(oRuleMap) * 2
|
| 885 |
+
+ 2 * oRuleMap[-1]
|
| 886 |
+
+ 2
|
| 887 |
+
+ 2 * len(self.startStates)
|
| 888 |
+
+ 3 * self.numRules
|
| 889 |
+
+ 3
|
| 890 |
+
+ 4 * self.numRules
|
| 891 |
+
+ 4
|
| 892 |
+
)
|
| 893 |
+
self.pcCode = (
|
| 894 |
+
self.fsmOffset + 2 * self.numTransitional * self.numColumns + 1 + base
|
| 895 |
+
)
|
| 896 |
+
self.rcCode = self.pcCode + len(self.passConstraints)
|
| 897 |
+
self.aCode = self.rcCode + len(constraintCode)
|
| 898 |
+
self.oDebug = 0
|
| 899 |
+
# now generate output
|
| 900 |
+
data = sstruct.pack(Silf_pass_format, self)
|
| 901 |
+
data += grUtils.bininfo(len(passRanges), 6)
|
| 902 |
+
data += b"".join(struct.pack(">3H", *p) for p in passRanges)
|
| 903 |
+
data += struct.pack((">%dH" % len(oRuleMap)), *oRuleMap)
|
| 904 |
+
flatrules = reduce(lambda a, x: a + x, self.rules, [])
|
| 905 |
+
data += struct.pack((">%dH" % oRuleMap[-1]), *flatrules)
|
| 906 |
+
data += struct.pack("BB", self.minRulePreContext, self.maxRulePreContext)
|
| 907 |
+
data += struct.pack((">%dH" % len(self.startStates)), *self.startStates)
|
| 908 |
+
data += struct.pack((">%dH" % self.numRules), *self.ruleSortKeys)
|
| 909 |
+
data += struct.pack(("%dB" % self.numRules), *self.rulePreContexts)
|
| 910 |
+
data += struct.pack(">BH", self.collisionThreshold, len(self.passConstraints))
|
| 911 |
+
data += struct.pack((">%dH" % (self.numRules + 1)), *oConstraints)
|
| 912 |
+
data += struct.pack((">%dH" % (self.numRules + 1)), *oActions)
|
| 913 |
+
return (
|
| 914 |
+
data
|
| 915 |
+
+ b"".join(transes)
|
| 916 |
+
+ struct.pack("B", 0)
|
| 917 |
+
+ self.passConstraints
|
| 918 |
+
+ constraintCode
|
| 919 |
+
+ b"".join(self.actions)
|
| 920 |
+
)
|
| 921 |
+
|
| 922 |
+
def toXML(self, writer, ttFont, version=2.0):
|
| 923 |
+
writesimple("info", self, writer, *pass_attrs_info)
|
| 924 |
+
writesimple("fsminfo", self, writer, *pass_attrs_fsm)
|
| 925 |
+
writer.begintag("colmap")
|
| 926 |
+
writer.newline()
|
| 927 |
+
wrapline(
|
| 928 |
+
writer,
|
| 929 |
+
[
|
| 930 |
+
"{}={}".format(*x)
|
| 931 |
+
for x in sorted(
|
| 932 |
+
self.colMap.items(), key=lambda x: ttFont.getGlyphID(x[0])
|
| 933 |
+
)
|
| 934 |
+
],
|
| 935 |
+
)
|
| 936 |
+
writer.endtag("colmap")
|
| 937 |
+
writer.newline()
|
| 938 |
+
writer.begintag("staterulemap")
|
| 939 |
+
writer.newline()
|
| 940 |
+
for i, r in enumerate(self.rules):
|
| 941 |
+
writer.simpletag(
|
| 942 |
+
"state",
|
| 943 |
+
number=self.numRows - self.numSuccess + i,
|
| 944 |
+
rules=" ".join(map(str, r)),
|
| 945 |
+
)
|
| 946 |
+
writer.newline()
|
| 947 |
+
writer.endtag("staterulemap")
|
| 948 |
+
writer.newline()
|
| 949 |
+
writer.begintag("rules")
|
| 950 |
+
writer.newline()
|
| 951 |
+
for i, action in enumerate(self.actions):
|
| 952 |
+
writer.begintag(
|
| 953 |
+
"rule",
|
| 954 |
+
index=i,
|
| 955 |
+
precontext=self.rulePreContexts[i],
|
| 956 |
+
sortkey=self.ruleSortKeys[i],
|
| 957 |
+
)
|
| 958 |
+
writer.newline()
|
| 959 |
+
if len(self.ruleConstraints[i]):
|
| 960 |
+
writecode("constraint", writer, self.ruleConstraints[i])
|
| 961 |
+
writecode("action", writer, action)
|
| 962 |
+
writer.endtag("rule")
|
| 963 |
+
writer.newline()
|
| 964 |
+
writer.endtag("rules")
|
| 965 |
+
writer.newline()
|
| 966 |
+
if len(self.passConstraints):
|
| 967 |
+
writecode("passConstraint", writer, self.passConstraints)
|
| 968 |
+
if len(self.stateTrans):
|
| 969 |
+
writer.begintag("fsm")
|
| 970 |
+
writer.newline()
|
| 971 |
+
writer.begintag("starts")
|
| 972 |
+
writer.write(" ".join(map(str, self.startStates)))
|
| 973 |
+
writer.endtag("starts")
|
| 974 |
+
writer.newline()
|
| 975 |
+
for i, s in enumerate(self.stateTrans):
|
| 976 |
+
writer.begintag("row", _i=i)
|
| 977 |
+
# no newlines here
|
| 978 |
+
writer.write(" ".join(map(str, s)))
|
| 979 |
+
writer.endtag("row")
|
| 980 |
+
writer.newline()
|
| 981 |
+
writer.endtag("fsm")
|
| 982 |
+
writer.newline()
|
| 983 |
+
|
| 984 |
+
def fromXML(self, name, attrs, content, ttFont, version=2.0):
|
| 985 |
+
if name == "info":
|
| 986 |
+
getSimple(self, attrs, *pass_attrs_info)
|
| 987 |
+
elif name == "fsminfo":
|
| 988 |
+
getSimple(self, attrs, *pass_attrs_fsm)
|
| 989 |
+
elif name == "colmap":
|
| 990 |
+
e = content_string(content)
|
| 991 |
+
for w in e.split():
|
| 992 |
+
x = w.split("=")
|
| 993 |
+
if len(x) != 2 or x[0] == "" or x[1] == "":
|
| 994 |
+
continue
|
| 995 |
+
self.colMap[x[0]] = int(x[1])
|
| 996 |
+
elif name == "staterulemap":
|
| 997 |
+
for e in content:
|
| 998 |
+
if not isinstance(e, tuple):
|
| 999 |
+
continue
|
| 1000 |
+
tag, a, c = e
|
| 1001 |
+
if tag == "state":
|
| 1002 |
+
self.rules.append([int(x) for x in a["rules"].split(" ")])
|
| 1003 |
+
elif name == "rules":
|
| 1004 |
+
for element in content:
|
| 1005 |
+
if not isinstance(element, tuple):
|
| 1006 |
+
continue
|
| 1007 |
+
tag, a, c = element
|
| 1008 |
+
if tag != "rule":
|
| 1009 |
+
continue
|
| 1010 |
+
self.rulePreContexts.append(int(a["precontext"]))
|
| 1011 |
+
self.ruleSortKeys.append(int(a["sortkey"]))
|
| 1012 |
+
con = b""
|
| 1013 |
+
act = b""
|
| 1014 |
+
for e in c:
|
| 1015 |
+
if not isinstance(e, tuple):
|
| 1016 |
+
continue
|
| 1017 |
+
tag, a, subc = e
|
| 1018 |
+
if tag == "constraint":
|
| 1019 |
+
con = readcode(subc)
|
| 1020 |
+
elif tag == "action":
|
| 1021 |
+
act = readcode(subc)
|
| 1022 |
+
self.actions.append(act)
|
| 1023 |
+
self.ruleConstraints.append(con)
|
| 1024 |
+
elif name == "passConstraint":
|
| 1025 |
+
self.passConstraints = readcode(content)
|
| 1026 |
+
elif name == "fsm":
|
| 1027 |
+
for element in content:
|
| 1028 |
+
if not isinstance(element, tuple):
|
| 1029 |
+
continue
|
| 1030 |
+
tag, a, c = element
|
| 1031 |
+
if tag == "row":
|
| 1032 |
+
s = array("H")
|
| 1033 |
+
e = content_string(c)
|
| 1034 |
+
s.extend(map(int, e.split()))
|
| 1035 |
+
self.stateTrans.append(s)
|
| 1036 |
+
elif tag == "starts":
|
| 1037 |
+
s = []
|
| 1038 |
+
e = content_string(c)
|
| 1039 |
+
s.extend(map(int, e.split()))
|
| 1040 |
+
self.startStates = s
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/S__i_l_l.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc import sstruct
|
| 2 |
+
from fontTools.misc.fixedTools import floatToFixedToStr
|
| 3 |
+
from fontTools.misc.textTools import safeEval
|
| 4 |
+
from . import DefaultTable
|
| 5 |
+
from . import grUtils
|
| 6 |
+
import struct
|
| 7 |
+
|
| 8 |
+
Sill_hdr = """
|
| 9 |
+
>
|
| 10 |
+
version: 16.16F
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class table_S__i_l_l(DefaultTable.DefaultTable):
|
| 15 |
+
"""Graphite Languages table
|
| 16 |
+
|
| 17 |
+
See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, tag=None):
|
| 21 |
+
DefaultTable.DefaultTable.__init__(self, tag)
|
| 22 |
+
self.langs = {}
|
| 23 |
+
|
| 24 |
+
def decompile(self, data, ttFont):
|
| 25 |
+
(_, data) = sstruct.unpack2(Sill_hdr, data, self)
|
| 26 |
+
self.version = float(floatToFixedToStr(self.version, precisionBits=16))
|
| 27 |
+
(numLangs,) = struct.unpack(">H", data[:2])
|
| 28 |
+
data = data[8:]
|
| 29 |
+
maxsetting = 0
|
| 30 |
+
langinfo = []
|
| 31 |
+
for i in range(numLangs):
|
| 32 |
+
(langcode, numsettings, offset) = struct.unpack(
|
| 33 |
+
">4sHH", data[i * 8 : (i + 1) * 8]
|
| 34 |
+
)
|
| 35 |
+
offset = int(offset / 8) - (numLangs + 1)
|
| 36 |
+
langcode = langcode.replace(b"\000", b"")
|
| 37 |
+
langinfo.append((langcode.decode("utf-8"), numsettings, offset))
|
| 38 |
+
maxsetting = max(maxsetting, offset + numsettings)
|
| 39 |
+
data = data[numLangs * 8 :]
|
| 40 |
+
finfo = []
|
| 41 |
+
for i in range(maxsetting):
|
| 42 |
+
(fid, val, _) = struct.unpack(">LHH", data[i * 8 : (i + 1) * 8])
|
| 43 |
+
finfo.append((fid, val))
|
| 44 |
+
self.langs = {}
|
| 45 |
+
for c, n, o in langinfo:
|
| 46 |
+
self.langs[c] = []
|
| 47 |
+
for i in range(o, o + n):
|
| 48 |
+
self.langs[c].append(finfo[i])
|
| 49 |
+
|
| 50 |
+
def compile(self, ttFont):
|
| 51 |
+
ldat = b""
|
| 52 |
+
fdat = b""
|
| 53 |
+
offset = len(self.langs)
|
| 54 |
+
for c, inf in sorted(self.langs.items()):
|
| 55 |
+
ldat += struct.pack(">4sHH", c.encode("utf8"), len(inf), 8 * offset + 20)
|
| 56 |
+
for fid, val in inf:
|
| 57 |
+
fdat += struct.pack(">LHH", fid, val, 0)
|
| 58 |
+
offset += len(inf)
|
| 59 |
+
ldat += struct.pack(">LHH", 0x80808080, 0, 8 * offset + 20)
|
| 60 |
+
return (
|
| 61 |
+
sstruct.pack(Sill_hdr, self)
|
| 62 |
+
+ grUtils.bininfo(len(self.langs))
|
| 63 |
+
+ ldat
|
| 64 |
+
+ fdat
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def toXML(self, writer, ttFont):
|
| 68 |
+
writer.simpletag("version", version=self.version)
|
| 69 |
+
writer.newline()
|
| 70 |
+
for c, inf in sorted(self.langs.items()):
|
| 71 |
+
writer.begintag("lang", name=c)
|
| 72 |
+
writer.newline()
|
| 73 |
+
for fid, val in inf:
|
| 74 |
+
writer.simpletag("feature", fid=grUtils.num2tag(fid), val=val)
|
| 75 |
+
writer.newline()
|
| 76 |
+
writer.endtag("lang")
|
| 77 |
+
writer.newline()
|
| 78 |
+
|
| 79 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 80 |
+
if name == "version":
|
| 81 |
+
self.version = float(safeEval(attrs["version"]))
|
| 82 |
+
elif name == "lang":
|
| 83 |
+
c = attrs["name"]
|
| 84 |
+
self.langs[c] = []
|
| 85 |
+
for element in content:
|
| 86 |
+
if not isinstance(element, tuple):
|
| 87 |
+
continue
|
| 88 |
+
tag, a, subcontent = element
|
| 89 |
+
if tag == "feature":
|
| 90 |
+
self.langs[c].append(
|
| 91 |
+
(grUtils.tag2num(a["fid"]), int(safeEval(a["val"])))
|
| 92 |
+
)
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_B_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its table source data.
|
| 3 |
+
|
| 4 |
+
TSIB contains the source text for the ``BASE`` table.
|
| 5 |
+
|
| 6 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from .T_S_I_V_ import table_T_S_I_V_
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class table_T_S_I_B_(table_T_S_I_V_):
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_C_.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its table source data.
|
| 3 |
+
|
| 4 |
+
TSIC contains the source text for the Variation CVT window and data for
|
| 5 |
+
the ``cvar`` table.
|
| 6 |
+
|
| 7 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from .otBase import BaseTTXConverter
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class table_T_S_I_C_(BaseTTXConverter):
|
| 14 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_D_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its table source data.
|
| 3 |
+
|
| 4 |
+
TSID contains the source text for the ``GDEF`` table.
|
| 5 |
+
|
| 6 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from .T_S_I_V_ import table_T_S_I_V_
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class table_T_S_I_D_(table_T_S_I_V_):
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_J_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its table source data.
|
| 3 |
+
|
| 4 |
+
TSIJ contains the source text for the ``JSTF`` table.
|
| 5 |
+
|
| 6 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from .T_S_I_V_ import table_T_S_I_V_
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class table_T_S_I_J_(table_T_S_I_V_):
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_P_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its table source data.
|
| 3 |
+
|
| 4 |
+
TSIP contains the source text for the ``GPOS`` table.
|
| 5 |
+
|
| 6 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from .T_S_I_V_ import table_T_S_I_V_
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class table_T_S_I_P_(table_T_S_I_V_):
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_S_.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its table source data.
|
| 3 |
+
|
| 4 |
+
TSIS contains the source text for the ``GSUB`` table.
|
| 5 |
+
|
| 6 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from .T_S_I_V_ import table_T_S_I_V_
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class table_T_S_I_S_(table_T_S_I_V_):
|
| 13 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I_V_.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{B,C,D,J,P,S,V} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its table source data.
|
| 3 |
+
|
| 4 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from fontTools.misc.textTools import strjoin, tobytes, tostr
|
| 8 |
+
from . import asciiTable
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class table_T_S_I_V_(asciiTable.asciiTable):
|
| 12 |
+
def toXML(self, writer, ttFont):
|
| 13 |
+
data = tostr(self.data)
|
| 14 |
+
# removing null bytes. XXX needed??
|
| 15 |
+
data = data.split("\0")
|
| 16 |
+
data = strjoin(data)
|
| 17 |
+
writer.begintag("source")
|
| 18 |
+
writer.newline()
|
| 19 |
+
writer.write_noindent(data.replace("\r", "\n"))
|
| 20 |
+
writer.newline()
|
| 21 |
+
writer.endtag("source")
|
| 22 |
+
writer.newline()
|
| 23 |
+
|
| 24 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 25 |
+
lines = strjoin(content).split("\n")
|
| 26 |
+
self.data = tobytes("\r".join(lines[1:-1]))
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__0.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its hinting source data.
|
| 3 |
+
|
| 4 |
+
TSI0 is the index table containing the lengths and offsets for the glyph
|
| 5 |
+
programs and 'extra' programs ('fpgm', 'prep', and 'cvt') that are contained
|
| 6 |
+
in the TSI1 table.
|
| 7 |
+
|
| 8 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import logging
|
| 12 |
+
import struct
|
| 13 |
+
|
| 14 |
+
from . import DefaultTable
|
| 15 |
+
|
| 16 |
+
log = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
tsi0Format = ">HHL"
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def fixlongs(glyphID, textLength, textOffset):
|
| 22 |
+
return int(glyphID), int(textLength), textOffset
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class table_T_S_I__0(DefaultTable.DefaultTable):
|
| 26 |
+
dependencies = ["TSI1"]
|
| 27 |
+
|
| 28 |
+
def decompile(self, data, ttFont):
|
| 29 |
+
numGlyphs = ttFont["maxp"].numGlyphs
|
| 30 |
+
indices = []
|
| 31 |
+
size = struct.calcsize(tsi0Format)
|
| 32 |
+
numEntries = len(data) // size
|
| 33 |
+
if numEntries != numGlyphs + 5:
|
| 34 |
+
diff = numEntries - numGlyphs - 5
|
| 35 |
+
log.warning(
|
| 36 |
+
"Number of glyphPrograms differs from the number of glyphs in the font "
|
| 37 |
+
f"by {abs(diff)} ({numEntries - 5} programs vs. {numGlyphs} glyphs)."
|
| 38 |
+
)
|
| 39 |
+
for _ in range(numEntries):
|
| 40 |
+
glyphID, textLength, textOffset = fixlongs(
|
| 41 |
+
*struct.unpack(tsi0Format, data[:size])
|
| 42 |
+
)
|
| 43 |
+
indices.append((glyphID, textLength, textOffset))
|
| 44 |
+
data = data[size:]
|
| 45 |
+
assert len(data) == 0
|
| 46 |
+
assert indices[-5] == (0xFFFE, 0, 0xABFC1F34), "bad magic number"
|
| 47 |
+
self.indices = indices[:-5]
|
| 48 |
+
self.extra_indices = indices[-4:]
|
| 49 |
+
|
| 50 |
+
def compile(self, ttFont):
|
| 51 |
+
if not hasattr(self, "indices"):
|
| 52 |
+
# We have no corresponding table (TSI1 or TSI3); let's return
|
| 53 |
+
# no data, which effectively means "ignore us".
|
| 54 |
+
return b""
|
| 55 |
+
data = b""
|
| 56 |
+
for index, textLength, textOffset in self.indices:
|
| 57 |
+
data = data + struct.pack(tsi0Format, index, textLength, textOffset)
|
| 58 |
+
data = data + struct.pack(tsi0Format, 0xFFFE, 0, 0xABFC1F34)
|
| 59 |
+
for index, textLength, textOffset in self.extra_indices:
|
| 60 |
+
data = data + struct.pack(tsi0Format, index, textLength, textOffset)
|
| 61 |
+
return data
|
| 62 |
+
|
| 63 |
+
def set(self, indices, extra_indices):
|
| 64 |
+
# gets called by 'TSI1' or 'TSI3'
|
| 65 |
+
self.indices = indices
|
| 66 |
+
self.extra_indices = extra_indices
|
| 67 |
+
|
| 68 |
+
def toXML(self, writer, ttFont):
|
| 69 |
+
writer.comment("This table will be calculated by the compiler")
|
| 70 |
+
writer.newline()
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__1.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its hinting source data.
|
| 3 |
+
|
| 4 |
+
TSI1 contains the text of the glyph programs in the form of low-level assembly
|
| 5 |
+
code, as well as the 'extra' programs 'fpgm', 'ppgm' (i.e. 'prep'), and 'cvt'.
|
| 6 |
+
|
| 7 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from . import DefaultTable
|
| 11 |
+
from fontTools.misc.loggingTools import LogMixin
|
| 12 |
+
from fontTools.misc.textTools import strjoin, tobytes, tostr
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class table_T_S_I__1(LogMixin, DefaultTable.DefaultTable):
|
| 16 |
+
extras = {0xFFFA: "ppgm", 0xFFFB: "cvt", 0xFFFC: "reserved", 0xFFFD: "fpgm"}
|
| 17 |
+
|
| 18 |
+
indextable = "TSI0"
|
| 19 |
+
|
| 20 |
+
def decompile(self, data, ttFont):
|
| 21 |
+
totalLength = len(data)
|
| 22 |
+
indextable = ttFont[self.indextable]
|
| 23 |
+
for indices, isExtra in zip(
|
| 24 |
+
(indextable.indices, indextable.extra_indices), (False, True)
|
| 25 |
+
):
|
| 26 |
+
programs = {}
|
| 27 |
+
for i, (glyphID, textLength, textOffset) in enumerate(indices):
|
| 28 |
+
if isExtra:
|
| 29 |
+
name = self.extras[glyphID]
|
| 30 |
+
else:
|
| 31 |
+
name = ttFont.getGlyphName(glyphID)
|
| 32 |
+
if textOffset > totalLength:
|
| 33 |
+
self.log.warning("textOffset > totalLength; %r skipped" % name)
|
| 34 |
+
continue
|
| 35 |
+
if textLength < 0x8000:
|
| 36 |
+
# If the length stored in the record is less than 32768, then use
|
| 37 |
+
# that as the length of the record.
|
| 38 |
+
pass
|
| 39 |
+
elif textLength == 0x8000:
|
| 40 |
+
# If the length is 32768, compute the actual length as follows:
|
| 41 |
+
isLast = i == (len(indices) - 1)
|
| 42 |
+
if isLast:
|
| 43 |
+
if isExtra:
|
| 44 |
+
# For the last "extra" record (the very last record of the
|
| 45 |
+
# table), the length is the difference between the total
|
| 46 |
+
# length of the TSI1 table and the textOffset of the final
|
| 47 |
+
# record.
|
| 48 |
+
nextTextOffset = totalLength
|
| 49 |
+
else:
|
| 50 |
+
# For the last "normal" record (the last record just prior
|
| 51 |
+
# to the record containing the "magic number"), the length
|
| 52 |
+
# is the difference between the textOffset of the record
|
| 53 |
+
# following the "magic number" (0xFFFE) record (i.e. the
|
| 54 |
+
# first "extra" record), and the textOffset of the last
|
| 55 |
+
# "normal" record.
|
| 56 |
+
nextTextOffset = indextable.extra_indices[0][2]
|
| 57 |
+
else:
|
| 58 |
+
# For all other records with a length of 0x8000, the length is
|
| 59 |
+
# the difference between the textOffset of the record in
|
| 60 |
+
# question and the textOffset of the next record.
|
| 61 |
+
nextTextOffset = indices[i + 1][2]
|
| 62 |
+
assert nextTextOffset >= textOffset, "entries not sorted by offset"
|
| 63 |
+
if nextTextOffset > totalLength:
|
| 64 |
+
self.log.warning(
|
| 65 |
+
"nextTextOffset > totalLength; %r truncated" % name
|
| 66 |
+
)
|
| 67 |
+
nextTextOffset = totalLength
|
| 68 |
+
textLength = nextTextOffset - textOffset
|
| 69 |
+
else:
|
| 70 |
+
from fontTools import ttLib
|
| 71 |
+
|
| 72 |
+
raise ttLib.TTLibError(
|
| 73 |
+
"%r textLength (%d) must not be > 32768" % (name, textLength)
|
| 74 |
+
)
|
| 75 |
+
text = data[textOffset : textOffset + textLength]
|
| 76 |
+
assert len(text) == textLength
|
| 77 |
+
text = tostr(text, encoding="utf-8")
|
| 78 |
+
if text:
|
| 79 |
+
programs[name] = text
|
| 80 |
+
if isExtra:
|
| 81 |
+
self.extraPrograms = programs
|
| 82 |
+
else:
|
| 83 |
+
self.glyphPrograms = programs
|
| 84 |
+
|
| 85 |
+
def compile(self, ttFont):
|
| 86 |
+
if not hasattr(self, "glyphPrograms"):
|
| 87 |
+
self.glyphPrograms = {}
|
| 88 |
+
self.extraPrograms = {}
|
| 89 |
+
data = b""
|
| 90 |
+
indextable = ttFont[self.indextable]
|
| 91 |
+
glyphNames = ttFont.getGlyphOrder()
|
| 92 |
+
|
| 93 |
+
indices = []
|
| 94 |
+
for i, name in enumerate(glyphNames):
|
| 95 |
+
if len(data) % 2:
|
| 96 |
+
data = (
|
| 97 |
+
data + b"\015"
|
| 98 |
+
) # align on 2-byte boundaries, fill with return chars. Yum.
|
| 99 |
+
if name in self.glyphPrograms:
|
| 100 |
+
text = tobytes(self.glyphPrograms[name], encoding="utf-8")
|
| 101 |
+
else:
|
| 102 |
+
text = b""
|
| 103 |
+
textLength = len(text)
|
| 104 |
+
if textLength >= 0x8000:
|
| 105 |
+
textLength = 0x8000
|
| 106 |
+
indices.append((i, textLength, len(data)))
|
| 107 |
+
data = data + text
|
| 108 |
+
|
| 109 |
+
extra_indices = []
|
| 110 |
+
for code, name in sorted(self.extras.items()):
|
| 111 |
+
if len(data) % 2:
|
| 112 |
+
data = (
|
| 113 |
+
data + b"\015"
|
| 114 |
+
) # align on 2-byte boundaries, fill with return chars.
|
| 115 |
+
if name in self.extraPrograms:
|
| 116 |
+
text = tobytes(self.extraPrograms[name], encoding="utf-8")
|
| 117 |
+
else:
|
| 118 |
+
text = b""
|
| 119 |
+
textLength = len(text)
|
| 120 |
+
if textLength >= 0x8000:
|
| 121 |
+
textLength = 0x8000
|
| 122 |
+
extra_indices.append((code, textLength, len(data)))
|
| 123 |
+
data = data + text
|
| 124 |
+
indextable.set(indices, extra_indices)
|
| 125 |
+
return data
|
| 126 |
+
|
| 127 |
+
def toXML(self, writer, ttFont):
|
| 128 |
+
names = sorted(self.glyphPrograms.keys())
|
| 129 |
+
writer.newline()
|
| 130 |
+
for name in names:
|
| 131 |
+
text = self.glyphPrograms[name]
|
| 132 |
+
if not text:
|
| 133 |
+
continue
|
| 134 |
+
writer.begintag("glyphProgram", name=name)
|
| 135 |
+
writer.newline()
|
| 136 |
+
writer.write_noindent(text.replace("\r", "\n"))
|
| 137 |
+
writer.newline()
|
| 138 |
+
writer.endtag("glyphProgram")
|
| 139 |
+
writer.newline()
|
| 140 |
+
writer.newline()
|
| 141 |
+
extra_names = sorted(self.extraPrograms.keys())
|
| 142 |
+
for name in extra_names:
|
| 143 |
+
text = self.extraPrograms[name]
|
| 144 |
+
if not text:
|
| 145 |
+
continue
|
| 146 |
+
writer.begintag("extraProgram", name=name)
|
| 147 |
+
writer.newline()
|
| 148 |
+
writer.write_noindent(text.replace("\r", "\n"))
|
| 149 |
+
writer.newline()
|
| 150 |
+
writer.endtag("extraProgram")
|
| 151 |
+
writer.newline()
|
| 152 |
+
writer.newline()
|
| 153 |
+
|
| 154 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 155 |
+
if not hasattr(self, "glyphPrograms"):
|
| 156 |
+
self.glyphPrograms = {}
|
| 157 |
+
self.extraPrograms = {}
|
| 158 |
+
lines = strjoin(content).replace("\r", "\n").split("\n")
|
| 159 |
+
text = "\r".join(lines[1:-1])
|
| 160 |
+
if name == "glyphProgram":
|
| 161 |
+
self.glyphPrograms[attrs["name"]] = text
|
| 162 |
+
elif name == "extraProgram":
|
| 163 |
+
self.extraPrograms[attrs["name"]] = text
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__2.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its hinting source data.
|
| 3 |
+
|
| 4 |
+
TSI2 is the index table containing the lengths and offsets for the glyph
|
| 5 |
+
programs that are contained in the TSI3 table. It uses the same format as
|
| 6 |
+
the TSI0 table.
|
| 7 |
+
|
| 8 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from fontTools import ttLib
|
| 12 |
+
|
| 13 |
+
superclass = ttLib.getTableClass("TSI0")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class table_T_S_I__2(superclass):
|
| 17 |
+
dependencies = ["TSI3"]
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__3.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its hinting source data.
|
| 3 |
+
|
| 4 |
+
TSI3 contains the text of the glyph programs in the form of 'VTTTalk' code.
|
| 5 |
+
|
| 6 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from fontTools import ttLib
|
| 10 |
+
|
| 11 |
+
superclass = ttLib.getTableClass("TSI1")
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class table_T_S_I__3(superclass):
|
| 15 |
+
extras = {
|
| 16 |
+
0xFFFA: "reserved0",
|
| 17 |
+
0xFFFB: "reserved1",
|
| 18 |
+
0xFFFC: "reserved2",
|
| 19 |
+
0xFFFD: "reserved3",
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
indextable = "TSI2"
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_S_I__5.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
|
| 2 |
+
tool to store its hinting source data.
|
| 3 |
+
|
| 4 |
+
TSI5 contains the VTT character groups.
|
| 5 |
+
|
| 6 |
+
See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import array
|
| 10 |
+
import logging
|
| 11 |
+
import sys
|
| 12 |
+
|
| 13 |
+
from fontTools.misc.textTools import safeEval
|
| 14 |
+
|
| 15 |
+
from . import DefaultTable
|
| 16 |
+
|
| 17 |
+
log = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class table_T_S_I__5(DefaultTable.DefaultTable):
|
| 21 |
+
def decompile(self, data, ttFont):
|
| 22 |
+
numGlyphs = ttFont["maxp"].numGlyphs
|
| 23 |
+
a = array.array("H")
|
| 24 |
+
a.frombytes(data)
|
| 25 |
+
if sys.byteorder != "big":
|
| 26 |
+
a.byteswap()
|
| 27 |
+
self.glyphGrouping = {}
|
| 28 |
+
numEntries = len(data) // 2
|
| 29 |
+
if numEntries != numGlyphs:
|
| 30 |
+
diff = numEntries - numGlyphs
|
| 31 |
+
log.warning(
|
| 32 |
+
"Number of entries differs from the number of glyphs in the font "
|
| 33 |
+
f"by {abs(diff)} ({numEntries} entries vs. {numGlyphs} glyphs)."
|
| 34 |
+
)
|
| 35 |
+
for i in range(numEntries):
|
| 36 |
+
self.glyphGrouping[ttFont.getGlyphName(i)] = a[i]
|
| 37 |
+
|
| 38 |
+
def compile(self, ttFont):
|
| 39 |
+
glyphNames = ttFont.getGlyphOrder()
|
| 40 |
+
a = array.array("H")
|
| 41 |
+
for glyphName in glyphNames:
|
| 42 |
+
a.append(self.glyphGrouping.get(glyphName, 0))
|
| 43 |
+
if sys.byteorder != "big":
|
| 44 |
+
a.byteswap()
|
| 45 |
+
return a.tobytes()
|
| 46 |
+
|
| 47 |
+
def toXML(self, writer, ttFont):
|
| 48 |
+
names = sorted(self.glyphGrouping.keys())
|
| 49 |
+
for glyphName in names:
|
| 50 |
+
writer.simpletag(
|
| 51 |
+
"glyphgroup", name=glyphName, value=self.glyphGrouping[glyphName]
|
| 52 |
+
)
|
| 53 |
+
writer.newline()
|
| 54 |
+
|
| 55 |
+
def fromXML(self, name, attrs, content, ttFont):
|
| 56 |
+
if not hasattr(self, "glyphGrouping"):
|
| 57 |
+
self.glyphGrouping = {}
|
| 58 |
+
if name != "glyphgroup":
|
| 59 |
+
return
|
| 60 |
+
self.glyphGrouping[attrs["name"]] = safeEval(attrs["value"])
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/T_T_F_A_.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import asciiTable
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class table_T_T_F_A_(asciiTable.asciiTable):
|
| 5 |
+
"""ttfautohint parameters table
|
| 6 |
+
|
| 7 |
+
The ``TTFA`` table is used by the free-software `ttfautohint` program
|
| 8 |
+
to record the parameters that `ttfautohint` was called with when it
|
| 9 |
+
was used to auto-hint the font.
|
| 10 |
+
|
| 11 |
+
See also http://freetype.org/ttfautohint/doc/ttfautohint.html#miscellaneous-1
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
pass
|
.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/TupleVariation.py
ADDED
|
@@ -0,0 +1,886 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fontTools.misc.fixedTools import (
|
| 2 |
+
fixedToFloat as fi2fl,
|
| 3 |
+
floatToFixed as fl2fi,
|
| 4 |
+
floatToFixedToStr as fl2str,
|
| 5 |
+
strToFixedToFloat as str2fl,
|
| 6 |
+
otRound,
|
| 7 |
+
)
|
| 8 |
+
from fontTools.misc.textTools import safeEval
|
| 9 |
+
import array
|
| 10 |
+
from collections import Counter, defaultdict
|
| 11 |
+
import logging
|
| 12 |
+
import struct
|
| 13 |
+
import sys
|
| 14 |
+
import warnings
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
# https://www.microsoft.com/typography/otspec/otvarcommonformats.htm
|
| 18 |
+
|
| 19 |
+
EMBEDDED_PEAK_TUPLE = 0x8000
|
| 20 |
+
INTERMEDIATE_REGION = 0x4000
|
| 21 |
+
PRIVATE_POINT_NUMBERS = 0x2000
|
| 22 |
+
|
| 23 |
+
DELTAS_ARE_ZERO = 0x80
|
| 24 |
+
DELTAS_ARE_WORDS = 0x40
|
| 25 |
+
DELTAS_ARE_LONGS = 0xC0
|
| 26 |
+
DELTAS_SIZE_MASK = 0xC0
|
| 27 |
+
DELTA_RUN_COUNT_MASK = 0x3F
|
| 28 |
+
|
| 29 |
+
POINTS_ARE_WORDS = 0x80
|
| 30 |
+
POINT_RUN_COUNT_MASK = 0x7F
|
| 31 |
+
|
| 32 |
+
TUPLES_SHARE_POINT_NUMBERS = 0x8000
|
| 33 |
+
TUPLE_COUNT_MASK = 0x0FFF
|
| 34 |
+
TUPLE_INDEX_MASK = 0x0FFF
|
| 35 |
+
|
| 36 |
+
log = logging.getLogger(__name__)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class TupleVariation(object):
|
| 40 |
+
def __init__(self, axes, coordinates):
|
| 41 |
+
self.axes = axes.copy()
|
| 42 |
+
self.coordinates = list(coordinates)
|
| 43 |
+
|
| 44 |
+
def __repr__(self):
|
| 45 |
+
axes = ",".join(
|
| 46 |
+
sorted(["%s=%s" % (name, value) for (name, value) in self.axes.items()])
|
| 47 |
+
)
|
| 48 |
+
return "<TupleVariation %s %s>" % (axes, self.coordinates)
|
| 49 |
+
|
| 50 |
+
def __eq__(self, other):
|
| 51 |
+
return self.coordinates == other.coordinates and self.axes == other.axes
|
| 52 |
+
|
| 53 |
+
def getUsedPoints(self):
|
| 54 |
+
# Empty set means "all points used".
|
| 55 |
+
if None not in self.coordinates:
|
| 56 |
+
return frozenset()
|
| 57 |
+
used = frozenset([i for i, p in enumerate(self.coordinates) if p is not None])
|
| 58 |
+
# Return None if no points used.
|
| 59 |
+
return used if used else None
|
| 60 |
+
|
| 61 |
+
def hasImpact(self):
|
| 62 |
+
"""Returns True if this TupleVariation has any visible impact.
|
| 63 |
+
|
| 64 |
+
If the result is False, the TupleVariation can be omitted from the font
|
| 65 |
+
without making any visible difference.
|
| 66 |
+
"""
|
| 67 |
+
return any(c is not None for c in self.coordinates)
|
| 68 |
+
|
| 69 |
+
def toXML(self, writer, axisTags):
|
| 70 |
+
writer.begintag("tuple")
|
| 71 |
+
writer.newline()
|
| 72 |
+
for axis in axisTags:
|
| 73 |
+
value = self.axes.get(axis)
|
| 74 |
+
if value is not None:
|
| 75 |
+
minValue, value, maxValue = value
|
| 76 |
+
defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
|
| 77 |
+
defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
|
| 78 |
+
if minValue == defaultMinValue and maxValue == defaultMaxValue:
|
| 79 |
+
writer.simpletag("coord", axis=axis, value=fl2str(value, 14))
|
| 80 |
+
else:
|
| 81 |
+
attrs = [
|
| 82 |
+
("axis", axis),
|
| 83 |
+
("min", fl2str(minValue, 14)),
|
| 84 |
+
("value", fl2str(value, 14)),
|
| 85 |
+
("max", fl2str(maxValue, 14)),
|
| 86 |
+
]
|
| 87 |
+
writer.simpletag("coord", attrs)
|
| 88 |
+
writer.newline()
|
| 89 |
+
wrote_any_deltas = False
|
| 90 |
+
for i, delta in enumerate(self.coordinates):
|
| 91 |
+
if type(delta) == tuple and len(delta) == 2:
|
| 92 |
+
writer.simpletag("delta", pt=i, x=delta[0], y=delta[1])
|
| 93 |
+
writer.newline()
|
| 94 |
+
wrote_any_deltas = True
|
| 95 |
+
elif type(delta) == int:
|
| 96 |
+
writer.simpletag("delta", cvt=i, value=delta)
|
| 97 |
+
writer.newline()
|
| 98 |
+
wrote_any_deltas = True
|
| 99 |
+
elif delta is not None:
|
| 100 |
+
log.error("bad delta format")
|
| 101 |
+
writer.comment("bad delta #%d" % i)
|
| 102 |
+
writer.newline()
|
| 103 |
+
wrote_any_deltas = True
|
| 104 |
+
if not wrote_any_deltas:
|
| 105 |
+
writer.comment("no deltas")
|
| 106 |
+
writer.newline()
|
| 107 |
+
writer.endtag("tuple")
|
| 108 |
+
writer.newline()
|
| 109 |
+
|
| 110 |
+
def fromXML(self, name, attrs, _content):
|
| 111 |
+
if name == "coord":
|
| 112 |
+
axis = attrs["axis"]
|
| 113 |
+
value = str2fl(attrs["value"], 14)
|
| 114 |
+
defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
|
| 115 |
+
defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
|
| 116 |
+
minValue = str2fl(attrs.get("min", defaultMinValue), 14)
|
| 117 |
+
maxValue = str2fl(attrs.get("max", defaultMaxValue), 14)
|
| 118 |
+
self.axes[axis] = (minValue, value, maxValue)
|
| 119 |
+
elif name == "delta":
|
| 120 |
+
if "pt" in attrs:
|
| 121 |
+
point = safeEval(attrs["pt"])
|
| 122 |
+
x = safeEval(attrs["x"])
|
| 123 |
+
y = safeEval(attrs["y"])
|
| 124 |
+
self.coordinates[point] = (x, y)
|
| 125 |
+
elif "cvt" in attrs:
|
| 126 |
+
cvt = safeEval(attrs["cvt"])
|
| 127 |
+
value = safeEval(attrs["value"])
|
| 128 |
+
self.coordinates[cvt] = value
|
| 129 |
+
else:
|
| 130 |
+
log.warning("bad delta format: %s" % ", ".join(sorted(attrs.keys())))
|
| 131 |
+
|
| 132 |
+
def compile(
|
| 133 |
+
self, axisTags, sharedCoordIndices={}, pointData=None, *, optimizeSize=True
|
| 134 |
+
):
|
| 135 |
+
assert set(self.axes.keys()) <= set(axisTags), (
|
| 136 |
+
"Unknown axis tag found.",
|
| 137 |
+
self.axes.keys(),
|
| 138 |
+
axisTags,
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
tupleData = []
|
| 142 |
+
auxData = []
|
| 143 |
+
|
| 144 |
+
if pointData is None:
|
| 145 |
+
usedPoints = self.getUsedPoints()
|
| 146 |
+
if usedPoints is None: # Nothing to encode
|
| 147 |
+
return b"", b""
|
| 148 |
+
pointData = self.compilePoints(usedPoints)
|
| 149 |
+
|
| 150 |
+
coord = self.compileCoord(axisTags)
|
| 151 |
+
flags = sharedCoordIndices.get(coord)
|
| 152 |
+
if flags is None:
|
| 153 |
+
flags = EMBEDDED_PEAK_TUPLE
|
| 154 |
+
tupleData.append(coord)
|
| 155 |
+
|
| 156 |
+
intermediateCoord = self.compileIntermediateCoord(axisTags)
|
| 157 |
+
if intermediateCoord is not None:
|
| 158 |
+
flags |= INTERMEDIATE_REGION
|
| 159 |
+
tupleData.append(intermediateCoord)
|
| 160 |
+
|
| 161 |
+
# pointData of b'' implies "use shared points".
|
| 162 |
+
if pointData:
|
| 163 |
+
flags |= PRIVATE_POINT_NUMBERS
|
| 164 |
+
auxData.append(pointData)
|
| 165 |
+
|
| 166 |
+
auxData.append(self.compileDeltas(optimizeSize=optimizeSize))
|
| 167 |
+
auxData = b"".join(auxData)
|
| 168 |
+
|
| 169 |
+
tupleData.insert(0, struct.pack(">HH", len(auxData), flags))
|
| 170 |
+
return b"".join(tupleData), auxData
|
| 171 |
+
|
| 172 |
+
def compileCoord(self, axisTags):
|
| 173 |
+
result = []
|
| 174 |
+
axes = self.axes
|
| 175 |
+
for axis in axisTags:
|
| 176 |
+
triple = axes.get(axis)
|
| 177 |
+
if triple is None:
|
| 178 |
+
result.append(b"\0\0")
|
| 179 |
+
else:
|
| 180 |
+
result.append(struct.pack(">h", fl2fi(triple[1], 14)))
|
| 181 |
+
return b"".join(result)
|
| 182 |
+
|
| 183 |
+
def compileIntermediateCoord(self, axisTags):
|
| 184 |
+
needed = False
|
| 185 |
+
for axis in axisTags:
|
| 186 |
+
minValue, value, maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
|
| 187 |
+
defaultMinValue = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
|
| 188 |
+
defaultMaxValue = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
|
| 189 |
+
if (minValue != defaultMinValue) or (maxValue != defaultMaxValue):
|
| 190 |
+
needed = True
|
| 191 |
+
break
|
| 192 |
+
if not needed:
|
| 193 |
+
return None
|
| 194 |
+
minCoords = []
|
| 195 |
+
maxCoords = []
|
| 196 |
+
for axis in axisTags:
|
| 197 |
+
minValue, value, maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
|
| 198 |
+
minCoords.append(struct.pack(">h", fl2fi(minValue, 14)))
|
| 199 |
+
maxCoords.append(struct.pack(">h", fl2fi(maxValue, 14)))
|
| 200 |
+
return b"".join(minCoords + maxCoords)
|
| 201 |
+
|
| 202 |
+
@staticmethod
|
| 203 |
+
def decompileCoord_(axisTags, data, offset):
|
| 204 |
+
coord = {}
|
| 205 |
+
pos = offset
|
| 206 |
+
for axis in axisTags:
|
| 207 |
+
coord[axis] = fi2fl(struct.unpack(">h", data[pos : pos + 2])[0], 14)
|
| 208 |
+
pos += 2
|
| 209 |
+
return coord, pos
|
| 210 |
+
|
| 211 |
+
@staticmethod
|
| 212 |
+
def compilePoints(points):
|
| 213 |
+
# If the set consists of all points in the glyph, it gets encoded with
|
| 214 |
+
# a special encoding: a single zero byte.
|
| 215 |
+
#
|
| 216 |
+
# To use this optimization, points passed in must be empty set.
|
| 217 |
+
# The following two lines are not strictly necessary as the main code
|
| 218 |
+
# below would emit the same. But this is most common and faster.
|
| 219 |
+
if not points:
|
| 220 |
+
return b"\0"
|
| 221 |
+
|
| 222 |
+
# In the 'gvar' table, the packing of point numbers is a little surprising.
|
| 223 |
+
# It consists of multiple runs, each being a delta-encoded list of integers.
|
| 224 |
+
# For example, the point set {17, 18, 19, 20, 21, 22, 23} gets encoded as
|
| 225 |
+
# [6, 17, 1, 1, 1, 1, 1, 1]. The first value (6) is the run length minus 1.
|
| 226 |
+
# There are two types of runs, with values being either 8 or 16 bit unsigned
|
| 227 |
+
# integers.
|
| 228 |
+
points = list(points)
|
| 229 |
+
points.sort()
|
| 230 |
+
numPoints = len(points)
|
| 231 |
+
|
| 232 |
+
result = bytearray()
|
| 233 |
+
# The binary representation starts with the total number of points in the set,
|
| 234 |
+
# encoded into one or two bytes depending on the value.
|
| 235 |
+
if numPoints < 0x80:
|
| 236 |
+
result.append(numPoints)
|
| 237 |
+
else:
|
| 238 |
+
result.append((numPoints >> 8) | 0x80)
|
| 239 |
+
result.append(numPoints & 0xFF)
|
| 240 |
+
|
| 241 |
+
MAX_RUN_LENGTH = 127
|
| 242 |
+
pos = 0
|
| 243 |
+
lastValue = 0
|
| 244 |
+
while pos < numPoints:
|
| 245 |
+
runLength = 0
|
| 246 |
+
|
| 247 |
+
headerPos = len(result)
|
| 248 |
+
result.append(0)
|
| 249 |
+
|
| 250 |
+
useByteEncoding = None
|
| 251 |
+
while pos < numPoints and runLength <= MAX_RUN_LENGTH:
|
| 252 |
+
curValue = points[pos]
|
| 253 |
+
delta = curValue - lastValue
|
| 254 |
+
if useByteEncoding is None:
|
| 255 |
+
useByteEncoding = 0 <= delta <= 0xFF
|
| 256 |
+
if useByteEncoding and (delta > 0xFF or delta < 0):
|
| 257 |
+
# we need to start a new run (which will not use byte encoding)
|
| 258 |
+
break
|
| 259 |
+
# TODO This never switches back to a byte-encoding from a short-encoding.
|
| 260 |
+
# That's suboptimal.
|
| 261 |
+
if useByteEncoding:
|
| 262 |
+
result.append(delta)
|
| 263 |
+
else:
|
| 264 |
+
result.append(delta >> 8)
|
| 265 |
+
result.append(delta & 0xFF)
|
| 266 |
+
lastValue = curValue
|
| 267 |
+
pos += 1
|
| 268 |
+
runLength += 1
|
| 269 |
+
if useByteEncoding:
|
| 270 |
+
result[headerPos] = runLength - 1
|
| 271 |
+
else:
|
| 272 |
+
result[headerPos] = (runLength - 1) | POINTS_ARE_WORDS
|
| 273 |
+
|
| 274 |
+
return result
|
| 275 |
+
|
| 276 |
+
@staticmethod
|
| 277 |
+
def decompilePoints_(numPoints, data, offset, tableTag):
|
| 278 |
+
"""(numPoints, data, offset, tableTag) --> ([point1, point2, ...], newOffset)"""
|
| 279 |
+
assert tableTag in ("cvar", "gvar")
|
| 280 |
+
pos = offset
|
| 281 |
+
numPointsInData = data[pos]
|
| 282 |
+
pos += 1
|
| 283 |
+
if (numPointsInData & POINTS_ARE_WORDS) != 0:
|
| 284 |
+
numPointsInData = (numPointsInData & POINT_RUN_COUNT_MASK) << 8 | data[pos]
|
| 285 |
+
pos += 1
|
| 286 |
+
if numPointsInData == 0:
|
| 287 |
+
return (range(numPoints), pos)
|
| 288 |
+
|
| 289 |
+
result = []
|
| 290 |
+
while len(result) < numPointsInData:
|
| 291 |
+
runHeader = data[pos]
|
| 292 |
+
pos += 1
|
| 293 |
+
numPointsInRun = (runHeader & POINT_RUN_COUNT_MASK) + 1
|
| 294 |
+
point = 0
|
| 295 |
+
if (runHeader & POINTS_ARE_WORDS) != 0:
|
| 296 |
+
points = array.array("H")
|
| 297 |
+
pointsSize = numPointsInRun * 2
|
| 298 |
+
else:
|
| 299 |
+
points = array.array("B")
|
| 300 |
+
pointsSize = numPointsInRun
|
| 301 |
+
points.frombytes(data[pos : pos + pointsSize])
|
| 302 |
+
if sys.byteorder != "big":
|
| 303 |
+
points.byteswap()
|
| 304 |
+
|
| 305 |
+
assert len(points) == numPointsInRun
|
| 306 |
+
pos += pointsSize
|
| 307 |
+
|
| 308 |
+
result.extend(points)
|
| 309 |
+
|
| 310 |
+
# Convert relative to absolute
|
| 311 |
+
absolute = []
|
| 312 |
+
current = 0
|
| 313 |
+
for delta in result:
|
| 314 |
+
current += delta
|
| 315 |
+
absolute.append(current)
|
| 316 |
+
result = absolute
|
| 317 |
+
del absolute
|
| 318 |
+
|
| 319 |
+
badPoints = {str(p) for p in result if p < 0 or p >= numPoints}
|
| 320 |
+
if badPoints:
|
| 321 |
+
log.warning(
|
| 322 |
+
"point %s out of range in '%s' table"
|
| 323 |
+
% (",".join(sorted(badPoints)), tableTag)
|
| 324 |
+
)
|
| 325 |
+
return (result, pos)
|
| 326 |
+
|
| 327 |
+
def compileDeltas(self, optimizeSize=True):
|
| 328 |
+
deltaX = []
|
| 329 |
+
deltaY = []
|
| 330 |
+
if self.getCoordWidth() == 2:
|
| 331 |
+
for c in self.coordinates:
|
| 332 |
+
if c is None:
|
| 333 |
+
continue
|
| 334 |
+
deltaX.append(c[0])
|
| 335 |
+
deltaY.append(c[1])
|
| 336 |
+
else:
|
| 337 |
+
for c in self.coordinates:
|
| 338 |
+
if c is None:
|
| 339 |
+
continue
|
| 340 |
+
deltaX.append(c)
|
| 341 |
+
bytearr = bytearray()
|
| 342 |
+
self.compileDeltaValues_(deltaX, bytearr, optimizeSize=optimizeSize)
|
| 343 |
+
self.compileDeltaValues_(deltaY, bytearr, optimizeSize=optimizeSize)
|
| 344 |
+
return bytearr
|
| 345 |
+
|
| 346 |
+
@staticmethod
|
| 347 |
+
def compileDeltaValues_(deltas, bytearr=None, *, optimizeSize=True):
|
| 348 |
+
"""[value1, value2, value3, ...] --> bytearray
|
| 349 |
+
|
| 350 |
+
Emits a sequence of runs. Each run starts with a
|
| 351 |
+
byte-sized header whose 6 least significant bits
|
| 352 |
+
(header & 0x3F) indicate how many values are encoded
|
| 353 |
+
in this run. The stored length is the actual length
|
| 354 |
+
minus one; run lengths are thus in the range [1..64].
|
| 355 |
+
If the header byte has its most significant bit (0x80)
|
| 356 |
+
set, all values in this run are zero, and no data
|
| 357 |
+
follows. Otherwise, the header byte is followed by
|
| 358 |
+
((header & 0x3F) + 1) signed values. If (header &
|
| 359 |
+
0x40) is clear, the delta values are stored as signed
|
| 360 |
+
bytes; if (header & 0x40) is set, the delta values are
|
| 361 |
+
signed 16-bit integers.
|
| 362 |
+
""" # Explaining the format because the 'gvar' spec is hard to understand.
|
| 363 |
+
if bytearr is None:
|
| 364 |
+
bytearr = bytearray()
|
| 365 |
+
|
| 366 |
+
pos = 0
|
| 367 |
+
numDeltas = len(deltas)
|
| 368 |
+
|
| 369 |
+
if optimizeSize:
|
| 370 |
+
while pos < numDeltas:
|
| 371 |
+
value = deltas[pos]
|
| 372 |
+
if value == 0:
|
| 373 |
+
pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
|
| 374 |
+
elif -128 <= value <= 127:
|
| 375 |
+
pos = TupleVariation.encodeDeltaRunAsBytes_(deltas, pos, bytearr)
|
| 376 |
+
elif -32768 <= value <= 32767:
|
| 377 |
+
pos = TupleVariation.encodeDeltaRunAsWords_(deltas, pos, bytearr)
|
| 378 |
+
else:
|
| 379 |
+
pos = TupleVariation.encodeDeltaRunAsLongs_(deltas, pos, bytearr)
|
| 380 |
+
else:
|
| 381 |
+
minVal, maxVal = min(deltas), max(deltas)
|
| 382 |
+
if minVal == 0 == maxVal:
|
| 383 |
+
pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
|
| 384 |
+
elif -128 <= minVal <= maxVal <= 127:
|
| 385 |
+
pos = TupleVariation.encodeDeltaRunAsBytes_(
|
| 386 |
+
deltas, pos, bytearr, optimizeSize=False
|
| 387 |
+
)
|
| 388 |
+
elif -32768 <= minVal <= maxVal <= 32767:
|
| 389 |
+
pos = TupleVariation.encodeDeltaRunAsWords_(
|
| 390 |
+
deltas, pos, bytearr, optimizeSize=False
|
| 391 |
+
)
|
| 392 |
+
else:
|
| 393 |
+
pos = TupleVariation.encodeDeltaRunAsLongs_(
|
| 394 |
+
deltas, pos, bytearr, optimizeSize=False
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
assert pos == numDeltas, (pos, numDeltas)
|
| 398 |
+
|
| 399 |
+
return bytearr
|
| 400 |
+
|
| 401 |
+
@staticmethod
|
| 402 |
+
def encodeDeltaRunAsZeroes_(deltas, offset, bytearr):
|
| 403 |
+
pos = offset
|
| 404 |
+
numDeltas = len(deltas)
|
| 405 |
+
while pos < numDeltas and deltas[pos] == 0:
|
| 406 |
+
pos += 1
|
| 407 |
+
runLength = pos - offset
|
| 408 |
+
while runLength >= 64:
|
| 409 |
+
bytearr.append(DELTAS_ARE_ZERO | 63)
|
| 410 |
+
runLength -= 64
|
| 411 |
+
if runLength:
|
| 412 |
+
bytearr.append(DELTAS_ARE_ZERO | (runLength - 1))
|
| 413 |
+
return pos
|
| 414 |
+
|
| 415 |
+
@staticmethod
|
| 416 |
+
def encodeDeltaRunAsBytes_(deltas, offset, bytearr, optimizeSize=True):
|
| 417 |
+
pos = offset
|
| 418 |
+
numDeltas = len(deltas)
|
| 419 |
+
while pos < numDeltas:
|
| 420 |
+
value = deltas[pos]
|
| 421 |
+
if not (-128 <= value <= 127):
|
| 422 |
+
break
|
| 423 |
+
# Within a byte-encoded run of deltas, a single zero
|
| 424 |
+
# is best stored literally as 0x00 value. However,
|
| 425 |
+
# if are two or more zeroes in a sequence, it is
|
| 426 |
+
# better to start a new run. For example, the sequence
|
| 427 |
+
# of deltas [15, 15, 0, 15, 15] becomes 6 bytes
|
| 428 |
+
# (04 0F 0F 00 0F 0F) when storing the zero value
|
| 429 |
+
# literally, but 7 bytes (01 0F 0F 80 01 0F 0F)
|
| 430 |
+
# when starting a new run.
|
| 431 |
+
if (
|
| 432 |
+
optimizeSize
|
| 433 |
+
and value == 0
|
| 434 |
+
and pos + 1 < numDeltas
|
| 435 |
+
and deltas[pos + 1] == 0
|
| 436 |
+
):
|
| 437 |
+
break
|
| 438 |
+
pos += 1
|
| 439 |
+
runLength = pos - offset
|
| 440 |
+
while runLength >= 64:
|
| 441 |
+
bytearr.append(63)
|
| 442 |
+
bytearr.extend(array.array("b", deltas[offset : offset + 64]))
|
| 443 |
+
offset += 64
|
| 444 |
+
runLength -= 64
|
| 445 |
+
if runLength:
|
| 446 |
+
bytearr.append(runLength - 1)
|
| 447 |
+
bytearr.extend(array.array("b", deltas[offset:pos]))
|
| 448 |
+
return pos
|
| 449 |
+
|
| 450 |
+
@staticmethod
|
| 451 |
+
def encodeDeltaRunAsWords_(deltas, offset, bytearr, optimizeSize=True):
|
| 452 |
+
pos = offset
|
| 453 |
+
numDeltas = len(deltas)
|
| 454 |
+
while pos < numDeltas:
|
| 455 |
+
value = deltas[pos]
|
| 456 |
+
|
| 457 |
+
# Within a word-encoded run of deltas, it is easiest
|
| 458 |
+
# to start a new run (with a different encoding)
|
| 459 |
+
# whenever we encounter a zero value. For example,
|
| 460 |
+
# the sequence [0x6666, 0, 0x7777] needs 7 bytes when
|
| 461 |
+
# storing the zero literally (42 66 66 00 00 77 77),
|
| 462 |
+
# and equally 7 bytes when starting a new run
|
| 463 |
+
# (40 66 66 80 40 77 77).
|
| 464 |
+
if optimizeSize and value == 0:
|
| 465 |
+
break
|
| 466 |
+
|
| 467 |
+
# Within a word-encoded run of deltas, a single value
|
| 468 |
+
# in the range (-128..127) should be encoded literally
|
| 469 |
+
# because it is more compact. For example, the sequence
|
| 470 |
+
# [0x6666, 2, 0x7777] becomes 7 bytes when storing
|
| 471 |
+
# the value literally (42 66 66 00 02 77 77), but 8 bytes
|
| 472 |
+
# when starting a new run (40 66 66 00 02 40 77 77).
|
| 473 |
+
if (
|
| 474 |
+
optimizeSize
|
| 475 |
+
and (-128 <= value <= 127)
|
| 476 |
+
and pos + 1 < numDeltas
|
| 477 |
+
and (-128 <= deltas[pos + 1] <= 127)
|
| 478 |
+
):
|
| 479 |
+
break
|
| 480 |
+
|
| 481 |
+
if not (-32768 <= value <= 32767):
|
| 482 |
+
break
|
| 483 |
+
|
| 484 |
+
pos += 1
|
| 485 |
+
runLength = pos - offset
|
| 486 |
+
while runLength >= 64:
|
| 487 |
+
bytearr.append(DELTAS_ARE_WORDS | 63)
|
| 488 |
+
a = array.array("h", deltas[offset : offset + 64])
|
| 489 |
+
if sys.byteorder != "big":
|
| 490 |
+
a.byteswap()
|
| 491 |
+
bytearr.extend(a)
|
| 492 |
+
offset += 64
|
| 493 |
+
runLength -= 64
|
| 494 |
+
if runLength:
|
| 495 |
+
bytearr.append(DELTAS_ARE_WORDS | (runLength - 1))
|
| 496 |
+
a = array.array("h", deltas[offset:pos])
|
| 497 |
+
if sys.byteorder != "big":
|
| 498 |
+
a.byteswap()
|
| 499 |
+
bytearr.extend(a)
|
| 500 |
+
return pos
|
| 501 |
+
|
| 502 |
+
@staticmethod
|
| 503 |
+
def encodeDeltaRunAsLongs_(deltas, offset, bytearr, optimizeSize=True):
|
| 504 |
+
pos = offset
|
| 505 |
+
numDeltas = len(deltas)
|
| 506 |
+
while pos < numDeltas:
|
| 507 |
+
value = deltas[pos]
|
| 508 |
+
if optimizeSize and -32768 <= value <= 32767:
|
| 509 |
+
break
|
| 510 |
+
pos += 1
|
| 511 |
+
runLength = pos - offset
|
| 512 |
+
while runLength >= 64:
|
| 513 |
+
bytearr.append(DELTAS_ARE_LONGS | 63)
|
| 514 |
+
a = array.array("i", deltas[offset : offset + 64])
|
| 515 |
+
if sys.byteorder != "big":
|
| 516 |
+
a.byteswap()
|
| 517 |
+
bytearr.extend(a)
|
| 518 |
+
offset += 64
|
| 519 |
+
runLength -= 64
|
| 520 |
+
if runLength:
|
| 521 |
+
bytearr.append(DELTAS_ARE_LONGS | (runLength - 1))
|
| 522 |
+
a = array.array("i", deltas[offset:pos])
|
| 523 |
+
if sys.byteorder != "big":
|
| 524 |
+
a.byteswap()
|
| 525 |
+
bytearr.extend(a)
|
| 526 |
+
return pos
|
| 527 |
+
|
| 528 |
+
@staticmethod
|
| 529 |
+
def decompileDeltas_(numDeltas, data, offset=0):
|
| 530 |
+
"""(numDeltas, data, offset) --> ([delta, delta, ...], newOffset)"""
|
| 531 |
+
result = []
|
| 532 |
+
pos = offset
|
| 533 |
+
while len(result) < numDeltas if numDeltas is not None else pos < len(data):
|
| 534 |
+
runHeader = data[pos]
|
| 535 |
+
pos += 1
|
| 536 |
+
numDeltasInRun = (runHeader & DELTA_RUN_COUNT_MASK) + 1
|
| 537 |
+
if (runHeader & DELTAS_SIZE_MASK) == DELTAS_ARE_ZERO:
|
| 538 |
+
result.extend([0] * numDeltasInRun)
|
| 539 |
+
else:
|
| 540 |
+
if (runHeader & DELTAS_SIZE_MASK) == DELTAS_ARE_LONGS:
|
| 541 |
+
deltas = array.array("i")
|
| 542 |
+
deltasSize = numDeltasInRun * 4
|
| 543 |
+
elif (runHeader & DELTAS_SIZE_MASK) == DELTAS_ARE_WORDS:
|
| 544 |
+
deltas = array.array("h")
|
| 545 |
+
deltasSize = numDeltasInRun * 2
|
| 546 |
+
else:
|
| 547 |
+
deltas = array.array("b")
|
| 548 |
+
deltasSize = numDeltasInRun
|
| 549 |
+
deltas.frombytes(data[pos : pos + deltasSize])
|
| 550 |
+
if sys.byteorder != "big":
|
| 551 |
+
deltas.byteswap()
|
| 552 |
+
assert len(deltas) == numDeltasInRun, (len(deltas), numDeltasInRun)
|
| 553 |
+
pos += deltasSize
|
| 554 |
+
result.extend(deltas)
|
| 555 |
+
assert numDeltas is None or len(result) == numDeltas
|
| 556 |
+
return (result, pos)
|
| 557 |
+
|
| 558 |
+
@staticmethod
|
| 559 |
+
def getTupleSize_(flags, axisCount):
|
| 560 |
+
size = 4
|
| 561 |
+
if (flags & EMBEDDED_PEAK_TUPLE) != 0:
|
| 562 |
+
size += axisCount * 2
|
| 563 |
+
if (flags & INTERMEDIATE_REGION) != 0:
|
| 564 |
+
size += axisCount * 4
|
| 565 |
+
return size
|
| 566 |
+
|
| 567 |
+
def getCoordWidth(self):
|
| 568 |
+
"""Return 2 if coordinates are (x, y) as in gvar, 1 if single values
|
| 569 |
+
as in cvar, or 0 if empty.
|
| 570 |
+
"""
|
| 571 |
+
firstDelta = next((c for c in self.coordinates if c is not None), None)
|
| 572 |
+
if firstDelta is None:
|
| 573 |
+
return 0 # empty or has no impact
|
| 574 |
+
if type(firstDelta) in (int, float):
|
| 575 |
+
return 1
|
| 576 |
+
if type(firstDelta) is tuple and len(firstDelta) == 2:
|
| 577 |
+
return 2
|
| 578 |
+
raise TypeError(
|
| 579 |
+
"invalid type of delta; expected (int or float) number, or "
|
| 580 |
+
"Tuple[number, number]: %r" % firstDelta
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
def scaleDeltas(self, scalar):
|
| 584 |
+
if scalar == 1.0:
|
| 585 |
+
return # no change
|
| 586 |
+
coordWidth = self.getCoordWidth()
|
| 587 |
+
self.coordinates = [
|
| 588 |
+
(
|
| 589 |
+
None
|
| 590 |
+
if d is None
|
| 591 |
+
else d * scalar if coordWidth == 1 else (d[0] * scalar, d[1] * scalar)
|
| 592 |
+
)
|
| 593 |
+
for d in self.coordinates
|
| 594 |
+
]
|
| 595 |
+
|
| 596 |
+
def roundDeltas(self):
|
| 597 |
+
coordWidth = self.getCoordWidth()
|
| 598 |
+
self.coordinates = [
|
| 599 |
+
(
|
| 600 |
+
None
|
| 601 |
+
if d is None
|
| 602 |
+
else otRound(d) if coordWidth == 1 else (otRound(d[0]), otRound(d[1]))
|
| 603 |
+
)
|
| 604 |
+
for d in self.coordinates
|
| 605 |
+
]
|
| 606 |
+
|
| 607 |
+
def calcInferredDeltas(self, origCoords, endPts):
|
| 608 |
+
from fontTools.varLib.iup import iup_delta
|
| 609 |
+
|
| 610 |
+
if self.getCoordWidth() == 1:
|
| 611 |
+
raise TypeError("Only 'gvar' TupleVariation can have inferred deltas")
|
| 612 |
+
if None in self.coordinates:
|
| 613 |
+
if len(self.coordinates) != len(origCoords):
|
| 614 |
+
raise ValueError(
|
| 615 |
+
"Expected len(origCoords) == %d; found %d"
|
| 616 |
+
% (len(self.coordinates), len(origCoords))
|
| 617 |
+
)
|
| 618 |
+
self.coordinates = iup_delta(self.coordinates, origCoords, endPts)
|
| 619 |
+
|
| 620 |
+
def optimize(self, origCoords, endPts, tolerance=0.5, isComposite=False):
|
| 621 |
+
from fontTools.varLib.iup import iup_delta_optimize
|
| 622 |
+
|
| 623 |
+
if isComposite:
|
| 624 |
+
warnings.warn(
|
| 625 |
+
"The isComposite argument is deprecated and may be removed in a future version",
|
| 626 |
+
DeprecationWarning,
|
| 627 |
+
)
|
| 628 |
+
|
| 629 |
+
if None in self.coordinates:
|
| 630 |
+
return # already optimized
|
| 631 |
+
|
| 632 |
+
deltaOpt = iup_delta_optimize(
|
| 633 |
+
self.coordinates, origCoords, endPts, tolerance=tolerance
|
| 634 |
+
)
|
| 635 |
+
if None in deltaOpt:
|
| 636 |
+
# Use "optimized" version only if smaller...
|
| 637 |
+
varOpt = TupleVariation(self.axes, deltaOpt)
|
| 638 |
+
|
| 639 |
+
# Shouldn't matter that this is different from fvar...?
|
| 640 |
+
axisTags = sorted(self.axes.keys())
|
| 641 |
+
tupleData, auxData = self.compile(axisTags)
|
| 642 |
+
unoptimizedLength = len(tupleData) + len(auxData)
|
| 643 |
+
tupleData, auxData = varOpt.compile(axisTags)
|
| 644 |
+
optimizedLength = len(tupleData) + len(auxData)
|
| 645 |
+
|
| 646 |
+
if optimizedLength < unoptimizedLength:
|
| 647 |
+
self.coordinates = varOpt.coordinates
|
| 648 |
+
|
| 649 |
+
def __imul__(self, scalar):
|
| 650 |
+
self.scaleDeltas(scalar)
|
| 651 |
+
return self
|
| 652 |
+
|
| 653 |
+
def __iadd__(self, other):
|
| 654 |
+
if not isinstance(other, TupleVariation):
|
| 655 |
+
return NotImplemented
|
| 656 |
+
deltas1 = self.coordinates
|
| 657 |
+
length = len(deltas1)
|
| 658 |
+
deltas2 = other.coordinates
|
| 659 |
+
if len(deltas2) != length:
|
| 660 |
+
raise ValueError("cannot sum TupleVariation deltas with different lengths")
|
| 661 |
+
# 'None' values have different meanings in gvar vs cvar TupleVariations:
|
| 662 |
+
# within the gvar, when deltas are not provided explicitly for some points,
|
| 663 |
+
# they need to be inferred; whereas for the 'cvar' table, if deltas are not
|
| 664 |
+
# provided for some CVT values, then no adjustments are made (i.e. None == 0).
|
| 665 |
+
# Thus, we cannot sum deltas for gvar TupleVariations if they contain
|
| 666 |
+
# inferred inferred deltas (the latter need to be computed first using
|
| 667 |
+
# 'calcInferredDeltas' method), but we can treat 'None' values in cvar
|
| 668 |
+
# deltas as if they are zeros.
|
| 669 |
+
if self.getCoordWidth() == 2:
|
| 670 |
+
for i, d2 in zip(range(length), deltas2):
|
| 671 |
+
d1 = deltas1[i]
|
| 672 |
+
try:
|
| 673 |
+
deltas1[i] = (d1[0] + d2[0], d1[1] + d2[1])
|
| 674 |
+
except TypeError:
|
| 675 |
+
raise ValueError("cannot sum gvar deltas with inferred points")
|
| 676 |
+
else:
|
| 677 |
+
for i, d2 in zip(range(length), deltas2):
|
| 678 |
+
d1 = deltas1[i]
|
| 679 |
+
if d1 is not None and d2 is not None:
|
| 680 |
+
deltas1[i] = d1 + d2
|
| 681 |
+
elif d1 is None and d2 is not None:
|
| 682 |
+
deltas1[i] = d2
|
| 683 |
+
# elif d2 is None do nothing
|
| 684 |
+
return self
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def decompileSharedTuples(axisTags, sharedTupleCount, data, offset):
|
| 688 |
+
result = []
|
| 689 |
+
for _ in range(sharedTupleCount):
|
| 690 |
+
t, offset = TupleVariation.decompileCoord_(axisTags, data, offset)
|
| 691 |
+
result.append(t)
|
| 692 |
+
return result
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
def compileSharedTuples(
|
| 696 |
+
axisTags, variations, MAX_NUM_SHARED_COORDS=TUPLE_INDEX_MASK + 1
|
| 697 |
+
):
|
| 698 |
+
coordCount = Counter()
|
| 699 |
+
for var in variations:
|
| 700 |
+
coord = var.compileCoord(axisTags)
|
| 701 |
+
coordCount[coord] += 1
|
| 702 |
+
# In python < 3.7, most_common() ordering is non-deterministic
|
| 703 |
+
# so apply a sort to make sure the ordering is consistent.
|
| 704 |
+
sharedCoords = sorted(
|
| 705 |
+
coordCount.most_common(MAX_NUM_SHARED_COORDS),
|
| 706 |
+
key=lambda item: (-item[1], item[0]),
|
| 707 |
+
)
|
| 708 |
+
return [c[0] for c in sharedCoords if c[1] > 1]
|
| 709 |
+
|
| 710 |
+
|
| 711 |
+
def compileTupleVariationStore(
|
| 712 |
+
variations,
|
| 713 |
+
pointCount,
|
| 714 |
+
axisTags,
|
| 715 |
+
sharedTupleIndices,
|
| 716 |
+
useSharedPoints=True,
|
| 717 |
+
*,
|
| 718 |
+
optimizeSize=True,
|
| 719 |
+
):
|
| 720 |
+
# pointCount is actually unused. Keeping for API compat.
|
| 721 |
+
del pointCount
|
| 722 |
+
newVariations = []
|
| 723 |
+
pointDatas = []
|
| 724 |
+
# Compile all points and figure out sharing if desired
|
| 725 |
+
sharedPoints = None
|
| 726 |
+
|
| 727 |
+
# Collect, count, and compile point-sets for all variation sets
|
| 728 |
+
pointSetCount = defaultdict(int)
|
| 729 |
+
for v in variations:
|
| 730 |
+
points = v.getUsedPoints()
|
| 731 |
+
if points is None: # Empty variations
|
| 732 |
+
continue
|
| 733 |
+
pointSetCount[points] += 1
|
| 734 |
+
newVariations.append(v)
|
| 735 |
+
pointDatas.append(points)
|
| 736 |
+
variations = newVariations
|
| 737 |
+
del newVariations
|
| 738 |
+
|
| 739 |
+
if not variations:
|
| 740 |
+
return (0, b"", b"")
|
| 741 |
+
|
| 742 |
+
n = len(variations[0].coordinates)
|
| 743 |
+
assert all(
|
| 744 |
+
len(v.coordinates) == n for v in variations
|
| 745 |
+
), "Variation sets have different sizes"
|
| 746 |
+
|
| 747 |
+
compiledPoints = {
|
| 748 |
+
pointSet: TupleVariation.compilePoints(pointSet) for pointSet in pointSetCount
|
| 749 |
+
}
|
| 750 |
+
|
| 751 |
+
tupleVariationCount = len(variations)
|
| 752 |
+
tuples = []
|
| 753 |
+
data = []
|
| 754 |
+
|
| 755 |
+
if useSharedPoints:
|
| 756 |
+
# Find point-set which saves most bytes.
|
| 757 |
+
def key(pn):
|
| 758 |
+
pointSet = pn[0]
|
| 759 |
+
count = pn[1]
|
| 760 |
+
return len(compiledPoints[pointSet]) * (count - 1)
|
| 761 |
+
|
| 762 |
+
sharedPoints = max(pointSetCount.items(), key=key)[0]
|
| 763 |
+
|
| 764 |
+
data.append(compiledPoints[sharedPoints])
|
| 765 |
+
tupleVariationCount |= TUPLES_SHARE_POINT_NUMBERS
|
| 766 |
+
|
| 767 |
+
# b'' implies "use shared points"
|
| 768 |
+
pointDatas = [
|
| 769 |
+
compiledPoints[points] if points != sharedPoints else b""
|
| 770 |
+
for points in pointDatas
|
| 771 |
+
]
|
| 772 |
+
|
| 773 |
+
for v, p in zip(variations, pointDatas):
|
| 774 |
+
thisTuple, thisData = v.compile(
|
| 775 |
+
axisTags, sharedTupleIndices, pointData=p, optimizeSize=optimizeSize
|
| 776 |
+
)
|
| 777 |
+
|
| 778 |
+
tuples.append(thisTuple)
|
| 779 |
+
data.append(thisData)
|
| 780 |
+
|
| 781 |
+
tuples = b"".join(tuples)
|
| 782 |
+
data = b"".join(data)
|
| 783 |
+
return tupleVariationCount, tuples, data
|
| 784 |
+
|
| 785 |
+
|
| 786 |
+
def decompileTupleVariationStore(
|
| 787 |
+
tableTag,
|
| 788 |
+
axisTags,
|
| 789 |
+
tupleVariationCount,
|
| 790 |
+
pointCount,
|
| 791 |
+
sharedTuples,
|
| 792 |
+
data,
|
| 793 |
+
pos,
|
| 794 |
+
dataPos,
|
| 795 |
+
):
|
| 796 |
+
numAxes = len(axisTags)
|
| 797 |
+
result = []
|
| 798 |
+
if (tupleVariationCount & TUPLES_SHARE_POINT_NUMBERS) != 0:
|
| 799 |
+
sharedPoints, dataPos = TupleVariation.decompilePoints_(
|
| 800 |
+
pointCount, data, dataPos, tableTag
|
| 801 |
+
)
|
| 802 |
+
else:
|
| 803 |
+
sharedPoints = []
|
| 804 |
+
for _ in range(tupleVariationCount & TUPLE_COUNT_MASK):
|
| 805 |
+
dataSize, flags = struct.unpack(">HH", data[pos : pos + 4])
|
| 806 |
+
tupleSize = TupleVariation.getTupleSize_(flags, numAxes)
|
| 807 |
+
tupleData = data[pos : pos + tupleSize]
|
| 808 |
+
pointDeltaData = data[dataPos : dataPos + dataSize]
|
| 809 |
+
result.append(
|
| 810 |
+
decompileTupleVariation_(
|
| 811 |
+
pointCount,
|
| 812 |
+
sharedTuples,
|
| 813 |
+
sharedPoints,
|
| 814 |
+
tableTag,
|
| 815 |
+
axisTags,
|
| 816 |
+
tupleData,
|
| 817 |
+
pointDeltaData,
|
| 818 |
+
)
|
| 819 |
+
)
|
| 820 |
+
pos += tupleSize
|
| 821 |
+
dataPos += dataSize
|
| 822 |
+
return result
|
| 823 |
+
|
| 824 |
+
|
| 825 |
+
def decompileTupleVariation_(
|
| 826 |
+
pointCount, sharedTuples, sharedPoints, tableTag, axisTags, data, tupleData
|
| 827 |
+
):
|
| 828 |
+
assert tableTag in ("cvar", "gvar"), tableTag
|
| 829 |
+
flags = struct.unpack(">H", data[2:4])[0]
|
| 830 |
+
pos = 4
|
| 831 |
+
if (flags & EMBEDDED_PEAK_TUPLE) == 0:
|
| 832 |
+
peak = sharedTuples[flags & TUPLE_INDEX_MASK]
|
| 833 |
+
else:
|
| 834 |
+
peak, pos = TupleVariation.decompileCoord_(axisTags, data, pos)
|
| 835 |
+
if (flags & INTERMEDIATE_REGION) != 0:
|
| 836 |
+
start, pos = TupleVariation.decompileCoord_(axisTags, data, pos)
|
| 837 |
+
end, pos = TupleVariation.decompileCoord_(axisTags, data, pos)
|
| 838 |
+
else:
|
| 839 |
+
start, end = inferRegion_(peak)
|
| 840 |
+
axes = {}
|
| 841 |
+
for axis in axisTags:
|
| 842 |
+
region = start[axis], peak[axis], end[axis]
|
| 843 |
+
if region != (0.0, 0.0, 0.0):
|
| 844 |
+
axes[axis] = region
|
| 845 |
+
pos = 0
|
| 846 |
+
if (flags & PRIVATE_POINT_NUMBERS) != 0:
|
| 847 |
+
points, pos = TupleVariation.decompilePoints_(
|
| 848 |
+
pointCount, tupleData, pos, tableTag
|
| 849 |
+
)
|
| 850 |
+
else:
|
| 851 |
+
points = sharedPoints
|
| 852 |
+
|
| 853 |
+
deltas = [None] * pointCount
|
| 854 |
+
|
| 855 |
+
if tableTag == "cvar":
|
| 856 |
+
deltas_cvt, pos = TupleVariation.decompileDeltas_(len(points), tupleData, pos)
|
| 857 |
+
for p, delta in zip(points, deltas_cvt):
|
| 858 |
+
if 0 <= p < pointCount:
|
| 859 |
+
deltas[p] = delta
|
| 860 |
+
|
| 861 |
+
elif tableTag == "gvar":
|
| 862 |
+
deltas_x, pos = TupleVariation.decompileDeltas_(len(points), tupleData, pos)
|
| 863 |
+
deltas_y, pos = TupleVariation.decompileDeltas_(len(points), tupleData, pos)
|
| 864 |
+
for p, x, y in zip(points, deltas_x, deltas_y):
|
| 865 |
+
if 0 <= p < pointCount:
|
| 866 |
+
deltas[p] = (x, y)
|
| 867 |
+
|
| 868 |
+
return TupleVariation(axes, deltas)
|
| 869 |
+
|
| 870 |
+
|
| 871 |
+
def inferRegion_(peak):
|
| 872 |
+
"""Infer start and end for a (non-intermediate) region
|
| 873 |
+
|
| 874 |
+
This helper function computes the applicability region for
|
| 875 |
+
variation tuples whose INTERMEDIATE_REGION flag is not set in the
|
| 876 |
+
TupleVariationHeader structure. Variation tuples apply only to
|
| 877 |
+
certain regions of the variation space; outside that region, the
|
| 878 |
+
tuple has no effect. To make the binary encoding more compact,
|
| 879 |
+
TupleVariationHeaders can omit the intermediateStartTuple and
|
| 880 |
+
intermediateEndTuple fields.
|
| 881 |
+
"""
|
| 882 |
+
start, end = {}, {}
|
| 883 |
+
for axis, value in peak.items():
|
| 884 |
+
start[axis] = min(value, 0.0) # -0.3 --> -0.3; 0.7 --> 0.0
|
| 885 |
+
end[axis] = max(value, 0.0) # -0.3 --> 0.0; 0.7 --> 0.7
|
| 886 |
+
return (start, end)
|