#21 fix widths for system font baskerville old face

This commit is contained in:
Eliot Jones
2018-12-11 21:29:39 +00:00
parent d1722dd23e
commit 29f9885fc4
6 changed files with 139 additions and 14 deletions

View File

@@ -134,6 +134,69 @@
} }
} }
[Fact]
public void WindowsOnlyCanWriteSinglePageHelloWorldSystemFont()
{
var builder = new PdfDocumentBuilder();
var page = builder.AddPage(PageSize.A4);
var file = @"C:\Windows\Fonts\BASKVILL.TTF";
if (!File.Exists(file))
{
return;
}
byte[] bytes;
try
{
bytes = File.ReadAllBytes(file);
}
catch
{
return;
}
var font = builder.AddTrueTypeFont(bytes);
var letters = page.AddText("Hello World!", 16, new PdfPoint(30, 520), font);
page.AddText("This is some further text continuing to write", 12, new PdfPoint(30, 500), font);
Assert.NotEmpty(page.Operations);
var b = builder.Build();
WriteFile(nameof(WindowsOnlyCanWriteSinglePageHelloWorldSystemFont), b);
Assert.NotEmpty(b);
using (var document = PdfDocument.Open(b))
{
var page1 = document.GetPage(1);
Assert.StartsWith("Hello World!", page1.Text);
var h = page1.Letters[0];
Assert.Equal("H", h.Value);
Assert.Equal("BaskOldFace", h.FontName);
for (int i = 0; i < letters.Count; i++)
{
var readerLetter = page1.Letters[i];
var writerLetter = letters[i];
Assert.Equal(readerLetter.Value, writerLetter.Value);
Assert.Equal(readerLetter.Location, writerLetter.Location);
Assert.Equal(readerLetter.FontSize, writerLetter.FontSize);
Assert.Equal(readerLetter.GlyphRectangle.Width, writerLetter.GlyphRectangle.Width);
Assert.Equal(readerLetter.GlyphRectangle.Height, writerLetter.GlyphRectangle.Height);
Assert.Equal(readerLetter.GlyphRectangle.BottomLeft, writerLetter.GlyphRectangle.BottomLeft);
}
}
}
private static void WriteFile(string name, byte[] bytes) private static void WriteFile(string name, byte[] bytes)
{ {
try try

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Writer namespace UglyToad.PdfPig.Writer
{ {
using System.IO; using System.IO;
using Core;
using Geometry; using Geometry;
using Tokens; using Tokens;
@@ -14,6 +15,8 @@
bool TryGetAdvanceWidth(char character, out decimal width); bool TryGetAdvanceWidth(char character, out decimal width);
TransformationMatrix GetFontMatrix();
ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context); ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context);
} }
} }

View File

