expose font details on individual letters

also fixes a regression for image extraction
This commit is contained in:
Eliot Jones
2020-04-25 17:15:26 +01:00
parent 98dd736f94
commit 09b951f667
21 changed files with 204 additions and 13 deletions

View File

@@ -29,6 +29,16 @@
/// </summary>
public TransformationMatrix FontMatrix => TopDictionary.FontMatrix;
/// <summary>
/// The value of Weight from the top dictionary or <see langword="null"/>.
/// </summary>
public string Weight => TopDictionary?.Weight;
/// <summary>
/// The value of Italic Angle from the top dictionary or 0.
/// </summary>
public decimal ItalicAngle => TopDictionary?.ItalicAngle ?? 0;
internal CompactFontFormatFont(CompactFontFormatTopLevelDictionary topDictionary, CompactFontFormatPrivateDictionary privateDictionary,
ICompactFontFormatCharset charset,
Union<Type1CharStrings, Type2CharStrings> charStrings, Encoding fontEncoding)

View File

@@ -91,6 +91,7 @@
"UglyToad.PdfPig.PdfFonts.DescriptorFontFile",
"UglyToad.PdfPig.PdfFonts.FontDescriptor",
"UglyToad.PdfPig.PdfFonts.FontDescriptorFlags",
"UglyToad.PdfPig.PdfFonts.FontDetails",
"UglyToad.PdfPig.PdfFonts.FontStretch",
"UglyToad.PdfPig.Geometry.GeometryExtensions",
"UglyToad.PdfPig.Graphics.Colors.CMYKColor",

View File

@@ -2,6 +2,7 @@
{
using Core;
using Graphics.Colors;
using PdfFonts;
/// <summary>
/// A glyph or combination of glyphs (characters) drawn by a PDF content stream.
@@ -53,7 +54,12 @@
/// <summary>
/// The name of the font.
/// </summary>
public string FontName { get; }
public string FontName => Font?.Name;
/// <summary>
/// Details about the font for this letter.
/// </summary>
public FontDetails Font { get; }
/// <summary>
/// The color of the letter.
@@ -79,7 +85,7 @@
PdfPoint endBaseLine,
double width,
double fontSize,
string fontName,
FontDetails font,
IColor color,
double pointSize,
int textSequence)
@@ -90,7 +96,7 @@
EndBaseLine = endBaseLine;
Width = width;
FontSize = fontSize;
FontName = fontName;
Font = font;
Color = color ?? GrayColor.Black;
PointSize = pointSize;
TextSequence = textSequence;

View File

@@ -251,7 +251,7 @@
transformedPdfBounds.BottomRight,
transformedPdfBounds.Width,
fontSize,
font.Name.Data,
font.Details,
color,
pointSize,
textSequence);

View File

@@ -34,6 +34,8 @@
/// </summary>
CharacterIdentifierSystemInfo SystemInfo { get; }
FontDetails Details { get; }
TransformationMatrix FontMatrix { get; }
CidFontType CidFontType { get; }

View File

