/*
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 it.stefanochizzolini.clown.bytes;
using it.stefanochizzolini.clown.documents;
using it.stefanochizzolini.clown.objects;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
namespace it.stefanochizzolini.clown.documents.contents.fonts{
/**
<summary>Type 1 font [PDF:1.6:5;AFM:4.1].</summary>
<remarks>
<para>Embedding is currently not supported.</para>
<para>Current version is instrumental in supporting standard type 1 fonts only.</para>
</remarks>
*/
//TODO:IMPL font embedding to be supported!!!
public class Type1Font
: Font
{
#region nested types
/**
<summary>Font header (Global font information).</summary>
*/
private sealed class FontMetrics
{
public bool IsCustomEncoding;
public string FontName;
public string Weight;
public float ItalicAngle;
public bool IsFixedPitch;
public short XMin;
public short YMin;
public short XMax;
public short YMax;
public short UnderlinePosition;
public short UnderlineThickness;
public short CapHeight;
public short XHeight;
public short Ascender;
public short Descender;
public short StemH;
public short StemV;
}
/**
<summary>Font data parser.</summary>
*/
private sealed class Parser
{
#region dynamic
#region fields
private FontMetrics metrics = new FontMetrics();
private Dictionary<int,int> kernings;
private Dictionary<int,int> widths;
private StreamReader fontMetricsData;
#endregion
#region constructors
internal Parser(
StreamReader fontMetricsData
)
{
this.fontMetricsData = fontMetricsData;
Load();
}
#endregion
#region interface
#region public
public Dictionary<int,int> Kernings
{get{return kernings;}}
public FontMetrics Metrics
{get{return metrics;}}
public Dictionary<int,int> Widths
{get{return widths;}}
#endregion
#region private
private void Load(
)
{
Load_FontHeader();
Load_CharMetrics();
Load_KerningData();
}
/**
<summary>Loads the font header [AFM:4.1:3,4,4.1,4.2].</summary>
*/
private void Load_FontHeader(
)
{
string line;
Regex linePattern = new Regex("(\\S+)\\s+(.+)");
while((line = fontMetricsData.ReadLine()) != null)
{
MatchCollection lineMatches = linePattern.Matches(line);
if(lineMatches.Count < 1)
continue;
Match lineMatch = lineMatches[0];
string key = lineMatch.Groups[1].Value;
switch(key)
{
case "FontName":
metrics.FontName = lineMatch.Groups[2].Value; break;
case "Weight":
metrics.Weight = lineMatch.Groups[2].Value; break;
case "ItalicAngle":
metrics.ItalicAngle = Single.Parse(lineMatch.Groups[2].Value); break;
case "IsFixedPitch":
metrics.IsFixedPitch = lineMatch.Groups[2].Value.Equals("true"); break;
case "FontBBox":
{
string[] coordinates = Regex.Split(lineMatch.Groups[2].Value,"\\s+");
metrics.XMin = Int16.Parse(coordinates[0]);
metrics.YMin = Int16.Parse(coordinates[1]);
metrics.XMax = Int16.Parse(coordinates[2]);
metrics.YMax = Int16.Parse(coordinates[3]);
} break;
case "UnderlinePosition":
metrics.UnderlinePosition = Int16.Parse(lineMatch.Groups[2].Value); break;
case "UnderlineThickness":
metrics.UnderlineThickness = Int16.Parse(lineMatch.Groups[2].Value); break;
case "EncodingScheme":
metrics.IsCustomEncoding = lineMatch.Groups[2].Value.Equals("FontSpecific"); break;
case "CapHeight":
metrics.CapHeight = Int16.Parse(lineMatch.Groups[2].Value); break;
case "XHeight":
metrics.XHeight = Int16.Parse(lineMatch.Groups[2].Value); break;
case "Ascender":
metrics.Ascender = Int16.Parse(lineMatch.Groups[2].Value); break;
case "Descender":
metrics.Descender = Int16.Parse(lineMatch.Groups[2].Value); break;
case "StdHW":
metrics.StemH = Int16.Parse(lineMatch.Groups[2].Value); break;
case "StdVW":
metrics.StemV = Int16.Parse(lineMatch.Groups[2].Value); break;
case "StartCharMetrics":
goto endParsing;
}
}
endParsing:
if(metrics.Ascender == 0)
{metrics.Ascender = metrics.YMax;}
if(metrics.Descender == 0)
{metrics.Descender = metrics.YMin;}
}
/**
<summary>Loads individual character metrics [AFM:4.1:3,4,4.4,8].</summary>
*/
private void Load_CharMetrics(
)
{
widths = new Dictionary<int,int>();
/*
NOTE: Customly-encoded fonts rely on character codes,
while standardly-encoded fonts rely on character names.
*/
if(metrics.IsCustomEncoding) // Custom encoding.
{
string line;
Regex linePattern = new Regex("C (\\S+) ; WX (\\S+)");
while((line = fontMetricsData.ReadLine()) != null)
{
MatchCollection lineMatches = linePattern.Matches(line);
if(lineMatches.Count < 1)
{
if(line.Equals("EndCharMetrics"))
break;
continue;
}
Match lineMatch = lineMatches[0];
int code = Int32.Parse(lineMatch.Groups[1].Value);
int width = Int32.Parse(lineMatch.Groups[2].Value);
widths[code] = width;
}
}
else // Standard encoding.
{
string line;
Regex linePattern = new Regex(" WX (\\S+) ; N (\\S+)");
while((line = fontMetricsData.ReadLine()) != null)
{
MatchCollection lineMatches = linePattern.Matches(line);
if(lineMatches.Count < 1)
{
if(line.Equals("EndCharMetrics"))
break;
continue;
}
Match lineMatch = lineMatches[0];
string name = lineMatch.Groups[2].Value;
int width = Int32.Parse(lineMatch.Groups[1].Value);
widths[GlyphMapping.NameToCode(name)] = width;
}
}
}
/**
<summary>Loads kerning data [AFM:4.1:3,4,4.5,9].</summary>
*/
private void Load_KerningData(
)
{
kernings = new Dictionary<int,int>();
string line;
while((line = fontMetricsData.ReadLine()) != null)
{
if(line.StartsWith("StartKernPairs"))
break;
}
Regex linePattern = new Regex("KPX (\\S+) (\\S+) (\\S+)");
while((line = fontMetricsData.ReadLine()) != null)
{
MatchCollection lineMatches = linePattern.Matches(line);
if(lineMatches.Count < 1)
{
if(line.Equals("EndKernPairs"))
break;
continue;
}
Match lineMatch = lineMatches[0];
int code1 = GlyphMapping.NameToCode(lineMatch.Groups[1].Value);
int code2 = GlyphMapping.NameToCode(lineMatch.Groups[2].Value);
int value = Int32.Parse(lineMatch.Groups[3].Value);
int pair = code1 << 16 + code2;
kernings[pair] = value;
}
}
#endregion
#endregion
#endregion
}
/**
<summary>Adobe standard glyph mapping (unicode-encoding against glyph-naming)
[PDF:1.6:D;AGL:2.0].</summary>
*/
private static class GlyphMapping
{
private static Dictionary<string,int> codes = new Dictionary<string,int>();
static GlyphMapping(
)
{Load();}
public static int NameToCode(
string name
)
{return codes[name];}
/**
<summary>Loads the glyph list mapping character names to character codes (unicode
encoding).</summary>
*/
private static void Load(
)
{
StreamReader glyphListStream = null;
try
{
// Open the glyph list!
/*
NOTE: The Adobe Glyph List [AGL:2.0] represents the reference name-to-unicode map
for consumer applications.
*/
glyphListStream = new StreamReader(
Assembly.GetExecutingAssembly().GetManifestResourceStream("fonts.AGL20.scsv")
);
// Parsing the glyph list...
string line;
Regex linePattern = new Regex("^(\\w+);([A-F0-9]+)$");
while((line = glyphListStream.ReadLine()) != null)
{
MatchCollection lineMatches = linePattern.Matches(line);
if(lineMatches.Count < 1)
continue;
Match lineMatch = lineMatches[0];
string name = lineMatch.Groups[1].Value;
int code = Int32.Parse(
lineMatch.Groups[2].Value,
NumberStyles.HexNumber
);
// Associate the character name with its corresponding character code!
codes[name] = code;
}
}
catch(Exception e)
{throw e;}
finally
{
// Close the glyph list!
if(glyphListStream != null)
{glyphListStream.Close();}
}
}
}
#endregion
private static readonly Encoding ISO88591Encoding = Encoding.GetEncoding("iso-8859-1");
#region dynamic
#region fields
private FontMetrics metrics;
private Dictionary<int,int> kernings;
private Dictionary<int,int> widths;
#endregion
#region constructors
//TODO:IMPL define outlines data support (PFB)!!!
// public Type1Font(
// Document context,
// IInputStream fontMetricsData,
// IInputStream fontOutlinesData
// ) : base(context)
// {
// throw new NotImplementedException("Outlines (PFB file) are to be supported yet.");
// load(fontMetricsData);
// }
//TODO:IMPL manage loading of already embedded font metrics to allow editing!!!
internal Type1Font(
PdfDirectObject baseObject
) : base(baseObject)
{}
protected Type1Font(
Document context
) : base(context)
{}
#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
)
{return ISO88591Encoding.GetBytes(text);}
public override double GetAscent(
double size
)
{
/*
NOTE: Standard Type 1 fonts SHOULD omit extended font descriptions [PDF:1.6:5.5.1],
so we are forced to deal with this circumstance.
*/
if(BaseDataObject.ContainsKey(PdfName.FontDescriptor))
{return base.GetAscent(size);}
else
{return (metrics.Ascender * GetScalingFactor(size));}
}
public override double GetDescent(
double size
)
{
/*
NOTE: Standard Type 1 fonts SHOULD omit extended font descriptions [PDF:1.6:5.5.1],
so we are forced to deal with this circumstance.
*/
if(BaseDataObject.ContainsKey(PdfName.FontDescriptor))
{return base.GetDescent(size);}
else
{return (metrics.Descender * GetScalingFactor(size));}
}
public override FlagsEnum Flags
{
get
{
/*
NOTE: Standard Type 1 fonts SHOULD omit extended font descriptions [PDF:1.6:5.5.1],
so we are forced to deal with this circumstance.
*/
if(BaseDataObject.ContainsKey(PdfName.FontDescriptor))
{return base.Flags;}
else
{/*TODO:IMPL!!!*/return 0;}
}
}
public override int GetKerning(
char textChar1,
char textChar2
)
{
try
{
return kernings[
((int)textChar1) << 16 // Left-hand character code.
+ ((int)textChar2) // Right-hand character code.
];
}
catch
{return 0;}
}
public override int GetKerning(
string text
)
{
int kerning = 0;
// Are kerning pairs available?
if(kernings != 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 double GetLineHeight(
double size
)
{
/*
NOTE: Standard Type 1 fonts SHOULD omit extended font descriptions [PDF:1.6:5.5.1],
so we are forced to deal with this circumstance.
*/
if(BaseDataObject.ContainsKey(PdfName.FontDescriptor))
return base.GetLineHeight(size);
else
return ((metrics.Ascender + Math.Abs(metrics.Descender)) * GetScalingFactor(size));
}
public override int GetWidth(
char textChar
)
{return widths[(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 private
/**
<summary>Loads the font data.</summary>
*/
protected void Load(
System.IO.Stream fontMetricsData,
bool standard
)
{
Parser parser = new Parser(
new StreamReader(fontMetricsData)
);
metrics = parser.Metrics;
kernings = parser.Kernings;
widths = parser.Widths;
// Subtype.
BaseDataObject[PdfName.Subtype] = PdfName.Type1;
// BaseFont.
BaseDataObject[PdfName.BaseFont] = new PdfName(metrics.FontName);
// Is the font a standard one?
/*
NOTE: Standard Type 1 fonts SHOULD omit extended font descriptions [PDF:1.6:5.5.1].
*/
if(standard)
return;
throw new NotImplementedException("Extended font descriptions are currently not supported.");
// Encoding.
// FirstChar.
// LastChar.
// Widths.
// FontDescriptor.
}
// TODO:IMPL!!!
// /**
// Creates the font descriptor.
// */
// private PdfIndirectObject Load_CreateFontDescriptor(
// )
// {
// PdfDictionary fontDescriptor = new PdfDictionary();
// // Type.
// fontDescriptor[PdfName.Type] = PdfName.FontDescriptor;
// // FontName.
// fontDescriptor[PdfName.FontName] = BaseDataObject[PdfName.BaseFont];
// // Flags [PDF:1.6:5.7.1].
// // TODO:IMPL see OpenTypeFont flags!!!
// fontDescriptor[PdfName.Flags] = new PdfInteger(flags);
// // FontBBox.
// fontDescriptor[PdfName.FontBBox] = new PdfRectangle(
// metrics.XMin,
// metrics.YMin,
// metrics.XMax,
// metrics.YMax
// );
// ItalicAngle.
// fontDescriptor[PdfName.ItalicAngle] = new PdfReal(metrics.ItalicAngle);
// // Ascent.
// fontDescriptor[PdfName.Ascent] = new PdfReal(metrics.Ascender);
// // Descent.
// fontDescriptor[PdfName.Descent] = new PdfReal(metrics.Descender);
// // CapHeight.
// fontDescriptor[PdfName.CapHeight] = new PdfInteger(metrics.CapHeight);
// // StemV.
// fontDescriptor[PdfName.StemV] = new PdfInteger(metrics.StemV);
// // FontFile.
// PdfIndirectObject fontFile = File.IndirectObjects.Add(
// new PdfStream(
// new Buffer(fontMetricsData.ToByteArray())
// )
// );
// fontDescriptor[PdfName.FontFile] = fontFile.Reference;
//
// return File.IndirectObjects.Add(fontDescriptor);
// }
#endregion
#endregion
#endregion
}
}
|