2017-12-03 15:01:17 +00:00
|
|
|
|
namespace UglyToad.Pdf.Fonts.Parser.Parts
|
|
|
|
|
{
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using CidFonts;
|
|
|
|
|
using ContentStream;
|
2017-12-26 19:18:19 +00:00
|
|
|
|
using ContentStream.TypedAccessors;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
using Cos;
|
2017-12-26 19:18:19 +00:00
|
|
|
|
using Exceptions;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
using Filters;
|
|
|
|
|
using Geometry;
|
2017-12-03 21:31:59 +00:00
|
|
|
|
using IO;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
using Pdf.Parser;
|
2018-01-05 23:08:20 +00:00
|
|
|
|
using Pdf.Parser.Parts;
|
2017-12-03 21:31:59 +00:00
|
|
|
|
using TrueType;
|
|
|
|
|
using TrueType.Parser;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
|
|
|
|
|
internal class CidFontFactory
|
|
|
|
|
{
|
|
|
|
|
private readonly FontDescriptorFactory descriptorFactory;
|
2017-12-03 21:31:59 +00:00
|
|
|
|
private readonly TrueTypeFontParser trueTypeFontParser;
|
2017-12-22 23:54:54 +00:00
|
|
|
|
private readonly IPdfObjectParser pdfObjectParser;
|
|
|
|
|
private readonly IFilterProvider filterProvider;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
|
2017-12-22 23:54:54 +00:00
|
|
|
|
public CidFontFactory(FontDescriptorFactory descriptorFactory, TrueTypeFontParser trueTypeFontParser,
|
|
|
|
|
IPdfObjectParser pdfObjectParser,
|
|
|
|
|
IFilterProvider filterProvider)
|
2017-12-03 15:01:17 +00:00
|
|
|
|
{
|
|
|
|
|
this.descriptorFactory = descriptorFactory;
|
2017-12-03 21:31:59 +00:00
|
|
|
|
this.trueTypeFontParser = trueTypeFontParser;
|
2017-12-22 23:54:54 +00:00
|
|
|
|
this.pdfObjectParser = pdfObjectParser;
|
|
|
|
|
this.filterProvider = filterProvider;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-22 23:54:54 +00:00
|
|
|
|
public ICidFont Generate(PdfDictionary dictionary, IRandomAccessRead reader, bool isLenientParsing)
|
2017-12-03 15:01:17 +00:00
|
|
|
|
{
|
|
|
|
|
var type = dictionary.GetName(CosName.TYPE);
|
|
|
|
|
if (!CosName.FONT.Equals(type))
|
|
|
|
|
{
|
2017-12-26 19:18:19 +00:00
|
|
|
|
throw new InvalidFontFormatException($"Expected \'Font\' dictionary but found \'{type.Name}\'");
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var widths = ReadWidths(dictionary);
|
|
|
|
|
var verticalWritingMetrics = ReadVerticalDisplacements(dictionary);
|
|
|
|
|
|
|
|
|
|
FontDescriptor descriptor = null;
|
2017-12-22 23:54:54 +00:00
|
|
|
|
if (TryGetFontDescriptor(dictionary, reader, out var descriptorDictionary))
|
2017-12-03 15:01:17 +00:00
|
|
|
|
{
|
2017-12-22 23:54:54 +00:00
|
|
|
|
descriptor = descriptorFactory.Generate(descriptorDictionary, isLenientParsing);
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-26 19:18:19 +00:00
|
|
|
|
var fontProgram = ReadDescriptorFile(descriptor, reader, isLenientParsing);
|
|
|
|
|
|
|
|
|
|
var baseFont = dictionary.GetName(CosName.BASE_FONT);
|
|
|
|
|
|
2018-01-05 23:08:20 +00:00
|
|
|
|
var systemInfo = GetSystemInfo(dictionary, reader, isLenientParsing);
|
2017-12-03 15:01:17 +00:00
|
|
|
|
|
|
|
|
|
var subType = dictionary.GetName(CosName.SUBTYPE);
|
|
|
|
|
if (CosName.CID_FONT_TYPE0.Equals(subType))
|
|
|
|
|
{
|
|
|
|
|
//return new PDCIDFontType0(dictionary, parent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CosName.CID_FONT_TYPE2.Equals(subType))
|
|
|
|
|
{
|
2017-12-26 19:18:19 +00:00
|
|
|
|
return new Type2CidFont(type, subType, baseFont, systemInfo, descriptor, fontProgram, verticalWritingMetrics, widths);
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-22 23:54:54 +00:00
|
|
|
|
private bool TryGetFontDescriptor(PdfDictionary dictionary, IRandomAccessRead reader, out PdfDictionary descriptorDictionary)
|
2017-12-03 15:01:17 +00:00
|
|
|
|
{
|
|
|
|
|
descriptorDictionary = null;
|
|
|
|
|
|
|
|
|
|
if (!dictionary.TryGetValue(CosName.FONT_DESC, out var baseValue) || !(baseValue is CosObject obj))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-22 23:54:54 +00:00
|
|
|
|
var descriptorObj = pdfObjectParser.Parse(obj.ToIndirectReference(), reader, false);
|
2017-12-03 15:01:17 +00:00
|
|
|
|
|
|
|
|
|
if (!(descriptorObj is PdfDictionary descriptor))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
descriptorDictionary = descriptor;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-26 19:18:19 +00:00
|
|
|
|
private ICidFontProgram ReadDescriptorFile(FontDescriptor descriptor, IRandomAccessRead reader, bool isLenientParsing)
|
2017-12-03 15:01:17 +00:00
|
|
|
|
{
|
|
|
|
|
if (descriptor?.FontFile == null)
|
|
|
|
|
{
|
2017-12-26 19:18:19 +00:00
|
|
|
|
return null;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-26 14:31:30 +00:00
|
|
|
|
var fontFileStream = pdfObjectParser.Parse(descriptor.FontFile.ObjectKey, reader, isLenientParsing) as PdfRawStream;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
|
|
|
|
|
if (fontFileStream == null)
|
|
|
|
|
{
|
2017-12-26 19:18:19 +00:00
|
|
|
|
return null;
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-22 23:54:54 +00:00
|
|
|
|
var fontFile = fontFileStream.Decode(filterProvider);
|
2017-12-04 07:46:43 +00:00
|
|
|
|
|
2017-12-03 21:31:59 +00:00
|
|
|
|
switch (descriptor.FontFile.FileType)
|
|
|
|
|
{
|
|
|
|
|
case DescriptorFontFile.FontFileType.TrueType:
|
|
|
|
|
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile));
|
2017-12-26 19:18:19 +00:00
|
|
|
|
return trueTypeFontParser.Parse(input);
|
2017-12-03 21:31:59 +00:00
|
|
|
|
default:
|
|
|
|
|
throw new NotSupportedException("Currently only TrueType fonts are supported.");
|
|
|
|
|
}
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IReadOnlyDictionary<int, decimal> ReadWidths(PdfDictionary dict)
|
|
|
|
|
{
|
|
|
|
|
var widths = new Dictionary<int, decimal>();
|
|
|
|
|
|
|
|
|
|
if (!dict.TryGetItemOfType(CosName.W, out COSArray widthArray))
|
|
|
|
|
{
|
|
|
|
|
return widths;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int size = widthArray.size();
|
|
|
|
|
int counter = 0;
|
|
|
|
|
while (counter < size)
|
|
|
|
|
{
|
|
|
|
|
var firstCode = (ICosNumber)widthArray.getObject(counter++);
|
|
|
|
|
var next = widthArray.getObject(counter++);
|
|
|
|
|
if (next is COSArray array)
|
|
|
|
|
{
|
|
|
|
|
int startRange = firstCode.AsInt();
|
|
|
|
|
int arraySize = array.size();
|
|
|
|
|
for (int i = 0; i < arraySize; i++)
|
|
|
|
|
{
|
|
|
|
|
var width = (ICosNumber)array.getObject(i);
|
|
|
|
|
widths[startRange + i] = width.AsDecimal();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var secondCode = (ICosNumber)next;
|
|
|
|
|
var rangeWidth = (ICosNumber)widthArray.getObject(counter++);
|
|
|
|
|
int startRange = firstCode.AsInt();
|
|
|
|
|
int endRange = secondCode.AsInt();
|
|
|
|
|
var width = rangeWidth.AsDecimal();
|
|
|
|
|
for (var i = startRange; i <= endRange; i++)
|
|
|
|
|
{
|
|
|
|
|
widths[i] = width;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return widths;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private VerticalWritingMetrics ReadVerticalDisplacements(PdfDictionary dict)
|
|
|
|
|
{
|
|
|
|
|
var verticalDisplacements = new Dictionary<int, decimal>();
|
|
|
|
|
var positionVectors = new Dictionary<int, PdfVector>();
|
|
|
|
|
|
|
|
|
|
VerticalVectorComponents dw2;
|
|
|
|
|
if (!dict.TryGetItemOfType(CosName.DW2, out COSArray arrayVerticalComponents))
|
|
|
|
|
{
|
|
|
|
|
dw2 = new VerticalVectorComponents(880, -1000);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var position = ((ICosNumber)arrayVerticalComponents.get(0)).AsDecimal();
|
|
|
|
|
var displacement = ((ICosNumber)arrayVerticalComponents.get(1)).AsDecimal();
|
|
|
|
|
|
|
|
|
|
dw2 = new VerticalVectorComponents(position, displacement);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// vertical metrics for individual CIDs.
|
|
|
|
|
if (dict.TryGetItemOfType(CosName.W2, out COSArray w2))
|
|
|
|
|
{
|
|
|
|
|
for (var i = 0; i < w2.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
var c = (ICosNumber)w2.get(i);
|
|
|
|
|
var next = w2.get(++i);
|
|
|
|
|
if (next is COSArray array)
|
|
|
|
|
{
|
|
|
|
|
for (int j = 0; j < array.size(); j++)
|
|
|
|
|
{
|
|
|
|
|
int cid = c.AsInt() + j;
|
|
|
|
|
var w1y = (ICosNumber)array.get(j);
|
|
|
|
|
var v1x = (ICosNumber)array.get(++j);
|
|
|
|
|
var v1y = (ICosNumber)array.get(++j);
|
|
|
|
|
|
|
|
|
|
verticalDisplacements[cid] = w1y.AsDecimal();
|
|
|
|
|
|
|
|
|
|
positionVectors[cid] = new PdfVector(v1x.AsDecimal(), v1y.AsDecimal());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int first = c.AsInt();
|
|
|
|
|
int last = ((ICosNumber)next).AsInt();
|
|
|
|
|
var w1y = (ICosNumber)w2.get(++i);
|
|
|
|
|
var v1x = (ICosNumber)w2.get(++i);
|
|
|
|
|
var v1y = (ICosNumber)w2.get(++i);
|
|
|
|
|
|
|
|
|
|
for (var cid = first; cid <= last; cid++)
|
|
|
|
|
{
|
|
|
|
|
verticalDisplacements[cid] = w1y.AsDecimal();
|
|
|
|
|
|
|
|
|
|
positionVectors[cid] = new PdfVector(v1x.AsDecimal(), v1y.AsDecimal());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new VerticalWritingMetrics(dw2, verticalDisplacements, positionVectors);
|
|
|
|
|
}
|
2017-12-26 19:18:19 +00:00
|
|
|
|
|
2018-01-05 23:08:20 +00:00
|
|
|
|
private CharacterIdentifierSystemInfo GetSystemInfo(PdfDictionary dictionary, IRandomAccessRead reader, bool isLenientParsing)
|
2017-12-26 19:18:19 +00:00
|
|
|
|
{
|
2018-01-05 23:08:20 +00:00
|
|
|
|
if(!dictionary.TryGetValue(CosName.CIDSYSTEMINFO, out var cidEntry))
|
2017-12-26 19:18:19 +00:00
|
|
|
|
{
|
2018-01-05 23:08:20 +00:00
|
|
|
|
throw new InvalidFontFormatException($"No CID System Info was found in the CID Font dictionary: {dictionary}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cidEntry is PdfDictionary cidDictionary)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if (cidEntry is CosObject cidObject)
|
|
|
|
|
{
|
|
|
|
|
cidDictionary =
|
|
|
|
|
DirectObjectFinder.Find<PdfDictionary>(cidObject, pdfObjectParser, reader, isLenientParsing);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidFontFormatException($"No CID System Info was found in the CID Font dictionary: {dictionary}");
|
2017-12-26 19:18:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var registry = (CosString) cidDictionary.GetItemOrDefault(CosName.REGISTRY);
|
|
|
|
|
var ordering = (CosString)cidDictionary.GetItemOrDefault(CosName.ORDERING);
|
|
|
|
|
var supplement = cidDictionary.GetIntOrDefault(CosName.SUPPLEMENT, 0);
|
|
|
|
|
|
|
|
|
|
return new CharacterIdentifierSystemInfo(registry.GetAscii(), ordering.GetAscii(), supplement);
|
|
|
|
|
}
|
2017-12-03 15:01:17 +00:00
|
|
|
|
}
|
|
|
|
|
}
|