@@ -17,7 +17,7 @@
internal class PdfDocumentBuilder internal class PdfDocumentBuilder
{ {
private static readonly byte Break = (byte)'\n'; private const byte Break = (byte) '\n';
private static readonly TrueTypeFontParser Parser = new TrueTypeFontParser(); private static readonly TrueTypeFontParser Parser = new TrueTypeFontParser();
private readonly Dictionary<int, PdfPageBuilder> pages = new Dictionary<int, PdfPageBuilder>(); private readonly Dictionary<int, PdfPageBuilder> pages = new Dictionary<int, PdfPageBuilder>();
@@ -26,6 +26,53 @@
public IReadOnlyDictionary<int, PdfPageBuilder> Pages => pages; public IReadOnlyDictionary<int, PdfPageBuilder> Pages => pages;
public IReadOnlyDictionary<Guid, IWritingFont> Fonts => fonts.ToDictionary(x => x.Key, x => x.Value.FontProgram); public IReadOnlyDictionary<Guid, IWritingFont> Fonts => fonts.ToDictionary(x => x.Key, x => x.Value.FontProgram);
public bool CanUseTrueTypeFont(IReadOnlyList<byte> fontFileBytes, out IReadOnlyList<string> reasons)
{
var reasonsMutable = new List<string>();
reasons = reasonsMutable;
try
{
if (fontFileBytes == null)
{
reasonsMutable.Add("Provided bytes were null.");
return false;
}
if (fontFileBytes.Count == 0)
{
reasonsMutable.Add("Provided bytes were empty.");
return false;
}
var font = Parser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(fontFileBytes)));
if (font.TableRegister.CMapTable == null)
{
reasonsMutable.Add("The provided font did not contain a cmap table, used to map character codes to glyph codes.");
return false;
}
if (font.TableRegister.Os2Table == null)
{
reasonsMutable.Add("The provided font did not contain an OS/2 table, used to fill in the font descriptor dictionary.");
return false;
}
if (font.TableRegister.PostScriptTable == null)
{
reasonsMutable.Add("The provided font did not contain a post PostScript table, used to map character codes to glyph codes.");
return false;
}
return true;
}
catch (Exception ex)
{
reasonsMutable.Add(ex.Message);
return false;
}
}
public AddedFont AddTrueTypeFont(IReadOnlyList<byte> fontFileBytes) public AddedFont AddTrueTypeFont(IReadOnlyList<byte> fontFileBytes)
{ {
try try

View File

@@ -51,20 +51,14 @@
throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0"); throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0");
} }
var fm = TransformationMatrix.FromValues(1 / 1000m, 0, 0, 1 / 1000m, 0, 0); var fm = fontProgram.GetFontMatrix();
var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y); var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y);
var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix); var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix);
try try
{ {
//var realWidth = widthRect.Width;
//if (realWidth + position.X > PageSize.Width)
//{
// throw new InvalidOperationException("Text would exceed the bounds.");
//}
var beginText = BeginText.Value; var beginText = BeginText.Value;
operations.Add(beginText); operations.Add(beginText);

View File

@@ -2,6 +2,7 @@
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Core;
using Fonts; using Fonts;
using Fonts.Encodings; using Fonts.Encodings;
using Geometry; using Geometry;
@@ -48,6 +49,11 @@
return true; return true;
} }
public TransformationMatrix GetFontMatrix()
{
return TransformationMatrix.FromValues(1/1000m, 0, 0, 1/1000m, 0, 0);
}
public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context) public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
{ {
var dictionary = new Dictionary<NameToken, IToken> var dictionary = new Dictionary<NameToken, IToken>

View File

@@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Core;
using Fonts; using Fonts;
using Fonts.Encodings; using Fonts.Encodings;
using Fonts.Exceptions; using Fonts.Exceptions;
@@ -37,6 +38,12 @@
return font.TryGetBoundingAdvancedWidth(character, out width); return font.TryGetBoundingAdvancedWidth(character, out width);
} }
public TransformationMatrix GetFontMatrix()
{
var unitsPerEm = font.GetFontMatrixMultiplier();
return TransformationMatrix.FromValues(1m/unitsPerEm, 0, 0, 1m/unitsPerEm, 0, 0);
}
public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context) public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
{ {
var bytes = fontFileBytes; var bytes = fontFileBytes;
@@ -86,7 +93,7 @@
descriptorDictionary[NameToken.StemV] = new NumericToken(bbox.Width * scaling * 0.13m); descriptorDictionary[NameToken.StemV] = new NumericToken(bbox.Width * scaling * 0.13m);
var metrics = charCodeToGlyphId.GetMetrics(); var metrics = charCodeToGlyphId.GetMetrics(scaling);
var widthsRef = context.WriteObject(outputStream, metrics.Widths); var widthsRef = context.WriteObject(outputStream, metrics.Widths);
@@ -131,7 +138,7 @@
this.font = font ?? throw new ArgumentNullException(nameof(font)); this.font = font ?? throw new ArgumentNullException(nameof(font));
} }
public FontDictionaryMetrics GetMetrics() public FontDictionaryMetrics GetMetrics(decimal scaling)
{ {
var encoding = ReadFontEncoding(); var encoding = ReadFontEncoding();
var firstCharacter = encoding.CodeToNameMap.Keys.Min(); var firstCharacter = encoding.CodeToNameMap.Keys.Min();
@@ -151,12 +158,17 @@
} }
var characterCode = (int) unicode[0]; var characterCode = (int) unicode[0];
if (!font.TryGetBoundingAdvancedWidth(characterCode, out var width)) if (characterCode < firstCharacter || characterCode > lastCharacter)
{ {
throw new InvalidFontFormatException(); continue;
} }
widths[pair.Key - firstCharacter] = new NumericToken(width); if (!font.TryGetBoundingAdvancedWidth(characterCode, out var width))
{
throw new InvalidFontFormatException($"Could not find advanced with for character named '{pair.Value}' with character code: {characterCode}.");
}
widths[pair.Key - firstCharacter] = new NumericToken(width * scaling);
} }
return new FontDictionaryMetrics return new FontDictionaryMetrics