/************************ * public methods */ public bool Validate(Validator v, OTFontVal fontOwner) { bool bRet = true; if (v.PerformTest(T.hhea_version)) { if (TableVersionNumber.GetUint() == 0x00010000) { v.Pass(T.hhea_version, P.hhea_P_version, m_tag); } else { v.Error(T.hhea_version, E.hhea_E_version, m_tag, "0x" + TableVersionNumber.GetUint().ToString("x8")); bRet = false; } } if (v.PerformTest(T.hhea_AscenderPositive)) { if (Ascender <= 0) { string s = "Ascender = " + Ascender; v.Error(T.hhea_AscenderPositive, E.hhea_E_AscenderPositive, m_tag, s); bRet = false; } else { v.Pass(T.hhea_AscenderPositive, P.hhea_P_AscenderPositive, m_tag); } } if (v.PerformTest(T.hhea_DescenderNegative)) { if (Descender >= 0) { string s = "Descender = " + Descender; v.Error(T.hhea_DescenderNegative, E.hhea_E_DescenderNegative, m_tag, s); bRet = false; } else { v.Pass(T.hhea_DescenderNegative, P.hhea_P_DescenderNegative, m_tag); } } Table_head headTable = (Table_head)fontOwner.GetTable("head"); if (headTable != null) { if (v.PerformTest(T.hhea_Ascender_yMax)) { if (Ascender > headTable.yMax) { string s = "Ascender = " + Ascender + ", head.yMax = " + headTable.yMax; v.Info(T.hhea_Ascender_yMax, I.hhea_I_Ascender_yMax, m_tag, s); // bRet = false; } else { v.Pass(T.hhea_Ascender_yMax, P.hhea_P_Ascender_yMax, m_tag); } } if (v.PerformTest(T.hhea_Descender_yMin)) { if (Descender < headTable.yMin) { string s = "Descender = " + Descender + ", head.yMin = " + headTable.yMin; v.Info(T.hhea_Descender_yMin, I.hhea_I_Descender_yMin, m_tag, s); // bRet = false; } else { v.Pass(T.hhea_Descender_yMin, P.hhea_P_Descender_yMin, m_tag); } } } else { v.Error(T.hhea_Ascender_yMax, E._TEST_E_TableMissing, m_tag, "head"); bRet = false; } if (v.PerformTest(T.hhea_LineGapPositive)) { if (LineGap < 0) { string s = "LineGap = " + LineGap; v.Warning(T.hhea_LineGapPositive, W.hhea_W_LineGapPositive, m_tag, s); //bRet = false; } else { v.Pass(T.hhea_LineGapPositive, P.hhea_P_LineGapPositive, m_tag); } } Table_OS2 OS2Table = (Table_OS2)fontOwner.GetTable("OS/2"); if (OS2Table != null) { if (v.PerformTest(T.hhea_Ascender_usWinAscent)) { if (OS2Table.usWinAscent != Ascender) { string s = "hhea.Ascender = " + Ascender + ", OS/2.usWinAscent = " + OS2Table.usWinAscent; v.Warning(T.hhea_Ascender_usWinAscent, W.hhea_W_Ascender_usWinAscent, m_tag, s); } else { v.Pass(T.hhea_Ascender_usWinAscent, P.hhea_P_Ascender_usWinAscent, m_tag); } } if (v.PerformTest(T.hhea_Descender_usWinDescent)) { if (OS2Table.usWinDescent != (Descender * -1)) { string s = "hhea.Descender = " + Descender + ", OS/2.usWinDescent = " + OS2Table.usWinDescent; v.Warning(T.hhea_Descender_usWinDescent, W.hhea_W_Descender_usWinDescent, m_tag, s); } else { v.Pass(T.hhea_Descender_usWinDescent, P.hhea_P_Descender_usWinDescent, m_tag); } } // Microsoft is recommending that Ascender, Descender and LineGap be in line with OS2.winAscent, OS2.winDescent // and formulated value for LineGap to make sure font displays the same on Apple as it does no Windows. if (v.PerformTest(T.hhea_LineGap_minGap)) { int sMinGap = (OS2Table.usWinAscent + OS2Table.usWinDescent) - (Ascender - Descender); if (LineGap < sMinGap) { string s = "LineGap = " + LineGap + ", recommended = " + sMinGap; v.Warning(T.hhea_LineGap_minGap, W.hhea_W_LineGap_minGap, m_tag, s); //bRet = false; } else { v.Pass(T.hhea_LineGap_minGap, P.hhea_P_LineGap_minGap, m_tag); } } else { v.Error(T.hhea_LineGap_minGap, E._TEST_E_TableMissing, m_tag, "OS/2"); } } else { v.Error(T.hhea_Ascender_usWinAscent, E._TEST_E_TableMissing, m_tag, "OS/2"); v.Error(T.hhea_Descender_usWinDescent, E._TEST_E_TableMissing, m_tag, "OS/2"); v.Error(T.hhea_LineGap_minGap, E._TEST_E_TableMissing, m_tag, "OS/2"); bRet = false; } if (v.PerformTest(T.hhea_MinMax)) { if (fontOwner.ContainsTrueTypeOutlines()) { Table_hmtx hmtxTable = (Table_hmtx)fontOwner.GetTable("hmtx"); Table_glyf glyfTable = (Table_glyf)fontOwner.GetTable("glyf"); Table_maxp maxpTable = (Table_maxp)fontOwner.GetTable("maxp"); if (hmtxTable == null) { v.Error(T.hhea_MinMax, E._TEST_E_TableMissing, m_tag, "hmtx"); bRet = false; } else if (glyfTable == null) { v.Error(T.hhea_MinMax, E._TEST_E_TableMissing, m_tag, "glyf"); bRet = false; } else if (maxpTable == null) { v.Error(T.hhea_MinMax, E._TEST_E_TableMissing, m_tag, "maxp"); bRet = false; } else { ushort numGlyphs = fontOwner.GetMaxpNumGlyphs(); ushort awMax = 0; short minLSB = 32767; short minRSB = 32767; short xmaxEx = -32768; Table_hmtx.longHorMetric hm = null; for (uint iGlyph = 0; iGlyph < numGlyphs; iGlyph++) { hm = hmtxTable.GetOrMakeHMetric(iGlyph, fontOwner); if (hm == null) { break; } if (awMax < hm.advanceWidth) { awMax = hm.advanceWidth; // We want to give the user feedback to know what glyph id // has the bad width assigned if (awMax > advanceWidthMax) { string s = "glyph ID = " + iGlyph + ", advance width = " + awMax; v.Error(T.hhea_MinMax, E.hhea_E_advanceWidthMax, m_tag, s); bRet = false; } } Table_glyf.header gh = glyfTable.GetGlyphHeader(iGlyph, fontOwner); // calculate for all non-zero contours...this includes composites if (gh != null && gh.numberOfContours != 0) { if (minLSB > hm.lsb) { minLSB = hm.lsb; } short rsb = (short)(hm.advanceWidth - hm.lsb - (gh.xMax - gh.xMin)); if (minRSB > rsb) { minRSB = rsb; } short extent = (short)(hm.lsb + (gh.xMax - gh.xMin)); if (xmaxEx < extent) { xmaxEx = extent; } } } if (hm != null) { if (advanceWidthMax == awMax) { v.Pass(T.hhea_MinMax, P.hhea_P_advanceWidthMax, m_tag); } else { string s = "actual = " + advanceWidthMax + ", calc = " + awMax; v.Error(T.hhea_MinMax, E.hhea_E_advanceWidthMax, m_tag, s); bRet = false; } if (minLeftSideBearing == minLSB) { v.Pass(T.hhea_MinMax, P.hhea_P_minLeftSideBearing, m_tag); } else { string s = "actual = " + minLeftSideBearing + ", calc = " + minLSB; v.Error(T.hhea_MinMax, E.hhea_E_minLeftSideBearing, m_tag, s); bRet = false; } if (minRightSideBearing == minRSB) { v.Pass(T.hhea_MinMax, P.hhea_P_minRightSideBearing, m_tag); } else { string s = "actual = " + minRightSideBearing + ", calc = " + minRSB; v.Error(T.hhea_MinMax, E.hhea_E_minRightSideBearing, m_tag, s); bRet = false; } if (xMaxExtent == xmaxEx) { v.Pass(T.hhea_MinMax, P.hhea_P_xMaxExtent, m_tag); } else { string s = "actual = " + xMaxExtent + ", calc = " + xmaxEx; v.Error(T.hhea_MinMax, E.hhea_E_xMaxExtent, m_tag, s); bRet = false; } } else { v.Warning(T.hhea_MinMax, W.hhea_W_hmtx_invalid, m_tag, "unable to parse hmtx table"); } } } else { v.Info(T.hhea_MinMax, I._TEST_I_NotForCFF, m_tag, "test = hhea_MinMax"); } } if (v.PerformTest(T.hhea_reserved)) { if (reserved1 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved1, m_tag, reserved1.ToString()); bRet = false; } else if (reserved2 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved2, m_tag, reserved2.ToString()); bRet = false; } else if (reserved3 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved3, m_tag, reserved3.ToString()); bRet = false; } else if (reserved4 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved4, m_tag, reserved4.ToString()); bRet = false; } else { v.Pass(T.hhea_reserved, P.hhea_P_reserved, m_tag); } } if (v.PerformTest(T.hhea_metricDataFormat)) { if (metricDataFormat == 0) { v.Pass(T.hhea_metricDataFormat, P.hhea_P_metricDataFormat, m_tag); } else { v.Error(T.hhea_metricDataFormat, E.hhea_E_metricDataFormat, m_tag, metricDataFormat.ToString()); bRet = false; } } if (v.PerformTest(T.hhea_numberOfHMetrics)) { Table_hmtx hmtxTable = (Table_hmtx)fontOwner.GetTable("hmtx"); Table_maxp maxpTable = (Table_maxp)fontOwner.GetTable("maxp"); if (hmtxTable == null) { v.Error(T.hhea_numberOfHMetrics, E._TEST_E_TableMissing, m_tag, "hmtx"); bRet = false; } else if (maxpTable == null) { v.Error(T.hhea_numberOfHMetrics, E._TEST_E_TableMissing, m_tag, "maxp"); bRet = false; } else { ushort numGlyphs = fontOwner.GetMaxpNumGlyphs(); if (numberOfHMetrics * 4 + (numGlyphs - numberOfHMetrics) * 2 == hmtxTable.GetLength()) { v.Pass(T.hhea_numberOfHMetrics, P.hhea_P_numberOfHMetrics, m_tag); } else { v.Error(T.hhea_numberOfHMetrics, E.hhea_E_numberOfHMetrics, m_tag); bRet = false; } } } if (v.PerformTest(T.hhea_caretSlope)) { bool bSlopeOk = true; Table_post postTable = (Table_post)fontOwner.GetTable("post"); if (postTable != null) { uint ia = postTable.italicAngle.GetUint(); double dItalicAngle = postTable.italicAngle.GetDouble(); if (ia == 0) { if (caretSlopeRun != 0) { v.Error(T.hhea_caretSlope, E.hhea_E_caretSlopeRunNonZero_italicAngle, m_tag); bSlopeOk = false; bRet = false; } } else { if (caretSlopeRun == 0) { v.Error(T.hhea_caretSlope, E.hhea_E_caretSlopeRunZero_italicAngle, m_tag); bSlopeOk = false; bRet = false; } else { double dActualAngle = 90.0 + postTable.italicAngle.GetDouble(); double dhheaAngle = (Math.Atan2(caretSlopeRise, caretSlopeRun)) * (180.0 / Math.PI); if (Math.Abs(dActualAngle - dhheaAngle) >= 1.0) { string sDetails = "caretSlope Rise:Run = " + caretSlopeRise + ":" + caretSlopeRun + " (" + (dhheaAngle - 90.0) + " degrees)" + ", post.italicAngle = 0x" + ia.ToString("x8") + " (" + postTable.italicAngle.GetDouble() + " degrees)"; v.Error(T.hhea_caretSlope, E.hhea_E_caretSlopeAngle_italicAngle, m_tag, sDetails); bSlopeOk = false; bRet = false; } } } } else { v.Error(T.hhea_caretSlope, E._TEST_E_TableMissing, m_tag, "post table missing, can't compare caret slope to italicAngle"); bSlopeOk = false; bRet = false; } if (bSlopeOk) { v.Pass(T.hhea_caretSlope, P.hhea_P_caretSlopeAngle_italicAngle, m_tag); } } return(bRet); }
/************************ * public methods */ public bool Validate(Validator v, OTFontVal fontOwner) { bool bRet = true; if (v.PerformTest(T.head_TableLength)) { if (m_bufTable.GetLength() == 54) { v.Pass(T.head_TableLength, P.head_P_TableLength, m_tag); } else { v.Error(T.head_TableLength, E.head_E_TableLength, m_tag, m_bufTable.GetLength().ToString()); bRet = false; } } if (v.PerformTest(T.head_TableVersion)) { if (TableVersionNumber.GetUint() == 0x00010000) { v.Pass(T.head_TableVersion, P.head_P_TableVersion, m_tag); } else { v.Error(T.head_TableVersion, E.head_E_TableVersion, m_tag, "0x"+TableVersionNumber.GetUint().ToString("x8")); bRet = false; } } if (v.PerformTest(T.head_fontRevision)) { string sVersion = fontOwner.GetFontVersion(); if (sVersion != null) { if (sVersion.Length >= 11 && sVersion.StartsWith("Version ") && Char.IsDigit(sVersion, 8)) { string sVersionNum = sVersion.Substring(8); bool bFoundDecPt = false; int nLastDigitPos = 0; for (int i=0; i<sVersionNum.Length; i++) { if (Char.IsDigit(sVersionNum, i)) { nLastDigitPos = i; } else if (sVersionNum[i] == '.') { if (!bFoundDecPt) { bFoundDecPt = true; } else { break; } } else { break; } } double fVersion = Double.Parse(sVersionNum.Substring(0, nLastDigitPos+1)); double fRevision = fontRevision.GetDouble(); if (Math.Round(fVersion, 3) == Math.Round(fRevision, 3)) { v.Pass(T.head_fontRevision, P.head_P_fontRevision, m_tag, fRevision.ToString("f3")); } else { string s = "revision: " + fRevision.ToString("f3") + ", version: " + sVersionNum; v.Warning(T.head_fontRevision, W.head_W_fontRevision, m_tag, s); } } } } if (v.PerformTest(T.head_ChecksumAdjustment)) { if (checkSumAdjustment == 0xb1b0afba - fontOwner.CalcChecksum()) { v.Pass(T.head_ChecksumAdjustment, P.head_P_FontChecksum, m_tag, "0x"+checkSumAdjustment.ToString("x8")); } else { v.Error(T.head_ChecksumAdjustment, E.head_E_FontChecksum, m_tag, "0x"+checkSumAdjustment.ToString("x8")); bRet = false; } } if (v.PerformTest(T.head_MagicNumber)) { if (magicNumber == 0x5f0f3cf5) { v.Pass(T.head_MagicNumber, P.head_P_MagicNumber, m_tag); } else { v.Error(T.head_MagicNumber, E.head_E_MagicNumber, m_tag, "0x"+magicNumber.ToString("x8")); bRet = false; } } if (v.PerformTest(T.head_FlagTests)) { ushort val = flags; // bit 0 indicates baseline for font at y=0 // bit 1 indicates left sidebearing point at x=0 // bit 2 indicates instructions may depend on point size // bit 3 indicates forcing ppem to integer values instead of fractional values // bit 4 indicates non-linear scaling - if set allows presence of LTSH and/or hdmx tables Table_hdmx hdmxTable = (Table_hdmx)fontOwner.GetTable("hdmx"); Table_LTSH LTSHTable = (Table_LTSH)fontOwner.GetTable("LTSH"); if ((val & 0x0010) == 0) { if (hdmxTable == null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_0_hdmx, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit4_0_hdmx, m_tag); } if (LTSHTable == null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_0_LTSH, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit4_0_LTSH, m_tag); } } else { if (hdmxTable != null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_1_hdmx, m_tag); } else { v.Warning(T.head_FlagTests, W.head_W_flags_bit4_1_hdmx, m_tag); } if (LTSHTable != null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_1_LTSH, m_tag); } else { v.Warning(T.head_FlagTests, W.head_W_flags_bit4_1_LTSH, m_tag); } } // bits 5 - 10 are used by apple and ignored by windows // bit 11 indicates font data is 'lossless' as a result of having been compressed/decompressed // bit 12 indicates a converted font // bit 13 indicates optimized for cleartype // bit 14 reserved if ((val & 0x4000) == 0) { v.Pass(T.head_FlagTests, P.head_P_flags_bit14, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit14, m_tag, val.ToString()); bRet = false; } // bit 15 reserved if ((val & 0x8000) == 0) { v.Pass(T.head_FlagTests, P.head_P_flags_bit15, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit15, m_tag, val.ToString()); bRet = false; } } if (v.PerformTest(T.head_UnitsPerEmValues)) { ushort val = unitsPerEm; bool bInRange = false; if (val < 16) // opentype spec says min value is 16 { v.Error(T.head_UnitsPerEmValues, E.head_E_unitsPerEm_LT16, m_tag, val.ToString()); bRet = false; } else if (val < 64) // apple spec says min value is 64 { v.Warning(T.head_UnitsPerEmValues, W.head_W_unitsPerEM_LT64, m_tag, val.ToString()); } else if (val > 16384) { v.Error(T.head_UnitsPerEmValues, E.head_E_unitsPerEM_GT16384, m_tag, val.ToString()); bRet = false; } else { bInRange = true; } // apple spec says unitsPerEm must be power of two // but don't check if it's a CFF font if (!fontOwner.ContainsPostScriptOutlines()) { bool bPowerOfTwo = false; for (int i=0; i<16; i++) { ushort nPow2 = (ushort)(1<<i); if (val == nPow2) { bPowerOfTwo = true; break; } } if (bPowerOfTwo) { if (bInRange) { v.Pass(T.head_UnitsPerEmValues, P.head_P_unitsPerEm, m_tag, val.ToString()); } } else { v.Warning(T.head_UnitsPerEmValues, W.head_W_unitsPerEm_Pow2, m_tag, val.ToString()); } } } if (v.PerformTest(T.head_Dates)) { DateTime dtBeforeTrueType = new DateTime(1985, 1, 1); if ((created >> 32) == 0) { DateTime dtCreated = this.GetCreatedDateTime(); if (created == 0) { v.Warning(T.head_Dates, W.head_W_created_0, m_tag); } else if (dtCreated < dtBeforeTrueType || dtCreated > DateTime.Now) { string sDetails = "created = " + created + " (" + dtCreated.ToString("f", null) + ")"; v.Warning(T.head_Dates, W.head_W_created_unlikely, m_tag, sDetails); } else { v.Pass(T.head_Dates, P.head_P_created_0, m_tag); } } else { string sDetails = "created = 0x" + created.ToString("x16"); v.Error(T.head_Dates, E.head_E_created_invalid, m_tag, sDetails); bRet = false; } if ((modified >> 32) == 0) { DateTime dtModified = this.GetModifiedDateTime(); if (modified == 0) { v.Warning(T.head_Dates, W.head_W_modified_0, m_tag); } else if (dtModified < dtBeforeTrueType || dtModified > DateTime.Now) { string sDetails = "modified = " + modified + " (" + dtModified.ToString("f", null) + ")"; v.Warning(T.head_Dates, W.head_W_modified_unlikely, m_tag, sDetails); } else { v.Pass(T.head_Dates, P.head_P_modified_0, m_tag); } } else { string sDetails = "modified = 0x" + modified.ToString("x16"); v.Error(T.head_Dates, E.head_E_modified_invalid, m_tag, sDetails); bRet = false; } } if (v.PerformTest(T.head_MinMaxValues)) { if (fontOwner.ContainsTrueTypeOutlines()) { if (xMin > xMax) { v.Error(T.head_MinMaxValues, E.head_E_xMin_GT_xMax, m_tag); bRet = false; } if (yMin > yMax) { v.Error(T.head_MinMaxValues, E.head_E_yMin_GT_yMax, m_tag); bRet = false; } short xMinExpected = 32767; short xMaxExpected = -32768; short yMinExpected = 32767; short yMaxExpected = -32768; Table_glyf glyfTable = (Table_glyf)fontOwner.GetTable("glyf"); if (glyfTable != null) { Table_maxp maxp = (Table_maxp)fontOwner.GetTable("maxp"); if (maxp != null) { for (uint i=0; i<fontOwner.GetMaxpNumGlyphs(); i++) { Table_glyf.header h = glyfTable.GetGlyphHeader(i, fontOwner); if (h != null) { if (xMinExpected > h.xMin) xMinExpected = h.xMin; if (xMaxExpected < h.xMax) xMaxExpected = h.xMax; if (yMinExpected > h.yMin) yMinExpected = h.yMin; if (yMaxExpected < h.yMax) yMaxExpected = h.yMax; } } if (xMin == xMinExpected) { String s = "xMin = " + xMin; v.Pass(T.head_MinMaxValues, P.head_P_xMin_glyf, m_tag, s); } else { string s = "actual: " + xMin + ", expected: " + xMinExpected; v.Error(T.head_MinMaxValues, E.head_E_xMin_glyf, m_tag, s); bRet = false; } if (yMin == yMinExpected) { String s = "yMin = " + yMin; v.Pass(T.head_MinMaxValues, P.head_P_yMin_glyf, m_tag, s); } else { string s = "actual: " + yMin + ", expected: " + yMinExpected; v.Error(T.head_MinMaxValues, E.head_E_yMin_glyf, m_tag, s); bRet = false; } if (xMax == xMaxExpected) { String s = "xMax = " + xMax; v.Pass(T.head_MinMaxValues, P.head_P_xMax_glyf, m_tag, s); } else { string s = "actual: " + xMax + ", expected: " + xMaxExpected; v.Error(T.head_MinMaxValues, E.head_E_xMax_glyf, m_tag, s); bRet = false; } if (yMax == yMaxExpected) { String s = "yMax = " + yMax; v.Pass(T.head_MinMaxValues, P.head_P_yMax_glyf, m_tag, s); } else { string s = "actual: " + yMax + ", expected: " + yMaxExpected; v.Error(T.head_MinMaxValues, E.head_E_yMax_glyf, m_tag, s); bRet = false; } } else { v.Error(T.head_MinMaxValues, E._TEST_E_TableMissing, m_tag, "maxp"); } } else { v.Error(T.head_MinMaxValues, E._TEST_E_TableMissing, m_tag, "glyf"); } } else { v.Info(T.head_MinMaxValues, I._TEST_I_NotForCFF, m_tag, "test = head_MinMaxValues"); } } if (v.PerformTest(T.head_MacStyleBits)) { bool bMacBold = ((macStyle & 0x0001) != 0); bool bMacItal = ((macStyle & 0x0002) != 0); // subfamily (style) string Table_name nameTable = (Table_name)fontOwner.GetTable("name"); string sStyle = null; string sStyleLower = null; if (nameTable != null) { sStyle = nameTable.GetStyleString(); if (sStyle != null) { sStyleLower = sStyle.ToLower(); } } if (sStyleLower != null) { if (bMacBold && sStyleLower.IndexOf("bold") == -1) { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold_subfamily, m_tag, "macStyle bold bit is set, but subfamily is " + sStyle); bRet = false; } else if (!bMacBold && sStyleLower.IndexOf("bold") != -1) { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold_subfamily, m_tag, "macStyle bold bit is clear, but subfamily is " + sStyle); bRet = false; } else { v.Pass(T.head_MacStyleBits, P.head_P_macStyleBold_subfamily, m_tag); } if (bMacItal && sStyleLower.IndexOf("italic") == -1 && sStyleLower.IndexOf("oblique") == -1) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal_subfamily, m_tag, "macStyle italic bit is set, but subfamily is " + sStyle); bRet = false; } else if (!bMacItal && (sStyleLower.IndexOf("italic") != -1 || sStyleLower.IndexOf("oblique") != -1)) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal_subfamily, m_tag, "macStyle italic bit is clear, but subfamily is " + sStyle); bRet = false; } else { v.Pass(T.head_MacStyleBits, P.head_P_macStyleItal_subfamily, m_tag); } } Table_OS2 OS2Table = (Table_OS2)fontOwner.GetTable("OS/2"); if (OS2Table != null) { // fsSelection bool bOS2Bold = ((OS2Table.fsSelection & 0x0020) != 0); bool bOS2Ital = ((OS2Table.fsSelection & 0x0001) != 0); if (bMacBold == bOS2Bold) { v.Pass(T.head_MacStyleBits, P.head_P_macStyleBold_OS2, m_tag); } else if (bMacBold) { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold1_OS2, m_tag); bRet = false; } else { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold0_OS2, m_tag); bRet = false; } if (bMacItal == bOS2Ital) { v.Pass(T.head_MacStyleBits, P.head_P_macStyleItal_OS2, m_tag); } else if (bMacItal) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal1_OS2, m_tag); bRet = false; } else { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal0_OS2, m_tag); bRet = false; } } Table_post postTable = (Table_post)fontOwner.GetTable("post"); if (postTable != null) { bool bPostItal = (postTable.italicAngle.GetUint() != 0); if (bMacItal == bPostItal) { v.Pass(T.head_MacStyleBits, P.head_P_macStyleItal_post, m_tag); } else if (bMacItal) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal1_post, m_tag); bRet = false; } else { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal0_post, m_tag); bRet = false; } } } if (v.PerformTest(T.head_LowestRecSize)) { if (lowestRecPPEM == 0) { v.Error(T.head_LowestRecSize, E.head_E_lowestRecPPEM_zero, m_tag); bRet = false; } else if (lowestRecPPEM >= 1 && lowestRecPPEM <= 6) { v.Warning(T.head_LowestRecSize, W.head_W_lowestRecPPEM_small, m_tag, "lowestRecPPEM = " + lowestRecPPEM.ToString()); } else if (lowestRecPPEM >= 36) { v.Warning(T.head_LowestRecSize, W.head_W_lowestRecPPEM_large, m_tag, "lowestRecPPEM = " + lowestRecPPEM.ToString()); } else { v.Pass(T.head_LowestRecSize, P.head_P_lowestRecPPEM, m_tag); } } if (v.PerformTest(T.head_FontDirectionHint)) { if (fontDirectionHint >= -2 && fontDirectionHint <= 2) { v.Pass(T.head_FontDirectionHint, P.head_P_fontDirectionHint, m_tag, fontDirectionHint.ToString()); } else { v.Error(T.head_FontDirectionHint, E.head_E_fontDirectionHint, m_tag, fontDirectionHint.ToString()); bRet = false; } } if (v.PerformTest(T.head_IndexToLocFormat)) { if (fontOwner.ContainsPostScriptOutlines()) { v.Pass(T.head_IndexToLocFormat, P.head_P_indexToLocFormat_ignore, m_tag, "indexToLocFormat = " + indexToLocFormat); } else { if (indexToLocFormat == 0 || indexToLocFormat == 1) { v.Pass(T.head_IndexToLocFormat, P.head_P_indexToLocFormat_range, m_tag, indexToLocFormat.ToString()); } else { v.Error(T.head_IndexToLocFormat, E.head_E_indexToLocFormat_range, m_tag, indexToLocFormat.ToString()); bRet = false; } if (indexToLocFormat == 0 || indexToLocFormat == 1) { Table_loca locaTable = (Table_loca)fontOwner.GetTable("loca"); if (locaTable != null) { Table_maxp maxpTable = (Table_maxp)fontOwner.GetTable("maxp"); if (maxpTable != null) { uint locaTableElementSize = 0; if (indexToLocFormat == 0) locaTableElementSize = 2; else if (indexToLocFormat == 1) locaTableElementSize = 4; uint CalcLocaLength = (uint)(maxpTable.NumGlyphs + 1) * locaTableElementSize; if (CalcLocaLength == locaTable.GetLength()) { v.Pass(T.head_IndexToLocFormat, P.head_P_indexToLocFormat_match, m_tag, indexToLocFormat.ToString()); } else { v.Error(T.head_IndexToLocFormat, E.head_E_indexToLocFormat_match, m_tag, indexToLocFormat.ToString()); bRet = false; } } else { v.Error(T.head_IndexToLocFormat, E._TEST_E_TableMissing, m_tag, "maxp"); } } else { v.Error(T.head_IndexToLocFormat, E._TEST_E_TableMissing, m_tag, "loca"); } } } } if (v.PerformTest(T.head_GlyphDataFormat)) { if (glyphDataFormat == 0) { v.Pass(T.head_GlyphDataFormat, P.head_P_glyphDataFormat, m_tag); } else { v.Error(T.head_GlyphDataFormat, E.head_E_glyphDataFormat, m_tag); bRet = false; } } return bRet; }
/************************ * public methods */ public bool Validate(Validator v, OTFontVal fontOwner) { bool bRet = true; if (v.PerformTest(T.OS_2_Version)) { if (version == 0 || version == 1 || version == 2) { v.Warning(T.OS_2_Version, W.OS_2_W_Version_old, m_tag, version.ToString()); } else if (version == 3) { v.Pass(T.OS_2_Version, P.OS_2_P_Version, m_tag, version.ToString()); } else { v.Error(T.OS_2_Version, E.OS_2_E_Version, m_tag, version.ToString()); bRet = false; } } if (v.PerformTest(T.OS_2_TableLength)) { uint len = GetLength(); if ((version == 0 && len == 78) || (version == 1 && len == 86) || (version == 2 && len == 96) || (version == 3 && len == 96)) { v.Pass(T.OS_2_TableLength, P.OS_2_P_TableLength, m_tag); } else { v.Error(T.OS_2_TableLength, E.OS_2_E_TableLength, m_tag); bRet = false; } } if (!bRet) { v.Warning(T.T_NULL, W._TEST_W_OtherErrorsInTable, m_tag, "OS/2 table appears to be corrupt. No further tests will be performed."); return bRet; } if (v.PerformTest(T.OS_2_xAvgCharWidth)) { if (fontOwner.GetTable("maxp") == null) { v.Warning(T.OS_2_xAvgCharWidth, W._TEST_W_ErrorInAnotherTable, m_tag, "maxp table inaccessible, can't check Avg Char Width"); } else { if (fontOwner.ContainsTrueTypeOutlines()) { Table_hmtx hmtxTable = (Table_hmtx)fontOwner.GetTable("hmtx"); val_loca locaTable = (val_loca)fontOwner.GetTable("loca"); val_maxp maxpTable = (val_maxp)fontOwner.GetTable("maxp"); if (hmtxTable == null) { v.Error(T.OS_2_xAvgCharWidth, E._TEST_E_TableMissing, m_tag, "hmtx"); bRet = false; } else if (locaTable == null) { v.Error(T.OS_2_xAvgCharWidth, E._TEST_E_TableMissing, m_tag, "loca"); bRet = false; } else if (maxpTable == null) { v.Error(T.OS_2_xAvgCharWidth, E._TEST_E_TableMissing, m_tag, "maxp"); bRet = false; } else { if ( version == 3 || fontOwner.ContainsMsSymbolEncodedCmap()) { int nTotalWidth = 0; int nTotalGlyphs = 0; for (uint iGlyph=0; iGlyph<fontOwner.GetMaxpNumGlyphs(); iGlyph++) { Table_hmtx.longHorMetric hm = hmtxTable.GetOrMakeHMetric(iGlyph, fontOwner); if (hm != null) { nTotalWidth += hm.advanceWidth; // only average non-zero width glyphs if (hm.advanceWidth > 0) { nTotalGlyphs ++; } } } short CalcAvgWidth = 0; if (nTotalGlyphs > 0) CalcAvgWidth = (short)(nTotalWidth / nTotalGlyphs); if (xAvgCharWidth == CalcAvgWidth) { v.Pass(T.OS_2_xAvgCharWidth, P.OS_2_P_xAvgCharWidth, m_tag); } else { string s = "actual = " + xAvgCharWidth + ", calc = " + CalcAvgWidth; v.Error(T.OS_2_xAvgCharWidth, E.OS_2_E_xAvgCharWidth, m_tag, s); bRet = false; } } else { // do weighed average ushort [] weightLC = { 64, 14, 27, 35, 100, 20, 14, 42, 63, 3, 6, 35, 20, 56, 56, 17, 4, 49, 56, 71, 31, 10, 18, 3, 18, 2 }; ushort weightSpace = 166; try { uint iSpaceGlyph = fontOwner.FastMapUnicodeToGlyphID(' '); if (iSpaceGlyph >= fontOwner.GetMaxpNumGlyphs()) { throw new ApplicationException("error in cmap table"); } Table_hmtx.longHorMetric hmSpace = hmtxTable.GetOrMakeHMetric(iSpaceGlyph, fontOwner); if (hmSpace != null) { int nTotalWeight = hmSpace.advanceWidth * weightSpace; int nLowerCase = 0; for (char c = 'a'; c <= 'z'; c++) { uint iGlyph = fontOwner.FastMapUnicodeToGlyphID(c); if (iGlyph > 0 && iGlyph < fontOwner.GetMaxpNumGlyphs()) { nLowerCase ++; Table_hmtx.longHorMetric hm = hmtxTable.GetOrMakeHMetric(iGlyph, fontOwner); nTotalWeight += hm.advanceWidth * weightLC[c-'a']; } } short CalcAvgWidth = 0; if (nLowerCase == 26) { CalcAvgWidth = (short)(nTotalWeight / 1000); } else { int nTotalWidth = 0; int nTotalGlyphs = 0; for (uint iGlyph=0; iGlyph<fontOwner.GetMaxpNumGlyphs(); iGlyph++) { Table_hmtx.longHorMetric hm = hmtxTable.GetOrMakeHMetric(iGlyph, fontOwner); nTotalWidth += hm.advanceWidth; nTotalGlyphs ++; } CalcAvgWidth = (short)(nTotalWidth / nTotalGlyphs); } if (xAvgCharWidth == CalcAvgWidth) { v.Pass(T.OS_2_xAvgCharWidth, P.OS_2_P_xAvgCharWidth, m_tag); } else { string s = "actual = " + xAvgCharWidth + ", calc = " + CalcAvgWidth; v.Error(T.OS_2_xAvgCharWidth, E.OS_2_E_xAvgCharWidth, m_tag, s); bRet = false; } } else { v.Warning(T.OS_2_xAvgCharWidth, W.OS_2_W_hmtx_invalid, m_tag, "unable to parse hmtx table"); } } catch (ApplicationException e) { v.Warning(T.OS_2_xAvgCharWidth, W._TEST_W_ErrorInAnotherTable, m_tag, "xAvgCharWidth cannot be validated due to " + e.Message); } } } } else { // not a TT outline font - should embedded bitmap only font be handled here? v.Info(T.OS_2_xAvgCharWidth, I._TEST_I_NotForCFF, m_tag, "test = OS/2_xAvgCharWidth"); } } } if (v.PerformTest(T.OS_2_WeightClass)) { if (usWeightClass >= 100 && usWeightClass <= 900 && usWeightClass% 100 == 0) { bool bWeightClassOk = true; // compare to the PANOSE weight value if (panose_byte1 == 2 || panose_byte1 == 3 || panose_byte1 == 4) // latin text, hand writing, or decorative { if (panose_byte3 > 1) { // convert PANOSE weight value from a range of [2..11] to a range of [100..900] (reasonably close anyway, given integer math) int nConvertedPANOSE = (panose_byte3-2)*89+100; int nDifference = Math.Abs(usWeightClass - nConvertedPANOSE); if (nDifference < 200) { v.Pass(T.OS_2_WeightClass, P.OS_2_P_WeightClass_PANOSE, m_tag); } else { v.Warning(T.OS_2_WeightClass, W.OS_2_W_WeightClass_PANOSE, m_tag, "usWeightClass = " + usWeightClass + ", PANOSE weight = " + panose_byte3); bWeightClassOk = false; } } } if (bWeightClassOk) { v.Pass(T.OS_2_WeightClass, P.OS_2_P_WeightClass, m_tag, usWeightClass.ToString()); } } else { v.Error(T.OS_2_WeightClass, E.OS_2_E_WeightClass, m_tag, usWeightClass.ToString()); bRet = false; } } if (v.PerformTest(T.OS_2_WidthClass)) { if (usWidthClass >=1 && usWidthClass <= 9) { v.Pass(T.OS_2_WidthClass, P.OS_2_P_WidthClass, m_tag, usWidthClass.ToString()); } else { v.Error(T.OS_2_WidthClass, E.OS_2_E_WidthClass, m_tag, usWidthClass.ToString()); bRet = false; } } if (v.PerformTest(T.OS_2_fsType)) { bool bPass = true; if ((fsType & 0xfcf1) != 0) { v.Error(T.OS_2_fsType, E.OS_2_E_fsTypeReserved, m_tag, "0x"+fsType.ToString("x4")); bRet = false; bPass = false; } else { int nExclusiveBits = 0; if ((fsType & 0x0002) !=0) //Restricted License Embedding { nExclusiveBits++; } if ((fsType & 0x0004) != 0) // Preview and Print embedding { nExclusiveBits++; } if ((fsType & 0x0008) != 0) // Editable embedding { nExclusiveBits++; } if (nExclusiveBits > 1) { string sDetails = "0x"+fsType.ToString("x4"); if ((fsType & 0x0002) !=0) { sDetails += ", Restricted License Embedding"; } if ((fsType & 0x0004) != 0) { sDetails += ", Preview and Print embedding"; } if ((fsType & 0x0008) != 0) { sDetails += ", Editable embedding"; } v.Error(T.OS_2_fsType, E.OS_2_E_fsTypeExclusiveBits, m_tag, sDetails); bRet = false; bPass = false; } } if (bPass) { string sDetails = "0x"+fsType.ToString("x4"); if (fsType == 0) { sDetails += ", Installable Embedding"; } if ((fsType & 0x0002) !=0) { sDetails += ", Restricted License Embedding"; } if ((fsType & 0x0004) != 0) { sDetails += ", Preview and Print embedding"; } if ((fsType & 0x0008) != 0) { sDetails += ", Editable embedding"; } if ((fsType & 0x0100) != 0) { sDetails += ", No subsetting"; } if ((fsType & 0x0200) != 0) { sDetails += ", Bitmap embedding only"; } v.Pass(T.OS_2_fsType, P.OS_2_P_fsType, m_tag, sDetails); } } if (v.PerformTest(T.OS_2_SubscriptSuperscript)) { Table_head headTable = (Table_head)fontOwner.GetTable("head"); if (headTable != null) { ushort unitsPerEm = headTable.unitsPerEm; ushort SmallestScript = (ushort)(unitsPerEm / 10); bool bNoWarnOrErr = true; if (ySubscriptXSize < SmallestScript || ySubscriptXSize > unitsPerEm ) { v.Warning(T.OS_2_SubscriptSuperscript, W.OS_2_W_ySubscriptXSize_unlikely, m_tag, ySubscriptXSize.ToString()); bNoWarnOrErr = false; } if (ySubscriptYSize < SmallestScript || ySubscriptYSize > unitsPerEm ) { v.Warning(T.OS_2_SubscriptSuperscript, W.OS_2_W_ySubscriptYSize_unlikely, m_tag, ySubscriptYSize.ToString()); bNoWarnOrErr = false; } if (ySubscriptYOffset < 0 ) { v.Warning(T.OS_2_SubscriptSuperscript, W.OS_2_W_ySubscriptYOffset_LTZero, m_tag, ySubscriptYOffset.ToString()); bNoWarnOrErr = false; } if (ySuperscriptXSize < SmallestScript || ySuperscriptXSize > unitsPerEm ) { v.Warning(T.OS_2_SubscriptSuperscript, W.OS_2_W_ySuperscriptXSize_unlikely, m_tag, ySuperscriptXSize.ToString()); bNoWarnOrErr = false; } if (ySuperscriptYSize < SmallestScript || ySuperscriptYSize > unitsPerEm ) { v.Warning(T.OS_2_SubscriptSuperscript, W.OS_2_W_ySuperscriptYSize_unlikely, m_tag, ySuperscriptYSize.ToString()); bNoWarnOrErr = false; } if (ySuperscriptYOffset < 0 ) { v.Warning(T.OS_2_SubscriptSuperscript, W.OS_2_W_ySuperscriptYOffset_unlikely, m_tag, ySuperscriptYOffset.ToString()); bNoWarnOrErr = false; } if (bNoWarnOrErr) { v.Pass(T.OS_2_SubscriptSuperscript, P.OS_2_P_SuperscriptSubscript, m_tag); } } else { v.Error(T.OS_2_SubscriptSuperscript, E._TEST_E_TableMissing, m_tag, "head"); bRet = false; } } if (v.PerformTest(T.OS_2_Strikeout)) { Table_head headTable = (Table_head)fontOwner.GetTable("head"); if (headTable != null) { ushort unitsPerEm = headTable.unitsPerEm; bool bNoWarnings = true; if (yStrikeoutSize < 0 || yStrikeoutSize > unitsPerEm / 2 ) { v.Warning(T.OS_2_Strikeout, W.OS_2_W_yStrikeoutSize_unlikely, m_tag, yStrikeoutSize.ToString()); bNoWarnings = false; } if (yStrikeoutPosition <= 0 ) { v.Warning(T.OS_2_Strikeout, W.OS_2_W_yStrikeoutPosition_unlikely, m_tag, yStrikeoutPosition.ToString()); bNoWarnings = false; } if (bNoWarnings) { v.Pass(T.OS_2_Strikeout, P.OS_2_P_Strikeout, m_tag); } } else { v.Error(T.OS_2_Strikeout, E._TEST_E_TableMissing, m_tag, "head"); bRet = false; } } if (v.PerformTest(T.OS_2_FamilyClass)) { bool bIDsOk = true; byte ClassID = (byte)(sFamilyClass >> 8); byte SubclassID = (byte)(sFamilyClass); if (ClassID == 6 || ClassID == 11 || ClassID == 13 || ClassID == 14 ) { v.Error(T.OS_2_FamilyClass, E.OS_2_E_sFamilyClass_classID_reserved, m_tag, ClassID.ToString()); bIDsOk = false; bRet = false; } if (ClassID > 14 ) { v.Error(T.OS_2_FamilyClass, E.OS_2_E_sFamilyClass_ClassID_undefined, m_tag, ClassID.ToString()); bIDsOk = false; bRet = false; } if (SubclassID > 15 ) { v.Error(T.OS_2_FamilyClass, E.OS_2_E_sFamilyClass_subclassID_undefined, m_tag, ClassID.ToString()); bIDsOk = false; bRet = false; } if (bIDsOk) { v.Pass(T.OS_2_FamilyClass, P.OS_2_P_sFamilyClass, m_tag); } } if (v.PerformTest(T.OS_2_Panose)) { bool bPanoseOk = true; if (panose_byte1 < 5) // panose kind valid, but not latin symbol { if (fontOwner.ContainsSymbolsOnly()) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_FamilyTypeNotSymbol, m_tag, "PANOSE byte 1 = " + panose_byte1.ToString()); bPanoseOk = false; bRet = false; } } else if (panose_byte1 == 5) // panose family kind == latin symbol { if (!fontOwner.ContainsSymbolsOnly()) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_FamilyTypeSymbol, m_tag, "PANOSE byte 1 = " + panose_byte1.ToString()); bPanoseOk = false; bRet = false; } } else if ( panose_byte1 > 5 ) // family kind is invalid { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bFamilyType, m_tag, panose_byte1.ToString()); bPanoseOk = false; bRet = false; } if (panose_byte1 == 2) // family kind == latin text { if ( panose_byte2 > 15 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bSerifStyle, m_tag, panose_byte2.ToString()); bPanoseOk = false; bRet = false; } } if ( panose_byte3 > 11 ) // byte3 always means weight { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bWeight, m_tag, panose_byte3.ToString()); bPanoseOk = false; bRet = false; } if (panose_byte1 == 5) // panose family kind == latin symbol { if (panose_byte3 != 1) // weight must be 1 for symbols { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_SymbolWeight, m_tag, panose_byte3.ToString()); bPanoseOk = false; bRet = false; } if (panose_byte5 != 1) // aspect ratio & contrast must be 1 for symbols { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_SymbolAspectRatio, m_tag, panose_byte5.ToString()); bPanoseOk = false; bRet = false; } } if (panose_byte1 == 2) // family kind == latin text { // the following tests are only valid when family kind is latin text if ( panose_byte4 > 9 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bProportion, m_tag, panose_byte4.ToString()); bPanoseOk = false; bRet = false; } if ( panose_byte5 > 9 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bContrast, m_tag, panose_byte5.ToString()); bPanoseOk = false; bRet = false; } if ( panose_byte6 > 10 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bStrokeVariation, m_tag, panose_byte6.ToString()); bPanoseOk = false; bRet = false; } if ( panose_byte7 > 11 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bArmStyle, m_tag, panose_byte7.ToString()); bPanoseOk = false; bRet = false; } if ( panose_byte8 > 15 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bLetterform, m_tag, panose_byte8.ToString()); bPanoseOk = false; bRet = false; } if ( panose_byte9 > 13 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bMidline, m_tag, panose_byte9.ToString()); bPanoseOk = false; bRet = false; } if ( panose_byte10 > 7 ) { v.Error(T.OS_2_Panose, E.OS_2_E_Panose_bXHeight, m_tag, panose_byte10.ToString()); bPanoseOk = false; bRet = false; } } if (panose_byte1 == 0 && panose_byte2 == 0 && panose_byte3 == 0 && panose_byte4 == 0 && panose_byte5 == 0 && panose_byte6 == 0 && panose_byte7 == 0 && panose_byte8 == 0 && panose_byte9 == 0 && panose_byte10 == 0 ) { v.Warning(T.OS_2_Panose, W.OS_2_W_Panose_undefined, m_tag); bPanoseOk = false; } if (bPanoseOk) { v.Pass(T.OS_2_Panose, P.OS_2_P_Panose, m_tag); } } if (v.PerformTest(T.OS_2_UnicodeRanges)) { bRet &= CheckUnicodeRanges(v, fontOwner); } if (v.PerformTest(T.OS_2_fsSelection)) { bool bSelOk = true; Table_name nameTable = (Table_name)fontOwner.GetTable("name"); string sStyle = null; string sStyleLower = null; if (nameTable != null) { sStyle = nameTable.GetStyleString(); if (sStyle != null) { sStyleLower = sStyle.ToLower(); } } // reserved bits if ( (fsSelection & 0xFF80 ) != 0 ) { // we need to look for Win 3.1 font pages // Fonts with these const ushort HEBREW_FONT_PAGE = 0xB100; const ushort SIMP_ARABIC_FONT_PAGE = 0xB200; const ushort TRAD_ARABIC_FONT_PAGE = 0xB300; const ushort OEM_ARABIC_FONT_PAGE = 0xB400; const ushort SIMP_FARSI_FONT_PAGE = 0xBA00; const ushort TRAD_FARSI_FONT_PAGE = 0xBB00; const ushort THAI_FONT_PAGE = 0xDE00; String sDetails = "Bit(s) "; bool bFoundFirstBadBit = false; if (version == 0) { switch (fsSelection & 0xFF00) { case HEBREW_FONT_PAGE: sDetails = "Hebrew Windows 3.1 font page"; break; case SIMP_ARABIC_FONT_PAGE: sDetails = "Simplified Arabic Windows 3.1 font page"; break; case TRAD_ARABIC_FONT_PAGE: sDetails = "Traditional Arabic Windows 3.1 font page"; break; case OEM_ARABIC_FONT_PAGE: sDetails = "OEM Arabic Windows 3.1 font page"; break; case SIMP_FARSI_FONT_PAGE: sDetails = "Simplified Farsi Windows 3.1 font page"; break; case TRAD_FARSI_FONT_PAGE: sDetails = "Traditional Farsi Windows 3.1 font page"; break; case THAI_FONT_PAGE: sDetails = "Thai Windows 3.1 font page"; break; default: for (int i=0; i<16; i++) { int nBitValue = 1<<i; if ((nBitValue & 0xFF80) != 0) { if ((fsSelection & nBitValue) != 0) { if (bFoundFirstBadBit) { sDetails += ", " + i; } else { sDetails += i.ToString(); bFoundFirstBadBit = true; } } } } break; } } else { for (int i=0; i<16; i++) { int nBitValue = 1<<i; if ((nBitValue & 0xFF80) != 0) { if ((fsSelection & nBitValue) != 0) { if (bFoundFirstBadBit) { sDetails += ", " + i; } else { sDetails += i.ToString(); bFoundFirstBadBit = true; } } } } } v.Error(T.OS_2_fsSelection, E.OS_2_E_fsSelection_undefbits, m_tag, sDetails); bSelOk = false; bRet = false; } // compare to head.macStyle italic and bold bits Table_head headTable = (Table_head)fontOwner.GetTable("head"); if (headTable != null) { bool bItalic = ((fsSelection & 0x01) != 0 ); bool bBold = ((fsSelection & 0x20) != 0 ); bool bMacBold = ((headTable.macStyle & 0x0001) != 0); bool bMacItal = ((headTable.macStyle & 0x0002) != 0); if (bItalic != bMacItal) { string sDetails = null; if (bItalic) sDetails = "fsSelection italic bit is set, but macstyle italic bit is clear"; else sDetails = "fsSelection italic bit is clear, but macstyle italic bit is set"; v.Error(T.OS_2_fsSelection, E.OS_2_E_fsSelection_macStyle_italic, m_tag, sDetails); bSelOk = false; bRet = false; } if (bBold != bMacBold) { string sDetails = null; if (bBold) sDetails = "fsSelection bold bit is set, but macstyle bold bit is clear"; else sDetails = "fsSelection bold bit is clear, but macstyle bold bit is set"; v.Error(T.OS_2_fsSelection, E.OS_2_E_fsSelection_macStyle_bold, m_tag, sDetails); bSelOk = false; bRet = false; } } if ((fsSelection & 0x01) != 0 ) // italic bit { // compare to name subfamily if (sStyleLower != null) { if (sStyleLower.IndexOf("italic") == -1 && sStyleLower.IndexOf("oblique") == -1) { v.Error(T.OS_2_fsSelection, E.OS_2_E_fsSelection_subfamily, m_tag, "fsSelection italic bit is set, but subfamily is " + sStyle); bSelOk = false; bRet = false; } } } if ((fsSelection & 0x20) != 0 ) // bold bit { // compare to name subfamily if (sStyleLower != null) { if (sStyleLower.IndexOf("bold") == -1) { v.Error(T.OS_2_fsSelection, E.OS_2_E_fsSelection_subfamily, m_tag, "fsSelection bold bit is set, but subfamily is " + sStyle); bSelOk = false; bRet = false; } } if (usWeightClass <= 500) { v.Warning(T.OS_2_fsSelection, W.OS_2_W_fsSelection_weight, m_tag, "usWeightClass = " + usWeightClass); bSelOk = false; } } if ((fsSelection & 0x40) != 0 ) // regular bit { if ((fsSelection & 0x20) != 0 ) { v.Error(T.OS_2_fsSelection, E.OS_2_E_reg_bold, m_tag); bSelOk = false; bRet = false; } if ((fsSelection & 0x01) != 0) { v.Error(T.OS_2_fsSelection, E.OS_2_E_reg_ital, m_tag); bSelOk = false; bRet = false; } if (sStyleLower != null) { // compare to name subfamily if (sStyleLower.CompareTo("regular") != 0) { v.Error(T.OS_2_fsSelection, E.OS_2_E_fsSelection_subfamily, m_tag, "fsSelection regular bit is set, but subfamily is " + sStyle); bSelOk = false; bRet = false; } } } if (bSelOk) { v.Pass(T.OS_2_fsSelection, P.OS_2_P_fsSelection, m_tag); } } if (v.PerformTest(T.OS_2_CharIndexes)) { Table_cmap cmapTable = (Table_cmap)fontOwner.GetTable("cmap"); if (cmapTable != null) { ushort charFirst = 0xffff; ushort charLast = 0; Table_cmap.EncodingTableEntry eteUni = cmapTable.GetEncodingTableEntry(3,1); if (eteUni == null) { // presumably a symbol font eteUni = cmapTable.GetEncodingTableEntry(3,0); } if (eteUni != null) { Table_cmap.Subtable st = cmapTable.GetSubtable(eteUni); if (st != null) { bool bCmapOk = true; byte[] charbuf = new byte[2]; // find the first char for (ushort c=0; c<0xffff; c++) { charbuf[0] = (byte)c; charbuf[1] = (byte)(c>>8); uint glyphID; try { glyphID = st.MapCharToGlyph(charbuf, 0); } catch { bCmapOk = false; break; } if (glyphID != 0) { charFirst = c; break; } } // find the last char (start at fffd: fffe and ffff aren't legal characters) if (bCmapOk) { for (ushort c=0xfffd; c>0; c--) { charbuf[0] = (byte)c; charbuf[1] = (byte)(c>>8); uint glyphID; try { glyphID = st.MapCharToGlyph(charbuf, 0); } catch { bCmapOk = false; break; } if (glyphID != 0) { charLast = c; break; } } } if (!bCmapOk) { v.Warning(T.OS_2_CharIndexes, W._TEST_W_ErrorInAnotherTable, m_tag, "usFirstCharIndex and usLastCharIndex cannot be validated due to errors in the cmap table"); } else if (usFirstCharIndex == charFirst && usLastCharIndex == charLast) { v.Pass(T.OS_2_CharIndexes, P.OS_2_P_CharIndexes, m_tag, "first = 0x" + usFirstCharIndex.ToString("x4") + ", last = 0x" + usLastCharIndex.ToString("x4")); } else { if (usFirstCharIndex != charFirst) { String sDetails = "actual = 0x" + usFirstCharIndex.ToString("x4") + ", calculated = 0x" + charFirst.ToString("x4"); v.Error(T.OS_2_CharIndexes, E.OS_2_E_usFirstCharIndex, m_tag, sDetails); bRet = false; } if (usLastCharIndex != charLast) { String sDetails = "actual = 0x" + usLastCharIndex.ToString("x4") + ", calculated = 0x" + charLast.ToString("x4"); v.Error(T.OS_2_CharIndexes, E.OS_2_E_usLastCharIndex, m_tag, sDetails); bRet = false; } } } else { v.Warning(T.OS_2_CharIndexes, W._TEST_W_ErrorInAnotherTable, m_tag, "usFirstCharIndex and usLastCharIndex cannot be validated due to errors in the cmap table"); } } } else { v.Error(T.OS_2_CharIndexes, E._TEST_E_TableMissing, m_tag, "cmap"); bRet = false; } } if (v.PerformTest(T.OS_2_TypoMetrics)) { bool bOk = true; if (sTypoAscender <= 0) { v.Error(T.OS_2_TypoMetrics, E.OS_2_E_sTypoAscender_notpositive, m_tag, sTypoAscender.ToString()); bOk = false; bRet = false; } if (sTypoDescender > 0) { v.Error(T.OS_2_TypoMetrics, E.OS_2_E_sTypoDescender_positive, m_tag, sTypoDescender.ToString()); bOk = false; bRet = false; } Table_head headTable = (Table_head)fontOwner.GetTable("head"); if (headTable != null) { if (sTypoAscender - sTypoDescender > headTable.unitsPerEm) { string sDetails = "sTypoAscender = " + sTypoAscender + ", sTypoDescender = " + sTypoDescender; v.Warning(T.OS_2_TypoMetrics, W.OS_2_W_sTypoAscenderDescender_difference, m_tag, sDetails); bOk = false; } } else { v.Error(T.OS_2_TypoMetrics, E._TEST_E_TableMissing, m_tag, "head"); bRet = false; } if (bOk) { string sDetails = "sTypoAscender = " + sTypoAscender + ", sTypoDescender = " + sTypoDescender; v.Pass(T.OS_2_TypoMetrics, P.OS_2_P_sTypoAscenderDescender, m_tag, sDetails); } } if (v.PerformTest(T.OS_2_CodePageRanges)) { if (version >= 1) { bRet &= CheckCodePageRanges(v, fontOwner); } } if (v.PerformTest(T.OS_2_sxHeight)) { if (version >= 2) { if (sxHeight == 0) { if (fontOwner.FastMapUnicodeToGlyphID((char)0x0078) != 0) { v.Error(T.OS_2_sxHeight, E.OS_2_E_sxHeight, m_tag); bRet = false; } else { v.Pass(T.OS_2_sxHeight, P.OS_2_P_sxHeight_zero, m_tag, sxHeight.ToString()); } } else { v.Pass(T.OS_2_sxHeight, P.OS_2_P_sxHeight_nonzero, m_tag, sxHeight.ToString()); } } } if (v.PerformTest(T.OS_2_sCapHeight)) { if (version >= 2) { if (sCapHeight == 0) { if (fontOwner.FastMapUnicodeToGlyphID((char)0x0048) != 0) { v.Error(T.OS_2_sCapHeight, E.OS_2_E_sCapHeight, m_tag); bRet = false; } else { v.Pass(T.OS_2_sCapHeight, P.OS_2_P_sCapHeight_zero, m_tag, sCapHeight.ToString()); } } else { v.Pass(T.OS_2_sCapHeight, P.OS_2_P_sCapHeight_nonzero, m_tag, sCapHeight.ToString()); } } } if (v.PerformTest(T.OS_2_usDefaultChar)) { if (version >= 2) { if (usDefaultChar == 0) { v.Pass(T.OS_2_usDefaultChar, P.OS_2_P_usDefaultChar_zero, m_tag); } else if (fontOwner.FastMapUnicodeToGlyphID((char)usDefaultChar) != 0) { v.Pass(T.OS_2_usDefaultChar, P.OS_2_P_usDefaultChar_nonzero, m_tag, "0x" + usDefaultChar.ToString("x4")); } else { v.Error(T.OS_2_usDefaultChar, E.OS_2_E_usDefaultChar_notmapped, m_tag, "0x" + usDefaultChar.ToString("x4")); bRet = false; } } } if (v.PerformTest(T.OS_2_usBreakChar)) { if (version >= 2) { if (fontOwner.FastMapUnicodeToGlyphID((char)usBreakChar) != 0) { v.Pass(T.OS_2_usBreakChar, P.OS_2_P_usBreakChar_mapped, m_tag); } else { v.Error(T.OS_2_usBreakChar, E.OS_2_E_usBreakChar_notmapped, m_tag, "0x" + usBreakChar.ToString("x4")); bRet = false; } } } if (v.PerformTest(T.OS_2_usMaxContext)) { if (version >= 2) { ushort GPOSMaxContext = 0; Table_GPOS GPOSTable = (Table_GPOS)fontOwner.GetTable("GPOS"); if (GPOSTable != null) { GPOSMaxContext = GPOSTable.GetMaxContext(); } ushort GSUBMaxContext = 0; Table_GSUB GSUBTable = (Table_GSUB)fontOwner.GetTable("GSUB"); if (GSUBTable != null) { GSUBMaxContext = GSUBTable.GetMaxContext(); } ushort CalcMaxContext = Math.Max(GPOSMaxContext, GSUBMaxContext); if (usMaxContext == CalcMaxContext) { v.Pass(T.OS_2_usMaxContext, P.OS_2_P_usMaxContext, m_tag, usMaxContext.ToString()); } else { v.Error(T.OS_2_usMaxContext, E.OS_2_E_usMaxContext, m_tag, "calc = " + CalcMaxContext + ", actual = " + usMaxContext); bRet = false; } } } return bRet; }
/************************ * public methods */ public bool Validate(Validator v, OTFontVal fontOwner) { bool bRet = true; if (v.PerformTest(T.head_TableLength)) { if (m_bufTable.GetLength() == 54) { v.Pass(T.head_TableLength, P.head_P_TableLength, m_tag); } else { v.Error(T.head_TableLength, E.head_E_TableLength, m_tag, m_bufTable.GetLength().ToString()); bRet = false; } } if (v.PerformTest(T.head_TableVersion)) { if (TableVersionNumber.GetUint() == 0x00010000) { v.Pass(T.head_TableVersion, P.head_P_TableVersion, m_tag); } else { v.Error(T.head_TableVersion, E.head_E_TableVersion, m_tag, "0x" + TableVersionNumber.GetUint().ToString("x8")); bRet = false; } } if (v.PerformTest(T.head_fontRevision)) { string sVersion = fontOwner.GetFontVersion(); if (sVersion != null) { if (sVersion.Length >= 11 && sVersion.StartsWith("Version ") && Char.IsDigit(sVersion, 8)) { string sVersionNum = sVersion.Substring(8); bool bFoundDecPt = false; int nLastDigitPos = 0; for (int i = 0; i < sVersionNum.Length; i++) { if (Char.IsDigit(sVersionNum, i)) { nLastDigitPos = i; } else if (sVersionNum[i] == '.') { if (!bFoundDecPt) { bFoundDecPt = true; } else { break; } } else { break; } } double fVersion = Double.Parse(sVersionNum.Substring(0, nLastDigitPos + 1)); double fRevision = fontRevision.GetDouble(); if (Math.Round(fVersion, 3) == Math.Round(fRevision, 3)) { v.Pass(T.head_fontRevision, P.head_P_fontRevision, m_tag, fRevision.ToString("f3")); } else { string s = "revision: " + fRevision.ToString("f3") + ", version: " + sVersionNum; v.Warning(T.head_fontRevision, W.head_W_fontRevision, m_tag, s); } } } } if (v.PerformTest(T.head_ChecksumAdjustment)) { if (checkSumAdjustment == 0xb1b0afba - fontOwner.CalcChecksum()) { v.Pass(T.head_ChecksumAdjustment, P.head_P_FontChecksum, m_tag, "0x" + checkSumAdjustment.ToString("x8")); } else { v.Error(T.head_ChecksumAdjustment, E.head_E_FontChecksum, m_tag, "0x" + checkSumAdjustment.ToString("x8")); bRet = false; } } if (v.PerformTest(T.head_MagicNumber)) { if (magicNumber == 0x5f0f3cf5) { v.Pass(T.head_MagicNumber, P.head_P_MagicNumber, m_tag); } else { v.Error(T.head_MagicNumber, E.head_E_MagicNumber, m_tag, "0x" + magicNumber.ToString("x8")); bRet = false; } } if (v.PerformTest(T.head_FlagTests)) { ushort val = flags; // bit 0 indicates baseline for font at y=0 // bit 1 indicates left sidebearing point at x=0 // bit 2 indicates instructions may depend on point size // bit 3 indicates forcing ppem to integer values instead of fractional values // bit 4 indicates non-linear scaling - if set allows presence of LTSH and/or hdmx tables Table_hdmx hdmxTable = (Table_hdmx)fontOwner.GetTable("hdmx"); Table_LTSH LTSHTable = (Table_LTSH)fontOwner.GetTable("LTSH"); if ((val & 0x0010) == 0) { if (hdmxTable == null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_0_hdmx, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit4_0_hdmx, m_tag); } if (LTSHTable == null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_0_LTSH, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit4_0_LTSH, m_tag); } } else { if (hdmxTable != null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_1_hdmx, m_tag); } else { v.Warning(T.head_FlagTests, W.head_W_flags_bit4_1_hdmx, m_tag); } if (LTSHTable != null) { v.Pass(T.head_FlagTests, P.head_P_flags_bit4_1_LTSH, m_tag); } else { v.Warning(T.head_FlagTests, W.head_W_flags_bit4_1_LTSH, m_tag); } } // bits 5 - 10 are used by apple and ignored by windows // bit 11 indicates font data is 'lossless' as a result of having been compressed/decompressed // bit 12 indicates a converted font // bit 13 indicates optimized for cleartype // bit 14 reserved if ((val & 0x4000) == 0) { v.Pass(T.head_FlagTests, P.head_P_flags_bit14, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit14, m_tag, val.ToString()); bRet = false; } // bit 15 reserved if ((val & 0x8000) == 0) { v.Pass(T.head_FlagTests, P.head_P_flags_bit15, m_tag); } else { v.Error(T.head_FlagTests, E.head_E_flags_bit15, m_tag, val.ToString()); bRet = false; } } if (v.PerformTest(T.head_UnitsPerEmValues)) { ushort val = unitsPerEm; bool bInRange = false; if (val < 16) // opentype spec says min value is 16 { v.Error(T.head_UnitsPerEmValues, E.head_E_unitsPerEm_LT16, m_tag, val.ToString()); bRet = false; } else if (val < 64) // apple spec says min value is 64 { v.Warning(T.head_UnitsPerEmValues, W.head_W_unitsPerEM_LT64, m_tag, val.ToString()); } else if (val > 16384) { v.Error(T.head_UnitsPerEmValues, E.head_E_unitsPerEM_GT16384, m_tag, val.ToString()); bRet = false; } else { bInRange = true; } // apple spec says unitsPerEm must be power of two // but don't check if it's a CFF font if (!fontOwner.ContainsPostScriptOutlines()) { bool bPowerOfTwo = false; for (int i = 0; i < 16; i++) { ushort nPow2 = (ushort)(1 << i); if (val == nPow2) { bPowerOfTwo = true; break; } } if (bPowerOfTwo) { if (bInRange) { v.Pass(T.head_UnitsPerEmValues, P.head_P_unitsPerEm, m_tag, val.ToString()); } } else { v.Warning(T.head_UnitsPerEmValues, W.head_W_unitsPerEm_Pow2, m_tag, val.ToString()); } } } if (v.PerformTest(T.head_Dates)) { DateTime dtBeforeTrueType = new DateTime(1985, 1, 1); if ((created >> 32) == 0) { DateTime dtCreated = this.GetCreatedDateTime(); string sDetails = "created = " + created + " (" + dtCreated.ToString("f", null) + ")"; if (created == 0) { v.Warning(T.head_Dates, W.head_W_created_0, m_tag); } else if (dtCreated < dtBeforeTrueType || dtCreated > DateTime.Now) { v.Warning(T.head_Dates, W.head_W_created_unlikely, m_tag, sDetails); } else { v.Pass(T.head_Dates, P.head_P_created_0, m_tag, sDetails); } } else { string sDetails = "created = 0x" + created.ToString("x16"); v.Error(T.head_Dates, E.head_E_created_invalid, m_tag, sDetails); bRet = false; } if ((modified >> 32) == 0) { DateTime dtModified = this.GetModifiedDateTime(); string sDetails = "modified = " + modified + " (" + dtModified.ToString("f", null) + ")"; if (modified == 0) { v.Warning(T.head_Dates, W.head_W_modified_0, m_tag); } else if (dtModified < dtBeforeTrueType || dtModified > DateTime.Now) { v.Warning(T.head_Dates, W.head_W_modified_unlikely, m_tag, sDetails); } else { v.Pass(T.head_Dates, P.head_P_modified_0, m_tag, sDetails); } } else { string sDetails = "modified = 0x" + modified.ToString("x16"); v.Error(T.head_Dates, E.head_E_modified_invalid, m_tag, sDetails); bRet = false; } } if (v.PerformTest(T.head_MinMaxValues)) { if (fontOwner.ContainsTrueTypeOutlines()) { if (xMin > xMax) { v.Error(T.head_MinMaxValues, E.head_E_xMin_GT_xMax, m_tag); bRet = false; } if (yMin > yMax) { v.Error(T.head_MinMaxValues, E.head_E_yMin_GT_yMax, m_tag); bRet = false; } short xMinExpected = 32767; short xMaxExpected = -32768; short yMinExpected = 32767; short yMaxExpected = -32768; Table_glyf glyfTable = (Table_glyf)fontOwner.GetTable("glyf"); if (glyfTable != null) { Table_maxp maxp = (Table_maxp)fontOwner.GetTable("maxp"); if (maxp != null) { for (uint i = 0; i < fontOwner.GetMaxpNumGlyphs(); i++) { Table_glyf.header h = glyfTable.GetGlyphHeader(i, fontOwner); if (h != null) { if (xMinExpected > h.xMin) { xMinExpected = h.xMin; } if (xMaxExpected < h.xMax) { xMaxExpected = h.xMax; } if (yMinExpected > h.yMin) { yMinExpected = h.yMin; } if (yMaxExpected < h.yMax) { yMaxExpected = h.yMax; } } } if (xMin == xMinExpected) { String s = "xMin = " + xMin; v.Pass(T.head_MinMaxValues, P.head_P_xMin_glyf, m_tag, s); } else { string s = "actual: " + xMin + ", expected: " + xMinExpected; v.Error(T.head_MinMaxValues, E.head_E_xMin_glyf, m_tag, s); bRet = false; } if (yMin == yMinExpected) { String s = "yMin = " + yMin; v.Pass(T.head_MinMaxValues, P.head_P_yMin_glyf, m_tag, s); } else { string s = "actual: " + yMin + ", expected: " + yMinExpected; v.Error(T.head_MinMaxValues, E.head_E_yMin_glyf, m_tag, s); bRet = false; } if (xMax == xMaxExpected) { String s = "xMax = " + xMax; v.Pass(T.head_MinMaxValues, P.head_P_xMax_glyf, m_tag, s); } else { string s = "actual: " + xMax + ", expected: " + xMaxExpected; v.Error(T.head_MinMaxValues, E.head_E_xMax_glyf, m_tag, s); bRet = false; } if (yMax == yMaxExpected) { String s = "yMax = " + yMax; v.Pass(T.head_MinMaxValues, P.head_P_yMax_glyf, m_tag, s); } else { string s = "actual: " + yMax + ", expected: " + yMaxExpected; v.Error(T.head_MinMaxValues, E.head_E_yMax_glyf, m_tag, s); bRet = false; } } else { v.Error(T.head_MinMaxValues, E._TEST_E_TableMissing, m_tag, "maxp"); } } else { v.Error(T.head_MinMaxValues, E._TEST_E_TableMissing, m_tag, "glyf"); } } else { v.Info(T.head_MinMaxValues, I._TEST_I_NotForCFF, m_tag, "test = head_MinMaxValues"); } } if (v.PerformTest(T.head_MacStyleBits)) { bool bMacBold = ((macStyle & 0x0001) != 0); bool bMacItal = ((macStyle & 0x0002) != 0); // subfamily (style) string Table_name nameTable = (Table_name)fontOwner.GetTable("name"); string sStyle = null; string sStyleLower = null; if (nameTable != null) { sStyle = nameTable.GetStyleString(); if (sStyle != null) { sStyleLower = sStyle.ToLower(); } } if (sStyleLower != null) { if (bMacBold && sStyleLower.IndexOf("bold") == -1) { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold_subfamily, m_tag, "macStyle bold bit is set, but subfamily is " + sStyle); bRet = false; } else if (!bMacBold && sStyleLower.IndexOf("bold") != -1) { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold_subfamily, m_tag, "macStyle bold bit is clear, but subfamily is " + sStyle); bRet = false; } else { v.Pass(T.head_MacStyleBits, P.head_P_macStyleBold_subfamily, m_tag); } if (bMacItal && sStyleLower.IndexOf("italic") == -1 && sStyleLower.IndexOf("oblique") == -1) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal_subfamily, m_tag, "macStyle italic bit is set, but subfamily is " + sStyle); bRet = false; } else if (!bMacItal && (sStyleLower.IndexOf("italic") != -1 || sStyleLower.IndexOf("oblique") != -1)) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal_subfamily, m_tag, "macStyle italic bit is clear, but subfamily is " + sStyle); bRet = false; } else { v.Pass(T.head_MacStyleBits, P.head_P_macStyleItal_subfamily, m_tag); } } Table_OS2 OS2Table = (Table_OS2)fontOwner.GetTable("OS/2"); if (OS2Table != null) { // fsSelection bool bOS2Bold = ((OS2Table.fsSelection & 0x0020) != 0); bool bOS2Ital = ((OS2Table.fsSelection & 0x0001) != 0); if (bMacBold == bOS2Bold) { v.Pass(T.head_MacStyleBits, P.head_P_macStyleBold_OS2, m_tag); } else if (bMacBold) { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold1_OS2, m_tag); bRet = false; } else { v.Error(T.head_MacStyleBits, E.head_E_macStyleBold0_OS2, m_tag); bRet = false; } if (bMacItal == bOS2Ital) { v.Pass(T.head_MacStyleBits, P.head_P_macStyleItal_OS2, m_tag); } else if (bMacItal) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal1_OS2, m_tag); bRet = false; } else { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal0_OS2, m_tag); bRet = false; } } Table_post postTable = (Table_post)fontOwner.GetTable("post"); if (postTable != null) { bool bPostItal = (postTable.italicAngle.GetUint() != 0); if (bMacItal == bPostItal) { v.Pass(T.head_MacStyleBits, P.head_P_macStyleItal_post, m_tag); } else if (bMacItal) { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal1_post, m_tag); bRet = false; } else { v.Error(T.head_MacStyleBits, E.head_E_macStyleItal0_post, m_tag); bRet = false; } } } if (v.PerformTest(T.head_LowestRecSize)) { if (lowestRecPPEM == 0) { v.Error(T.head_LowestRecSize, E.head_E_lowestRecPPEM_zero, m_tag); bRet = false; } else if (lowestRecPPEM >= 1 && lowestRecPPEM <= 6) { v.Warning(T.head_LowestRecSize, W.head_W_lowestRecPPEM_small, m_tag, "lowestRecPPEM = " + lowestRecPPEM.ToString()); } else if (lowestRecPPEM >= 36) { v.Warning(T.head_LowestRecSize, W.head_W_lowestRecPPEM_large, m_tag, "lowestRecPPEM = " + lowestRecPPEM.ToString()); } else { v.Pass(T.head_LowestRecSize, P.head_P_lowestRecPPEM, m_tag); } } if (v.PerformTest(T.head_FontDirectionHint)) { if (fontDirectionHint >= -2 && fontDirectionHint <= 2) { v.Pass(T.head_FontDirectionHint, P.head_P_fontDirectionHint, m_tag, fontDirectionHint.ToString()); } else { v.Error(T.head_FontDirectionHint, E.head_E_fontDirectionHint, m_tag, fontDirectionHint.ToString()); bRet = false; } } if (v.PerformTest(T.head_IndexToLocFormat)) { if (fontOwner.ContainsPostScriptOutlines()) { v.Pass(T.head_IndexToLocFormat, P.head_P_indexToLocFormat_ignore, m_tag, "indexToLocFormat = " + indexToLocFormat); } else { if (indexToLocFormat == 0 || indexToLocFormat == 1) { v.Pass(T.head_IndexToLocFormat, P.head_P_indexToLocFormat_range, m_tag, indexToLocFormat.ToString()); } else { v.Error(T.head_IndexToLocFormat, E.head_E_indexToLocFormat_range, m_tag, indexToLocFormat.ToString()); bRet = false; } if (indexToLocFormat == 0 || indexToLocFormat == 1) { Table_loca locaTable = (Table_loca)fontOwner.GetTable("loca"); if (locaTable != null) { Table_maxp maxpTable = (Table_maxp)fontOwner.GetTable("maxp"); if (maxpTable != null) { uint locaTableElementSize = 0; if (indexToLocFormat == 0) { locaTableElementSize = 2; } else if (indexToLocFormat == 1) { locaTableElementSize = 4; } uint CalcLocaLength = (uint)(maxpTable.NumGlyphs + 1) * locaTableElementSize; if (CalcLocaLength == locaTable.GetLength()) { v.Pass(T.head_IndexToLocFormat, P.head_P_indexToLocFormat_match, m_tag, indexToLocFormat.ToString()); } else { v.Error(T.head_IndexToLocFormat, E.head_E_indexToLocFormat_match, m_tag, indexToLocFormat.ToString()); bRet = false; } } else { v.Error(T.head_IndexToLocFormat, E._TEST_E_TableMissing, m_tag, "maxp"); } } else { v.Error(T.head_IndexToLocFormat, E._TEST_E_TableMissing, m_tag, "loca"); } } } } if (v.PerformTest(T.head_GlyphDataFormat)) { if (glyphDataFormat == 0) { v.Pass(T.head_GlyphDataFormat, P.head_P_glyphDataFormat, m_tag); } else { v.Error(T.head_GlyphDataFormat, E.head_E_glyphDataFormat, m_tag); bRet = false; } } return(bRet); }
/************************ * public methods */ public bool Validate(Validator v, OTFontVal fontOwner) { bool bRet = true; if (v.PerformTest(T.hhea_version)) { if (TableVersionNumber.GetUint() == 0x00010000) { v.Pass(T.hhea_version, P.hhea_P_version, m_tag); } else { v.Error(T.hhea_version, E.hhea_E_version, m_tag, "0x"+TableVersionNumber.GetUint().ToString("x8")); bRet = false; } } if (v.PerformTest(T.hhea_AscenderPositive)) { if (Ascender <= 0) { string s = "Ascender = " + Ascender; v.Error(T.hhea_AscenderPositive, E.hhea_E_AscenderPositive, m_tag, s); bRet = false; } else { v.Pass(T.hhea_AscenderPositive, P.hhea_P_AscenderPositive, m_tag); } } if (v.PerformTest(T.hhea_DescenderNegative)) { if (Descender >= 0) { string s = "Descender = " + Descender; v.Error(T.hhea_DescenderNegative, E.hhea_E_DescenderNegative, m_tag, s); bRet = false; } else { v.Pass(T.hhea_DescenderNegative, P.hhea_P_DescenderNegative, m_tag); } } Table_head headTable = (Table_head)fontOwner.GetTable("head"); if (headTable != null) { if (v.PerformTest(T.hhea_Ascender_yMax)) { if (Ascender > headTable.yMax) { string s = "Ascender = " + Ascender + ", head.yMax = " + headTable.yMax; v.Info(T.hhea_Ascender_yMax, I.hhea_I_Ascender_yMax, m_tag, s); // bRet = false; } else { v.Pass(T.hhea_Ascender_yMax, P.hhea_P_Ascender_yMax, m_tag); } } if (v.PerformTest(T.hhea_Descender_yMin)) { if (Descender < headTable.yMin) { string s = "Descender = " + Descender + ", head.yMin = " + headTable.yMin; v.Info(T.hhea_Descender_yMin, I.hhea_I_Descender_yMin, m_tag, s); // bRet = false; } else { v.Pass(T.hhea_Descender_yMin, P.hhea_P_Descender_yMin, m_tag); } } } else { v.Error(T.hhea_Ascender_yMax, E._TEST_E_TableMissing, m_tag, "head"); bRet = false; } if (v.PerformTest(T.hhea_LineGapPositive)) { if (LineGap < 0) { string s = "LineGap = " + LineGap; v.Warning(T.hhea_LineGapPositive, W.hhea_W_LineGapPositive, m_tag, s); //bRet = false; } else { v.Pass(T.hhea_LineGapPositive, P.hhea_P_LineGapPositive, m_tag); } } Table_OS2 OS2Table = (Table_OS2)fontOwner.GetTable("OS/2"); if (OS2Table != null) { if (v.PerformTest(T.hhea_Ascender_usWinAscent)) { if (OS2Table.usWinAscent != Ascender) { string s = "hhea.Ascender = " + Ascender + ", OS/2.usWinAscent = " + OS2Table.usWinAscent; v.Warning(T.hhea_Ascender_usWinAscent, W.hhea_W_Ascender_usWinAscent, m_tag, s); } else { v.Pass(T.hhea_Ascender_usWinAscent, P.hhea_P_Ascender_usWinAscent, m_tag); } } if (v.PerformTest(T.hhea_Descender_usWinDescent)) { if (OS2Table.usWinDescent != (Descender * -1)) { string s = "hhea.Descender = " + Descender + ", OS/2.usWinDescent = " + OS2Table.usWinDescent; v.Warning(T.hhea_Descender_usWinDescent, W.hhea_W_Descender_usWinDescent, m_tag, s); } else { v.Pass(T.hhea_Descender_usWinDescent, P.hhea_P_Descender_usWinDescent, m_tag); } } // Microsoft is recommending that Ascender, Descender and LineGap be in line with OS2.winAscent, OS2.winDescent // and formulated value for LineGap to make sure font displays the same on Apple as it does no Windows. if (v.PerformTest(T.hhea_LineGap_minGap)) { int sMinGap = (OS2Table.usWinAscent + OS2Table.usWinDescent) - (Ascender - Descender); if (LineGap < sMinGap) { string s = "LineGap = " + LineGap + ", recommended = " + sMinGap; v.Warning(T.hhea_LineGap_minGap, W.hhea_W_LineGap_minGap, m_tag, s); //bRet = false; } else { v.Pass(T.hhea_LineGap_minGap, P.hhea_P_LineGap_minGap, m_tag); } } else { v.Error(T.hhea_LineGap_minGap, E._TEST_E_TableMissing, m_tag, "OS/2"); } } else { v.Error(T.hhea_Ascender_usWinAscent, E._TEST_E_TableMissing, m_tag, "OS/2"); v.Error(T.hhea_Descender_usWinDescent, E._TEST_E_TableMissing, m_tag, "OS/2"); v.Error(T.hhea_LineGap_minGap, E._TEST_E_TableMissing, m_tag, "OS/2"); bRet = false; } if (v.PerformTest(T.hhea_MinMax)) { if (fontOwner.ContainsTrueTypeOutlines()) { Table_hmtx hmtxTable = (Table_hmtx)fontOwner.GetTable("hmtx"); Table_glyf glyfTable = (Table_glyf)fontOwner.GetTable("glyf"); Table_maxp maxpTable = (Table_maxp)fontOwner.GetTable("maxp"); if (hmtxTable == null) { v.Error(T.hhea_MinMax, E._TEST_E_TableMissing, m_tag, "hmtx"); bRet = false; } else if (glyfTable == null) { v.Error(T.hhea_MinMax, E._TEST_E_TableMissing, m_tag, "glyf"); bRet = false; } else if (maxpTable == null) { v.Error(T.hhea_MinMax, E._TEST_E_TableMissing, m_tag, "maxp"); bRet = false; } else { ushort numGlyphs = fontOwner.GetMaxpNumGlyphs(); ushort awMax = 0; short minLSB = 32767; short minRSB = 32767; short xmaxEx = -32768; Table_hmtx.longHorMetric hm = null; for (uint iGlyph = 0; iGlyph < numGlyphs; iGlyph++) { hm = hmtxTable.GetOrMakeHMetric(iGlyph, fontOwner); if (hm == null) { break; } if (awMax < hm.advanceWidth) { awMax = hm.advanceWidth; // We want to give the user feedback to know what glyph id // has the bad width assigned if (awMax > advanceWidthMax) { string s = "glyph ID = " + iGlyph + ", advance width = " + awMax; v.Error(T.hhea_MinMax, E.hhea_E_advanceWidthMax, m_tag, s); bRet = false; } } Table_glyf.header gh = glyfTable.GetGlyphHeader(iGlyph, fontOwner); // calculate for all non-zero contours...this includes composites if (gh != null && gh.numberOfContours != 0) { if (minLSB > hm.lsb) minLSB = hm.lsb; short rsb = (short)(hm.advanceWidth - hm.lsb - (gh.xMax - gh.xMin)); if (minRSB > rsb) minRSB = rsb; short extent = (short)(hm.lsb + (gh.xMax - gh.xMin)); if (xmaxEx < extent) xmaxEx = extent; } } if (hm != null) { if (advanceWidthMax == awMax) { v.Pass(T.hhea_MinMax, P.hhea_P_advanceWidthMax, m_tag); } else { string s = "actual = " + advanceWidthMax + ", calc = " + awMax; v.Error(T.hhea_MinMax, E.hhea_E_advanceWidthMax, m_tag, s); bRet = false; } if (minLeftSideBearing == minLSB) { v.Pass(T.hhea_MinMax, P.hhea_P_minLeftSideBearing, m_tag); } else { string s = "actual = " + minLeftSideBearing + ", calc = " + minLSB; v.Error(T.hhea_MinMax, E.hhea_E_minLeftSideBearing, m_tag, s); bRet = false; } if (minRightSideBearing == minRSB) { v.Pass(T.hhea_MinMax, P.hhea_P_minRightSideBearing, m_tag); } else { string s = "actual = " + minRightSideBearing + ", calc = " + minRSB; v.Error(T.hhea_MinMax, E.hhea_E_minRightSideBearing, m_tag, s); bRet = false; } if (xMaxExtent == xmaxEx) { v.Pass(T.hhea_MinMax, P.hhea_P_xMaxExtent, m_tag); } else { string s = "actual = " + xMaxExtent + ", calc = " + xmaxEx; v.Error(T.hhea_MinMax, E.hhea_E_xMaxExtent, m_tag, s); bRet = false; } } else { v.Warning(T.hhea_MinMax, W.hhea_W_hmtx_invalid, m_tag, "unable to parse hmtx table"); } } } else { v.Info(T.hhea_MinMax, I._TEST_I_NotForCFF, m_tag, "test = hhea_MinMax"); } } if (v.PerformTest(T.hhea_reserved)) { if (reserved1 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved1, m_tag, reserved1.ToString()); bRet = false; } else if (reserved2 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved2, m_tag, reserved2.ToString()); bRet = false; } else if (reserved3 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved3, m_tag, reserved3.ToString()); bRet = false; } else if (reserved4 != 0) { v.Error(T.hhea_reserved, E.hhea_E_reserved4, m_tag, reserved4.ToString()); bRet = false; } else { v.Pass(T.hhea_reserved, P.hhea_P_reserved, m_tag); } } if (v.PerformTest(T.hhea_metricDataFormat)) { if (metricDataFormat == 0) { v.Pass(T.hhea_metricDataFormat, P.hhea_P_metricDataFormat, m_tag); } else { v.Error(T.hhea_metricDataFormat, E.hhea_E_metricDataFormat, m_tag, metricDataFormat.ToString()); bRet = false; } } if (v.PerformTest(T.hhea_numberOfHMetrics)) { Table_hmtx hmtxTable = (Table_hmtx)fontOwner.GetTable("hmtx"); Table_maxp maxpTable = (Table_maxp)fontOwner.GetTable("maxp"); if (hmtxTable == null) { v.Error(T.hhea_numberOfHMetrics, E._TEST_E_TableMissing, m_tag, "hmtx"); bRet = false; } else if (maxpTable == null) { v.Error(T.hhea_numberOfHMetrics, E._TEST_E_TableMissing, m_tag, "maxp"); bRet = false; } else { ushort numGlyphs = fontOwner.GetMaxpNumGlyphs(); if (numberOfHMetrics*4 + (numGlyphs-numberOfHMetrics)*2 == hmtxTable.GetLength()) { v.Pass(T.hhea_numberOfHMetrics, P.hhea_P_numberOfHMetrics, m_tag); } else { v.Error(T.hhea_numberOfHMetrics, E.hhea_E_numberOfHMetrics, m_tag); bRet = false; } } } if (v.PerformTest(T.hhea_caretSlope)) { bool bSlopeOk = true; Table_post postTable = (Table_post)fontOwner.GetTable("post"); if (postTable != null) { uint ia = postTable.italicAngle.GetUint(); double dItalicAngle = postTable.italicAngle.GetDouble(); if (ia == 0) { if (caretSlopeRun != 0) { v.Error(T.hhea_caretSlope, E.hhea_E_caretSlopeRunNonZero_italicAngle, m_tag); bSlopeOk = false; bRet = false; } } else { if (caretSlopeRun == 0) { v.Error(T.hhea_caretSlope, E.hhea_E_caretSlopeRunZero_italicAngle, m_tag); bSlopeOk = false; bRet = false; } else { double dActualAngle = 90.0 + postTable.italicAngle.GetDouble(); double dhheaAngle = (Math.Atan2(caretSlopeRise, caretSlopeRun)) * (180.0 / Math.PI); if (Math.Abs(dActualAngle-dhheaAngle) >= 1.0) { string sDetails = "caretSlope Rise:Run = " + caretSlopeRise + ":" + caretSlopeRun + " (" + (dhheaAngle-90.0) + " degrees)" + ", post.italicAngle = 0x" + ia.ToString("x8") + " (" + postTable.italicAngle.GetDouble() + " degrees)"; v.Error(T.hhea_caretSlope, E.hhea_E_caretSlopeAngle_italicAngle, m_tag, sDetails); bSlopeOk = false; bRet = false; } } } } else { v.Error(T.hhea_caretSlope, E._TEST_E_TableMissing, m_tag, "post table missing, can't compare caret slope to italicAngle"); bSlopeOk = false; bRet = false; } if (bSlopeOk) { v.Pass(T.hhea_caretSlope, P.hhea_P_caretSlopeAngle_italicAngle, m_tag); } } return bRet; }