mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 19:54:52 +08:00
add more type1 charstring commands and fix type1 tests
This commit is contained in:
@@ -1,6 +1,27 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Brackets an outline section for the dots in letters such as "i", "j" and "!".
|
||||||
|
/// </summary>
|
||||||
internal class DotSectionCommand
|
internal class DotSectionCommand
|
||||||
{
|
{
|
||||||
|
public const string Name = "dotsection";
|
||||||
|
|
||||||
|
public static readonly byte First = 12;
|
||||||
|
public static readonly byte? Second = 0;
|
||||||
|
|
||||||
|
public bool TakeFromStackBottom { get; } = false;
|
||||||
|
public bool ClearsOperandStack { get; } = true;
|
||||||
|
|
||||||
|
public static DotSectionCommand Instance { get; } = new DotSectionCommand();
|
||||||
|
|
||||||
|
private DotSectionCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,50 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Declares the vertical ranges of three horizontal stem zones:
|
||||||
|
/// 1st: between y0 and y0 + dy0
|
||||||
|
/// 2nd: between y1 and y1 + dy1
|
||||||
|
/// 3rd: between y2 and y2 + dy2
|
||||||
|
/// Where y0, y1 and y2 are all relative to the y coordinate of the left sidebearing point.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Suited to letters with 3 horizontal stems like 'E'.
|
||||||
|
/// </remarks>
|
||||||
internal class HStem3Command
|
internal class HStem3Command
|
||||||
{
|
{
|
||||||
|
public const string Name = "hstem3";
|
||||||
|
|
||||||
|
public static readonly byte First = 12;
|
||||||
|
public static readonly byte? Second = 2;
|
||||||
|
|
||||||
|
public bool TakeFromStackBottom { get; } = true;
|
||||||
|
public bool ClearsOperandStack { get; } = true;
|
||||||
|
|
||||||
|
public int Y0 { get; }
|
||||||
|
|
||||||
|
public int DeltaY0 { get; }
|
||||||
|
|
||||||
|
public int Y1 { get; }
|
||||||
|
|
||||||
|
public int DeltaY1 { get; }
|
||||||
|
|
||||||
|
public int Y2 { get; }
|
||||||
|
|
||||||
|
public int DeltaY2 { get; }
|
||||||
|
|
||||||
|
public HStem3Command(int y0, int deltaY0, int y1, int deltaY1, int y2, int deltaY2)
|
||||||
|
{
|
||||||
|
Y0 = y0;
|
||||||
|
DeltaY0 = deltaY0;
|
||||||
|
Y1 = y1;
|
||||||
|
DeltaY1 = deltaY1;
|
||||||
|
Y2 = y2;
|
||||||
|
DeltaY2 = deltaY2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Y0} {DeltaY0} {Y1} {DeltaY1} {Y2} {DeltaY2} {Name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,43 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Declares the vertical range of a horizontal stem zone between the y coordinates y and y+dy,
|
||||||
|
/// where y is relative to the y coordinate of the left sidebearing point.
|
||||||
|
/// </summary>
|
||||||
internal class HStemCommand
|
internal class HStemCommand
|
||||||
{
|
{
|
||||||
|
public const string Name = "hstem";
|
||||||
|
|
||||||
|
public static readonly byte First = 1;
|
||||||
|
public static readonly byte? Second = null;
|
||||||
|
|
||||||
|
public bool TakeFromStackBottom { get; } = true;
|
||||||
|
public bool ClearsOperandStack { get; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first Y coordinate for the stem zone, relative to the current left sidebearing Y point.
|
||||||
|
/// </summary>
|
||||||
|
public int Y { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance to move from Y vertically for the horizontal stem zone.
|
||||||
|
/// </summary>
|
||||||
|
public int DeltaY { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="HStemCommand"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="y">The lower Y coordinate of the stem zone.</param>
|
||||||
|
/// <param name="deltaY">The distance to move from Y vertically for the stem zone.</param>
|
||||||
|
public HStemCommand(int y, int deltaY)
|
||||||
|
{
|
||||||
|
Y = y;
|
||||||
|
DeltaY = deltaY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Y} {DeltaY} {Name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,50 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Declares the horizontal ranges of three vertical stem zones.
|
||||||
|
/// 1st: between x0 and x0 + dx0
|
||||||
|
/// 2nd: between x1 and x1 + dx1
|
||||||
|
/// 3rd: between x2 and x2 + dx2
|
||||||
|
/// Where x0, x1 and x2 are all relative to the x coordinate of the left sidebearing point.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Suited to letters with 3 vertical stems, for instance 'm'.
|
||||||
|
/// </remarks>
|
||||||
internal class VStem3Command
|
internal class VStem3Command
|
||||||
{
|
{
|
||||||
|
public const string Name = "vstem3";
|
||||||
|
|
||||||
|
public static readonly byte First = 12;
|
||||||
|
public static readonly byte? Second = 1;
|
||||||
|
|
||||||
|
public bool TakeFromStackBottom { get; } = true;
|
||||||
|
public bool ClearsOperandStack { get; } = true;
|
||||||
|
|
||||||
|
public int X0 { get; }
|
||||||
|
|
||||||
|
public int DeltaX0 { get; }
|
||||||
|
|
||||||
|
public int X1 { get; }
|
||||||
|
|
||||||
|
public int DeltaX1 { get; }
|
||||||
|
|
||||||
|
public int X2 { get; }
|
||||||
|
|
||||||
|
public int DeltaX2 { get; }
|
||||||
|
|
||||||
|
public VStem3Command(int x0, int deltaX0, int x1, int deltaX1, int x2, int deltaX2)
|
||||||
|
{
|
||||||
|
X0 = x0;
|
||||||
|
DeltaX0 = deltaX0;
|
||||||
|
X1 = x1;
|
||||||
|
DeltaX1 = deltaX1;
|
||||||
|
X2 = x2;
|
||||||
|
DeltaX2 = deltaX2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{X0} {DeltaX0} {X1} {DeltaX1} {X2} {DeltaX2} {Name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,38 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Declares the horizontal range of a vertical stem zone between the x coordinates x and x+dx,
|
||||||
|
/// where x is relative to the x coordinate of the left sidebearing point.
|
||||||
|
/// </summary>
|
||||||
internal class VStemCommand
|
internal class VStemCommand
|
||||||
{
|
{
|
||||||
|
public const string Name = "vstem";
|
||||||
|
|
||||||
|
public static readonly byte First = 3;
|
||||||
|
public static readonly byte? Second = null;
|
||||||
|
|
||||||
|
public bool TakeFromStackBottom { get; } = true;
|
||||||
|
public bool ClearsOperandStack { get; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first X coordinate for the stem zone, relative to the current left sidebearing X point.
|
||||||
|
/// </summary>
|
||||||
|
public int X { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance to move from X horizontally for the vertical stem zone.
|
||||||
|
/// </summary>
|
||||||
|
public int DeltaX { get; }
|
||||||
|
|
||||||
|
public VStemCommand(int x, int deltaX)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
DeltaX = deltaX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{X} {DeltaX} {Name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,30 @@
|
|||||||
using System;
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
|
|
||||||
{
|
{
|
||||||
class Type1CharStringCommandFactory
|
using System.Collections.Generic;
|
||||||
|
using Commands.Hint;
|
||||||
|
|
||||||
|
internal static class Type1CharStringCommandFactory
|
||||||
{
|
{
|
||||||
|
public static object GetCommand(List<int> arguments, byte v, IReadOnlyList<byte> bytes, ref int i)
|
||||||
|
{
|
||||||
|
switch (v)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
return new HStemCommand(arguments[0], arguments[1]);
|
||||||
|
}
|
||||||
|
case 12:
|
||||||
|
{
|
||||||
|
var next = bytes[++i];
|
||||||
|
switch (next)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,60 @@
|
|||||||
using System;
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
|
|
||||||
{
|
{
|
||||||
class Type1CharStringParser
|
using System.Collections.Generic;
|
||||||
|
using Parser;
|
||||||
|
|
||||||
|
internal class Type1CharStringParser
|
||||||
{
|
{
|
||||||
|
public static void Parse(IReadOnlyList<Type1CharstringDecryptedBytes> charStrings, IReadOnlyList<Type1CharstringDecryptedBytes> subroutines)
|
||||||
|
{
|
||||||
|
foreach (var charString in charStrings)
|
||||||
|
{
|
||||||
|
ParseSingle(charString.Bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseSingle(IReadOnlyList<byte> charStringBytes)
|
||||||
|
{
|
||||||
|
var numberStack = new List<int>();
|
||||||
|
for (var i = 0; i < charStringBytes.Count; i++)
|
||||||
|
{
|
||||||
|
var b = charStringBytes[i];
|
||||||
|
|
||||||
|
if (b <= 31)
|
||||||
|
{
|
||||||
|
var command = Type1CharStringCommandFactory.GetCommand(numberStack, b, charStringBytes, ref i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var val = InterpretNumber(b, charStringBytes, ref i);
|
||||||
|
|
||||||
|
numberStack.Add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int InterpretNumber(byte b, IReadOnlyList<byte> bytes, ref int i)
|
||||||
|
{
|
||||||
|
if (b >= 32 && b <= 246)
|
||||||
|
{
|
||||||
|
return b - 139;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b >= 247 && b <= 250)
|
||||||
|
{
|
||||||
|
var w = bytes[++i];
|
||||||
|
return ((b - 247) * 256) + w + 108;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b >= 251 && b <= 254)
|
||||||
|
{
|
||||||
|
var w = bytes[++i];
|
||||||
|
return -((b - 251) * 256) - w - 108;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = bytes[++i] << 24 + bytes[++i] << 16 + bytes[++i] << 8 + bytes[++i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using CharStrings;
|
||||||
using IO;
|
using IO;
|
||||||
using PdfPig.Parser.Parts;
|
using PdfPig.Parser.Parts;
|
||||||
using Tokenization.Tokens;
|
using Tokenization.Tokens;
|
||||||
@@ -69,8 +70,9 @@
|
|||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
var token = tokenizer.GetNext();
|
var token = tokenizer.GetNext();
|
||||||
|
|
||||||
// premature end
|
// premature end
|
||||||
if (token.Type != Type1Token.TokenType.Literal)
|
if (token == null || token.Type != Type1Token.TokenType.Literal)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -80,18 +82,21 @@
|
|||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
case Type1Symbols.RdProcedure:
|
case Type1Symbols.RdProcedure:
|
||||||
|
case Type1Symbols.RdProcedureAlt:
|
||||||
{
|
{
|
||||||
var procedureTokens = ReadProcedure(tokenizer);
|
var procedureTokens = ReadProcedure(tokenizer);
|
||||||
ReadTillDef(tokenizer);
|
ReadTillDef(tokenizer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type1Symbols.NoAccessDef:
|
case Type1Symbols.NoAccessDef:
|
||||||
|
case Type1Symbols.NoAccessDefAlt:
|
||||||
{
|
{
|
||||||
var procedureTokens = ReadProcedure(tokenizer);
|
var procedureTokens = ReadProcedure(tokenizer);
|
||||||
ReadTillDef(tokenizer);
|
ReadTillDef(tokenizer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type1Symbols.NoAccessPut:
|
case Type1Symbols.NoAccessPut:
|
||||||
|
case Type1Symbols.NoAccessPutAlt:
|
||||||
{
|
{
|
||||||
var procedureTokens = ReadProcedure(tokenizer);
|
var procedureTokens = ReadProcedure(tokenizer);
|
||||||
ReadTillDef(tokenizer);
|
ReadTillDef(tokenizer);
|
||||||
@@ -193,6 +198,7 @@
|
|||||||
case Type1Symbols.Len4:
|
case Type1Symbols.Len4:
|
||||||
{
|
{
|
||||||
lenIv = (int)ReadNumeric(tokenizer);
|
lenIv = (int)ReadNumeric(tokenizer);
|
||||||
|
ReadTillDef(tokenizer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type1Symbols.BlueShift:
|
case Type1Symbols.BlueShift:
|
||||||
@@ -240,17 +246,51 @@
|
|||||||
ReadTillDef(tokenizer);
|
ReadTillDef(tokenizer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Type1Symbols.ExpansionFactor:
|
||||||
|
{
|
||||||
|
builder.ExpansionFactor = ReadNumeric(tokenizer);
|
||||||
|
ReadTillDef(tokenizer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Type1Symbols.Erode:
|
||||||
|
{
|
||||||
|
ReadTillDef(tokenizer, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ReadTillDef(tokenizer, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentToken = tokenizer.CurrentToken;
|
var currentToken = tokenizer.CurrentToken;
|
||||||
while (currentToken.Type != Type1Token.TokenType.Literal
|
|
||||||
&& !string.Equals(currentToken.Text, "CharStrings", StringComparison.OrdinalIgnoreCase))
|
IReadOnlyList<Type1CharstringDecryptedBytes> charStrings;
|
||||||
|
if (currentToken != null)
|
||||||
{
|
{
|
||||||
currentToken = tokenizer.GetNext();
|
while (currentToken != null && currentToken.Type != Type1Token.TokenType.Literal
|
||||||
|
&& !string.Equals(currentToken.Text, "CharStrings", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
currentToken = tokenizer.GetNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentToken != null)
|
||||||
|
{
|
||||||
|
charStrings = ReadCharStrings(tokenizer, lenIv, isLenientParsing);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
charStrings = new Type1CharstringDecryptedBytes[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
charStrings = new Type1CharstringDecryptedBytes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
var charStrings = ReadCharStrings(tokenizer, lenIv, isLenientParsing);
|
Type1CharStringParser.Parse(charStrings, builder.Subroutines ?? new Type1CharstringDecryptedBytes[0]);
|
||||||
|
|
||||||
return decrypted;
|
return decrypted;
|
||||||
}
|
}
|
||||||
@@ -438,7 +478,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReadTillDef(Type1Tokenizer tokenizer)
|
private static void ReadTillDef(Type1Tokenizer tokenizer, bool skip = false)
|
||||||
{
|
{
|
||||||
Type1Token token;
|
Type1Token token;
|
||||||
while ((token = tokenizer.GetNext()) != null)
|
while ((token = tokenizer.GetNext()) != null)
|
||||||
@@ -455,7 +495,7 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (!skip)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Encountered unexpected non-name token while reading till 'def' token: {token}");
|
throw new InvalidOperationException($"Encountered unexpected non-name token while reading till 'def' token: {token}");
|
||||||
}
|
}
|
||||||
@@ -614,7 +654,7 @@
|
|||||||
|
|
||||||
private static IReadOnlyList<Type1CharstringDecryptedBytes> ReadCharStrings(Type1Tokenizer tokenizer, int lenIv, bool isLenientParsing)
|
private static IReadOnlyList<Type1CharstringDecryptedBytes> ReadCharStrings(Type1Tokenizer tokenizer, int lenIv, bool isLenientParsing)
|
||||||
{
|
{
|
||||||
var length = (int) ReadNumeric(tokenizer);
|
var length = (int)ReadNumeric(tokenizer);
|
||||||
|
|
||||||
ReadExpected(tokenizer, Type1Token.TokenType.Name, "dict");
|
ReadExpected(tokenizer, Type1Token.TokenType.Name, "dict");
|
||||||
|
|
||||||
|
@@ -14,7 +14,9 @@
|
|||||||
public const string Len4 = "lenIV";
|
public const string Len4 = "lenIV";
|
||||||
public const string MinFeature = "MinFeature";
|
public const string MinFeature = "MinFeature";
|
||||||
public const string NoAccessDef = "ND";
|
public const string NoAccessDef = "ND";
|
||||||
|
public const string NoAccessDefAlt = "|-";
|
||||||
public const string NoAccessPut = "NP";
|
public const string NoAccessPut = "NP";
|
||||||
|
public const string NoAccessPutAlt = "|";
|
||||||
public const string OtherBlues = "OtherBlues";
|
public const string OtherBlues = "OtherBlues";
|
||||||
public const string OtherSubroutines = "OtherSubrs";
|
public const string OtherSubroutines = "OtherSubrs";
|
||||||
public const string Password = "password";
|
public const string Password = "password";
|
||||||
@@ -27,5 +29,9 @@
|
|||||||
public const string StemSnapVerticalWidths = "StemSnapV";
|
public const string StemSnapVerticalWidths = "StemSnapV";
|
||||||
public const string Subroutines = "Subrs";
|
public const string Subroutines = "Subrs";
|
||||||
public const string UniqueId = "UniqueID";
|
public const string UniqueId = "UniqueID";
|
||||||
|
/// <summary>
|
||||||
|
/// This is shown briefly in the Type 1 spec but nowhere is it specified what it means or does.
|
||||||
|
/// </summary>
|
||||||
|
public const string Erode = "Erode";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -115,7 +115,7 @@
|
|||||||
throw new InvalidOperationException($"The binary portion of the type 1 font was invalid at position {bytes.CurrentOffset}.");
|
throw new InvalidOperationException($"The binary portion of the type 1 font was invalid at position {bytes.CurrentOffset}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.Equals("RD") || name.Equals("-|"))
|
if (name.Equals(Type1Symbols.RdProcedure, StringComparison.OrdinalIgnoreCase) || name.Equals(Type1Symbols.RdProcedureAlt))
|
||||||
{
|
{
|
||||||
if (previousToken.Type == Type1Token.TokenType.Integer)
|
if (previousToken.Type == Type1Token.TokenType.Integer)
|
||||||
{
|
{
|
||||||
@@ -377,7 +377,7 @@
|
|||||||
// Skip preceding space.
|
// Skip preceding space.
|
||||||
bytes.MoveNext();
|
bytes.MoveNext();
|
||||||
// TODO: may be wrong
|
// TODO: may be wrong
|
||||||
bytes.MoveNext();
|
// bytes.MoveNext();
|
||||||
|
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
|
@@ -52,6 +52,8 @@
|
|||||||
public MinFeature MinFeature { get; set; }
|
public MinFeature MinFeature { get; set; }
|
||||||
|
|
||||||
public bool RoundStemUp { get; set; }
|
public bool RoundStemUp { get; set; }
|
||||||
|
|
||||||
|
public decimal? ExpansionFactor { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MinFeature
|
public class MinFeature
|
||||||
|
Reference in New Issue
Block a user