supports font name a loading
This commit is contained in:
Scott Williams 2017-02-04 23:43:47 +00:00
Родитель 17994eb127
Коммит bff94a2e6f
44 изменённых файлов: 2908 добавлений и 0 удалений

3
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,3 @@
[*.cs]
indent_style = space
indent_size = 4

1
.gitignore поставляемый
Просмотреть файл

@ -21,6 +21,7 @@ bld/
[Bb]in/
[Oo]bj/
[Ll]og/
artifacts/
# Visual Studio 2015 cache/options directory
.vs/

Просмотреть файл

@ -1,2 +1,19 @@
# Fonts
Font loading and drawing library.
## Features
- Reading font description (name, family, subname etc plus other string metadata)
## API Examples
### Read font description
```c#
FontDescription description = null;
using(var fs = File.OpenReader("Font.ttf")){
description = FontDescription.Load(fs); // once it has loaded the data the stream is no longer required and can be disposed off
}
string name = description.FontName;
```

6
SixLabors.Fonts.ruleset Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Shaper2D" ToolsVersion="14.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1413" Action="None" />
</Rules>
</RuleSet>

49
SixLabors.Fonts.sln Normal file
Просмотреть файл

@ -0,0 +1,49 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
global.json = global.json
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9E574A07-F879-4811-9C41-5CBDC6BAFDB7}"
ProjectSection(SolutionItems) = preProject
src\Shared\AssemblyInfo.Common.cs = src\Shared\AssemblyInfo.Common.cs
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SixLabors.Fonts", "src\SixLabors.Fonts\SixLabors.Fonts.xproj", "{09E744EC-4852-4FC7-BE78-C1B399F17967}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SixLabors.Fonts.Tests", "tests\SixLabors.Fonts.Tests\SixLabors.Fonts.Tests.xproj", "{F836E8E6-B4D9-4208-8346-140C74678B91}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{09E744EC-4852-4FC7-BE78-C1B399F17967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09E744EC-4852-4FC7-BE78-C1B399F17967}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09E744EC-4852-4FC7-BE78-C1B399F17967}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09E744EC-4852-4FC7-BE78-C1B399F17967}.Release|Any CPU.Build.0 = Release|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F836E8E6-B4D9-4208-8346-140C74678B91}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9E574A07-F879-4811-9C41-5CBDC6BAFDB7} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{09E744EC-4852-4FC7-BE78-C1B399F17967} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1200/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1201/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1401/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1600/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1633/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>

26
appveyor.yml Normal file
Просмотреть файл

@ -0,0 +1,26 @@
version: 0.0.{build}
install:
- choco install gitversion.portable -pre -y
before_build:
- ps: gitversion /l console /output buildserver
build_script:
- cmd: build.cmd
- cmd: tests\CodeCoverage\CodeCoverage.cmd
after_build:
- cmd: appveyor PushArtifact "artifacts\SixLabors.Fonts.%GitVersion_NuGetVersion%.nupkg"
deploy:
- provider: NuGet
server: https://www.myget.org/F/sixlabors/api/v2/package
symbol_server: https://www.myget.org/F/sixlabors/symbols/api/v2/package
api_key:
secure: SyrSERGrjkK21TSCsHtqke5279SMxXCg2NXKjR2qaErP0khEplwxPwE8Ch5bxzyf
artifact: /.*\.nupkg/
on:
branch: master
test: off

36
build.cmd Normal file
Просмотреть файл

@ -0,0 +1,36 @@
@echo Off
REM No glob support on Windows
dotnet restore
dotnet test ./tests/SixLabors.Fonts.Tests/ -c Release
if not "%errorlevel%"=="0" goto failure
REM run only if gitversion has ran i.e. from appveyor
if not "%GitVersion_NuGetVersion%" == "" (
cd src/SixLabors.Shapes
ECHO Setting version number to "%GitVersion_NuGetVersion%"
dotnet version "%GitVersion_NuGetVersion%"
cd ../../
if not "%errorlevel%"=="0" goto failure
)
ECHO Building nuget packages
if not "%GitVersion_NuGetVersion%" == "" (
dotnet pack -c Release --output ./artifacts ./src/SixLabors.Fonts/project.json
)ELSE (
dotnet pack -c Release --version-suffix "local-build" --output ./artifacts ./src/SixLabors.Fonts/project.json
)
if not "%errorlevel%"=="0" goto failure
:success
ECHO successfully built project
REM exit 0
goto end
:failure
ECHO failed to build.
REM exit -1
goto end
:end

21
codecov.yml Normal file
Просмотреть файл

@ -0,0 +1,21 @@
codecov:
notify:
require_ci_to_pass: true
comment:off
coverage:
precision: 2
range:
- 70.0
- 100.0
round: down
status:
changes: false
patch: true
project: true
parsers:
gcov:
branch_detection:
conditional: true
loop: true
macro: false
method: false

13
gitversion.yml Normal file
Просмотреть файл

@ -0,0 +1,13 @@
# to create a new package you create a new release/tag
# in github appveyor will build it without the -cixxx tag
# it will then be deployable cleanly to nuget.org
branches:
master:
tag: ci
mode: ContinuousDeployment
increment: Minor
prevent-increment-of-merged-branch-version: false
track-merge-target: true
ignore:
sha: []

6
global.json Normal file
Просмотреть файл

