/** * This will read the required data from the stream. * * @param ttf The font that is being read. * @param data The stream to read the data from. * @ If there is an error reading the data. */ public override void Read(TrueTypeFont ttf, TTFDataStream data) { HorizontalHeaderTable hHeader = ttf.HorizontalHeader; if (hHeader == null) { throw new IOException("Could not get hhea table"); } numHMetrics = hHeader.NumberOfHMetrics; int numGlyphs = ttf.NumberOfGlyphs; int bytesRead = 0; advanceWidth = new int[numHMetrics]; leftSideBearing = new short[numHMetrics]; for (int i = 0; i < numHMetrics; i++) { advanceWidth[i] = data.ReadUnsignedShort(); leftSideBearing[i] = data.ReadSignedShort(); bytesRead += 4; } int numberNonHorizontal = numGlyphs - numHMetrics; // handle bad fonts with too many hmetrics if (numberNonHorizontal < 0) { numberNonHorizontal = numGlyphs; } // make sure that table is never null and correct size, even with bad fonts that have no // "leftSideBearing" table although they should nonHorizontalLeftSideBearing = new short[numberNonHorizontal]; if (bytesRead < Length) { for (int i = 0; i < numberNonHorizontal; i++) { if (bytesRead < Length) { nonHorizontalLeftSideBearing[i] = data.ReadSignedShort(); bytesRead += 2; } } } initialized = true; }
private byte[] buildHheaTable() { using (var bos = new MemoryStream()) using (var output = new BinaryWriter(bos)) { HorizontalHeaderTable h = ttf.HorizontalHeader; WriteFixed(output, h.Version); WriteSInt16(output, h.Ascender); WriteSInt16(output, h.Descender); WriteSInt16(output, h.LineGap); WriteUint16(output, h.AdvanceWidthMax); WriteSInt16(output, h.MinLeftSideBearing); WriteSInt16(output, h.MinRightSideBearing); WriteSInt16(output, h.XMaxExtent); WriteSInt16(output, h.CaretSlopeRise); WriteSInt16(output, h.CaretSlopeRun); WriteSInt16(output, h.Reserved1); // caretOffset WriteSInt16(output, h.Reserved2); WriteSInt16(output, h.Reserved3); WriteSInt16(output, h.Reserved4); WriteSInt16(output, h.Reserved5); WriteSInt16(output, h.MetricDataFormat); // input there a GID >= numberOfHMetrics ? Then keep the last entry of original hmtx table, // (add if it isn't in our set of GIDs), see also in buildHmtxTable() int hmetrics = glyphIds.GetViewBetween(0, h.NumberOfHMetrics).Count(); if (glyphIds.LastOrDefault() >= h.NumberOfHMetrics && !glyphIds.Contains(h.NumberOfHMetrics - 1)) { ++hmetrics; } WriteUint16(output, hmetrics); output.Flush(); return(bos.ToArray()); } }
private byte[] BuildHmtxTable() { MemoryStream bos = new MemoryStream(); HorizontalHeaderTable h = ttf.HorizontalHeader; HorizontalMetricsTable hm = ttf.HorizontalMetrics; Bytes.Buffer input = ttf.OriginalData; // more info: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6hmtx.html int lastgid = h.NumberOfHMetrics - 1; // true if lastgid input not in the set: we'll need its width (but not its left side bearing) later bool needLastGidWidth = false; if (glyphIds.LastOrDefault() > lastgid && !glyphIds.Contains(lastgid)) { needLastGidWidth = true; } try { long isResult = input.Skip(hm.Offset); if (isResult.CompareTo(hm.Offset) != 0) { Debug.WriteLine($"debug: Tried skipping {hm.Offset} bytes but only {isResult} bytes skipped"); } long lastOffset = 0; foreach (int glyphId in glyphIds) { // offset in original file long offset; if (glyphId <= lastgid) { // copy width and lsb offset = glyphId * 4L; lastOffset = CopyBytes(input, bos, offset, lastOffset, 4); } else { if (needLastGidWidth) { // one time only: copy width from lastgid, whose width applies // to all later glyphs needLastGidWidth = false; offset = lastgid * 4L; lastOffset = CopyBytes(input, bos, offset, lastOffset, 2); // then go on with lsb from actual glyph (lsb are individual even in monotype fonts) } // copy lsb only, as we are beyond numOfHMetrics offset = h.NumberOfHMetrics * 4L + (glyphId - h.NumberOfHMetrics) * 2L; lastOffset = CopyBytes(input, bos, offset, lastOffset, 2); } } return(bos.ToArray()); } finally { input.Dispose(); } }
private TTFTable ReadTableDirectory(TrueTypeFont font, TTFDataStream raf) { TTFTable table; string tag = raf.ReadString(4); switch (tag) { case CmapTable.TAG: table = new CmapTable(font); break; case GlyphTable.TAG: table = new GlyphTable(font); break; case HeaderTable.TAG: table = new HeaderTable(font); break; case HorizontalHeaderTable.TAG: table = new HorizontalHeaderTable(font); break; case HorizontalMetricsTable.TAG: table = new HorizontalMetricsTable(font); break; case IndexToLocationTable.TAG: table = new IndexToLocationTable(font); break; case MaximumProfileTable.TAG: table = new MaximumProfileTable(font); break; case NamingTable.TAG: table = new NamingTable(font); break; case OS2WindowsMetricsTable.TAG: table = new OS2WindowsMetricsTable(font); break; case PostScriptTable.TAG: table = new PostScriptTable(font); break; case DigitalSignatureTable.TAG: table = new DigitalSignatureTable(font); break; case KerningTable.TAG: table = new KerningTable(font); break; case VerticalHeaderTable.TAG: table = new VerticalHeaderTable(font); break; case VerticalMetricsTable.TAG: table = new VerticalMetricsTable(font); break; case VerticalOriginTable.TAG: table = new VerticalOriginTable(font); break; case GlyphSubstitutionTable.TAG: table = new GlyphSubstitutionTable(font); break; default: table = ReadTable(font, tag); break; } table.Tag = tag; table.CheckSum = raf.ReadUnsignedInt(); table.Offset = raf.ReadUnsignedInt(); table.Length = raf.ReadUnsignedInt(); // skip tables with zero length (except glyf) if (table.Length == 0 && !tag.Equals(GlyphTable.TAG, StringComparison.Ordinal)) { return(null); } return(table); }
/** * Parse all tables and check if all needed tables are present. * * @param font the TrueTypeFont instance holding the parsed data. * @ If there is an error parsing the TrueType font. */ private void ParseTables(TrueTypeFont font) { foreach (TTFTable table in font.Tables) { if (!table.Initialized) { font.ReadTable(table); } } bool isPostScript = AllowCFF && font.TableMap.ContainsKey(CFFTable.TAG); HeaderTable head = font.Header; if (head == null) { throw new IOException("head is mandatory"); } HorizontalHeaderTable hh = font.HorizontalHeader; if (hh == null) { throw new IOException("hhead is mandatory"); } MaximumProfileTable maxp = font.MaximumProfile; if (maxp == null) { throw new IOException("maxp is mandatory"); } PostScriptTable post = font.PostScript; if (post == null && !isEmbedded) { // in an embedded font this table is optional throw new IOException("post is mandatory"); } if (!isPostScript) { IndexToLocationTable loc = font.IndexToLocation; if (loc == null) { throw new IOException("loca is mandatory"); } if (font.Glyph == null) { throw new IOException("glyf is mandatory"); } } if (font.Naming == null && !isEmbedded) { throw new IOException("name is mandatory"); } if (font.HorizontalMetrics == null) { throw new IOException("hmtx is mandatory"); } if (!isEmbedded && font.Cmap == null) { throw new IOException("cmap is mandatory"); } }