OpenTypeFont.cs :  » PDF » PDF-Clown » it » stefanochizzolini » clown » documents » contents » fonts » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » PDF » PDF Clown 
PDF Clown » it » stefanochizzolini » clown » documents » contents » fonts » OpenTypeFont.cs
/*
  Copyright 2007,2008 Stefano Chizzolini. http://clown.stefanochizzolini.it

  Contributors:
    * Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it)

  This file should be part of the source code distribution of "PDF Clown library"
  (the Program): see the accompanying README files for more info.

  This Program is free software; you can redistribute it and/or modify it under
  the terms of the GNU General Public License as published by the Free Software
  Foundation; either version 2 of the License, or (at your option) any later version.

  This Program is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY, either expressed or implied; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.

  You should have received a copy of the GNU General Public License along with this
  Program (see README files); if not, go to the GNU website (http://www.gnu.org/).

  Redistribution and use, with or without modification, are permitted provided that such
  redistributions retain the above copyright notice, license and disclaimer, along with
  this list of conditions.
*/

using bytesit.stefanochizzolini.clown.bytes;
using it.stefanochizzolini.clown.documents;
using it.stefanochizzolini.clown.objects;
using it.stefanochizzolini.clown.util.io;

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace it.stefanochizzolini.clown.documents.contents.fonts{
  /**
    <summary>OpenType font [PDF:1.6:5;OTF:1.4].</summary>
  */
  public class OpenTypeFont
    : Font
  {
    #region nested types
    /**
      <summary>Font metrics.</summary>
    */
    private sealed class FontMetrics
    {
      #region dynamic
      #region fields
      /**
        <summary>Whether the encoding is custom (symbolic font).</summary>
      */
      public bool IsCustomEncoding;
      /**
        <summary>Unit normalization coefficient.</summary>
      */
      public float UnitNorm;
      /*
        Font Header ('head' table).
      */
      public int Flags; // USHORT.
      public int UnitsPerEm; // USHORT.
      public short XMin;
      public short YMin;
      public short XMax;
      public short YMax;
      public int MacStyle; // USHORT.
      /*
        Horizontal Header ('hhea' table).
      */
      public short Ascender;
      public short Descender;
      public short LineGap;
      public int AdvanceWidthMax; // UFWORD.
      public short MinLeftSideBearing;
      public short MinRightSideBearing;
      public short XMaxExtent;
      public short CaretSlopeRise;
      public short CaretSlopeRun;
      public int NumberOfHMetrics; // USHORT.
      /*
        OS/2 table ('OS/2' table).
      */
      public short STypoAscender;
      public short STypoDescender;
      public short STypoLineGap;
      public short SxHeight;
      public short SCapHeight;
      /*
        PostScript table ('post' table).
      */
      public float ItalicAngle;
      public short UnderlinePosition;
      public short UnderlineThickness;
      public bool IsFixedPitch;
      #endregion
      #endregion
    }

    /**
      <summary>Outline format.</summary>
    */
    private enum OutlineFormatEnum
    {
      /**
        <summary>TrueType format outlines.</summary>
      */
      TrueType,
      /**
        <summary>Compact Font Format outlines.</summary>
      */
      CFF
    }

    /**
      <summary>Font data parser.</summary>
    */
    private sealed class Parser
    {
      #region static
      #region fields
      private const int MicrosoftLanguage_UsEnglish = 0x409;
      private const int NameID_FontPostscriptName = 6;

      private const int PlatformID_Unicode = 0;
      private const int PlatformID_Macintosh = 1;
      private const int PlatformID_Microsoft = 3;
      #endregion
      #endregion

      #region dynamic
      #region fields
      private string fontName;
      private OutlineFormatEnum outlineFormat;

      private bool unicode;

      private FontMetrics metrics;

      private Dictionary<int,int> glyphIndexes;
      private Dictionary<int,int> glyphKernings;
      private int[] glyphWidths;

      private Dictionary<string,int> tableOffsets;

      private Stream fontData;
      private BigEndianBinaryReader fontDataReader;
      #endregion

      #region constructors
      internal Parser(
        Stream fontData
        )
      {
        this.fontData = fontData;
        this.fontDataReader = new BigEndianBinaryReader(fontData);

        Load();
      }
      #endregion

      #region interface
      #region public
      public string FontName
      {get{return fontName;}}

      public Dictionary<int,int> GlyphIndexes
      {get{return glyphIndexes;}}

      public Dictionary<int,int> GlyphKernings
      {get{return glyphKernings;}}

      public int[] GlyphWidths
      {get{return glyphWidths;}}

      public FontMetrics Metrics
      {get{return metrics;}}

      public OutlineFormatEnum OutlineFormat
      {get{return outlineFormat;}}

      public bool Unicode
      {get{return unicode;}}
      #endregion

      #region private
      /**
        <summary>Loads the font data.</summary>
      */
      private void Load(
        )
      {
        metrics = new FontMetrics();

        // 1. Offset Table.
        // Which font file format ('sfnt') version?
        switch(fontDataReader.ReadInt32())
        {
          case(0x00010000): // TrueType.
            outlineFormat = OutlineFormatEnum.TrueType;
            break;
          case(0x4F54544F): // CFF (Type 1).
            outlineFormat = OutlineFormatEnum.CFF;
            break;
          default:
            throw new FontFileFormatException("Not a valid OpenType font file.");
        }
        // Get the number of tables!
        int tableCount = fontDataReader.ReadUInt16();
        tableOffsets = new Dictionary<string,int>(tableCount);

        // Skip to the beginning of the table directory!
        fontData.Seek(6,SeekOrigin.Current);
        // 2. Table Directory.
        // Collecting the table offsets...
        for(
          int index = 0;
          index < tableCount;
          index++
          )
        {
          // Get the table tag!
          String tag = ReadString_Ascii(4);
          // Skip to the table offset!
          fontData.Seek(4,SeekOrigin.Current);
          // Get the table offset!
          int offset = fontDataReader.ReadInt32();
          // Collect the table offset!
          tableOffsets[tag] = offset;

          // Skip to the next entry!
          fontData.Seek(4,SeekOrigin.Current);
        }

        fontName = Load_GetName(NameID_FontPostscriptName);

        Load_Tables();
        Load_CMap();
        Load_GlyphWidths();
        Load_GlyphKerning();
      }

      /**
        <summary>Loads the character to glyph index mapping table.</summary>
      */
      private void Load_CMap(
        )
      {
        /*
          NOTE: A 'cmap' table may contain one or more subtables that represent multiple encodings
          intended for use on different platforms (such as Mac OS and Windows).
          Each subtable is identified by the two numbers, such as (3,1), that represent a combination
          of a platform ID and a platform-specific encoding ID, respectively.
          A symbolic font (used to display glyphs that do not use standard encodings, i.e. neither
          MacRomanEncoding nor WinAnsiEncoding) program's "cmap" table should contain a (1,0) subtable.
          It may also contain a (3,0) subtable; if present, this subtable should map from character
          codes in the range 0xF000 to 0xF0FF by prepending the single-byte codes in the (1,0) subtable
          with 0xF0 and mapping to the corresponding glyph descriptions.
        */
        // Character To Glyph Index Mapping Table ('cmap' table).
        // Retrieve the location info!
        int tableOffset = tableOffsets["cmap"];
        if(tableOffset == 0)
          throw new FontFileFormatException("'cmap' table does NOT exist.");

        int map10Offset = 0;
        int map31Offset = 0;
        // Header.
        // Go to the number of tables!
        fontData.Seek(tableOffset + 2,SeekOrigin.Begin);
        int tableCount = fontDataReader.ReadUInt16();

        // Encoding records.
        /*
          NOTE: Symbolic fonts use specific (non-standard, i.e. neither Unicode nor platform-standard)
          font encodings.
        */
        metrics.IsCustomEncoding = false;
        // Iterating through the entries...
        for(
          int tableIndex = 0;
          tableIndex < tableCount;
          tableIndex++
          )
        {
          // Platform ID.
          int platformID = fontDataReader.ReadUInt16();
          // Encoding ID.
          int encodingID = fontDataReader.ReadUInt16();
          // Subtable offset.
          int offset = fontDataReader.ReadInt32();
    //TODO:DEBUG verify whether to use mac 1,0 or 3,0 tables for symbolic fonts!!!
          switch(platformID)
          {
            case PlatformID_Macintosh:
              if(encodingID == 0)
                map10Offset = offset;
              break;
            case PlatformID_Microsoft:
              /*
                NOTE: When building a symbol font for Windows, the platform ID should be 3 and the
                encoding ID should be 0.
                When building a Unicode font for Windows, the platform ID should be 3 and the
                encoding ID should be 1.
              */
              switch(encodingID)
              {
                case 0: // Symbolic font.
                  metrics.IsCustomEncoding = true;
                  break;
                case 1: // Nonsymbolic font.
                  map31Offset = offset;
                  break;
              }
              break;
          }
        }

        // Is it symbolic?
        if(metrics.IsCustomEncoding) // Symbolic.
        {
          // Does map(1,0) exist?
          if(map10Offset > 0)
          {
            // Go to the beginning of the subtable!
            fontData.Seek(tableOffset + map10Offset,SeekOrigin.Begin);
          }
          else
          {throw new FontFileFormatException("(1,0) symbolic table does NOT exist.");}
        }
        else // Nonsymbolic.
        {
          // Does map(3,1) exist?
          if(map31Offset > 0)
          {
            // Go to the beginning of the subtable!
            fontData.Seek(tableOffset + map31Offset,SeekOrigin.Begin);
          }
          else
          {throw new FontFileFormatException("(3,1) nonsymbolic table does NOT exist.");}
        }

        int format;
        format = fontDataReader.ReadUInt16();
        // Which cmap table format?
        switch(format)
        {
          case 0: // Byte encoding table.
            Load_CMap_Format0();
            break;
          case 4: // Segment mapping to delta values.
            Load_CMap_Format4();
            break;
          case 6: // Trimmed table mapping.
            Load_CMap_Format6();
            break;
          default:
            throw new FontFileFormatException("Format " + format + " cmap table NOT supported.");
        }
      }

      /**
        <summary>Loads format-0 cmap subtable (Byte encoding table, i.e. Apple standard
        character-to-glyph index mapping table).</summary>
        <returns>Map of character codes to glyph indexes.</returns>
      */
      private void Load_CMap_Format0(
        )
      {
        /*
          NOTE: This is a simple 1-to-1 mapping of character codes to glyph indices.
          The glyph collection is limited to 256 entries.
        */
        glyphIndexes = new Dictionary<int,int>(256);

        // Skip to the mapping array!
        fontData.Seek(4,SeekOrigin.Current);
        // Glyph index array.
        // Iterating through the glyph indexes...
        for(
          int code = 0;
          code < 256;
          code++
          )
        {
          glyphIndexes[
            code // Character code.
            ] = fontData.ReadByte() // Glyph index.
            ;
        }
      }

      /**
        <summary>Loads format-4 cmap subtable (Segment mapping to delta values, i.e. Microsoft standard
        character to glyph index mapping table for fonts that support Unicode ranges other than the
        range [U+D800 - U+DFFF] (defined as Surrogates Area, in Unicode v 3.0)).</summary>
        <returns>Map of character codes to glyph indexes.</returns>
      */
      private void Load_CMap_Format4(
        )
      {
        /*
          NOTE: This format is used when the character codes for the characters represented by a font
          fall into several contiguous ranges, possibly with holes in some or all of the ranges (i.e.
          some of the codes in a range may not have a representation in the font).
          The format-dependent data is divided into three parts, which must occur in the following
          order:
            1. A header gives parameters for an optimized search of the segment list;
            2. Four parallel arrays (end characters, start characters, deltas and range offsets)
            describe the segments (one segment for each contiguous range of codes);
            3. A variable-length array of glyph IDs.
        */
        unicode = true;

        // 1. Header.
        // Get the table length!
        int tableLength = fontDataReader.ReadUInt16(); // USHORT.

        // Skip to the segment count!
        fontData.Seek(2,SeekOrigin.Current);
        // Get the segment count!
        int segmentCount = fontDataReader.ReadUInt16() / 2;

        // 2. Arrays describing the segments.
        // Skip to the array of end character code for each segment!
        fontData.Seek(6,SeekOrigin.Current);
        // End character code for each segment.
        int[] endCodes = new int[segmentCount]; // USHORT.
        for(
          int index = 0;
          index < segmentCount;
          index++
          )
        {endCodes[index] = fontDataReader.ReadUInt16();}

        // Skip to the array of start character code for each segment!
        fontData.Seek(2,SeekOrigin.Current);
        // Start character code for each segment.
        int[] startCodes = new int[segmentCount]; // USHORT.
        for(
          int index = 0;
          index < segmentCount;
          index++
          )
        {startCodes[index] = fontDataReader.ReadUInt16();}

        // Delta for all character codes in segment.
        short[] deltas = new short[segmentCount];
        for(
          int index = 0;
          index < segmentCount;
          index++
          )
        {deltas[index] = fontDataReader.ReadInt16();}

        // Offsets into glyph index array.
        int[] rangeOffsets = new int[segmentCount]; // USHORT.
        for(
          int index = 0;
          index < segmentCount;
          index++
          )
        {rangeOffsets[index] = fontDataReader.ReadUInt16();}

        // 3. Glyph ID array.
        /*
          NOTE: There's no explicit field defining the array length; it must be inferred from
          the space left by the known fields.
        */
        int glyphIndexCount = tableLength / 2 // Number of 16-bit words inside the table.
          - 8 // Number of single-word header fields (8 fields: format, length, language, segCountX2, searchRange, entrySelector, rangeShift, reservedPad).
          - segmentCount * 4; // Number of single-word items in the arrays describing the segments (4 arrays of segmentCount items).
        int[] glyphIds = new int[glyphIndexCount]; // USHORT.
        for(
          int index = 0;
          index < glyphIds.Length;
          index++
          )
        {glyphIds[index] = fontDataReader.ReadUInt16();}

        glyphIndexes = new Dictionary<int,int>(glyphIndexCount);
        // Iterating through the segments...
        for(
          int segmentIndex = 0;
          segmentIndex < segmentCount;
          segmentIndex++
          )
        {
          int endCode = endCodes[segmentIndex];
          // Is it NOT the last end character code?
          /*
            NOTE: The final segment's endCode MUST be 0xFFFF. This segment need not (but MAY)
            contain any valid mappings (it can just map the single character code 0xFFFF to
            missing glyph). However, the segment MUST be present.
          */
          if(endCode < 0xFFFF)
          {endCode++;}
          // Iterating inside the current segment...
          for(
            int code = startCodes[segmentIndex];
            code < endCode;
            code++
            )
          {
            int glyphIndex;
            // Doesn't the mapping of character codes rely on glyph ID?
            if(rangeOffsets[segmentIndex] == 0) // No glyph-ID reliance.
            {
              /*
                NOTE: If the range offset is 0, the delta value is added directly to the character
                code to get the corresponding glyph index. The delta arithmetic is modulo 65536.
              */
              glyphIndex = (code + deltas[segmentIndex]) & 0xFFFF;
            }
            else // Glyph-ID reliance.
            {
              /*
                NOTE: If the range offset is NOT 0, the mapping of character codes relies on glyph ID.
                The character code offset from start code is added to the range offset. This sum is
                used as an offset from the current location within range offset itself to index out
                the correct glyph ID. This obscure indexing trick (sic!) works because glyph ID
                immediately follows range offset in the font file. The C expression that yields the
                address to the glyph ID is:
                  *(rangeOffsets[segmentIndex]/2
                  + (code - startCodes[segmentIndex])
                  + &idRangeOffset[segmentIndex])
                As safe C# semantics don't deal directly with pointers, we have to further
                exploit such a trick reasoning with 16-bit displacements in order to yield an index
                instead of an address (sooo-good!).
              */
              // Retrieve the glyph index!
              int glyphIdIndex = rangeOffsets[segmentIndex] / 2 // 16-bit word range offset.
                + (code - startCodes[segmentIndex]) // Character code offset from start code.
                - (segmentCount - segmentIndex); // Physical offset between the offsets into glyph index array and the glyph index array.

              /*
                NOTE: The delta value is added to the glyph ID to get the corresponding glyph index.
                The delta arithmetic is modulo 65536.
              */
              glyphIndex = (glyphIds[glyphIdIndex] + deltas[segmentIndex]) & 0xFFFF;
            }

            glyphIndexes[
              code // Character code.
              ] = glyphIndex // Glyph index.
              ;
          }
        }
      }

      /**
        <summary>Loads format-6 cmap subtable (Trimmed table mapping).</summary>
        <returns>Map of character codes to glyph indexes.</returns>
      */
      private void Load_CMap_Format6(
        )
      {
        // Skip to the first character code!
        fontData.Seek(4,SeekOrigin.Current);
        int firstCode = fontDataReader.ReadUInt16();
        int codeCount = fontDataReader.ReadUInt16();
        glyphIndexes = new Dictionary<int,int>(codeCount);
        for(
          int code = firstCode,
            lastCode = firstCode + codeCount;
          code < lastCode;
          code++
          )
        {
          glyphIndexes[
            code // Character code.
            ] = fontDataReader.ReadUInt16() // Glyph index.
            ;
        }
      }

      /**
        <summary>Gets a name.</summary>
        <param name="id">Name identifier.</param>
      */
      private string Load_GetName(
        int id
        )
      {
        // Naming Table ('name' table).
        // Retrieve the location info!
        int tableOffset = tableOffsets["name"];
        if(tableOffset == 0)
          throw new FontFileFormatException("'name' table does NOT exist.");

        // Go to the number of name records!
        fontData.Seek(tableOffset + 2,SeekOrigin.Begin);

        int recordCount = fontDataReader.ReadUInt16(); // USHORT.
        int storageOffset = fontDataReader.ReadUInt16(); // USHORT.
        // Iterating through the name records...
        for(
          int recordIndex = 0;
          recordIndex < recordCount;
          recordIndex++
          )
        {
          int platformID = fontDataReader.ReadUInt16(); // USHORT.
          // Is it the default platform?
          if(platformID == PlatformID_Microsoft)
          {
            fontData.Seek(2,SeekOrigin.Current);
            int languageID = fontDataReader.ReadUInt16(); // USHORT.
            // Is it the default language?
            if(languageID == MicrosoftLanguage_UsEnglish)
            {
              int nameID = fontDataReader.ReadUInt16(); // USHORT.
              // Does the name ID equal the searched one?
              if(nameID == id)
              {
                int length = fontDataReader.ReadUInt16(); // USHORT.
                int offset = fontDataReader.ReadUInt16(); // USHORT.

                // Go to the name string!
                fontData.Seek(tableOffset + storageOffset + offset,SeekOrigin.Begin);

                return ReadString(length,platformID);
              }
              else
              {fontData.Seek(4,SeekOrigin.Current);}
            }
            else
            {fontData.Seek(6,SeekOrigin.Current);}
          }
          else
          {fontData.Seek(10,SeekOrigin.Current);}
        }

        return null; // Not found.
      }

      /**
        <summary>Loads the glyph kerning.</summary>
      */
      private void Load_GlyphKerning(
        )
      {
        // Kerning ('kern' table).
        // Retrieve the location info!
        int tableOffset;
        try
        {tableOffset = tableOffsets["kern"];}
        catch
        {return;}

        // Go to the table count!
        fontData.Seek(tableOffset + 2,SeekOrigin.Begin);
        int subtableCount = fontDataReader.ReadUInt16(); // USHORT.

        glyphKernings = new Dictionary<int,int>();
        int subtableOffset = (int)fontData.Position;
        // Iterating through the subtables...
        for(
          int subtableIndex = 0;
          subtableIndex < subtableCount;
          subtableIndex++
          )
        {
          // Go to the subtable length!
          fontData.Seek(subtableOffset + 2,SeekOrigin.Begin);
          // Get the subtable length!
          int length = fontDataReader.ReadUInt16(); // USHORT.

          // Get the type of information contained in the subtable!
          int coverage = fontDataReader.ReadUInt16(); // USHORT.
          // Is it a format-0 subtable?
          /*
            NOTE: coverage bits 8-15 (format of the subtable) MUST be all zeros
            (representing format 0).
          */
          //
          if((coverage & 0xff00) == 0x0000)
          {
            int pairCount = fontDataReader.ReadUInt16(); // USHORT.

            // Skip to the beginning of the list!
            fontData.Seek(6,SeekOrigin.Current);
            // List of kerning pairs and values.
            for(
              int pairIndex = 0;
              pairIndex < pairCount;
              pairIndex++
              )
            {
              // Get the glyph index pair (left-hand and right-hand)!
              int pair = fontDataReader.ReadInt32(); // USHORT USHORT.
              // Get the normalized kerning value!
              int value = (int)(fontDataReader.ReadInt16() * metrics.UnitNorm);

              glyphKernings[pair] = value;
            }
          }

          subtableOffset += length;
        }
      }

      /**
        <summary>Loads the glyph widths.</summary>
      */
      private void Load_GlyphWidths(
        )
      {
        // Horizontal Metrics ('hmtx' table).
        // Retrieve the location info!
        int tableOffset = tableOffsets["hmtx"];
        if(tableOffset == 0)
          throw new FontFileFormatException("'hmtx' table does NOT exist.");

        // Go to the glyph horizontal-metrics entries!
        fontData.Seek(tableOffset,SeekOrigin.Begin);
        glyphWidths = new int[metrics.NumberOfHMetrics]; // USHORT.
        for(
          int index = 0;
          index < metrics.NumberOfHMetrics;
          index++
          )
        {
          // Get the glyph advance width!
          glyphWidths[index] = (int)(fontDataReader.ReadUInt16() * metrics.UnitNorm);
          // Skip the left side bearing!
          fontData.Seek(2,SeekOrigin.Current);
        }
      }

      /**
        <summary>Loads general tables.</summary>
      */
      private void Load_Tables(
        )
      {
        // Font Header ('head' table).
        // Retrieve the location info!
        int tableOffset = tableOffsets["head"];
        if(tableOffset == 0)
          throw new FontFileFormatException("'head' table does NOT exist.");

        // Go to the font flags!
        fontData.Seek(tableOffset + 16,SeekOrigin.Begin);
        metrics.Flags = fontDataReader.ReadUInt16();
        metrics.UnitsPerEm = fontDataReader.ReadUInt16();
        metrics.UnitNorm = 1000f / metrics.UnitsPerEm;
        // Go to the bounding box limits!
        fontData.Seek(16,SeekOrigin.Current);
        metrics.XMin = fontDataReader.ReadInt16();
        metrics.YMin = fontDataReader.ReadInt16();
        metrics.XMax = fontDataReader.ReadInt16();
        metrics.YMax = fontDataReader.ReadInt16();
        metrics.MacStyle = fontDataReader.ReadUInt16();

        // Font Header ('head' table).
        // Retrieve the location info!
        tableOffset = tableOffsets["OS/2"];
        if(tableOffset == 0)
          throw new FontFileFormatException("'OS/2' table does NOT exist.");

        fontData.Seek(tableOffset,SeekOrigin.Begin);
        int version = fontDataReader.ReadUInt16();
        // Go to the ascender!
        fontData.Seek(66,SeekOrigin.Current);
        metrics.STypoAscender = fontDataReader.ReadInt16();
        metrics.STypoDescender = fontDataReader.ReadInt16();
        metrics.STypoLineGap = fontDataReader.ReadInt16();
        if(version >= 2)
        {
          fontData.Seek(12,SeekOrigin.Current);
          metrics.SxHeight = fontDataReader.ReadInt16();
          metrics.SCapHeight = fontDataReader.ReadInt16();
        }
        else
        {
          /*
            NOTE: These are just rule-of-thumb values, in case the xHeight and CapHeight fields
            aren't available.
          */
          metrics.SxHeight = (short)(.5 * metrics.UnitsPerEm);
          metrics.SCapHeight = (short)(.7 * metrics.UnitsPerEm);
        }

        // Horizontal Header ('hhea' table).
        // Retrieve the location info!
        tableOffset = tableOffsets["hhea"];
        if(tableOffset == 0)
          throw new FontFileFormatException("'hhea' table does NOT exist.");

        // Go to the ascender!
        fontData.Seek(tableOffset + 4,SeekOrigin.Begin);
        metrics.Ascender = fontDataReader.ReadInt16();
        metrics.Descender = fontDataReader.ReadInt16();
        metrics.LineGap = fontDataReader.ReadInt16();
        metrics.AdvanceWidthMax = fontDataReader.ReadUInt16();
        metrics.MinLeftSideBearing = fontDataReader.ReadInt16();
        metrics.MinRightSideBearing = fontDataReader.ReadInt16();
        metrics.XMaxExtent = fontDataReader.ReadInt16();
        metrics.CaretSlopeRise = fontDataReader.ReadInt16();
        metrics.CaretSlopeRun = fontDataReader.ReadInt16();
        // Go to the horizontal metrics count!
        fontData.Seek(12,SeekOrigin.Current);
        metrics.NumberOfHMetrics = fontDataReader.ReadUInt16();

        // PostScript ('post' table).
        // Retrieve the location info!
        tableOffset = tableOffsets["post"];
        if(tableOffset == 0)
          throw new FontFileFormatException("'post' table does NOT exist.");

        // Go to the italic angle!
        fontData.Seek(tableOffset + 4,SeekOrigin.Begin);
        metrics.ItalicAngle =
          fontDataReader.ReadInt16() // Fixed-point mantissa (16 bits).
          + (float)fontDataReader.ReadUInt16() / 16384; // Fixed-point fraction (16 bits).
        metrics.UnderlinePosition = fontDataReader.ReadInt16();
        metrics.UnderlineThickness = fontDataReader.ReadInt16();
        metrics.IsFixedPitch = (fontDataReader.ReadInt32() != 0);
      }

      /**
        <summary>Reads a string.</summary>
      */
      private string ReadString(
        int length,
        int platformID
        )
      {
        // Which platform?
        switch(platformID)
        {
          case PlatformID_Unicode:
          case PlatformID_Microsoft:
            return ReadString_Unicode(length);
          default:
            return ReadString_Ascii(length);
        }
      }

      /**
        <summary>Reads a string from the font file using the extended ASCII encoding.</summary>
      */
      private String ReadString_Ascii(
        int length
        )
      {
        /*
          NOTE: Extended ASCII (Latin1) is a single-byte encoding
          (I know you already knew that!).
        */
        byte[] data = new byte[length];
        fontData.Read(data,0,length);

        return Encoding.ASCII.GetString(data);
      }

      /**
        <summary>Reads a string from the font file using the Unicode encoding.</summary>
      */
      private string ReadString_Unicode(
        int length
        )
      {
        /*
          NOTE: Unicode is a double-byte encoding
          (I know you already knew that!).
        */
        byte[] data = new byte[length];
        fontData.Read(data,0,length);

        return Encoding.Unicode.GetString(data);
      }
      #endregion
      #endregion
      #endregion
    }
    #endregion

    private static readonly Encoding ISO88591Encoding = Encoding.GetEncoding("iso-8859-1");

    #region dynamic
    #region fields
    private FontMetrics metrics;

    private Dictionary<int,int> glyphIndexes;
    private Dictionary<int,int> glyphKernings;
    private int[] glyphWidths;

    private bool composite;

    /**
      <summary>Unicode-to-character-code mapping.</summary>
    */
    private Dictionary<int,byte[]> charCodes;
    #endregion

    #region constructors
    public OpenTypeFont(
      Document context,
      string path
      ) : this(
        context,
        new FileStream(
          path,
          System.IO.FileMode.Open,
          System.IO.FileAccess.Read
          )
        )
    {}

    public OpenTypeFont(
      Document context,
      Stream fontData
      ) : base(context)
    {Load(fontData);}

  //TODO:IMPL manage loading of already embedded font metrics to allow editing!!!
    internal OpenTypeFont(
      PdfDirectObject baseObject
      ) : base(baseObject)
    {}
    #endregion

    #region interface
    #region public
    public override Object Clone(
      Document context
      )
    {throw new NotImplementedException();}

    public override string Decode(
      byte[] code
      )
    {throw new NotImplementedException();}

    public override byte[] Encode(
      string text
      )
    {
      if(composite) // Composite font.
      {
        char[] textChars = text.ToCharArray();
        int textCharsLength = textChars.Length;
        byte[] encodedBytes = new byte[textCharsLength*2];
        int encodedByteIndex = 0;
        for(
          int textCharsIndex = 0;
          textCharsIndex < textCharsLength;
          textCharsIndex++
          )
        {
          byte[] charCode = charCodes[(int)textChars[textCharsIndex]];
          for(int charCodeBytesIndex = 0,
              charCodeBytesLength = charCode.Length;
            charCodeBytesIndex < charCodeBytesLength;
            charCodeBytesIndex++
            )
          {encodedBytes[encodedByteIndex++] = charCode[charCodeBytesIndex];}
        }

        byte[] returnBytes = new byte[encodedByteIndex];
        Array.Copy(encodedBytes,0,returnBytes,0,encodedByteIndex);

        return returnBytes;
      }
      else // Simple font.
      {
      //TODO: explicit encoding (see composite font encoding)!!!
        return ISO88591Encoding.GetBytes(text);
      }
    }

    public override int GetKerning(
      char textChar1,
      char textChar2
      )
    {
      try
      {
        return glyphKernings[
          glyphIndexes[(int)textChar1] << 16 // Left-hand glyph index.
            + glyphIndexes[(int)textChar2] // Right-hand glyph index.
          ];
      }
      catch
      {return 0;}
    }

    public override int GetKerning(
      string text
      )
    {
      int kerning = 0;
      // Are kerning pairs available?
      if(glyphKernings != null)
      {
        char[] textChars = text.ToCharArray();
        for(
          int index = 0,
            length = text.Length - 1;
          index < length;
          index++
          )
        {
          kerning += GetKerning(
            textChars[index],
            textChars[index + 1]
            );
        }
      }

      return kerning;
    }

    public override int GetWidth(
      char textChar
      )
    {return glyphWidths[glyphIndexes[(int)textChar]];}

    public override int GetWidth(
      string text
      )
    {
      int width = 0;
      char[] textChars = text.ToCharArray();
      for(
        int index = 0,
          length = text.Length;
        index < length;
        index++
        )
      {width += GetWidth(textChars[index]);}

      return width;
    }
    #endregion

    #region protected
    protected override PdfDictionary Descriptor
    {
      get
      {
        if(BaseDataObject[PdfName.Subtype].Equals(PdfName.Type0))
        {
          return (PdfDictionary)File.Resolve(((PdfDictionary)File.Resolve(((PdfArray)BaseDataObject[PdfName.DescendantFonts])[0]))[PdfName.FontDescriptor]);
        }
        else
        {return base.Descriptor;}
      }
    }
    #endregion

    #region private
    /**
      <summary>Loads the font data.</summary>
    */
    private void Load(
      Stream fontData
      )
    {
      Parser parser = new Parser(fontData);
      glyphIndexes = parser.GlyphIndexes;
      glyphKernings = parser.GlyphKernings;
      glyphWidths = parser.GlyphWidths;
      metrics = parser.Metrics;

      PdfDictionary baseDataObject = BaseDataObject;

      // BaseFont.
      baseDataObject[PdfName.BaseFont] = new PdfName(parser.FontName);

      composite = parser.Unicode;
      if(composite)
      {
        // Subtype.
        baseDataObject[PdfName.Subtype] = PdfName.Type0;

        // Encoding.
        baseDataObject[PdfName.Encoding] = PdfName.IdentityH; //TODO: this is a simplification (to refine later).

        // Descendant font.
        PdfDictionary cidFontDictionary = new PdfDictionary(
          new PdfName[]{PdfName.Type},
          new PdfDirectObject[]{PdfName.Font}
          ); // CIDFont dictionary [PDF:1.6:5.6.3].
        {
          // Subtype.
          PdfName subType;
          switch(parser.OutlineFormat)
          {
            case OutlineFormatEnum.TrueType: subType = PdfName.CIDFontType2; break;
            case OutlineFormatEnum.CFF: subType = PdfName.CIDFontType0; break;
            default: throw new NotImplementedException();
          }
          cidFontDictionary[PdfName.Subtype] = subType;

          // BaseFont.
          cidFontDictionary[PdfName.BaseFont] = new PdfName(parser.FontName);

          // CIDSystemInfo.
          cidFontDictionary[PdfName.CIDSystemInfo] = new PdfDictionary(
            new PdfName[]
            {
              PdfName.Registry,
              PdfName.Ordering,
              PdfName.Supplement
            },
            new PdfDirectObject[]
            {
              new PdfTextString("Adobe"),
              new PdfTextString("Identity"),
              new PdfInteger(0)
            }
            ); // Generic predefined CMap (Identity-H/V (Adobe-Identity-0)) [PDF:1.6:5.6.4].

          // FontDescriptor.
          cidFontDictionary[PdfName.FontDescriptor] = Load_CreateFontDescriptor(fontData);

          // Encoding.
          Load_CreateEncoding(baseDataObject,cidFontDictionary);
        }
        baseDataObject[PdfName.DescendantFonts] = new PdfArray(new PdfDirectObject[]{File.Register(cidFontDictionary)});
      }
      else
      {
        // Subtype.
        PdfName subType;
        switch(parser.OutlineFormat)
        {
          case OutlineFormatEnum.TrueType: subType = PdfName.TrueType; break;
          case OutlineFormatEnum.CFF: subType = PdfName.Type1; break;
          default: throw new NotImplementedException();
        }
        baseDataObject[PdfName.Subtype] = subType;

        // Encoding.
        /*
          NOTE: PDF font dictionary's Encoding entry is used in conjunction with a "cmap" to map
          from a character code in a string to a glyph description in an OpenType font program.
          * A nonsymbolic font SHOULD specify MacRomanEncoding or WinAnsiEncoding as the value of its
          Encoding entry, with no Differences array.
          * A symbolic font (used to display glyphs that do not use MacRomanEncoding or
          WinAnsiEncoding) SHOULD NOT specify an Encoding entry. The font descriptor's Symbolic flag
          should be set.
        */
        // Is it symbolic?
        if(metrics.IsCustomEncoding) // Symbolic.
        {
          /*
            NOTE: Encoding entry MUST be null,
            whilst the font descriptor's Symbolic flag MUST be set.
          */
        }
        else // Nonsymbolic.
        {BaseDataObject[PdfName.Encoding] = PdfName.WinAnsiEncoding;}

        // TODO:IMPL subsetting to be managed!!!
        // FirstChar.
        int firstChar = 0;
        BaseDataObject[PdfName.FirstChar] = new PdfInteger(firstChar);

        // LastChar.
        int lastChar = 255;
        BaseDataObject[PdfName.LastChar] = new PdfInteger(lastChar);

        // Widths.
        PdfArray widthsObject = new PdfArray(lastChar - firstChar + 1);
        for(
          int code = firstChar;
          code <= lastChar;
          code++
          )
        {
          int width;
          try
          {width = glyphWidths[glyphIndexes[code]];}
          catch
          {width = 0;}

          widthsObject.Add(new PdfInteger(width));
        }
        BaseDataObject[PdfName.Widths] = widthsObject;

        // FontDescriptor.
        BaseDataObject[PdfName.FontDescriptor] = Load_CreateFontDescriptor(fontData);
      }
    }

    /**
      <summary>Creates the character code mapping for composite fonts.</summary>
    */
    private void Load_CreateEncoding(
      PdfDictionary font,
      PdfDictionary cidFont
      )
    {
      // CMap [PDF:1.6:5.6.4].
      bytes::Buffer cmapBuffer = new bytes::Buffer();
      cmapBuffer.Append(
        "%!PS-Adobe-3.0 Resource-CMap\n"
          + "%%DocumentNeededResources: ProcSet (CIDInit)\n"
          + "%%IncludeResource: ProcSet (CIDInit)\n"
          + "%%BeginResource: CMap (Adobe-Identity-UCS)\n"
          + "%%Title: (Adobe-Identity-UCS Adobe Identity 0)\n"
          + "%%Version: 1\n"
          + "%%EndComments\n"
          + "/CIDInit /ProcSet findresource begin\n"
          + "12 dict begin\n"
          + "begincmap\n"
          + "/CIDSystemInfo\n"
          + "3 dict dup begin\n"
          + "/Registry (Adobe) def\n"
          + "/Ordering (Identity) def\n"
          + "/Supplement 0 def\n"
          + "end def\n"
          + "/CMapName /Adobe-Identity-UCS def\n"
          + "/CMapVersion 1 def\n"
          + "/CMapType 0 def\n"
          + "/WMode 0 def\n"
          + "2 begincodespacerange\n"
          + "<20> <20>\n"
          + "<0000> <19FF>\n"
          + "endcodespacerange\n"
          + glyphIndexes.Count + " begincidchar\n"
        );
      // ToUnicode [PDF:1.6:5.9.2].
      bytes::Buffer toUnicodeBuffer = new bytes::Buffer();
      toUnicodeBuffer.Append(
        "/CIDInit /ProcSet findresource begin\n"
          + "12 dict begin\n"
          + "begincmap\n"
          + "/CIDSystemInfo\n"
          + "<< /Registry (Adobe)\n"
          + "/Ordering (UCS)\n"
          + "/Supplement 0\n"
          + ">> def\n"
          + "/CMapName /Adobe-Identity-UCS def\n"
          + "/CMapVersion 10.001 def\n"
          + "/CMapType 2 def\n"
          + "2 begincodespacerange\n"
          + "<20> <20>\n"
          + "<0000> <19FF>\n"
          + "endcodespacerange\n"
          + glyphIndexes.Count + " beginbfchar\n"
        );
      // CIDToGIDMap [PDF:1.6:5.6.3].
      bytes::Buffer gIdBuffer = new bytes::Buffer();
      gIdBuffer.Append((byte)0);
      gIdBuffer.Append((byte)0);
      int code = 0;
      charCodes = new Dictionary<int,byte[]>(glyphIndexes.Count);
      PdfArray widthsObject = new PdfArray(glyphWidths.Length);
      foreach(KeyValuePair<int,int> glyphIndexEntry in glyphIndexes)
      {
        // Character code (unicode to codepoint) entry.
        code++;
        byte[] charCode = (glyphIndexEntry.Key == 32
          ? new byte[]{32}
          : new byte[]
            {
              (byte)((code >> 8) & 0xFF),
              (byte)(code & 0xFF)
            });
        charCodes[glyphIndexEntry.Key] = charCode;

        // CMap entry.
        cmapBuffer.Append("<");
        toUnicodeBuffer.Append("<");
        for(int charCodeBytesIndex = 0,
            charCodeBytesLength = charCode.Length;
          charCodeBytesIndex < charCodeBytesLength;
          charCodeBytesIndex++
          )
        {
          string hex = ((int)charCode[charCodeBytesIndex]).ToString("X2");
          cmapBuffer.Append(hex);
          toUnicodeBuffer.Append(hex);
        }
        cmapBuffer.Append("> " + code + "\n");
        toUnicodeBuffer.Append("> <" + glyphIndexEntry.Key.ToString("X4") + ">\n");

        // CID-to-GID entry.
        int glyphIndex = glyphIndexEntry.Value;
        gIdBuffer.Append((byte)((glyphIndex >> 8) & 0xFF));
        gIdBuffer.Append((byte)(glyphIndex & 0xFF));

        // Width.
        int width;
        try
        {width = glyphWidths[glyphIndex];if(width>1000){width=1000;}}
        catch(Exception e)
        {width = 0;}
        widthsObject.Add(new PdfInteger(width));
      }
      cmapBuffer.Append(
        "endcidchar\n"
          + "endcmap\n"
          + "CMapName currentdict /CMap defineresource pop\n"
          + "end\n"
          + "end\n"
          + "%%EndResource\n"
          + "%%EOF"
        );
      PdfStream cmapStream = new PdfStream(cmapBuffer);
      PdfDictionary cmapHead = cmapStream.Header;
      cmapHead[PdfName.Type] = PdfName.CMap;
      cmapHead[PdfName.CMapName] = new PdfName("Adobe-Identity-UCS");
      cmapHead[PdfName.CIDSystemInfo] = new PdfDictionary(
        new PdfName[]
        {
          PdfName.Registry,
          PdfName.Ordering,
          PdfName.Supplement
        },
        new PdfDirectObject[]
        {
          new PdfTextString("Adobe"),
          new PdfTextString("Identity"),
          new PdfInteger(0)
        }
        ); // Generic predefined CMap (Identity-H/V (Adobe-Identity-0)) [PDF:1.6:5.6.4].
      font[PdfName.Encoding] = File.Register(cmapStream);

      PdfStream gIdStream = new PdfStream(gIdBuffer);
      cidFont[PdfName.CIDToGIDMap] = File.Register(gIdStream);

      cidFont[PdfName.W] = new PdfArray(new PdfDirectObject[]{new PdfInteger(1),widthsObject});

      toUnicodeBuffer.Append(
        "endbfchar\n"
          + "endcmap\n"
          + "CMapName currentdict /CMap defineresource pop\n"
          + "end\n"
          + "end\n"
        );
      PdfStream toUnicodeStream = new PdfStream(toUnicodeBuffer);
      font[PdfName.ToUnicode] = File.Register(toUnicodeStream);
    }

    /**
      <summary>Creates the font descriptor.</summary>
    */
    private PdfReference Load_CreateFontDescriptor(
      Stream fontData
      )
    {
      PdfDictionary fontDescriptor = new PdfDictionary();
      // Type.
      fontDescriptor[PdfName.Type] = PdfName.FontDescriptor;
      // FontName.
      fontDescriptor[PdfName.FontName] = BaseDataObject[PdfName.BaseFont];
      // Flags [PDF:1.6:5.7.1].
      FlagsEnum flags = 0;
      if(metrics.IsFixedPitch)
      {flags |= FlagsEnum.FixedPitch;}
      if(metrics.IsCustomEncoding)
      {flags |= FlagsEnum.Symbolic;}
      else
      {flags |= FlagsEnum.Nonsymbolic;}
      fontDescriptor[PdfName.Flags] = new PdfInteger(Convert.ToInt32(flags));
      // FontBBox.
      fontDescriptor[PdfName.FontBBox] = new PdfRectangle(
        metrics.XMin * metrics.UnitNorm,
        metrics.YMin * metrics.UnitNorm,
        metrics.XMax * metrics.UnitNorm,
        metrics.YMax * metrics.UnitNorm
        );
      // ItalicAngle.
      fontDescriptor[PdfName.ItalicAngle] = new PdfReal(metrics.ItalicAngle);
      // Ascent.
      if(metrics.Ascender == 0)
      {fontDescriptor[PdfName.Ascent] = new PdfReal(metrics.STypoAscender * metrics.UnitNorm);}
      else
      {fontDescriptor[PdfName.Ascent] = new PdfReal(metrics.Ascender * metrics.UnitNorm);}
      // Descent.
      if(metrics.Descender == 0)
      {fontDescriptor[PdfName.Descent] = new PdfReal(metrics.STypoDescender * metrics.UnitNorm);}
      else
      {fontDescriptor[PdfName.Descent] = new PdfReal(metrics.Descender * metrics.UnitNorm);}
      // Leading.
      fontDescriptor[PdfName.Leading] = new PdfReal(metrics.STypoLineGap * metrics.UnitNorm);
      // CapHeight.
      fontDescriptor[PdfName.CapHeight] = new PdfReal(metrics.SCapHeight * metrics.UnitNorm);
      // StemV.
      /*
        NOTE: '100' is just a rule-of-thumb value, 'cause I've still to solve the
        'cvt' table puzzle (such a harsh headache!) for TrueType fonts...
        TODO:IMPL TrueType and CFF stemv real value to extract!!!
      */
      fontDescriptor[PdfName.StemV] = new PdfInteger(100);
      // FontFile.
  //TODO:IMPL distinguish between truetype (FontDescriptor.FontFile2) and opentype (FontDescriptor.FontFile3 and FontStream.subtype=OpenType)!!!
      PdfIndirectObject fontFile = File.IndirectObjects.Add(
        new PdfStream(
          new PdfDictionary(
            new PdfName[]{PdfName.Subtype},
            new PdfDirectObject[]{PdfName.OpenType}
            ),
          new it.stefanochizzolini.clown.bytes.Buffer(fontData)
          )
        );
      fontDescriptor[PdfName.FontFile3] = fontFile.Reference;

      return File.Register(fontDescriptor);
    }
    #endregion
    #endregion
    #endregion
  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.