@ -0,0 +1,6 @@
{
"projects": [ "src" ],
"sdk": {
"version": "1.0.0-preview2-003121"
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// <copyright file="AssemblyInfo.Common.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyDescription("A cross-platform library for processing of image files; written in C#")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Scott Williams")]
[assembly: AssemblyProduct("SixLabors.Shapes")]
[assembly: AssemblyCopyright("Copyright (c) Scott Williams and contributors.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
// Major Version
// Minor Version
// Build Number
// Revision
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]
// Ensure the internals can be tested.
[assembly: InternalsVisibleTo("SixLabors.Fonts.Tests")]

0
src/Shared/stylecop.json Normal file
Просмотреть файл

Просмотреть файл

@ -0,0 +1,657 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using SixLabors.Fonts.Tables;
namespace SixLabors.Fonts
{
/// <summary>
/// Equivalent of <see cref="System.BitConverter"/>, but with either endianness.
/// <remarks>
/// Adapted from Miscellaneous Utility Library <see href="http://jonskeet.uk/csharp/miscutil/"/>
/// This product includes software developed by Jon Skeet and Marc Gravell. Contact <see href="mailto:skeet@pobox.com"/>, or see
/// <see href="http://www.pobox.com/~skeet/"/>.
/// </remarks>
/// </summary>
internal class BigEndianBitConverter
{
/// <summary>
/// Converts the specified double-precision floating point number to a
/// 64-bit signed integer. Note: the endianness of this converter does not
/// affect the returned value.
/// </summary>
/// <param name="value">The number to convert. </param>
/// <returns>A 64-bit signed integer whose value is equivalent to value.</returns>
public long DoubleToInt64Bits(double value)
{
return BitConverter.DoubleToInt64Bits(value);
}
/// <summary>
/// Converts the specified 64-bit signed integer to a double-precision
/// floating point number. Note: the endianness of this converter does not
/// affect the returned value.
/// </summary>
/// <param name="value">The number to convert. </param>
/// <returns>A double-precision floating point number whose value is equivalent to value.</returns>
public double Int64BitsToDouble(long value)
{
return BitConverter.Int64BitsToDouble(value);
}
/// <summary>
/// Converts the specified single-precision floating point number to a
/// 32-bit signed integer. Note: the endianness of this converter does not
/// affect the returned value.
/// </summary>
/// <param name="value">The number to convert. </param>
/// <returns>A 32-bit signed integer whose value is equivalent to value.</returns>
public int SingleToInt32Bits(float value)
{
return new Int32SingleUnion(value).AsInt32;
}
/// <summary>
/// Converts the specified 32-bit signed integer to a single-precision floating point
/// number. Note: the endianness of this converter does not
/// affect the returned value.
/// </summary>
/// <param name="value">The number to convert. </param>
/// <returns>A single-precision floating point number whose value is equivalent to value.</returns>
public float Int32BitsToSingle(int value)
{
return new Int32SingleUnion(value).AsSingle;
}
/// <summary>
/// Returns a Boolean value converted from one byte at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>true if the byte at startIndex in value is nonzero; otherwise, false.</returns>
public bool ToBoolean(byte[] value, int startIndex)
{
CheckByteArgument(value, startIndex, 1);
return BitConverter.ToBoolean(value, startIndex);
}
/// <summary>
/// Returns a Unicode character converted from two bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A character formed by two bytes beginning at startIndex.</returns>
public char ToChar(byte[] value, int startIndex)
{
return unchecked((char)this.CheckedFromBytes(value, startIndex, 2));
}
/// <summary>
/// Returns a double-precision floating point number converted from eight bytes
/// at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A double precision floating point number formed by eight bytes beginning at startIndex.</returns>
public double ToDouble(byte[] value, int startIndex)
{
return this.Int64BitsToDouble(this.ToInt64(value, startIndex));
}
/// <summary>
/// Returns a single-precision floating point number converted from four bytes
/// at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A single precision floating point number formed by four bytes beginning at startIndex.</returns>
public float ToSingle(byte[] value, int startIndex)
{
return this.Int32BitsToSingle(this.ToInt32(value, startIndex));
}
/// <summary>
/// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A 16-bit signed integer formed by two bytes beginning at startIndex.</returns>
public short ToInt16(byte[] value, int startIndex)
{
return unchecked((short)this.CheckedFromBytes(value, startIndex, 2));
}
/// <summary>
/// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A 32-bit signed integer formed by four bytes beginning at startIndex.</returns>
public int ToInt32(byte[] value, int startIndex)
{
return unchecked((int)this.CheckedFromBytes(value, startIndex, 4));
}
/// <summary>
/// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A 64-bit signed integer formed by eight bytes beginning at startIndex.</returns>
public long ToInt64(byte[] value, int startIndex)
{
return this.CheckedFromBytes(value, startIndex, 8);
}
/// <summary>
/// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A 16-bit unsigned integer formed by two bytes beginning at startIndex.</returns>
public ushort ToUInt16(byte[] value, int startIndex)
{
return unchecked((ushort)this.CheckedFromBytes(value, startIndex, 2));
}
/// <summary>
/// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A 32-bit unsigned integer formed by four bytes beginning at startIndex.</returns>
public uint ToUInt32(byte[] value, int startIndex)
{
return unchecked((uint)this.CheckedFromBytes(value, startIndex, 4));
}
/// <summary>
/// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A 64-bit unsigned integer formed by eight bytes beginning at startIndex.</returns>
public ulong ToUInt64(byte[] value, int startIndex)
{
return unchecked((ulong)this.CheckedFromBytes(value, startIndex, 8));
}
private void CopyBytesImpl(long value, int bytes, byte[] buffer, int index)
{
int endOffset = index + bytes - 1;
for (int i = 0; i < bytes; i++)
{
buffer[endOffset - i] = unchecked((byte)(value & 0xff));
value = value >> 8;
}
}
private long FromBytes(byte[] buffer, int startIndex, int bytesToConvert)
{
long ret = 0;
for (int i = 0; i < bytesToConvert; i++)
{
ret = unchecked((ret << 8) | buffer[startIndex + i]);
}
return ret;
}
/// <summary>
/// Checks the given argument for validity.
/// </summary>
/// <param name="value">The byte array passed in</param>
/// <param name="startIndex">The start index passed in</param>
/// <param name="bytesRequired">The number of bytes required</param>
/// <exception cref="System.ArgumentNullException">value is a null reference</exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// startIndex is less than zero or greater than the length of value minus bytesRequired.
/// </exception>
[SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Keeps code DRY")]
private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (startIndex < 0 || startIndex > value.Length - bytesRequired)
{
throw new ArgumentOutOfRangeException(nameof(startIndex));
}
}
/// <summary>
/// Checks the arguments for validity before calling FromBytes
/// (which can therefore assume the arguments are valid).
/// </summary>
/// <param name="value">The bytes to convert after checking</param>
/// <param name="startIndex">The index of the first byte to convert</param>
/// <param name="bytesToConvert">The number of bytes to convert</param>
/// <returns>The <see cref="long"/></returns>
private long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert)
{
CheckByteArgument(value, startIndex, bytesToConvert);
return this.FromBytes(value, startIndex, bytesToConvert);
}
/// <summary>
/// Returns a String converted from the elements of a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <remarks>All the elements of value are converted.</remarks>
/// <returns>
/// A String of hexadecimal pairs separated by hyphens, where each pair
/// represents the corresponding element in value; for example, "7F-2C-4A".
/// </returns>
public static string ToString(byte[] value)
{
return BitConverter.ToString(value);
}
/// <summary>
/// Returns a String converted from the elements of a byte array starting at a specified array position.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <remarks>The elements from array position startIndex to the end of the array are converted.</remarks>
/// <returns>
/// A String of hexadecimal pairs separated by hyphens, where each pair
/// represents the corresponding element in value; for example, "7F-2C-4A".
/// </returns>
public static string ToString(byte[] value, int startIndex)
{
return BitConverter.ToString(value, startIndex);
}
/// <summary>
/// Returns a String converted from a specified number of bytes at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <param name="length">The number of bytes to convert.</param>
/// <remarks>The length elements from array position startIndex are converted.</remarks>
/// <returns>
/// A String of hexadecimal pairs separated by hyphens, where each pair
/// represents the corresponding element in value; for example, "7F-2C-4A".
/// </returns>
public static string ToString(byte[] value, int startIndex, int length)
{
return BitConverter.ToString(value, startIndex, length);
}
/// <summary>
/// Returns a decimal value converted from sixteen bytes
/// at a specified position in a byte array.
/// </summary>
/// <param name="value">An array of bytes.</param>
/// <param name="startIndex">The starting position within value.</param>
/// <returns>A decimal formed by sixteen bytes beginning at startIndex.</returns>
public decimal ToDecimal(byte[] value, int startIndex)
{
// HACK: This always assumes four parts, each in their own endianness,
// starting with the first part at the start of the byte array.
// On the other hand, there's no real format specified...
int[] parts = new int[4];
for (int i = 0; i < 4; i++)
{
parts[i] = this.ToInt32(value, startIndex + (i * 4));
}
return new decimal(parts);
}
/// <summary>
/// Returns the specified decimal value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 16.</returns>
public byte[] GetBytes(decimal value)
{
byte[] bytes = new byte[16];
int[] parts = decimal.GetBits(value);
for (int i = 0; i < 4; i++)
{
this.CopyBytesImpl(parts[i], 4, bytes, i * 4);
}
return bytes;
}
/// <summary>
/// Copies the specified decimal value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">A character to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(decimal value, byte[] buffer, int index)
{
int[] parts = decimal.GetBits(value);
for (int i = 0; i < 4; i++)
{
this.CopyBytesImpl(parts[i], 4, buffer, (i * 4) + index);
}
}
/// <summary>
/// Returns an array with the given number of bytes formed
/// from the least significant bytes of the specified value.
/// This is used to implement the other GetBytes methods.
/// </summary>
/// <param name="value">The value to get bytes for</param>
/// <param name="bytes">The number of significant bytes to return</param>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
private byte[] GetBytes(long value, int bytes)
{
byte[] buffer = new byte[bytes];
this.CopyBytes(value, bytes, buffer, 0);
return buffer;
}
/// <summary>
/// Returns the specified Boolean value as an array of bytes.
/// </summary>
/// <param name="value">A Boolean value.</param>
/// <returns>An array of bytes with length 1.</returns>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
public byte[] GetBytes(bool value)
{
return BitConverter.GetBytes(value);
}
/// <summary>
/// Returns the specified Unicode character value as an array of bytes.
/// </summary>
/// <param name="value">A character to convert.</param>
/// <returns>An array of bytes with length 2.</returns>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
public byte[] GetBytes(char value)
{
return this.GetBytes(value, 2);
}
/// <summary>
/// Returns the specified double-precision floating point value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 8.</returns>
public byte[] GetBytes(double value)
{
return this.GetBytes(this.DoubleToInt64Bits(value), 8);
}
/// <summary>
/// Returns the specified 16-bit signed integer value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 2.</returns>
public byte[] GetBytes(short value)
{
return this.GetBytes(value, 2);
}
/// <summary>
/// Returns the specified 32-bit signed integer value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 4.</returns>
public byte[] GetBytes(int value)
{
return this.GetBytes(value, 4);
}
/// <summary>
/// Returns the specified 64-bit signed integer value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 8.</returns>
public byte[] GetBytes(long value)
{
return this.GetBytes(value, 8);
}
/// <summary>
/// Returns the specified single-precision floating point value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 4.</returns>
public byte[] GetBytes(float value)
{
return this.GetBytes(this.SingleToInt32Bits(value), 4);
}
/// <summary>
/// Returns the specified 16-bit unsigned integer value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 2.</returns>
public byte[] GetBytes(ushort value)
{
return this.GetBytes(value, 2);
}
/// <summary>
/// Returns the specified 32-bit unsigned integer value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 4.</returns>
public byte[] GetBytes(uint value)
{
return this.GetBytes(value, 4);
}
/// <summary>
/// Returns the specified 64-bit unsigned integer value as an array of bytes.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <returns>An array of bytes with length 8.</returns>
public byte[] GetBytes(ulong value)
{
return this.GetBytes(unchecked((long)value), 8);
}
/// <summary>
/// Copies the given number of bytes from the least-specific
/// end of the specified value into the specified byte array, beginning
/// at the specified index.
/// This is used to implement the other CopyBytes methods.
/// </summary>
/// <param name="value">The value to copy bytes for</param>
/// <param name="bytes">The number of significant bytes to copy</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
private void CopyBytes(long value, int bytes, byte[] buffer, int index)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer), "Byte array must not be null");
}
if (buffer.Length < index + bytes)
{
throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer not big enough for value");
}
this.CopyBytesImpl(value, bytes, buffer, index);
}
/// <summary>
/// Copies the specified Boolean value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">A Boolean value.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(bool value, byte[] buffer, int index)
{
this.CopyBytes(value ? 1 : 0, 1, buffer, index);
}
/// <summary>
/// Copies the specified Unicode character value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">A character to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(char value, byte[] buffer, int index)
{
this.CopyBytes(value, 2, buffer, index);
}
/// <summary>
/// Copies the specified double-precision floating point value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(double value, byte[] buffer, int index)
{
this.CopyBytes(this.DoubleToInt64Bits(value), 8, buffer, index);
}
/// <summary>
/// Copies the specified 16-bit signed integer value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(short value, byte[] buffer, int index)
{
this.CopyBytes(value, 2, buffer, index);
}
/// <summary>
/// Copies the specified 32-bit signed integer value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(int value, byte[] buffer, int index)
{
this.CopyBytes(value, 4, buffer, index);
}
/// <summary>
/// Copies the specified 64-bit signed integer value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(long value, byte[] buffer, int index)
{
this.CopyBytes(value, 8, buffer, index);
}
/// <summary>
/// Copies the specified single-precision floating point value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(float value, byte[] buffer, int index)
{
this.CopyBytes(this.SingleToInt32Bits(value), 4, buffer, index);
}
/// <summary>
/// Copies the specified 16-bit unsigned integer value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(ushort value, byte[] buffer, int index)
{
this.CopyBytes(value, 2, buffer, index);
}
/// <summary>
/// Copies the specified 32-bit unsigned integer value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(uint value, byte[] buffer, int index)
{
this.CopyBytes(value, 4, buffer, index);
}
/// <summary>
/// Copies the specified 64-bit unsigned integer value into the specified byte array,
/// beginning at the specified index.
/// </summary>
/// <param name="value">The number to convert.</param>
/// <param name="buffer">The byte array to copy the bytes into</param>
/// <param name="index">The first index into the array to copy the bytes into</param>
public void CopyBytes(ulong value, byte[] buffer, int index)
{
this.CopyBytes(unchecked((long)value), 8, buffer, index);
}
/// <summary>
/// Union used solely for the equivalent of DoubleToInt64Bits and vice versa.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
private struct Int32SingleUnion
{
/// <summary>
/// Int32 version of the value.
/// </summary>
[FieldOffset(0)]
private readonly int i;
/// <summary>
/// Single version of the value.
/// </summary>
[FieldOffset(0)]
private readonly float f;
/// <summary>
/// Initializes a new instance of the <see cref="Int32SingleUnion"/> struct.
/// </summary>
/// <param name="i">The integer value of the new instance.</param>
internal Int32SingleUnion(int i)
{
this.f = 0; // Just to keep the compiler happy
this.i = i;
}
/// <summary>
/// Initializes a new instance of the <see cref="Int32SingleUnion"/> struct.
/// </summary>
/// <param name="f">
/// The floating point value of the new instance.
/// </param>
internal Int32SingleUnion(float f)
{
this.i = 0; // Just to keep the compiler happy
this.f = f;
}
/// <summary>
/// Gets the value of the instance as an integer.
/// </summary>
internal int AsInt32 => this.i;
/// <summary>
/// Gets the value of the instance as a floating point number.
/// </summary>
internal float AsSingle => this.f;
}
}
}