@@ -8,6 +8,8 @@
/// </summary>
internal interface ICidFontProgram
{
FontDetails Details { get; }
bool TryGetBoundingBox(int characterIdentifier, out PdfRectangle boundingBox);
bool TryGetBoundingBox(int characterIdentifier, Func<int, int?> characterCodeToGlyphId, out PdfRectangle boundingBox);

View File

@@ -8,9 +8,36 @@
{
private readonly CompactFontFormatFontCollection fontCollection;
public FontDetails Details { get; }
public PdfCidCompactFontFormatFont(CompactFontFormatFontCollection fontCollection)
{
this.fontCollection = fontCollection;
Details = GetDetails(fontCollection?.FirstFont);
}
private static FontDetails GetDetails(CompactFontFormatFont font)
{
if (font == null)
{
return FontDetails.GetDefault();
}
FontDetails WithWeightValues(bool isbold, int weight) => new FontDetails(null, isbold, weight, font.ItalicAngle != 0);
switch (font.Weight?.ToLowerInvariant())
{
case "light":
return WithWeightValues(false, 300);
case "semibold":
return WithWeightValues(true, 600);
case "bold":
return WithWeightValues(true, FontDetails.BoldWeight);
case "black":
return WithWeightValues(true, 900);
default:
return WithWeightValues(false, FontDetails.DefaultWeight);
}
}
public TransformationMatrix GetFontTransformationMatrix() => fontCollection.GetFirstTransformationMatrix();

View File

@@ -3,14 +3,23 @@
using System;
using Core;
using Fonts.TrueType;
using Fonts.TrueType.Tables;
internal class PdfCidTrueTypeFont : ICidFontProgram
{
private readonly TrueTypeFont font;
public FontDetails Details { get; }
public PdfCidTrueTypeFont(TrueTypeFont font)
{
this.font = font ?? throw new ArgumentNullException(nameof(font));
var header = font.TableRegister.HeaderTable;
var isBold = header.MacStyle.HasFlag(HeaderTable.HeaderMacStyle.Bold);
Details = new FontDetails(font.Name, isBold,
isBold ? FontDetails.BoldWeight : FontDetails.DefaultWeight,
header.MacStyle.HasFlag(HeaderTable.HeaderMacStyle.Italic));
}
public bool TryGetBoundingBox(int characterIdentifier, out PdfRectangle boundingBox) => TryGetBoundingBox(characterIdentifier, null, out boundingBox);

View File

@@ -25,6 +25,9 @@
public CharacterIdentifierSystemInfo SystemInfo { get; }
public FontDetails Details => fontProgram?.Details ?? Descriptor?.ToDetails(BaseFont?.Data)
?? FontDetails.GetDefault(BaseFont?.Data);
public TransformationMatrix FontMatrix { get; }
public CidFontType CidFontType => CidFontType.Type0;

View File

@@ -32,6 +32,9 @@
public FontDescriptor Descriptor { get; }
public FontDetails Details => fontProgram?.Details ?? Descriptor?.ToDetails(BaseFont?.Data)
?? FontDetails.GetDefault(BaseFont?.Data);
public Type2CidFont(NameToken type, NameToken subType, NameToken baseFont, CharacterIdentifierSystemInfo systemInfo,
FontDescriptor descriptor, ICidFontProgram fontProgram,
VerticalWritingMetrics verticalWritingMetrics,

View File

@@ -36,6 +36,8 @@
public bool IsVertical => CMap.WritingMode == WritingMode.Vertical;
public FontDetails Details { get; }
public Type0Font(NameToken baseFont, ICidFont cidFont, CMap cmap, CMap toUnicodeCMap,
CMap ucs2CMap,
bool isChineseJapaneseOrKorean)
@@ -47,6 +49,8 @@
CidFont = cidFont ?? throw new ArgumentNullException(nameof(cidFont));
CMap = cmap ?? throw new ArgumentNullException(nameof(cmap));
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
Details = cidFont.Details?.WithName(Name.Data)
?? FontDetails.GetDefault(Name.Data);
}
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)

View File

@@ -179,6 +179,14 @@
CharSet = builder.CharSet;
}
internal FontDetails ToDetails(string name = null)
{
return new FontDetails(name ?? FontName ?? string.Empty,
FontWeight > 500,
(int)FontWeight,
Flags.HasFlag(FontDescriptorFlags.Italic));
}
/// <summary>
/// Provides a mutable way to construct a <see cref="FontDescriptor"/>.
/// </summary>

View File

@@ -0,0 +1,66 @@
namespace UglyToad.PdfPig.PdfFonts
{
/// <summary>
/// Summary details of the font used to draw a glyph.
/// </summary>
public class FontDetails
{
/// <summary>
/// The normal weight for a font.
/// </summary>
public const int DefaultWeight = 500;
/// <summary>
/// The bold weight for a font.
/// </summary>
public const int BoldWeight = 700;
/// <summary>
/// The font name.
/// </summary>
public string Name { get; }
/// <summary>
/// Whether the font is bold.
/// </summary>
public bool IsBold { get; }
/// <summary>
/// The font weight, values above 500 represent bold.
/// </summary>
public int Weight { get; }
/// <summary>
/// Whether the font is italic.
/// </summary>
public bool IsItalic { get; }
/// <summary>
/// Create a new <see cref="FontDetails"/>.
/// </summary>
public FontDetails(string name, bool isBold, int weight, bool isItalic)
{
Name = name ?? string.Empty;
IsBold = isBold;
Weight = weight;
IsItalic = isItalic;
}
internal static FontDetails GetDefault(string name = null) => new FontDetails(name ?? string.Empty,
false,
DefaultWeight,
false);
internal FontDetails WithName(string name) => name != null
? new FontDetails(name, IsBold, Weight, IsItalic)
: this;
/// <inheritdoc />
public override string ToString()
{
var boldString = IsBold ? " (bold)" : string.Empty;
var italicString = IsItalic ? " (italic)" : string.Empty;
return $"{Name}{boldString}{italicString}";
}
}
}

View File

@@ -9,6 +9,8 @@
bool IsVertical { get; }
FontDetails Details { get; }
int ReadCharacterCode(IInputBytes bytes, out int codeLength);
bool TryGetUnicode(int characterCode, out string value);

View File

@@ -35,6 +35,8 @@
public bool IsVertical { get; }
public FontDetails Details { get; }
[NotNull]
public ToUnicodeCMap ToUnicode { get; set; }
@@ -55,6 +57,8 @@
Name = name;
IsVertical = false;
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
Details = descriptor?.ToDetails(Name?.Data)
?? FontDetails.GetDefault(Name?.Data);
}
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)

View File

