Bug 1923596 - Update OTS to upstream rev 5daecc9 to resolve empty-glyph-components issue for Windows. r=gfx-reviewers,lsalzman

This resolves the issue with missing glyphs on Windows discussed at
https://github.com/mozilla/pdf.js/issues/18848.

Differential Revision: https://phabricator.services.mozilla.com/D225036
This commit is contained in:
Jonathan Kew 2024-10-10 08:32:43 +00:00
parent f0c4cf3c39
commit dc96249b24
3 changed files with 145 additions and 14 deletions

View File

@ -10,8 +10,8 @@ origin:
url: https://github.com/khaledhosny/ots
release: f31e5827277fc4f8af4025e0a951c7cf77e647b2 (2024-10-02T19:09:36Z).
revision: f31e5827277fc4f8af4025e0a951c7cf77e647b2
release: 5daecc97607ea99c804724475064353ccf34ccc9 (2024-10-09T11:04:07Z).
revision: 5daecc97607ea99c804724475064353ccf34ccc9
license: BSD-3-Clause
license-file: LICENSE

View File

@ -15,6 +15,8 @@
// glyf - Glyph Data
// http://www.microsoft.com/typography/otspec/glyf.htm
#define TABLE_NAME "glyf"
namespace ots {
bool OpenTypeGLYF::ParseFlagsForSimpleGlyph(Buffer &glyph,
@ -229,7 +231,7 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph,
this->iov.push_back(std::make_pair(glyph.buffer(), 2));
// output a fixed-up version of the bounding box
uint8_t* fixed_bbox = new uint8_t[8];
fixed_bboxes.push_back(fixed_bbox);
replacements.push_back(fixed_bbox);
xmin = ots_htons(xmin);
std::memcpy(fixed_bbox, &xmin, 2);
ymin = ots_htons(ymin);
@ -259,10 +261,25 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph,
bool OpenTypeGLYF::ParseCompositeGlyph(
Buffer &glyph,
ComponentPointCount* component_point_count) {
unsigned glyph_id,
ComponentPointCount* component_point_count,
unsigned* skip_count) {
uint16_t flags = 0;
uint16_t gid = 0;
enum class edit_t : uint8_t {
skip_bytes, // param is number of bytes to skip from offset
set_flag, // param is flag to be set (in the 16-bit field at offset)
clear_flag, // param is flag to be cleared
};
// List of glyph data edits to be applied: first value is offset in the data,
// second is a pair of <edit-action, param>.
typedef std::pair<unsigned, std::pair<edit_t, unsigned>> edit_rec;
std::vector<edit_rec> edits;
unsigned prev_start = 0;
bool we_have_instructions = false;
do {
unsigned start = glyph.offset();
if (!glyph.ReadU16(&flags) || !glyph.ReadU16(&gid)) {
return Error("Can't read composite glyph flags or glyphIndex");
}
@ -309,12 +326,50 @@ bool OpenTypeGLYF::ParseCompositeGlyph(
}
}
// Push inital components on stack at level 1
if (this->loca->offsets[gid] == this->loca->offsets[gid + 1]) {
Warning("empty gid %u used as component in glyph %u", gid, glyph_id);
// DirectWrite chokes on composite glyphs that have a completely empty glyph
// as a component; see https://github.com/mozilla/pdf.js/issues/18848.
// To work around this, we attempt to drop empty components.
// But we don't drop the component if it's the only (remaining) one in the composite.
if (prev_start > 0 || (flags & MORE_COMPONENTS)) {
if (!(flags & MORE_COMPONENTS)) {
// We're dropping the last component, so we need to clear the MORE_COMPONENTS flag
// on the previous one.
edits.push_back(edit_rec{prev_start, std::make_pair(edit_t::clear_flag, MORE_COMPONENTS)});
}
// If this component was the first to provide WE_HAVE_INSTRUCTIONS, set it on the previous (if any).
if ((flags & WE_HAVE_INSTRUCTIONS) && !we_have_instructions && prev_start > 0) {
edits.push_back(edit_rec{prev_start, std::make_pair(edit_t::set_flag, WE_HAVE_INSTRUCTIONS)});
}
// Finally, skip the actual bytes of this component.
edits.push_back(edit_rec{start, std::make_pair(edit_t::skip_bytes, glyph.offset() - start)});
}
} else {
// If this is the first component we're keeping, but we already saw WE_HAVE_INSTRUCTIONS
// (on a dropped component), we need to ensure that flag is set here.
if (prev_start == 0 && we_have_instructions && !(flags & WE_HAVE_INSTRUCTIONS)) {
edits.push_back(edit_rec{start, std::make_pair(edit_t::set_flag, WE_HAVE_INSTRUCTIONS)});
}
prev_start = start;
}
we_have_instructions = we_have_instructions || (flags & WE_HAVE_INSTRUCTIONS);
// Push initial components on stack at level 1
// to traverse them in parent function.
component_point_count->gid_stack.push_back({gid, 1});
} while (flags & MORE_COMPONENTS);
if (flags & WE_HAVE_INSTRUCTIONS) {
// Sort any required edits by offset in the glyph data.
struct {
bool operator() (const edit_rec& a, const edit_rec& b) const {
return a.first < b.first;
}
} cmp;
std::sort(edits.begin(), edits.end(), cmp);
if (we_have_instructions) {
uint16_t bytecode_length;
if (!glyph.ReadU16(&bytecode_length)) {
return Error("Can't read instructions size");
@ -333,7 +388,69 @@ bool OpenTypeGLYF::ParseCompositeGlyph(
}
}
this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset()));
// Record the glyph data in this->iov, accounting for any required edits.
*skip_count = 0;
unsigned offset = 0;
while (!edits.empty()) {
auto& edit = edits.front();
// Handle any glyph data between current offset and the next edit position.
if (edit.first > offset) {
this->iov.push_back(std::make_pair(glyph.buffer() + offset, edit.first - offset));
offset = edit.first;
}
// Handle the edit. Note that there may be multiple set_flag/clear_flag edits
// at the same offset, but skip_bytes will never coincide with another edit.
auto& action = edit.second;
switch (action.first) {
case edit_t::set_flag:
case edit_t::clear_flag: {
// Read the existing flags word.
uint16_t flags;
std::memcpy(&flags, glyph.buffer() + offset, 2);
flags = ots_ntohs(flags);
// Apply all flag changes for the current offset.
while (!edits.empty() && edits.front().first == offset) {
auto& e = edits.front();
switch (e.second.first) {
case edit_t::set_flag:
flags |= e.second.second;
break;
case edit_t::clear_flag:
flags &= ~e.second.second;
break;
default:
assert(false);
break;
}
edits.erase(edits.begin());
}
// Record the modified flags word.
flags = ots_htons(flags);
uint8_t* flags_data = new uint8_t[2];
std::memcpy(flags_data, &flags, 2);
replacements.push_back(flags_data);
this->iov.push_back(std::make_pair(flags_data, 2));
offset += 2;
break;
}
case edit_t::skip_bytes:
offset = edit.first + action.second;
*skip_count += action.second;
edits.erase(edits.begin());
break;
default:
assert(false);
break;
}
}
// Handle any remaining glyph data after the last edit.
if (glyph.offset() > offset) {
this->iov.push_back(std::make_pair(glyph.buffer() + offset, glyph.offset() - offset));
}
return true;
}
@ -353,6 +470,7 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) {
GetFont()->GetTypedTable(OTS_TAG_NAME));
bool is_tricky = name->IsTrickyFont();
this->loca = loca;
this->maxp = maxp;
const unsigned num_glyphs = maxp->num_glyphs;
@ -366,6 +484,9 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) {
uint32_t current_offset = 0;
for (unsigned i = 0; i < num_glyphs; ++i) {
// Used by ParseCompositeGlyph to return the number of bytes being skipped
// in the glyph description, so we can adjust offsets properly.
unsigned skip_count = 0;
Buffer glyph(GetGlyphBufferSection(data, length, offsets, i));
if (!glyph.buffer())
@ -414,7 +535,7 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) {
} else {
ComponentPointCount component_point_count;
if (!ParseCompositeGlyph(glyph, &component_point_count)) {
if (!ParseCompositeGlyph(glyph, i, &component_point_count, &skip_count)) {
return Error("Failed to parse glyph %d", i);
}
@ -465,7 +586,7 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) {
}
}
size_t new_size = glyph.offset();
size_t new_size = glyph.offset() - skip_count;
resulting_offsets[i] = current_offset;
// glyphs must be four byte aligned
// TODO(yusukes): investigate whether this padding is really necessary.
@ -622,7 +743,7 @@ Buffer OpenTypeGLYF::GetGlyphBufferSection(
bool OpenTypeGLYF::Serialize(OTSStream *out) {
for (unsigned i = 0; i < this->iov.size(); ++i) {
if (!out->Write(this->iov[i].first, this->iov[i].second)) {
return Error("Falied to write glyph %d", i);
return Error("Failed to write glyph %d", i);
}
}
@ -630,3 +751,5 @@ bool OpenTypeGLYF::Serialize(OTSStream *out) {
}
} // namespace ots
#undef TABLE_NAME

View File

@ -12,6 +12,7 @@
#include "ots.h"
namespace ots {
class OpenTypeLOCA;
class OpenTypeMAXP;
class OpenTypeGLYF : public Table {
@ -20,7 +21,7 @@ class OpenTypeGLYF : public Table {
: Table(font, tag, tag), maxp(NULL) { }
~OpenTypeGLYF() {
for (auto* p : fixed_bboxes) {
for (auto* p : replacements) {
delete[] p;
}
}
@ -53,10 +54,14 @@ class OpenTypeGLYF : public Table {
int16_t xmax,
int16_t ymax,
bool is_tricky_font);
// The skip_count outparam returns the number of bytes from the original
// glyph description that are being skipped on output (normally zero).
bool ParseCompositeGlyph(
Buffer &glyph,
ComponentPointCount* component_point_count);
unsigned glyph_id,
ComponentPointCount* component_point_count,
unsigned* skip_count);
bool TraverseComponentsCountingPoints(
Buffer& glyph,
@ -70,11 +75,14 @@ class OpenTypeGLYF : public Table {
const std::vector<uint32_t>& loca_offsets,
unsigned glyph_id);
OpenTypeLOCA* loca;
OpenTypeMAXP* maxp;
std::vector<std::pair<const uint8_t*, size_t> > iov;
std::vector<uint8_t*> fixed_bboxes;
// Any blocks of replacement data created during parsing are stored here
// to be available during serialization.
std::vector<uint8_t*> replacements;
};
} // namespace ots