Skip to content

[semi-off-topic] SFNTQA / Porting FontValidator from C# to Python? #416

@twardoch

Description

@twardoch

Apologies for a semi-off-topic issue.

Microsoft recently opensourced Font Validator (FontVal) under the super-liberal MIT license. Somewhat unfortunately, Font Validator is written in C#, but it seems to me that, generally speaking, that code is relatively self-contained, and seems to be cleanly written. Do you think it would be feasible to port it to Python (to make "PyFontVal" — which shouldn't really become part of fontTools, much more likely a wholly-separate package)? Given that C# is more strongly typed than Python, it should be possible, at least theoretically.

I've just looked at one function, val_JSTF.cs, and tried to automatically convert a bit of it to Python using the VaryCode converter, which, after the free registration, limits the conversion to 2048 characters.

Here's the input:

 using System;

using OTFontFile;
using OTFontFile.OTL;

namespace OTFontFileVal
{
    /// <summary>
    /// Summary description for val_JSTF.
    /// </summary>
    public class val_JSTF : Table_JSTF, ITableValidate
    {
        public val_JSTF(OTTag tag, MBOBuffer buf) : base(tag, buf)
        {
            m_DataOverlapDetector = new DataOverlapDetector();
        }


        public bool Validate(Validator v, OTFontVal fontOwner)
        {
            bool bRet = true;


            // check the version
            if (v.PerformTest(T.JSTF_Version))
            {
                if (Version.GetUint() == 0x00010000)
                {
                    v.Pass(T.JSTF_Version, P.JSTF_P_Version, m_tag);
                }
                else
                {
                    v.Error(T.JSTF_Version, E.JSTF_E_Version, m_tag, "0x" + Version.GetUint().ToString("x8"));
                    bRet = false;
                }
            }

            // check the JstfScriptRecord array length
            if (v.PerformTest(T.JSTF_JstfScriptRecord_length))
            {
                if ((uint)FieldOffsets.JstfScriptRecords + JstfScriptCount*6 > m_bufTable.GetLength())
                {
                    v.Error(T.JSTF_JstfScriptRecord_length, E.JSTF_E_Array_pastEOT, m_tag, "JstfScriptRecord array");
                    bRet = false;
                }
            }

            // check that the JstfScriptRecord array is in alphabetical order
            if (v.PerformTest(T.JSTF_JstfScriptRecord_order))
            {
                if (JstfScriptCount > 1)
                {
                    for (uint i=0; i<JstfScriptCount-1; i++)
                    {
                        JstfScriptRecord jsrCurr = GetJstfScriptRecord_val(i);
                        JstfScriptRecord jsrNext = GetJstfScriptRecord_val(i+1);

                        if (jsrCurr.JstfScriptTag >= jsrNext.JstfScriptTag)
                        {
                            v.Error(T.JSTF_JstfScriptRecord_order, E.JSTF_E_Array_order, m_tag, "JstfScriptRecord array");
                            bRet = false;
                            break;
                        }
                    }
                }
            }

            // check each JstfScriptRecord
            if (v.PerformTest(T.JSTF_JstfScriptRecords))
            {
                for (uint i=0; i<JstfScriptCount; i++)
                {
                    JstfScriptRecord_val jsr = GetJstfScriptRecord_val(i);

                    // check the tag
                    if (!jsr.JstfScriptTag.IsValid())
                    {
                        v.Error(T.JSTF_JstfScriptRecords, E.JSTF_E_tag, m_tag, "JstfScriptRecord[" + i + "]");
                        bRet = false;
                    }

                    // check the offset
                    if (jsr.JstfScriptOffset > m_bufTable.GetLength())
                    {
                        v.Error(T.JSTF_JstfScriptRecords, E.JSTF_E_Offset_PastEOT, m_tag, "JstfScriptRecord[" + i + "]");
                        bRet = false;
                    }

                    // validate the JstfScript table
                    JstfScript_val js = jsr.GetJstfScriptTable_val();
...

and here is the generated Python code:

from System import *
from OTFontFile import *
from OTFontFile.OTL import *

class val_JSTF(Table_JSTF, ITableValidate):
    """ <summary>
     Summary description for val_JSTF.
     </summary>
    """
    def __init__(self, tag, buf):
        self._m_DataOverlapDetector = DataOverlapDetector()

    def Validate(self, v, fontOwner):
        bRet = True
        # check the version
        if v.PerformTest(T.JSTF_Version):
            if Version.GetUint() == 0x00010000:
                v.Pass(T.JSTF_Version, P.JSTF_P_Version, m_tag)
            else:
                v.Error(T.JSTF_Version, E.JSTF_E_Version, m_tag, "0x" + Version.GetUint().ToString("x8"))
                bRet = False
        # check the JstfScriptRecord array length
        if v.PerformTest(T.JSTF_JstfScriptRecord_length):
            if FieldOffsets.JstfScriptRecords + JstfScriptCount * 6 > m_bufTable.GetLength():
                v.Error(T.JSTF_JstfScriptRecord_length, E.JSTF_E_Array_pastEOT, m_tag, "JstfScriptRecord array")
                bRet = False
        # check that the JstfScriptRecord array is in alphabetical order
        if v.PerformTest(T.JSTF_JstfScriptRecord_order):
            if JstfScriptCount > 1:
                i = 0
                while i < JstfScriptCount - 1:
                    jsrCurr = self.GetJstfScriptRecord_val(i)
                    jsrNext = self.GetJstfScriptRecord_val(i + 1)
                    if jsrCurr.JstfScriptTag >= jsrNext.JstfScriptTag:
                        v.Error(T.JSTF_JstfScriptRecord_order, E.JSTF_E_Array_order, m_tag, "JstfScriptRecord array")
                        bRet = False
                        break
                    i += 1
        # check each JstfScriptRecord
        if v.PerformTest(T.JSTF_JstfScriptRecords):
            i = 0
            while i < JstfScriptCount:
                jsr = self.GetJstfScriptRecord_val(i)
                # check the tag
                if not jsr.JstfScriptTag.IsValid():
                    v.Error(T.JSTF_JstfScriptRecords, E.JSTF_E_tag, m_tag, "JstfScriptRecord[" + i + "]")
                    bRet = False
                # check the offset
                if jsr.JstfScriptOffset > m_bufTable.GetLength():
                    v.Error(T.JSTF_JstfScriptRecords, E.JSTF_E_Offset_PastEOT, m_tag, "JstfScriptRecord[" + i + "]")
                    bRet = False
                # validate the JstfScript table
                js = jsr.GetJstfScriptTable_val()
...

At least it does not look bad. Perhaps, after a conversion and some refactoring, this could be used as a basis for a more-generic font validation library which could integrate fontTools, the "PyFontVal" port and a somewhat refactored AFDKO CompareFamily.py ?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions