Skip to content

Add support for glyphs stored in CFF formats - #265

Merged
JimBobSquarePants merged 40 commits into
mainfrom
js/cff-support
Jun 10, 2022
Merged

Add support for glyphs stored in CFF formats#265
JimBobSquarePants merged 40 commits into
mainfrom
js/cff-support

Conversation

@JimBobSquarePants

@JimBobSquarePants JimBobSquarePants commented May 20, 2022

Copy link
Copy Markdown
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

Fixes #12

Adds support for CFF fonts.

NOTE: This does not add support for CCF2 variable fonts.

There's a lot going on here so I thought best to open a draft so review can be a gradual process. I had to do significant refactoring in order to allow both TTF and CFF glyph formats.

I'm basing the work primarily on LayoutFarm Typograph with cleanup and missing features based upon UglyToad PdfPig

Initial rendering experiments seems to be working well and the new architecture allows shaping for both formats.

TODO:

  • Investigate TODO's and implement missing features
  • Massive cleanup (Allow release build) and performance run
  • Unit tests

@JimBobSquarePants JimBobSquarePants added this to the 1.0.0-rc1 milestone May 23, 2022
@codecov

codecov Bot commented May 27, 2022

Copy link
Copy Markdown

Codecov Report

Merging #265 (efbb09b) into main (95b19cf) will decrease coverage by 1%.
The diff coverage is 72%.

@@          Coverage Diff           @@
##           main    #265     +/-   ##
======================================
- Coverage    83%     81%     -2%     
======================================
  Files       188     216     +28     
  Lines      9856   11264   +1408     
  Branches   1589    1750    +161     