@@ -26,6 +26,8 @@
public bool IsVertical { get; } = false;
public FontDetails Details { get; set; }
public TrueTypeStandard14FallbackSimpleFont(NameToken name, AdobeFontMetrics fontMetrics, Encoding encoding, TrueTypeFont font,
MetricOverrides overrides)
{
@@ -34,6 +36,10 @@
this.font = font;
this.overrides = overrides;
Name = name;
Details = fontMetrics == null ? FontDetails.GetDefault(Name?.Data) : new FontDetails(Name?.Data,
fontMetrics.Weight == "Bold",
fontMetrics.Weight == "Bold" ? 700 : FontDetails.DefaultWeight,
fontMetrics.ItalicAngle != 0);
}
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)

View File

@@ -41,6 +41,8 @@
public bool IsVertical { get; } = false;
public FontDetails Details { get; }
public Type1FontSimple(NameToken name, int firstChar, int lastChar, double[] widths, FontDescriptor fontDescriptor, Encoding encoding,
CMap toUnicodeCMap,
Union<Type1Font, CompactFontFormatFontCollection> fontProgram)
@@ -70,6 +72,8 @@
fontMatrix = matrix;
Name = name;
Details = fontDescriptor?.ToDetails(name?.Data)
?? FontDetails.GetDefault(name?.Data);
}
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)

View File

@@ -17,8 +17,11 @@ namespace UglyToad.PdfPig.PdfFonts.Simple
private readonly Encoding encoding;
public NameToken Name { get; }
public bool IsVertical { get; }
public FontDetails Details { get; }
private readonly TransformationMatrix fontMatrix = TransformationMatrix.FromValues(0.001, 0, 0, 0.001, 0, 0);
public Type1Standard14Font(AdobeFontMetrics standardFontMetrics, Encoding overrideEncoding = null)
@@ -29,6 +32,10 @@ namespace UglyToad.PdfPig.PdfFonts.Simple
Name = NameToken.Create(standardFontMetrics.FontName);
IsVertical = false;
Details = new FontDetails(Name.Data,
standardFontMetrics.Weight == "Bold",
standardFontMetrics.Weight == "Bold" ? 700 : FontDetails.DefaultWeight,
standardFontMetrics.ItalicAngle != 0);
}
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)

View File

@@ -24,6 +24,8 @@
public bool IsVertical { get; } = false;
public FontDetails Details { get; }
public Type3Font(NameToken name, PdfRectangle boundingBox, TransformationMatrix fontMatrix,
Encoding encoding, int firstChar, int lastChar, double[] widths,
CMap toUnicodeCMap)
@@ -37,6 +39,7 @@
this.lastChar = lastChar;
this.widths = widths;
this.toUnicodeCMap = new ToUnicodeCMap(toUnicodeCMap);
Details = FontDetails.GetDefault(name?.Data);
}
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)

View File

@@ -16,8 +16,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using PdfFonts;
using Tokens;
using UglyToad.PdfPig.Graphics.Operations.PathPainting;
using Graphics.Operations.PathPainting;
/// <summary>
/// A builder used to add construct a page in a PDF document.
@@ -388,7 +389,7 @@
var documentSpace = textMatrix.Transform(renderingMatrix.Transform(fontMatrix.Transform(rect)));
var letter = new Letter(c.ToString(), documentSpace, advanceRect.BottomLeft, advanceRect.BottomRight, width, (double)fontSize, font.Name,
var letter = new Letter(c.ToString(), documentSpace, advanceRect.BottomLeft, advanceRect.BottomRight, width, (double)fontSize, FontDetails.GetDefault(font.Name),
GrayColor.Black,
(double)fontSize,
textSequence);

View File

@@ -1,4 +1,6 @@
namespace UglyToad.PdfPig.XObjects
using UglyToad.PdfPig.Parser.Parts;
namespace UglyToad.PdfPig.XObjects
{
using System;
using System.Collections.Generic;
@@ -66,14 +68,35 @@
var interpolate = dictionary.TryGet(NameToken.Interpolate, pdfScanner, out BooleanToken interpolateToken)
&& interpolateToken.Data;
var filters = filterProvider.GetFilters(xObject.Stream.StreamDictionary);
var supportsFilters = true;
foreach (var filter in filters)
DictionaryToken filterDictionary = xObject.Stream.StreamDictionary;
if (xObject.Stream.StreamDictionary.TryGet(NameToken.Filter, out var filterToken)
&& filterToken is IndirectReferenceToken)
{
if (!filter.IsSupported)
if (filterDictionary.TryGet(NameToken.Filter, pdfScanner, out ArrayToken filterArray))
{
supportsFilters = false;
break;
filterDictionary = filterDictionary.With(NameToken.Filter, filterArray);
}
else if (filterDictionary.TryGet(NameToken.Filter, pdfScanner, out NameToken filterNameToken))
{
filterDictionary = filterDictionary.With(NameToken.Filter, filterNameToken);
}
else
{
filterDictionary = null;
}
}
var supportsFilters = filterDictionary != null;
if (filterDictionary != null)
{
var filters = filterProvider.GetFilters(filterDictionary);
foreach (var filter in filters)
{
if (!filter.IsSupported)
{
supportsFilters = false;
break;
}
}
}