2018-04-28 19:33:50 +01:00
|
|
|
|
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
|
|
|
|
|
{
|
2018-11-16 21:30:59 +00:00
|
|
|
|
using System;
|
2018-11-16 23:32:18 +00:00
|
|
|
|
using System.Collections.Generic;
|
2018-11-17 14:59:58 +00:00
|
|
|
|
using System.Linq;
|
2018-11-16 23:32:18 +00:00
|
|
|
|
using Charsets;
|
2018-11-16 21:30:59 +00:00
|
|
|
|
using CharStrings;
|
2018-04-29 14:42:54 +01:00
|
|
|
|
using Dictionaries;
|
2018-11-17 14:59:58 +00:00
|
|
|
|
using Type1.CharStrings;
|
|
|
|
|
|
using Util;
|
2018-04-28 19:33:50 +01:00
|
|
|
|
|
|
|
|
|
|
internal class CompactFontFormatIndividualFontParser
|
|
|
|
|
|
{
|
2018-04-29 14:42:54 +01:00
|
|
|
|
private readonly CompactFontFormatIndexReader indexReader;
|
|
|
|
|
|
private readonly CompactFontFormatTopLevelDictionaryReader topLevelDictionaryReader;
|
|
|
|
|
|
private readonly CompactFontFormatPrivateDictionaryReader privateDictionaryReader;
|
2018-04-28 19:33:50 +01:00
|
|
|
|
|
2018-04-29 14:42:54 +01:00
|
|
|
|
public CompactFontFormatIndividualFontParser(CompactFontFormatIndexReader indexReader,
|
|
|
|
|
|
CompactFontFormatTopLevelDictionaryReader topLevelDictionaryReader,
|
|
|
|
|
|
CompactFontFormatPrivateDictionaryReader privateDictionaryReader)
|
2018-04-28 19:33:50 +01:00
|
|
|
|
{
|
2018-04-29 14:42:54 +01:00
|
|
|
|
this.indexReader = indexReader;
|
|
|
|
|
|
this.topLevelDictionaryReader = topLevelDictionaryReader;
|
|
|
|
|
|
this.privateDictionaryReader = privateDictionaryReader;
|
2018-04-28 19:33:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-17 14:59:58 +00:00
|
|
|
|
public CompactFontFormatFont Parse(CompactFontFormatData data, string name, IReadOnlyList<byte> topDictionaryIndex, IReadOnlyList<string> stringIndex,
|
|
|
|
|
|
CompactFontFormatIndex globalSubroutineIndex)
|
2018-04-28 19:33:50 +01:00
|
|
|
|
{
|
2018-11-17 14:59:58 +00:00
|
|
|
|
var individualData = new CompactFontFormatData(topDictionaryIndex.ToArray());
|
2018-04-28 19:33:50 +01:00
|
|
|
|
|
2018-11-16 23:32:18 +00:00
|
|
|
|
var topDictionary = topLevelDictionaryReader.Read(individualData, stringIndex);
|
2018-04-28 19:33:50 +01:00
|
|
|
|
|
2018-11-17 22:16:27 +00:00
|
|
|
|
var privateDictionary = CompactFontFormatPrivateDictionary.GetDefault();
|
2018-04-28 19:33:50 +01:00
|
|
|
|
|
2018-12-20 18:18:32 +00:00
|
|
|
|
if (topDictionary.PrivateDictionaryLocation.HasValue && topDictionary.PrivateDictionaryLocation.Value.Size > 0)
|
2018-04-28 19:33:50 +01:00
|
|
|
|
{
|
2018-11-18 13:53:43 +00:00
|
|
|
|
var privateDictionaryBytes = data.SnapshotPortion(topDictionary.PrivateDictionaryLocation.Value.Offset,
|
|
|
|
|
|
topDictionary.PrivateDictionaryLocation.Value.Size);
|
2018-04-28 19:33:50 +01:00
|
|
|
|
|
2018-11-18 13:53:43 +00:00
|
|
|
|
privateDictionary = privateDictionaryReader.Read(privateDictionaryBytes, stringIndex);
|
2018-04-28 19:33:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-16 23:32:18 +00:00
|
|
|
|
if (topDictionary.CharStringsOffset < 0)
|
2018-04-28 19:33:50 +01:00
|
|
|
|
{
|
2018-11-16 23:32:18 +00:00
|
|
|
|
throw new InvalidOperationException("Expected CFF to contain a CharString offset.");
|
2018-04-28 19:33:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-17 14:59:58 +00:00
|
|
|
|
var localSubroutines = CompactFontFormatIndex.None;
|
2018-11-18 13:53:43 +00:00
|
|
|
|
if (privateDictionary.LocalSubroutineOffset.HasValue && topDictionary.PrivateDictionaryLocation.HasValue)
|
2018-11-17 14:59:58 +00:00
|
|
|
|
{
|
2018-11-18 13:53:43 +00:00
|
|
|
|
data.Seek(privateDictionary.LocalSubroutineOffset.Value + topDictionary.PrivateDictionaryLocation.Value.Offset);
|
2018-11-17 14:59:58 +00:00
|
|
|
|
|
|
|
|
|
|
localSubroutines = indexReader.ReadDictionaryData(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-16 23:32:18 +00:00
|
|
|
|
data.Seek(topDictionary.CharStringsOffset);
|
2018-04-28 19:33:50 +01:00
|
|
|
|
|
2018-11-16 23:32:18 +00:00
|
|
|
|
var charStringIndex = indexReader.ReadDictionaryData(data);
|
2018-11-16 21:30:59 +00:00
|
|
|
|
|
2018-11-17 14:59:58 +00:00
|
|
|
|
ICompactFontFormatCharset charset = null;
|
2018-11-16 23:32:18 +00:00
|
|
|
|
|
|
|
|
|
|
if (topDictionary.IsCidFont && topDictionary.CharSetOffset >= 0 && topDictionary.CharSetOffset <= 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (topDictionary.CharSetOffset)
|
2018-11-16 21:30:59 +00:00
|
|
|
|
{
|
2018-11-16 23:32:18 +00:00
|
|
|
|
case 0:
|
|
|
|
|
|
charset = CompactFontFormatIsoAdobeCharset.Value;
|
2018-11-16 21:30:59 +00:00
|
|
|
|
break;
|
2018-11-16 23:32:18 +00:00
|
|
|
|
case 1:
|
|
|
|
|
|
charset = CompactFontFormatExpertCharset.Value;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
charset = CompactFontFormatExpertSubsetCharset.Value;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
data.Seek(topDictionary.CharSetOffset);
|
|
|
|
|
|
|
|
|
|
|
|
var format = data.ReadCard8();
|
|
|
|
|
|
|
|
|
|
|
|
switch (format)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
{
|
|
|
|
|
|
var glyphToNamesAndStringId = new List<(int glyphId, int stringId, string name)>();
|
|
|
|
|
|
|
2018-11-17 14:59:58 +00:00
|
|
|
|
for (var glyphId = 1; glyphId < charStringIndex.Count; glyphId++)
|
2018-11-16 23:32:18 +00:00
|
|
|
|
{
|
|
|
|
|
|
var stringId = data.ReadSid();
|
|
|
|
|
|
glyphToNamesAndStringId.Add((glyphId, stringId, ReadString(stringId, stringIndex)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
charset = new CompactFontFormatFormat0Charset(glyphToNamesAndStringId);
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
{
|
|
|
|
|
|
var glyphToNamesAndStringId = new List<(int glyphId, int stringId, string name)>();
|
|
|
|
|
|
|
2018-11-17 14:59:58 +00:00
|
|
|
|
for (var glyphId = 1; glyphId < charStringIndex.Count; glyphId++)
|
2018-11-16 23:32:18 +00:00
|
|
|
|
{
|
|
|
|
|
|
var firstSid = data.ReadSid();
|
|
|
|
|
|
var numberInRange = format == 1 ? data.ReadCard8() : data.ReadCard16();
|
|
|
|
|
|
|
|
|
|
|
|
glyphToNamesAndStringId.Add((glyphId, firstSid, ReadString(firstSid, stringIndex)));
|
|
|
|
|
|
for (var i = 0; i < numberInRange; i++)
|
|
|
|
|
|
{
|
2018-11-17 20:21:59 +00:00
|
|
|
|
glyphId++;
|
2018-11-17 14:59:58 +00:00
|
|
|
|
var sid = firstSid + i + 1;
|
|
|
|
|
|
glyphToNamesAndStringId.Add((glyphId, sid, ReadString(sid, stringIndex)));
|
2018-11-16 23:32:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (format == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
charset = new CompactFontFormatFormat1Charset(glyphToNamesAndStringId);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
charset = new CompactFontFormatFormat2Charset(glyphToNamesAndStringId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2018-11-16 21:30:59 +00:00
|
|
|
|
default:
|
2018-11-16 23:32:18 +00:00
|
|
|
|
throw new InvalidOperationException($"Unrecognized format for the Charset table in a CFF font. Got: {format}.");
|
2018-11-16 21:30:59 +00:00
|
|
|
|
}
|
2018-04-28 19:33:50 +01:00
|
|
|
|
}
|
2018-11-16 23:32:18 +00:00
|
|
|
|
|
|
|
|
|
|
data.Seek(topDictionary.CharStringsOffset);
|
|
|
|
|
|
|
|
|
|
|
|
Type2CharStrings charStrings;
|
|
|
|
|
|
switch (topDictionary.CharStringType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case CompactFontFormatCharStringType.Type1:
|
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
|
case CompactFontFormatCharStringType.Type2:
|
2018-11-17 20:21:59 +00:00
|
|
|
|
charStrings = Type2CharStringParser.Parse(charStringIndex, localSubroutines, globalSubroutineIndex, charset);
|
2018-11-16 23:32:18 +00:00
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new ArgumentOutOfRangeException($"Unexpected CharString type in CFF font: {topDictionary.CharStringType}.");
|
|
|
|
|
|
}
|
2018-11-17 14:59:58 +00:00
|
|
|
|
|
|
|
|
|
|
return new CompactFontFormatFont(topDictionary, privateDictionary, charset, Union<Type1CharStrings, Type2CharStrings>.Two(charStrings));
|
2018-11-16 23:32:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-17 14:59:58 +00:00
|
|
|
|
private static string ReadString(int index, IReadOnlyList<string> stringIndex)
|
2018-11-16 23:32:18 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (index >= 0 && index <= 390)
|
|
|
|
|
|
{
|
|
|
|
|
|
return CompactFontFormatStandardStrings.GetName(index);
|
|
|
|
|
|
}
|
2018-11-17 14:59:58 +00:00
|
|
|
|
if (index - 391 < stringIndex.Count)
|
2018-11-16 23:32:18 +00:00
|
|
|
|
{
|
|
|
|
|
|
return stringIndex[index - 391];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-19 20:41:52 +00:00
|
|
|
|
// technically this maps to .notdef, but PDFBox uses this
|
2018-11-16 23:32:18 +00:00
|
|
|
|
return "SID" + index;
|
2018-04-28 19:33:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|