======================================
+ Hits       8222    9233   +1011     
- Misses     1266    1607    +341     
- Partials    368     424     +56     
Flag Coverage Δ
unittests 81% <72%> (-2%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/SixLabors.Fonts/FontDescription.cs 90% <ø> (ø)
src/SixLabors.Fonts/GlyphLayout.cs 76% <ø> (ø)
...nts/Tables/AdvancedTypographic/GPos/AnchorTable.cs 36% <0%> (-2%) ⬇️
...ables/AdvancedTypographic/SkippingGlyphIterator.cs 97% <ø> (ø)
src/SixLabors.Fonts/Tables/Cff/Cff2Table.cs 0% <0%> (ø)
...SixLabors.Fonts/Tables/Cff/CffPrivateDictionary.cs 0% <0%> (ø)
src/SixLabors.Fonts/Tables/Cff/GlyphNameMap.cs 0% <0%> (ø)
...c/SixLabors.Fonts/Tables/General/Colr/ColrTable.cs 100% <ø> (ø)
...ixLabors.Fonts/Tables/General/Kern/KerningTable.cs 100% <ø> (ø)
...c/SixLabors.Fonts/Tables/General/Post/PostTable.cs 82% <ø> (ø)
... and 62 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 95b19cf...efbb09b. Read the comment docs.

@brianpopow brianpopow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, this is a really huge PR. This will be a great improvement for the Fonts library!

{
Span<byte> slice = bufferSpan.Slice(0, length);
int actualRead = reader.BaseStream.Read(slice);
#if DEBUG

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this only thrown when debugging? Is this not considered an error in any case?

switch (entry.Operator.Name)
{
default:
#if DEBUG

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont like those #if DEBUG statements much.

Maybe we should introduce a proper logger for such cases? I really like serilog for such things. I am a big fan of structured logging. I can really help finding bugs/issues.

{
return reader.ReadInt16();
}
else if (b0 == 29)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would omit those redundant else's

Comment thread src/SixLabors.Fonts/Tables/Cff/Cff1Parser.cs Outdated
// Note 6 The charstring itself may end with a call(g)subr; the subroutine must
// then end with an endchar operator
// endchar
throw new Exception("invalid end byte?");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again why only throw in Debug mode? If not only in debug mode, maybe a more specific exception/exception message .

Comment thread src/SixLabors.Fonts/Tables/Cff/Cff1Parser.cs Outdated
Comment thread src/SixLabors.Fonts/Tables/Cff/CffEvaluationEngine.cs Outdated
Comment thread src/SixLabors.Fonts/Tables/Cff/Type2CharStringParser.cs Outdated
Comment thread src/SixLabors.Fonts/Tables/Cff/Type2CharStringParser.cs Outdated
Comment thread src/SixLabors.Fonts/Tables/Cff/Type2CharStringParser.cs Outdated
this.currentGlyphIndex = 0;
this.currentSelectedRangeIndex = 0;

if (ranges != null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (ranges != null)
if (ranges?.Length > 0)

Comment thread src/SixLabors.Fonts/StreamFontMetrics.Cff.cs Outdated
@brianpopow

Copy link
Copy Markdown
Contributor

Maybe we could use some of those fonts here for testing: https://v-fonts.com/tags/C107

Comment thread src/SixLabors.Fonts/Tables/Cff/CffTable.cs
byte[] header = reader.ReadBytes(4);
byte major = header[0];
byte minor = header[1];
byte hdrSize = header[2];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec recommends using the hdrSize to locate the Name INDEX.

private static void Register(Dictionary<int, CFFOperator> dictionary, byte b0, string operatorName, OperatorOperandKind opopKind)
=> dictionary.Add(b0, new CFFOperator(operatorName, b0, 0, opopKind));

#if DEBUG

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use DebuggerDisplay attribute instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just a chore that's all....

/// <param name="encoding">The encoding.</param>
/// <param name="buffer">The buffer.</param>
/// <returns>The string.</returns>
public static string GetString(this Encoding encoding, ReadOnlySpan<byte> buffer)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ReadString and ReadTag methods in BigEndianBinaryReader could maybe make use of this extension method

}
}

private void ReadCharsetsFormat0(BigEndianBinaryReader reader, string[] stringIndex, CffGlyphData[] glyphs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadCharsetsFormat0 and ReadCharsetsFormat1 are not covered by tests according to the code coverage report. Would be good, if we have tests for those cases.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll double check. The should be getting called.

@JimBobSquarePants

Copy link
Copy Markdown
Member Author

@brianpopow thanks for the review so far!

I've got some experimental code locally this is pinches fingers "this close" to working that allows me to delete a ton of code that is creating second set of commands rather than the raw literals used for rendering the output.

I'll do a whole heap of cleanup once it's working.

Comment thread src/SixLabors.Fonts/FontReader.cs Outdated
/// A Type 2 charstring program is a sequence of unsigned 8-bit bytes that encode numbers and operators.
/// The byte value specifies a operator, a number, or subsequent bytes that are to be interpreted in a specific manner
/// </remarks>
internal ref struct CffEvaluationEngine

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brianpopow this is a complete rewrite of the evaluator based upon the Fontkit implementation and sense checked against PDFPig. It's much, much more efficient that the original implementation saving a secondary buffer allocation for each set of operand instructions in the font.

Comment thread src/SixLabors.Fonts/Tables/Cff/CffEvaluationEngine.cs Outdated
Comment thread samples/DrawWithImageSharp/Program.cs Outdated
bool a;
bool b;
byte twoByteOperator = reader.ReadByte();
if (twoByteOperator < 38)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nesting makes it kind of hard to read. Maybe invert the if statement here and throw ThrowInvalidOperator(twoByteOperator) to reduce one level?

{
ThrowInvalidOperator(twoByteOperator);
}
else

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention was to remove the else nesting completely now, since twoByteOperator < 38, but that's just a nitpick. Leave it like it is, if you want.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, sorry! Brains elsewhere just now

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@brianpopow brianpopow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I think its time to get this into the main branch 👍

@JimBobSquarePants
JimBobSquarePants merged commit cb8a60c into main Jun 10, 2022
@JimBobSquarePants
JimBobSquarePants deleted the js/cff-support branch June 10, 2022 08:06
@brianpopow

Copy link
Copy Markdown
Contributor

@JimBobSquarePants I think we missed to update the readme. We can now remove the Limitations section and add CFF glyph's to the feature list

@JimBobSquarePants

Copy link
Copy Markdown
Member Author

Yup. Missed that! We’ll spotted!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add support for glyphs stored in CFF formats

2 participants