/// <summary> /// overall calculated line spacing /// </summary> static int Calculate_TypoMetricLineSpacing(TtfTypeface typeface) { //from https://www.microsoft.com/typography/OTSpec/recom.htm#tad //sTypoAscender, sTypoDescender and sTypoLineGap //sTypoAscender is used to determine the optimum offset from the top of a text frame to the first baseline. //sTypoDescender is used to determine the optimum offset from the last baseline to the bottom of the text frame. //The value of (sTypoAscender - sTypoDescender) is recommended to equal one em. // //While the OpenType specification allows for CJK (Chinese, Japanese, and Korean) fonts' sTypoDescender and sTypoAscender //fields to specify metrics different from the HorizAxis.ideo and HorizAxis.idtp baselines in the 'BASE' table, //CJK font developers should be aware that existing applications may not read the 'BASE' table at all but simply use //the sTypoDescender and sTypoAscender fields to describe the bottom and top edges of the ideographic em-box. //If developers want their fonts to work correctly with such applications, //they should ensure that any ideographic em-box values in the 'BASE' table describe the same bottom and top edges as the sTypoDescender and //sTypoAscender fields. //See the sections “OpenType CJK Font Guidelines“ and ”Ideographic Em-Box“ for more details. //For Western fonts, the Ascender and Descender fields in Type 1 fonts' AFM files are a good source of sTypoAscender //and sTypoDescender, respectively. //The Minion Pro font family (designed on a 1000-unit em), //for example, sets sTypoAscender = 727 and sTypoDescender = -273. //sTypoAscender, sTypoDescender and sTypoLineGap specify the recommended line spacing for single-spaced horizontal text. //The baseline-to-baseline value is expressed by: //OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap //sTypoLineGap will usually be set by the font developer such that the value of the above expression is approximately 120% of the em. //The application can use this value as the default horizontal line spacing. //The Minion Pro font family (designed on a 1000-unit em), for example, sets sTypoLineGap = 200. return(typeface.Ascender - typeface.Descender + typeface.LineGap); }
/// <summary> /// calculate Baseline-to-Baseline Distance (BTBD) for macOS /// </summary> /// <param name="typeface"></param> /// <returns>return 'unscaled-to-pixel' BTBD value</returns> static int CalculateBTBD_Mac(TtfTypeface typeface) { //from https://www.microsoft.com/typography/otspec/recom.htm#tad //Ascender and Descender are metrics defined by Apple //and are not to be confused with the Windows ascent or descent, //nor should they be confused with the true typographic ascender and descender that are found in AFM files. //The Macintosh metrics below are returned by the Apple Advanced Typography(AAT) GetFontInfo() API. // // //Macintosh Metric OpenType Metric //ascender Ascender //descender Descender //leading LineGap //The suggested BTBD = ascent + descent + leading //If pixels extend above the ascent or below the descent, //the character will be squashed in the vertical direction //so that all pixels fit within these limitations; this is true for screen display only. //TODO: please test this HorizontalHeader hhea = typeface.HheaTable; return(hhea.Ascent + hhea.Descent + hhea.LineGap); }
/// <summary> /// get glyph layout plan or create if not exists /// </summary> /// <param name="typeface"></param> /// <param name="scriptLang"></param> /// <returns></returns> public GlyphLayoutPlanContext GetPlanOrCreate(TtfTypeface typeface, ScriptLang scriptLang) { GlyphLayoutPlanKey key = new GlyphLayoutPlanKey(typeface, scriptLang.internalName); GlyphLayoutPlanContext context; if (!collection.TryGetValue(key, out context)) { var glyphSubstitution = (typeface.GSUBTable != null) ? new GlyphSubstitution(typeface, scriptLang.shortname) : null; var glyphPosition = (typeface.GPOSTable != null) ? new GlyphSetPosition(typeface, scriptLang.shortname) : null; collection.Add(key, context = new GlyphLayoutPlanContext(glyphSubstitution, glyphPosition)); } return(context); }
public bool LoadTTF(Stream stream) { var reader = new OpenFontReader(); ttfTypeFace = reader.Read(stream); if (ttfTypeFace != null) { this.ascent = ttfTypeFace.Ascender; this.descent = ttfTypeFace.Descender; this.unitsPerEm = ttfTypeFace.UnitsPerEm; return(true); } return(false); }
public static int CalculateLineSpacing(this TtfTypeface typeface, LineSpacingChoice choice) { switch (choice) { default: case LineSpacingChoice.Windows: return(Calculate_BTBD_Windows(typeface)); case LineSpacingChoice.Mac: return(CalculateBTBD_Mac(typeface)); case LineSpacingChoice.TypoMetric: return(Calculate_TypoMetricLineSpacing(typeface)); } }
public static bool RecommendToUseTypoMetricsForLineSpacing(this TtfTypeface typeface) { //https://www.microsoft.com/typography/otspec/os2.htm // //fsSelection ... // //bit name //7 USE_TYPO_METRICS // // Description // If set, it is strongly recommended to use // OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap // as a value for default line spacing for this font. return(((typeface.OS2Table.fsSelection >> 7) & 1) != 0); }
/// <summary> /// calculate Baseline-to-Baseline Distance (BTBD) for Windows /// </summary> /// <param name="typeface"></param> /// <returns>return 'unscaled-to-pixel' BTBD value</returns> static int Calculate_BTBD_Windows(TtfTypeface typeface) { //from https://www.microsoft.com/typography/otspec/recom.htm#tad //Baseline to Baseline Distances //The 'OS/2' table fields sTypoAscender, sTypoDescender, and sTypoLineGap //free applications from Macintosh-or Windows - specific metrics //which are constrained by backward compatibility requirements. // //The following discussion only pertains to the platform-specific metrics. //The suggested Baseline to Baseline Distance(BTBD) is computed differently for Windows and the Macintosh, //and it is based on different OpenType metrics. //However, if the recommendations below are followed, the BTBD will be the same for both Windows and the Mac. //Windows Metric OpenType Metric //ascent usWinAscent //descent usWinDescent //internal leading usWinAscent + usWinDescent - unitsPerEm //external leading MAX(0, LineGap - ((usWinAscent + usWinDescent) - (Ascender - Descender))) //The suggested BTBD = ascent + descent + external leading //It should be clear that the “external leading” can never be less than zero. //Pixels above the ascent or below the descent will be clipped from the character; //this is true for all output devices. //The usWinAscent and usWinDescent are values //from the 'OS/2' table. //The unitsPerEm value is from the 'head' table. //The LineGap, Ascender and Descender values are from the 'hhea' table. int usWinAscent = typeface.OS2Table.usWinAscent; int usWinDescent = typeface.OS2Table.usWinDescent; int internal_leading = usWinAscent + usWinDescent - typeface.UnitsPerEm; HorizontalHeader hhea = typeface.HheaTable; int external_leading = System.Math.Max(0, hhea.LineGap - ((usWinAscent + usWinDescent) - (hhea.Ascent - hhea.Descent))); return(usWinAscent + usWinDescent + external_leading); }
public static void CollectAllAssociateGlyphIndex(this TtfTypeface typeface, List <ushort> outputGlyphIndexList, ScriptLang scLang, UnicodeLangBits[] selectedRangs = null) { //----------- //general glyph index in the unicode range //if user dose not specific the unicode lanf bit ranges //the we try to select it ourself. UnicodeLangBits[] unicodeLangBitsRanges; if (ScriptLangs.TryGenUnicodeLangBitsArray(scLang.shortname, out unicodeLangBitsRanges)) { //one lang may contains may ranges if (selectedRangs != null) { //select only in range unicodeLangBitsRanges = FilterOnlySelectedRange(unicodeLangBitsRanges, selectedRangs); } foreach (UnicodeLangBits unicodeLangBits in unicodeLangBitsRanges) { UnicodeRangeInfo rngInfo = unicodeLangBits.ToUnicodeRangeInfo(); int endAt = rngInfo.EndAt; for (int codePoint = rngInfo.StartAt; codePoint <= endAt; ++codePoint) { ushort glyghIndex = typeface.LookupIndex(codePoint); if (glyghIndex > 0) { //add this glyph index outputGlyphIndexList.Add(glyghIndex); } } } } //----------- var gsub = new GlyphSubstitution(typeface, scLang.shortname); gsub.CollectAdditionalSubstitutionGlyphIndices(outputGlyphIndexList); }
public static bool DoesSupportUnicode( this TtfTypeface typeface, UnicodeLangBits unicodeLangBits) { if (typeface.OS2Table == null) { return(false); } //----------------------------- long bits = (long)unicodeLangBits; int bitpos = (int)(bits >> 32); if (bitpos == 0) { return(true); //default } else if (bitpos < 32) { //use range 1 return((typeface.OS2Table.ulUnicodeRange1 & (1 << bitpos)) != 0); } else if (bitpos < 64) { return((typeface.OS2Table.ulUnicodeRange2 & (1 << (bitpos - 32))) != 0); } else if (bitpos < 96) { return((typeface.OS2Table.ulUnicodeRange3 & (1 << (bitpos - 64))) != 0); } else if (bitpos < 128) { return((typeface.OS2Table.ulUnicodeRange4 & (1 << (bitpos - 96))) != 0); } else { throw new System.NotSupportedException(); } }
public static int CalculateRecommendLineSpacing(this TtfTypeface typeface, out LineSpacingChoice choice) { //check if we are on Windows env or macOS eve if (RecommendToUseTypoMetricsForLineSpacing(typeface)) { choice = LineSpacingChoice.TypoMetric; return(Calculate_TypoMetricLineSpacing(typeface)); } else { //check if we are on Windows or mac if (CurrentEnv.CurrentOSName == CurrentOSName.Mac) { choice = LineSpacingChoice.Mac; return(CalculateBTBD_Mac(typeface)); } else { choice = LineSpacingChoice.Windows; return(Calculate_BTBD_Windows(typeface)); } } }
public GlyphSubstitution(TtfTypeface typeface, string lang) { _language = lang; _typeface = typeface; _mustRebuildTables = true; }
public static int CalculateRecommendLineSpacing(this TtfTypeface typeface) { LineSpacingChoice selectedChoice; return(CalculateRecommendLineSpacing(typeface, out selectedChoice)); }
public void Clear() { _typeface = null; _glyphs.Clear(); }
public GlyphLayoutPlanKey(TtfTypeface t, int scriptInternameName) { this.t = t; this.scriptInternameName = scriptInternameName; }
public GlyphSetPosition(TtfTypeface typeface, string lang) { this.Lang = lang; this.typeface = typeface; //check if this lang has this.gposTable = typeface.GPOSTable; if (gposTable == null) { return; } ScriptTable scriptTable = gposTable.ScriptList[lang]; //--------- if (scriptTable == null) { return; } // early exit if no lookup tables //--------- ScriptTable.LangSysTable defaultLang = scriptTable.defaultLang; if (defaultLang == null) { return; } // early exit if no default language if (defaultLang.HasRequireFeature) { } //other feature if (defaultLang.featureIndexList != null) { //get features var features = new List <FeatureList.FeatureTable>(); for (int i = 0; i < defaultLang.featureIndexList.Length; ++i) { FeatureList.FeatureTable feature = gposTable.FeatureList.featureTables[defaultLang.featureIndexList[i]]; switch (feature.TagName) { case "mark": //mark=> mark to base case "mkmk": //mkmk => mark to mask //current version we implement this 2 features features.Add(feature); break; case "kern": //test with Candara font features.Add(feature); //If palt is activated, there is no requirement that kern must also be activated. //If kern is activated, palt must also be activated if it exists. //https://www.microsoft.com/typography/OTSpec/features_pt.htm#palt break; case "palt": break; default: break; } } //----------------------- lookupTables = new List <GPOS.LookupTable>(); int j = features.Count; for (int i = 0; i < j; ++i) { FeatureList.FeatureTable feature = features[i]; foreach (ushort lookupIndex in feature.LookupListIndices) { lookupTables.Add(gposTable.LookupList[lookupIndex]); } } } }