-
Notifications
You must be signed in to change notification settings - Fork 65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add MATH
table
#87
add MATH
table
#87
Conversation
Thanks for the PR. I'm glad you have been able to grok ttf-parser in a couple of days. And thanks for all the valid criticism in the comment. I will try to address all of it. TestingTrueType testing is a total pain. That's why we don't have that many tests. I'm working on it, but it takes forever. My current solution is to write table-specific binary data directly, by hand. Yes it's a bit verbose and confusing to newcomers, but I don't see any other way. This way tests are self-contained (everything inside one function, no need to look up some random files and so on), fast and easily versioned. So for you to test the Marking tables optionalIf the spec is not clear enough, I would suggest following the most fail-safe approach. ttf-parser should be able to retrieve as much data as possible. Parsing APIYes, OpenType tables was a new addition (specifically GDEF, GPOS and GSUB one) and I don't mind any additions to Lazy(Offset)Array16,32 APII think a custom type is fine. I've tried multiple times to come up with some generic solutions, but they end being way more complicated than needed. You just have to accept the fact that every table in TrueType has a unique structure and there is now way/point to unify it. Some verbosity would not hurt. That's why DocumentationYes, a short but your own comment is better than copy-pasting MS docs. I don't think they would mind, but better not too. Also, ttf-parser docs should explain how ttf-parser works, not how TrueType works. We have a spec (multiple of them) for that. Code FormattingI personally don't like code formatters and therefore don't use them. At the same time I'm basically following the default rustfmt logic with some small exceptions. So for a new file you could use auto-formatting, but leave other files as is. Code Generation by External ScriptI think it's fine as is. I don't think we would be modifying it anytime soon. And if so, it would be easy to done by hand. Going a bit off-topicYes, error processing is a very questionable area. Just a quick note about So the original ttf-parser version actually did have proper errors (you can find leftovers of them in I do have plans on implementing a proper error processing, but still not sure in what way. Probably each table/module would have its own error type. There is no way we would be able to have a single/generic error. I guess my point here is that good error processing is only good for ttf-parser developers, me, for debugging. For end-user they are kinda useless, because malformed tables would be skipped anyway and there is no way to handle this. So at least printing a warning would be better that silently ignoring a table. |
src/tables/math.rs
Outdated
/// to compensate for rounding errors and hinting corrections at a lower resolution. The | ||
/// `min_connector_overlap` value tells how much overlap is necessary for this particular font. | ||
pub min_connector_overlap: u16, | ||
/// Offset to Coverage table, from the beginning of the MathVariants table. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not an offset anymore. We're already resolved it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the documentation.
It is a bit repetitive in its current form. I plan to add a CoverageTable
abstraction to group the Coverage
and the array, because this way the data should appear better organised, and we can add some higher-level glyph-based API for looking up those arrays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I introduced a new type CoverageMapping
for combining the Coverage
table and the array for values. For the abstraction to work, I had to add a public trait Array
in module parser
, with two associated types Index
and Value
, and three public methods len
, is_empty
, and get
. Unfortunately this is a breaking change (because these three are not inherent methods any more, and cannot be accessed without a use parser::Array;
). Personally I think it is good to have a universal interface for all the arrays, but it is really up to you to decide. I hope this change is acceptable to you.
Relevant code:
https://github.com/ruifengx/ttf-parser/blob/math-table/src/parser.rs#L243-L255
https://github.com/ruifengx/ttf-parser/blob/math-table/src/tables/math.rs#L707-L715
Add some comments. I guess I would have to add Also, tests would help. |
Thanks for the detailed reply and comments. I'll start addressing these problems. |
The `part_flags` field was missing, and now it is added, so we should use correct size for this record.
Hey, I'm very interested in the current state of this PR. As I happen to need the math table parsing, I would also be happy to help out with or finish up this PR. |
I personally haven't looked into it much, but this table is pretty complex. And making a good API would be challenging. @ruifengx any updates on this? |
This has indeed been a long time, and now I honestly don't remember the details clearly. But as I recall, the implementation should have covered all low-level pieces in the MATH table (though it still need proper testing and debugging). I see now this PR has merge conflicts, and that involves And I don't know if the following still applies:
If so, we will also need to work on ttf-explorer (which I believe is written in C++ with Qt). |
@ruifengx Thanks! Don't worry about ttf-explorer, that's on me. |
I have had a bit closer look now and the PR really looks pretty complete already. To finish it up, I would thus:
|
Can't you commit to this PR directly? Yes, testing is the hardest part. There are not that many fonts that use this table to begin with. They are pretty rare. |
I don't think so. I don't have write access to ruifengx's fork. |
@laurmaedje Do you prefer to commit directly to my fork? If that is easier/clearer, I could add you as a collaborator there. EDIT: collaborator added. |
That sounds good! Then, we can keep this PR and discussion. |
@RazrFalcon ruifengx added an |
Not really. I would prefer as little traits/generics as possible. |
287e5bb
to
aab6bc0
Compare
aab6bc0
to
8a68791
Compare
This should be more or less ready now:
|
Add MATH to readme and Cargo.toml opentype-layout comment and we're good. Afaik, FreeType doesn't support MATH. Same with stb_truetype. |
By the way, the general rule in ttf-parser is that there are no Offset16/Offset32 and |
I also couldn't find anything math-related for FreeType and stb_truetype. Harfbuzz does support the table though. Regarding offsets: Yes, the new table does not expose offsets or binary data. |
harfbuzz exposes MATH, but doesn't actually use it. It's true for multiple tables. The idea is that you can use harfbuzz as TrueType parser as well. |
Thanks to @ruifengx and @laurmaedje ! I will fix CFF parsing soon and will publish a new version. |
https://docs.microsoft.com/en-us/typography/opentype/spec/math
Changes
opentype-math
math
inFaceTables
tables::math
:math::Table
for OpenTypeMATH
tables;MathConstants
,MathGlyphInfo
, andMathVariants
subtables;Unresolved Questions
I will read through your code for other tables for reference, and ponder on these questions for the next several days. Also, I will be very glad to hear your opinions and decisions.
Testing. The whole OpenType spec is not easy to comprehend in any sense, and it is very likely that the current version of the parser fails to handle some edge cases. I have been manually comparing the values parsed here and obtained from FontForge, but that is not efficient nor comprehensive.
Marking tables optional. Some subtables are "officially" optional (with a "May be NULL" note for the offset) and thus is already wrapped in
Option
. For some others, there is no "May be NULL" note, but it might still be desirable to make those tables optional. For example, in Latin Modern Math (which I used for testing), theMathKernInfo
inMathGlyphInfo
is missing, and apparently we don't want the lack of this single subtable fail the whole parsing.Parsing API.
Stream::read
only supportFromData
types, I added a private convenience methodStream::parse_at_offset16
intables::math
(basically a combination ofStream::read_at_offset16
andFromSlice::parse
). I wonder whether or not we should make this method part of the public API.Lazy(Offset)Array{16,32}
API. ForMATH
tables, the combination of aCoverage
table and an array appear several times, and I would like to provide a universalCoverageTable
interface for all of them. The problem is that the arrays are not necessarily representable withLazy(Offset)Array16
(because the record types consist of both data fields and offset fields), and I had to invent new array types (MathValueArray
forMathValueRecord
andMathKernInfoArray
forMathKernInfoRecord
). Here I have two possible solutions:Array
trait to abstract over these arrays.LazyOffsetArray
, supporting complex record types (which may have zero or more data fields and zero or more offset fields) as array items.Both solutions will affect the public API (and the benefit may or may not worth the cost). So I think I should first discuss this with you before implementing the solutions.
Documentation. I am copy-pasting texts in the spec as doc comments for the table types and their fields. I am not entirely sure whether or not there would be some copyright problems here.
Code Formatting. I rely on IntelliJ Rust to format my code, and I noticed some slight differences with your original formatting. (I noticed this because I had to edit
lib.rs
to add new modules and new fields, and the formatter made some changes to your original code. Certainly, I did revert those changes and preserved the original formatting inlib.rs
, but the same cannot be done to the newly added files.) I did not find this topic in the Contribution section in the project README, so I think it is better to make this explicit here.Code Generation by External Script. I used a Haskell script to generate the field accessors and
*_OFFSET
constants for theMathConstants
table. The script is not included in the source tree, but if you want it, I can provide a copy or simply commit it in the repo.Going a bit off-topic
Using
Option
to represent parse error makes it really hard to figure out why parsing a table failed (especially when debugging the parser). Also, a field beingNone
could mean either "this field does not appear in the table (possibly because of a NULL offset)" or "this field does appear in the table, but parsing the subtable failed". I don't have a solution (and I am even not sure if this really is a problem), but I thinks there is no harm to raise this topic here.