Просмотреть файл

@ -0,0 +1,390 @@
using System;
using System.IO;
using System.Text;
namespace SixLabors.Fonts
{
/// <summary>
/// BinaryReader using bigendian encoding.
/// </summary>
internal class BinaryReader
{
/// <summary>
/// Buffer used for temporary storage before conversion into primitives
/// </summary>
private readonly byte[] storageBuffer = new byte[16];
/// <summary>
/// Whether or not this reader has been disposed yet.
/// </summary>
private bool disposed;
/// <summary>
/// Initializes a new instance of the <see cref="BinaryReader" /> class.
/// Constructs a new binary reader with the given bit converter, reading
/// to the given stream, using the given encoding.
/// </summary>
/// <param name="stream">Stream to read data from</param>
public BinaryReader(Stream stream)
{
this.BaseStream = stream;
this.BitConverter = new BigEndianBitConverter();
}
/// <summary>
/// Gets the underlying stream of the EndianBinaryReader.
/// </summary>
public Stream BaseStream { get; }
/// <summary>
/// Gets the bit converter used to read values from the stream.
/// </summary>
internal BigEndianBitConverter BitConverter { get; }
/// <summary>
/// Closes the reader, including the underlying stream.
/// </summary>
public void Close()
{
}
/// <summary>
/// Seeks within the stream.
/// </summary>
/// <param name="offset">Offset to seek to.</param>
/// <param name="origin">Origin of seek operation.</param>
public void Seek(int offset, SeekOrigin origin)
{
this.CheckDisposed();
this.BaseStream.Seek(offset, origin);
}
/// <summary>
/// Reads a single byte from the stream.
/// </summary>
/// <returns>The byte read</returns>
public byte ReadByte()
{
this.ReadInternal(this.storageBuffer, 1);
return this.storageBuffer[0];
}
/// <summary>
/// Reads a single signed byte from the stream.
/// </summary>
/// <returns>The byte read</returns>
public sbyte ReadSByte()
{
this.ReadInternal(this.storageBuffer, 1);
return unchecked((sbyte)this.storageBuffer[0]);
}
/// <summary>
/// Reads a boolean from the stream. 1 byte is read.
/// </summary>
/// <returns>The boolean read</returns>
public bool ReadBoolean()
{
this.ReadInternal(this.storageBuffer, 1);
return this.BitConverter.ToBoolean(this.storageBuffer, 0);
}
/// <summary>
/// Reads a 16-bit signed integer from the stream, using the bit converter
/// for this reader. 2 bytes are read.
/// </summary>
/// <returns>The 16-bit integer read</returns>
public short ReadInt16()
{
this.ReadInternal(this.storageBuffer, 2);
return this.BitConverter.ToInt16(this.storageBuffer, 0);
}
/// <summary>
/// Reads a 32-bit signed integer from the stream, using the bit converter
/// for this reader. 4 bytes are read.
/// </summary>
/// <returns>The 32-bit integer read</returns>
public int ReadInt32()
{
this.ReadInternal(this.storageBuffer, 4);
return this.BitConverter.ToInt32(this.storageBuffer, 0);
}
/// <summary>
/// Reads a 64-bit signed integer from the stream, using the bit converter
/// for this reader. 8 bytes are read.
/// </summary>
/// <returns>The 64-bit integer read</returns>
public long ReadInt64()
{
this.ReadInternal(this.storageBuffer, 8);
return this.BitConverter.ToInt64(this.storageBuffer, 0);
}
/// <summary>
/// Reads a 16-bit unsigned integer from the stream, using the bit converter
/// for this reader. 2 bytes are read.
/// </summary>
/// <returns>The 16-bit unsigned integer read</returns>
public ushort ReadUInt16()
{
this.ReadInternal(this.storageBuffer, 2);
return this.BitConverter.ToUInt16(this.storageBuffer, 0);
}
/// <summary>
/// Reads a 32-bit unsigned integer from the stream, using the bit converter
/// for this reader. 4 bytes are read.
/// </summary>
/// <returns>The 32-bit unsigned integer read</returns>
public uint ReadUInt32()
{
this.ReadInternal(this.storageBuffer, 4);
return this.BitConverter.ToUInt32(this.storageBuffer, 0);
}
public uint? ReadOffset32()
{
uint value = this.ReadUInt32();
if (value == 0)
{
return null;
}
return value;
}
/// <summary>
/// Reads a 64-bit unsigned integer from the stream, using the bit converter
/// for this reader. 8 bytes are read.
/// </summary>
/// <returns>The 64-bit unsigned integer read</returns>
public ulong ReadUInt64()
{
this.ReadInternal(this.storageBuffer, 8);
return this.BitConverter.ToUInt64(this.storageBuffer, 0);
}
/// <summary>
/// Reads a single-precision floating-point value from the stream, using the bit converter
/// for this reader. 4 bytes are read.
/// </summary>
/// <returns>The floating point value read</returns>
public float ReadSingle()
{
this.ReadInternal(this.storageBuffer, 4);
return this.BitConverter.ToSingle(this.storageBuffer, 0);
}
/// <summary>
/// Reads a double-precision floating-point value from the stream, using the bit converter
/// for this reader. 8 bytes are read.
/// </summary>
/// <returns>The floating point value read</returns>
public double ReadDouble()
{
this.ReadInternal(this.storageBuffer, 8);
return this.BitConverter.ToDouble(this.storageBuffer, 0);
}
/// <summary>
/// Reads a decimal value from the stream, using the bit converter
/// for this reader. 16 bytes are read.
/// </summary>
/// <returns>The decimal value read</returns>
public decimal ReadDecimal()
{
this.ReadInternal(this.storageBuffer, 16);
return this.BitConverter.ToDecimal(this.storageBuffer, 0);
}
/// <summary>
/// Reads the specified number of bytes into the given buffer, starting at
/// the given index.
/// </summary>
/// <param name="buffer">The buffer to copy data into</param>
/// <param name="index">The first index to copy data into</param>
/// <param name="count">The number of bytes to read</param>
/// <returns>The number of bytes actually read. This will only be less than
/// the requested number of bytes if the end of the stream is reached.
/// </returns>
public int Read(byte[] buffer, int index, int count)
{
this.CheckDisposed();
int read = 0;
while (count > 0)
{
int block = this.BaseStream.Read(buffer, index, count);
if (block == 0)
{
return read;
}
index += block;
read += block;
count -= block;
}
return read;
}
/// <summary>
/// Reads the specified number of bytes, returning them in a new byte array.
/// If not enough bytes are available before the end of the stream, this
/// method will return what is available.
/// </summary>
/// <param name="count">The number of bytes to read</param>
/// <returns>The bytes read</returns>
public byte[] ReadBytes(int count)
{
this.CheckDisposed();
byte[] ret = new byte[count];
int index = 0;
while (index < count)
{
int read = this.BaseStream.Read(ret, index, count - index);
// Stream has finished half way through. That's fine, return what we've got.
if (read == 0)
{
byte[] copy = new byte[index];
Buffer.BlockCopy(ret, 0, copy, 0, index);
return copy;
}
index += read;
}
return ret;
}
/// <summary>
/// Reads the specified number of bytes, returning them in a new byte array.
/// If not enough bytes are available before the end of the stream, this
/// method will throw an IOException.
/// </summary>
/// <param name="count">The number of bytes to read</param>
/// <returns>The bytes read</returns>
public byte[] ReadBytesOrThrow(int count)
{
byte[] ret = new byte[count];
this.ReadInternal(ret, count);
return ret;
}
/// <summary>
/// Reads a 7-bit encoded integer from the stream. This is stored with the least significant
/// information first, with 7 bits of information per byte of value, and the top
/// bit as a continuation flag. This method is not affected by the endianness
/// of the bit converter.
/// </summary>
/// <returns>The 7-bit encoded integer read from the stream.</returns>
public int Read7BitEncodedInt()
{
this.CheckDisposed();
int ret = 0;
for (int shift = 0; shift < 35; shift += 7)
{
int b = this.BaseStream.ReadByte();
if (b == -1)
{
throw new EndOfStreamException();
}
ret = ret | ((b & 0x7f) << shift);
if ((b & 0x80) == 0)
{
return ret;
}
}
// Still haven't seen a byte with the high bit unset? Dodgy data.
throw new IOException("Invalid 7-bit encoded integer in stream.");
}
/// <summary>
/// Reads a string of a specific length, which specifies the number of bytes
/// to read from the stream. These bytes are then converted into a string with
/// the encoding for this reader.
/// </summary>
/// <param name="bytesToRead">The bytes to read.</param>
/// <param name="encoding">The encoding.</param>
/// <returns>
/// The string read from the stream.
/// </returns>
public string ReadString(int bytesToRead, Encoding encoding)
{
byte[] data = new byte[bytesToRead];
this.ReadInternal(data, bytesToRead);
return encoding.GetString(data, 0, data.Length);
}
public string ReadUint32String()
{
return this.ReadString(4, Encoding.UTF8);
}
/// <summary>
/// Checks whether or not the reader has been disposed, throwing an exception if so.
/// </summary>
private void CheckDisposed()
{
if (this.disposed)
{
throw new ObjectDisposedException("EndianBinaryReader");
}
}
/// <summary>
/// Reads the given number of bytes from the stream, throwing an exception
/// if they can't all be read.
/// </summary>
/// <param name="data">Buffer to read into</param>
/// <param name="size">Number of bytes to read</param>
private void ReadInternal(byte[] data, int size)
{
this.CheckDisposed();
int index = 0;
while (index < size)
{
int read = this.BaseStream.Read(data, index, size - index);
if (read == 0)
{
throw new EndOfStreamException($"End of stream reached with {size - index} byte{(size - index == 1 ? "s" : string.Empty)} left to read.");
}
index += read;
}
}
/// <summary>
/// Reads the given number of bytes from the stream if possible, returning
/// the number of bytes actually read, which may be less than requested if
/// (and only if) the end of the stream is reached.
/// </summary>
/// <param name="data">Buffer to read into</param>
/// <param name="size">Number of bytes to read</param>
/// <returns>Number of bytes actually read</returns>
private int TryReadInternal(byte[] data, int size)
{
this.CheckDisposed();
int index = 0;
while (index < size)
{
int read = this.BaseStream.Read(data, index, size - index);
if (read == 0)
{
return index;
}
index += read;
}
return index;
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
using System.IO;
using SixLabors.Fonts.Tables;
using SixLabors.Fonts.Tables.General;
namespace SixLabors.Fonts
{
/// <summary>
/// provide metadata about a font.
/// </summary>
public class FontDescription
{
private NameTable nameTable;
/// <summary>
/// Initializes a new instance of the <see cref="FontDescription"/> class.
/// </summary>
/// <param name="nameTable">The name table.</param>
internal FontDescription(NameTable nameTable)
{
this.nameTable = nameTable;
}
/// <summary>
/// Gets the name of the font.
/// </summary>
public string FontName => this.nameTable.FontName;
/// <summary>
/// Gets the name of the font.
/// </summary>
public string FontFamily => this.nameTable.FontFamilyName;
/// <summary>
/// Gets the font sub family.
/// </summary>
public string FontSubFamilyName => this.nameTable.FontSubFamilyName;
/// <summary>
/// Reads a <see cref="FontDescription"/> from the specified stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns>a <see cref="FontDescription"/>.</returns>
public static FontDescription Load(Stream stream)
{
// only read the name table
var reader = new FontReader(stream, typeof(NameTable));
var nameTable = reader.GetTable<NameTable>();
return new FontDescription(nameTable);
}
}
}

Просмотреть файл

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
using SixLabors.Fonts.Tables;
namespace SixLabors.Fonts
{
internal class FontReader
{
internal FontReader(Stream stream, Type[] tableTypes, TableLoader loader)
{
BinaryReader reader;
var startOfFilePosition = stream.Position;
reader = new BinaryReader(stream);
// we should immediately read the table header to learn which tables we have and what order they are in
uint version = reader.ReadUInt32();
this.OutlineType = (OutlineTypes)version;
ushort tableCount = reader.ReadUInt16();
ushort searchRange = reader.ReadUInt16();
ushort entrySelector = reader.ReadUInt16();
ushort rangeShift = reader.ReadUInt16();
TableHeader[] headers = new TableHeader[tableCount];
for (int i = 0; i < tableCount; i++)
{
headers[i] = TableHeader.Read(reader);
}
var tablesToLoad = tableTypes?.Select(loader.GetTag).Where(x => x != null).ToArray();
var tableCountToLoad = tablesToLoad?.Length ?? tableCount;
List<Table> tables = new List<Table>(tableCountToLoad);
foreach (var header in headers.Where(x => x.Offset.HasValue).OrderBy(x => x.Offset.Value))
{
if (tablesToLoad == null || tablesToLoad.Contains(header.Tag))
{
var startOfString = header.Offset.Value + startOfFilePosition;
var diff = startOfString - reader.BaseStream.Position;
reader.BaseStream.Seek(diff, SeekOrigin.Current);// only seek forward, if we find issues with this we will consume forwards as the idea is we will never need to backtrack
Table t = loader.Load(header.Tag, reader);
tables.Add(t);
}
}
this.Tables = ImmutableArray.Create(tables.ToArray());
}
public OutlineTypes OutlineType { get; }
public ImmutableArray<Table> Tables { get; }
public FontReader(Stream stream)
: this(stream, null)
{
}
public FontReader(Stream stream, params Type[] tableToLoad)
: this(stream, tableToLoad, TableLoader.Default)
{
}
public TTableType GetTable<TTableType>() where TTableType : Table
{
foreach (var table in this.Tables)
{
if (table is TTableType)
{
return (TTableType)table;
}
}
return null;
}
public enum OutlineTypes : uint
{
TrueType = 0x00010000,
CFF = 0x4F54544F
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
// <copyright file="AssemblyInfo.cs" company="Scott Williams">
// Copyright (c) Scott Williams and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// Common values read from `AssemblyInfo.Common.cs`

Просмотреть файл

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>09e744ec-4852-4fc7-be78-c1b399f17967</ProjectGuid>
<RootNamespace>SixLabors.Fonts</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

Просмотреть файл

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SixLabors.Fonts.Utilities;
using SixLabors.Fonts.WellKnownIds;
namespace SixLabors.Fonts.Tables.General
{
internal class NameRecord
{
private readonly string value;
public PlatformIDs Platform { get; }
public ushort LanguageID { get; }
public NameIds NameID { get; }
internal StringLoader StringReader { get; private set; }
public string Value => this.StringReader?.Value ?? this.value;
public NameRecord(PlatformIDs platform, ushort languageId, NameIds nameId, string value)
{
this.Platform = platform;
this.LanguageID = languageId;
this.NameID = nameId;
this.value = value;
}
public static NameRecord Read(BinaryReader reader)
{
var platform = (PlatformIDs)reader.ReadUInt16();
Encoding encoding = ((EncodingIDs)reader.ReadUInt16()).AsEncoding();
var languageID = reader.ReadUInt16();
var nameID = (NameIds)reader.ReadUInt16();
var stringReader = StringLoader.Create(reader, encoding);
return new NameRecord(platform, languageID, nameID, null)
{
StringReader = stringReader
};
}
}
}

Просмотреть файл

@ -0,0 +1,133 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using SixLabors.Fonts.Utilities;
using SixLabors.Fonts.WellKnownIds;
namespace SixLabors.Fonts.Tables.General
{
[TableName("name")]
internal class NameTable : Table
{
private NameRecord[] names;
public static NameTable Load(BinaryReader reader)
{
List<StringLoader> strings = new List<StringLoader>();
ushort format = reader.ReadUInt16();
ushort nameCount = reader.ReadUInt16();
ushort stringOffset = reader.ReadUInt16();
NameRecord[] names = new NameRecord[nameCount];
for (var i = 0; i < nameCount; i++)
{
names[i] = NameRecord.Read(reader);
strings.Add(names[i].StringReader);
}
StringLoader[] langs = null;
if (format == 1)
{
// format 1 adds language data
ushort langCount = reader.ReadUInt16();
langs = new StringLoader[langCount];
for (var i = 0; i < langCount; i++)
{
langs[i] = StringLoader.Create(reader);
strings.Add(langs[i]);
}
}
var startOfStrings = reader.BaseStream.Position;
foreach (var readable in strings.OrderBy(x => x.Offset))
{
var startOfString = readable.Offset + startOfStrings;
var diff = startOfString - reader.BaseStream.Position;
reader.BaseStream.Seek(diff, SeekOrigin.Current);// only seek forward, if we find issues with this we will consume forwards as the idea is we will never need to backtrack
readable.LoadValue(reader);
}
var langNames = langs?.Select(x => x.Value).ToArray() ?? new string[0];
return new NameTable(names, langNames);
}
internal NameTable(NameRecord[] names, string[] languages)
{
this.names = names;
foreach (var name in names)
{
switch (name.NameID)
{
case NameIds.FontFamilyName:
this.FontFamilyName = name.Value;
break;
case NameIds.FontSubfamilyName:
this.FontSubFamilyName = name.Value;
break;
case NameIds.UniqueFontID:
this.Id = name.Value;
break;
case NameIds.FullFontName:
this.FontName = name.Value;
break;
}
}
}
/// <summary>
/// Gets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public string Id { get; private set; }
/// <summary>
/// Gets the name of the font.
/// </summary>
/// <value>
/// The name of the font.
/// </value>
public string FontName { get; private set; }
/// <summary>
/// Gets the name of the font family.
/// </summary>
/// <value>
/// The name of the font family.
/// </value>
public string FontFamilyName { get; private set; }
/// <summary>
/// Gets the name of the font sub family.
/// </summary>
/// <value>
/// The name of the font sub family.
/// </value>
public string FontSubFamilyName { get; private set; }
public string GetNameById(NameIds nameId)
{
foreach (var name in this.names)
{
if (name.NameID == nameId)
{
return name.Value;
}
}
return null;
}
public string GetNameById(ushort nameId)
{
return this.GetNameById((NameIds)nameId);
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
namespace SixLabors.Fonts.Tables
{
internal abstract class Table
{
}
}

Просмотреть файл

@ -0,0 +1,38 @@
using System;
using System.Text;
namespace SixLabors.Fonts.Tables
{
internal class TableHeader
{
public TableHeader(string tag, uint checkSum, uint? offset, uint len)
{
this.Tag = tag;
this.CheckSum = checkSum;
this.Offset = offset;
this.Length = len;
}
public string Tag { get; }
public uint? Offset { get; }
public uint CheckSum { get; }
public uint Length { get; }
public override string ToString()
{
return "{" + this.Tag + "}";
}
public static TableHeader Read(BinaryReader reader)
{
return new TableHeader(
reader.ReadUint32String(),
reader.ReadUInt32(),
reader.ReadOffset32(),
reader.ReadUInt32());
}
}
}

Просмотреть файл

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SixLabors.Fonts.Tables.General;
namespace SixLabors.Fonts.Tables
{
internal class TableLoader
{
public static TableLoader Default { get; } = new TableLoader();
public TableLoader()
{
// we will hard code mapping registration in here for all the tables
this.Register(NameTable.Load);
}
private Dictionary<string, Func<BinaryReader, Table>> loaders = new Dictionary<string, Func<BinaryReader, Table>>();
private Dictionary<Type, string> types = new Dictionary<Type, string>();
public string GetTag(Type type)
{
if (this.types.ContainsKey(type))
{
return this.types[type];
}
return null;
}
internal IEnumerable<string> TableTags(IEnumerable<Type> typeFilter)
{
if (typeFilter == null)
{
return this.types.Values;
}
return typeFilter.Select(x => this.types[x]).ToArray();
}
internal IEnumerable<Type> RegisterdTypes()
{
return this.types.Keys;
}
private void Register<T>(string tag, Func<BinaryReader, T> createFunc) where T : Table
{
lock (this.loaders)
{
if (!this.loaders.ContainsKey(tag))
{
this.loaders.Add(tag, createFunc);
this.types.Add(typeof(T), tag);
}
}
}
private void Register<T>(Func<BinaryReader, T> createFunc) where T : Table
{
var name =
typeof(T).GetTypeInfo()
.CustomAttributes
.First(x => x.AttributeType == typeof(TableNameAttribute))
.ConstructorArguments[0].Value.ToString();
this.Register(name, createFunc);
}
internal Table Load(string Tag, BinaryReader reader)
{
// loader missing register an unknow type loader and carry on
if (!this.loaders.ContainsKey(Tag))
{
return new UnknownTable(Tag);
}
return this.loaders[Tag]?.Invoke(reader);
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
using System;
namespace SixLabors.Fonts.Tables
{
internal class TableNameAttribute : Attribute
{
public TableNameAttribute(string name)
{
this.Name = name;
}
public string Name { get; }
}
}

Просмотреть файл

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SixLabors.Fonts.Tables
{
using System.IO;
using System.Text;
internal class UnknownTable : Table
{
internal UnknownTable(string name)
{
this.Name = name;
}
public string Name { get; }
}
}

Просмотреть файл

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SixLabors.Fonts.Utilities
{
internal class StringLoader
{
public StringLoader(ushort length, ushort offset, Encoding encoding)
{
this.Length = length;
this.Offset = offset;
this.Encoding = encoding;
}
public ushort Length { get; }
public ushort Offset { get; }
public string Value { get; private set; }
public Encoding Encoding { get; private set; }
public static StringLoader Create(BinaryReader reader)
{
return Create(reader, Encoding.BigEndianUnicode);
}
public static StringLoader Create(BinaryReader reader, Encoding encoding)
{
return new StringLoader(reader.ReadUInt16(), reader.ReadUInt16(), encoding);
}
public void LoadValue(BinaryReader reader)
{
this.Value = reader.ReadString(this.Length, this.Encoding);
}
}
}

Просмотреть файл

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SixLabors.Fonts.WellKnownIds
{
/// <summary>
/// Encoding IDS
/// </summary>
public enum EncodingIDs : ushort
{
/// <summary>
/// Unicode 1.0 semantics
/// </summary>
Unicode1 = 0,
/// <summary>
/// Unicode 1.1 semantics
/// </summary>
Unicode11 = 1,
/// <summary>
/// ISO/IEC 10646 semantics
/// </summary>
ISO10646 = 2,
/// <summary>
/// Unicode 2.0 and onwards semantics, Unicode BMP only (cmap subtable formats 0, 4, 6).
/// </summary>
Unicode2 = 3,
/// <summary>
/// Unicode 2.0 and onwards semantics, Unicode full repertoire (cmap subtable formats 0, 4, 6, 10, 12).
/// </summary>
Unicode2Plus = 4,
/// <summary>
/// Unicode Variation Sequences (cmap subtable format 14).
/// </summary>
UnicodeVariationSequences = 5,
/// <summary>
/// Unicode full repertoire (cmap subtable formats 0, 4, 6, 10, 12, 13)
/// </summary>
UnicodeFull = 6,
}
/// <summary>
/// Converts encoding ID to TextEncoding
/// </summary>
public static class EncodingIDExtensions
{
/// <summary>
/// Converts encoding ID to TextEncoding
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns></returns>
public static Encoding AsEncoding(this EncodingIDs id)
{
switch (id)
{
case EncodingIDs.Unicode11:
case EncodingIDs.Unicode2:
return Encoding.BigEndianUnicode;
default:
return Encoding.UTF8;
}
}
}
}

Просмотреть файл

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SixLabors.Fonts.WellKnownIds
{
/// <summary>
/// Common name ids
/// </summary>
public enum NameIds : ushort
{
/// <summary>
/// The copyright notice
/// </summary>
CopyrightNotice = 0,
/// <summary>
/// The font family name; Up to four fonts can share the Font Family name, forming a font style linking group (regular, italic, bold, bold italic — as defined by OS/2.fsSelection bit settings).
/// </summary>
FontFamilyName = 1,
/// <summary>
/// The font subfamily name; The Font Subfamily name distinguishes the font in a group with the same Font Family name (name ID 1). This is assumed to address style (italic, oblique) and weight (light, bold, black, etc.). A font with no particular differences in weight or style (e.g. medium weight, not italic and fsSelection bit 6 set) should have the string “Regular” stored in this position.
/// </summary>
FontSubfamilyName = 2,
/// <summary>
/// The unique font identifier
/// </summary>
UniqueFontID = 3,
/// <summary>
/// The full font name; a combination of strings 1 and 2, or a similar human-readable variant. If string 2 is "Regular", it is sometimes omitted from name ID 4.
/// </summary>
FullFontName = 4,
/// <summary>
/// Version string. Should begin with the syntax 'Version &lt;number&gt;.&lt;number>' (upper case, lower case, or mixed, with a space between “Version” and the number).
/// The string must contain a version number of the following form: one or more digits (0-9) of value less than 65,535, followed by a period, followed by one or more digits of value less than 65,535. Any character other than a digit will terminate the minor number. A character such as “;” is helpful to separate different pieces of version information.
/// The first such match in the string can be used by installation software to compare font versions. Note that some installers may require the string to start with “Version ”, followed by a version number as above.
/// </summary>
Version = 5,
/// <summary>
/// Postscript name for the font; Name ID 6 specifies a string which is used to invoke a PostScript language font that corresponds to this OpenType font. When translated to ASCII, the name string must be no longer than 63 characters and restricted to the printable ASCII subset, codes 33 to 126, except for the 10 characters '[', ']', '(', ')', '{', '}', '&lt;', '&gt;', '/', '%'.
/// In a CFF OpenType font, there is no requirement that this name be the same as the font name in the CFFs Name INDEX. Thus, the same CFF may be shared among multiple font components in a Font Collection. See the 'name' table section of Recommendations for OpenType fonts "" for additional information.
/// </summary>
PostscriptName = 6,
/// <summary>
/// Trademark; this is used to save any trademark notice/information for this font. Such information should be based on legal advice. This is distinctly separate from the copyright.
/// </summary>
Trademark = 7,
/// <summary>
/// The manufacturer
/// </summary>
Manufacturer = 8,
/// <summary>
/// Designer; name of the designer of the typeface.
/// </summary>
Designer = 9,
/// <summary>
/// Description; description of the typeface. Can contain revision information, usage recommendations, history, features, etc.
/// </summary>
Description = 10,
/// <summary>
/// URL Vendor; URL of font vendor (with protocol, e.g., http://, ftp://). If a unique serial number is embedded in the URL, it can be used to register the font.
/// </summary>
VendorUrl = 11,
/// <summary>
/// URL Designer; URL of typeface designer (with protocol, e.g., http://, ftp://).
/// </summary>
DesignerUrl = 12,
/// <summary>
/// License Description; description of how the font may be legally used, or different example scenarios for licensed use. This field should be written in plain language, not legalese.
/// </summary>
LicenseDescription = 13,
/// <summary>
/// License Info URL; URL where additional licensing information can be found.
/// </summary>
LicenseInfoUrl = 14,
/// <summary>
/// Typographic Family name: The typographic family grouping doesn't impose any constraints on the number of faces within it, in contrast with the 4-style family grouping (ID 1), which is present both for historical reasons and to express style linking groups. If name ID 16 is absent, then name ID 1 is considered to be the typographic family name. (In earlier versions of the specification, name ID 16 was known as "Preferred Family".)
/// </summary>
TypographicFamilyName = 16,
/// <summary>
/// Typographic Subfamily name: This allows font designers to specify a subfamily name within the typographic family grouping. This string must be unique within a particular typographic family. If it is absent, then name ID 2 is considered to be the typographic subfamily name. (In earlier versions of the specification, name ID 17 was known as "Preferred Subfamily".)
/// </summary>
TypographicSubfamilyName = 17,
/// <summary>
/// Sample text; This can be the font name, or any other text that the designer thinks is the best sample to display the font in.
/// </summary>
SampleText = 19,
}
}

Просмотреть файл

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SixLabors.Fonts.WellKnownIds
{
/// <summary>
/// platforms ids
/// </summary>
public enum PlatformIDs : ushort
{
/// <summary>
/// Unicode platform
/// </summary>
Unicode = 0,
/// <summary>
/// Script manager code
/// </summary>
Macintosh = 1,
/// <summary>
/// [deprecated] ISO encoding
/// </summary>
ISO = 2,
/// <summary>
/// Window encoding
/// </summary>
Windows = 3,
/// <summary>
/// Custom platform
/// </summary>
Custom = 4 // Custom None
}
}

Просмотреть файл

@ -0,0 +1,74 @@
{
"version": "0.1.0-alpha1-*",
"title": "SixLabors.Fonts",
"description": "Netstandard, cross platform, font loading and layout library",
"authors": [
"Scott Williams and contributors"
],
"packOptions": {
"owners": [
"Scott Williams and contributors"
],
"summary": "Netstandard cross platform font loading and layout library",
"projectUrl": "https://github.com/SixLabors/Fonts",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
//"iconUrl": "https://raw.githubusercontent.com/SixLabors/Fonts/master/icons/icon.png",
"requireLicenseAcceptance": false,
"repository": {
"type": "git",
"url": "https://github.com/SixLabors/Fonts"
},
"tags": [
"font",
"truetype",
"opentype"
]
},
"buildOptions": {
"allowUnsafe": true,
"xmlDoc": true,
"additionalArguments": [ "/additionalfile:../Shared/stylecop.json", "/ruleset:../../SixLabors.Fonts.ruleset" ],
"compile": [
"../Shared/*.cs"
]
},
"configurations": {
"Release": {
"buildOptions": {
"warningsAsErrors": false,
"optimize": true
}
}
},
"dependencies": {
"StyleCop.Analyzers": {
"version": "1.0.0",
"type": "build"
},
"System.Collections.Immutable": "1.2.0",
"System.Buffers": "4.0.0"
},
"frameworks": {
"netstandard1.1": {
"dependencies": {
"System.Collections": "4.0.11",
"System.Diagnostics.Debug": "4.0.11",
"System.Diagnostics.Tools": "4.0.1",
"System.Linq": "4.1.0",
"System.ObjectModel": "4.0.12",
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime.Extensions": "4.1.0",
"System.Runtime.InteropServices": "4.1.0",
"System.Numerics.Vectors": "4.1.1",
"System.Runtime.Numerics": "4.0.1"
}
},
"net451": {
"dependencies": {
}
}
},
"tools": {
"dotnet-version": "1.1.0"
}
}

Просмотреть файл

@ -0,0 +1,21 @@
@echo off
cd tests\CodeCoverage
nuget restore packages.config -PackagesDirectory .
cd ..\SixLabors.Shapes.Tests
dotnet restore
cd ..
cd ..
rem The -threshold options prevents this taking ages...
tests\CodeCoverage\OpenCover.4.6.519\tools\OpenCover.Console.exe -target:"C:\Program Files\dotnet\dotnet.exe" -targetargs:"test tests\SixLabors.Fonts.Tests -c Release -f net451" -threshold:10 -register:user -filter:"+[SixLabors.Shapes*]*" -excludebyattribute:*.ExcludeFromCodeCoverage* -hideskipped:All -returntargetcode -output:.\SixLabors.Shapes.Coverage.xml
if %errorlevel% neq 0 exit /b %errorlevel%
SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%
pip install codecov
codecov -f "SixLabors.Shapes.Coverage.xml"

Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="OpenCover" version="4.6.519" />
</packages>

Просмотреть файл

@ -0,0 +1,394 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SixLabors.Fonts.Tests
{
internal class BinaryWriter
{
/// <summary>
/// Buffer used for temporary storage during conversion from primitives
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// Buffer used for Write(char)
/// </summary>
private readonly char[] charBuffer = new char[1];
/// <summary>
/// Whether or not this writer has been disposed yet.
/// </summary>
private bool disposed;
public BinaryWriter()
: this(new MemoryStream())
{
}
public BinaryWriter(Stream stream)
{
var bitConverter = new BigEndianBitConverter();
// TODO: Use Guard
if (bitConverter == null)
{
throw new ArgumentNullException("bitConverter");
}
if (stream == null)
{
throw new ArgumentNullException("stream");
}
if (!stream.CanWrite)
{
throw new ArgumentException("Stream isn't writable", "stream");
}
this.BaseStream = stream;
this.BitConverter = bitConverter;
}
/// <summary>
/// Gets the underlying stream of the EndianBinaryWriter.
/// </summary>
public Stream BaseStream { get; }
/// <summary>
/// Gets the bit converter used to write values to the stream
/// </summary>
internal BigEndianBitConverter BitConverter { get; }
/// <summary>
/// Closes the writer, including the underlying stream.
/// </summary>
public void Close()
{
this.Dispose();
}
/// <summary>
/// Flushes the underlying stream.
/// </summary>
public void Flush()
{
this.CheckDisposed();
this.BaseStream.Flush();
}
public BinaryReader GetReader()
{
return new BinaryReader(GetStream());
}
public MemoryStream GetStream()
{
this.Flush();
var p = this.BaseStream.Position;
this.BaseStream.Position = 0;
var ms = new MemoryStream();
this.BaseStream.CopyTo(ms);
ms.Position = 0;
this.BaseStream.Position = 0;
return ms;
}
/// <summary>
/// Seeks within the stream.
/// </summary>
/// <param name="offset">Offset to seek to.</param>
/// <param name="origin">Origin of seek operation.</param>
public void Seek(int offset, SeekOrigin origin)
{
this.CheckDisposed();
this.BaseStream.Seek(offset, origin);
}
/// <summary>
/// Writes a boolean value to the stream. 1 byte is written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(bool value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 1);
}
/// <summary>
/// Writes a 16-bit signed integer to the stream, using the bit converter
/// for this writer. 2 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(short value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 2);
}
/// <summary>
/// Writes a 32-bit signed integer to the stream, using the bit converter
/// for this writer. 4 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(int value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 4);
}
/// <summary>
/// Writes a 64-bit signed integer to the stream, using the bit converter
/// for this writer. 8 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(long value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 8);
}
public void WriteUint32(uint value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 4);
}
public void WriteOffset32(uint? value)
{
this.WriteUint32(value ?? 0);
}
/// <summary>
/// Writes a 32-bit unsigned integer to the stream, using the bit converter
/// for this writer. 4 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(uint value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 4);
}
/// <summary>
/// Writes a 64-bit unsigned integer to the stream, using the bit converter
/// for this writer. 8 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(ulong value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 8);
}
/// <summary>
/// Writes a single-precision floating-point value to the stream, using the bit converter
/// for this writer. 4 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(float value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 4);
}
/// <summary>
/// Writes a double-precision floating-point value to the stream, using the bit converter
/// for this writer. 8 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(double value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 8);
}
/// <summary>
/// Writes a decimal value to the stream, using the bit converter for this writer.
/// 16 bytes are written.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(decimal value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 16);
}
/// <summary>
/// Writes a signed byte to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(byte value)
{
this.buffer[0] = value;
this.WriteInternal(this.buffer, 1);
}
/// <summary>
/// Writes an unsigned byte to the stream.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(sbyte value)
{
this.buffer[0] = unchecked((byte)value);
this.WriteInternal(this.buffer, 1);
}
/// <summary>
/// Writes an array of bytes to the stream.
/// </summary>
/// <param name="value">The values to write</param>
public void Write(byte[] value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
this.WriteInternal(value, value.Length);
}
/// <summary>
/// Writes a portion of an array of bytes to the stream.
/// </summary>
/// <param name="value">An array containing the bytes to write</param>
/// <param name="offset">The index of the first byte to write within the array</param>
/// <param name="count">The number of bytes to write</param>
public void Write(byte[] value, int offset, int count)
{
this.CheckDisposed();
this.BaseStream.Write(value, offset, count);
}
/// <summary>
/// Writes a single character to the stream, using the encoding for this writer.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(char value, Encoding encoding)
{
this.charBuffer[0] = value;
this.Write(this.charBuffer, encoding);
}
/// <summary>
/// Writes an array of characters to the stream, using the encoding for this writer.
/// </summary>
/// <param name="value">An array containing the characters to write</param>
public void Write(char[] value, Encoding encoding)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
this.CheckDisposed();
byte[] data = encoding.GetBytes(value, 0, value.Length);
this.WriteInternal(data, data.Length);
}
public void WriteUint32(string text)
{
if (text.Length != 4)
{
throw new Exception("text must be exactly 4 characters long");
}
this.WriteNoLength(text, Encoding.UTF8);
}
/// <summary>
/// Writes a string to the stream, using the encoding for this writer.
/// </summary>
/// <param name="value">The value to write. Must not be null.</param>
/// <exception cref="System.ArgumentNullException">value is null</exception>
public void WriteNoLength(string value, Encoding encoding)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
this.CheckDisposed();
byte[] data = encoding.GetBytes(value);
// this.Write7BitEncodedInt(data.Length);
this.WriteInternal(data, data.Length);
}
/// <summary>
/// Writes a 7-bit encoded integer from the stream. This is stored with the least significant
/// information first, with 7 bits of information per byte of value, and the top
/// bit as a continuation flag.
/// </summary>
/// <param name="value">The 7-bit encoded integer to write to the stream</param>
public void Write7BitEncodedInt(int value)
{
this.CheckDisposed();
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than or equal to 0.");
}
int index = 0;
while (value >= 128)
{
this.buffer[index++] = (byte)((value & 0x7f) | 0x80);
value = value >> 7;
index++;
}
this.buffer[index++] = (byte)value;
this.BaseStream.Write(this.buffer, 0, index);
}
/// <summary>
/// Disposes of the underlying stream.
/// </summary>
public void Dispose()
{
if (!this.disposed)
{
this.Flush();
this.disposed = true;
((IDisposable)this.BaseStream).Dispose();
}
}
/// <summary>
/// Checks whether or not the writer has been disposed, throwing an exception if so.
/// </summary>
private void CheckDisposed()
{
if (this.disposed)
{
throw new ObjectDisposedException("EndianBinaryWriter");
}
}
/// <summary>
/// Writes the specified number of bytes from the start of the given byte array,
/// after checking whether or not the writer has been disposed.
/// </summary>
/// <param name="bytes">The array of bytes to write from</param>
/// <param name="length">The number of bytes to write</param>
private void WriteInternal(byte[] bytes, int length)
{
this.CheckDisposed();
this.BaseStream.Write(bytes, 0, length);
}
public void WriteUint16(ushort value)
{
this.BitConverter.CopyBytes(value, this.buffer, 0);
this.WriteInternal(this.buffer, 2);
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SixLabors.Fonts.Tests
{
using Xunit;
public class FontLoaderTests
{
[Fact]
public void LoadFontMetadata()
{
FontDescription description = FontDescription.Load(TestFonts.SimpleFontFileData());
Assert.Equal("SixLaborsSamplesAB", description.FontName);
Assert.Equal("AB", description.FontSubFamilyName);
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SixLabors.Fonts.Tables;
using Xunit;
namespace SixLabors.Fonts.Tests
{
public class FontReaderTests
{
[Fact]
public void ReadTrueTypeOutlineType()
{
var writer = new BinaryWriter();
writer.WriteTrueTypeFileHeader(0, 0, 0, 0);
var reader = new FontReader(writer.GetStream());
Assert.Equal(FontReader.OutlineTypes.TrueType, reader.OutlineType);
}
[Fact]
public void ReadCcfOutlineType()
{
var writer = new BinaryWriter();
writer.WriteCffFileHeader(0, 0, 0, 0);
var reader = new FontReader(writer.GetStream());
Assert.Equal(FontReader.OutlineTypes.CFF, reader.OutlineType);
}
[Fact]
public void ReadTableHeaders()
{
var writer = new BinaryWriter();
writer.WriteTrueTypeFileHeader(2, 0, 0, 0);
writer.WriteTableHeader("TAG1", 0, 10, 0);
writer.WriteTableHeader("TAG2", 0, 1, 0);
var reader = new FontReader(writer.GetStream());
Assert.Equal(2, reader.Tables.Length);
var names = reader.Tables.OfType<UnknownTable>().Select(x => x.Name);
Assert.Contains("TAG1", names);
Assert.Contains("TAG2", names);
}
}
}

Двоичные данные
tests/SixLabors.Fonts.Tests/Fonts/SixLaborsSamplesAB.ttf Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,23 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SixLabors.Shapes.Tests")]
[assembly: AssemblyDescription("A cross-platform library for processing of image files written in C#")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SixLabors.Shapes.Tests")]
[assembly: AssemblyCopyright("Copyright © Scott Williams and contributors.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f836e8e6-b4d9-4208-8346-140c74678b91")]

Просмотреть файл

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>f836e8e6-b4d9-4208-8346-140c74678b91</ProjectGuid>
<RootNamespace>SixLabors.Fonts.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
</PropertyGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

Просмотреть файл

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SixLabors.Fonts.Tables;
using Xunit;
namespace SixLabors.Fonts.Tests
{
public class TableHeaderTests
{
public static TheoryData<string, uint, uint?, uint> ReadAllValuesData =
new TheoryData<string, uint, uint?, uint>
{
{ "TAG1", 98, 18, 1218 },
{ "TAG2", 198, null, 121 },
};
[Theory]
[MemberData(nameof(ReadAllValuesData))]
public void ReadAllValues(string tag, uint checksum, uint? offset, uint length)
{
var writer = new BinaryWriter();
writer.WriteTableHeader(tag, checksum, offset, length);
var header = TableHeader.Read(writer.GetReader());
Assert.Equal(checksum, header.CheckSum);
Assert.Equal(length, header.Length);
Assert.Equal(offset, header.Offset);
Assert.Equal(tag, header.Tag);
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using SixLabors.Fonts.Tables;
using Xunit;
namespace SixLabors.Fonts.Tests
{
public class TableLoaderTests
{
public static IEnumerable<object[]> RegisterableTableTypes
{
get
{
var tableTypeInfo = typeof(Table).GetTypeInfo();
return
typeof(Table).GetTypeInfo()
.Assembly.DefinedTypes
.Where(x => tableTypeInfo.IsAssignableFrom(x))
.Select(x => new object[] { x.AsType(), x.GetCustomAttribute<TableNameAttribute>()?.Name })
.Where(x => x[1] != null);
}
}
[Theory]
[MemberData(nameof(RegisterableTableTypes))]
public void AllNamedTablesAreRegistered(Type type, string name)
{
var tl = new TableLoader();
Assert.Contains(type, tl.RegisterdTypes());
Assert.Contains(name, tl.TableTags(null));
}
[Fact]
public void DefaultIsnotNull()
{
Assert.NotNull(TableLoader.Default);
}
[Fact]
public void PassingNullToGetTagsReturnsAllRegisteredTableTags()
{
var loader = new TableLoader();
Assert.NotEmpty(loader.TableTags(null));
}
[Fact]
public void TryingToLoadUnregisteredTagReturnsUnknownTable()
{
var loader = new TableLoader();
string tag = Guid.NewGuid().ToString();
var result = loader.Load(tag, null);
var table = Assert.IsType<UnknownTable>(result);
Assert.Equal(tag, table.Name);
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SixLabors.Fonts.Tests
{
using System.IO;
using System.Reflection;
using Xunit;
public static class TestFonts
{
private static Dictionary<string, Stream> cache= new Dictionary<string, Stream>();
public static string SimpleFontFile => GetFullPath("SixLaborsSamplesAB.ttf");
public static Stream SimpleFontFileData() => OpenStream(SimpleFontFile);
private static Stream OpenStream(string path)
{
if (cache.ContainsKey(path))
{
return cache[path].Clone();
}
lock (cache)
{
if (cache.ContainsKey(path))
{
return cache[path].Clone();
}
using (var fs = File.OpenRead(path))
{
cache.Add(path, fs.Clone());
return cache[path].Clone();
}
}
}
private static Stream Clone(this Stream src)
{
var ms = new MemoryStream();
src.Position = 0;
src.CopyTo(ms);
ms.Position = 0;
return ms;
}
private static string GetFullPath(string path)
{
var root = new Uri(typeof(TestFonts).GetTypeInfo().Assembly.CodeBase).LocalPath;
var paths = new[] { "Fonts", @"..\..\Fonts", @"..\..\..\..\Fonts" };
var fullPaths = paths.Select(x => Path.GetFullPath(Path.Combine(root, x)));
var rootPath = fullPaths
.Where(x => Directory.Exists(x))
.FirstOrDefault();
Assert.True(rootPath != null, $"could not find the font folder in any of these location, \n{string.Join("\n", fullPaths)}");
return Path.Combine(rootPath, path);
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SixLabors.Fonts.Tables;
namespace SixLabors.Fonts.Tests
{
internal static class WriterExtensions
{
public static void WriteTableHeader(this BinaryWriter writer, string tag, uint checksum, uint? offset, uint length)
{
// table header
// Record Type | Name | Description
// ------------|----------|---------------------------------------------------
// uint32 | tag | 4 - byte identifier.
// uint32 | checkSum | CheckSum for this table.
// Offset32 | offset | Offset from beginning of TrueType font file.
// uint32 | length | Length of this table.
writer.WriteUint32(tag);
writer.WriteUint32(checksum);
writer.WriteOffset32(offset);
writer.WriteUint32(length);
}
public static void WriteTrueTypeFileHeader(this BinaryWriter writer, ushort tableCount, ushort searchRange, ushort entrySelector, ushort rangeShift)
{
// uint32 | sfntVersion 0x00010000 or 0x4F54544F('OTTO') — see below.
writer.WriteFileHeader(0x00010000, tableCount, searchRange, entrySelector, rangeShift);
}
public static void WriteCffFileHeader(this BinaryWriter writer, ushort tableCount, ushort searchRange, ushort entrySelector, ushort rangeShift)
{
// uint32 | sfntVersion 0x00010000 or 0x4F54544F('OTTO') — see below.
writer.WriteFileHeader(0x4F54544F, tableCount, searchRange, entrySelector, rangeShift);
}
private static void WriteFileHeader(this BinaryWriter writer, uint version, ushort tableCount, ushort searchRange, ushort entrySelector, ushort rangeShift)
{
// file header
// Type Name | Description
// ----------|---------------------------------------------
// uint32 | sfntVersion 0x00010000 or 0x4F54544F('OTTO') — see below.
// uint16 | numTables Number of tables.
// uint16 | searchRange (Maximum power of 2 <= numTables) x 16.
// uint16 | entrySelector Log2(maximum power of 2 <= numTables).
// uint16 | rangeShift NumTables x 16 - searchRange.
writer.WriteUint32(version);
writer.WriteUint16(tableCount);
writer.WriteUint16(searchRange);
writer.WriteUint16(entrySelector);
writer.WriteUint16(rangeShift);
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"version": "0.0.0-*",
"configurations": {
"Release": {
"buildOptions": {
"warningsAsErrors": false
}
}
},
"dependencies": {
"SixLabors.Fonts": {
"target": "project"
},
"xunit": "2.2.0-*",
"dotnet-test-xunit": "2.2.0-*",
"Moq": "4.6.38-alpha"
},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"System.Linq": "4.3.0",
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0-*"
},
"Microsoft.CodeCoverage": "1.0.2"
}
}
},
"testRunner": "xunit"
}